-
Notifications
You must be signed in to change notification settings - Fork 24
Add feature for Embedded Workflows for Images (PNG, SVG, JPEG) #376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
fb4646b
feea304
3d9d8a8
d277b62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,8 +40,9 @@ const app = ({ superState, dispatcher }) => { | |
| e.preventDefault(); | ||
| fileRef.current.value = null; | ||
| const droppedFile = e.dataTransfer.files[0]; | ||
| const ext = droppedFile && droppedFile.name.split('.').slice(-1)[0]; | ||
| if (e.dataTransfer.files.length === 1 && (ext === 'graphml' || ext === 'json')) { | ||
| const ext = droppedFile && droppedFile.name.split('.').slice(-1)[0]?.toLowerCase(); | ||
| const allowed = ['graphml', 'json', 'png', 'svg', 'jpg', 'jpeg']; | ||
| if (e.dataTransfer.files.length === 1 && allowed.includes(ext)) { | ||
| readFile(superStateRef.current, dispatcherRef.current, droppedFile); | ||
|
Comment on lines
+43
to
46
|
||
| } | ||
| }; | ||
|
|
@@ -70,7 +71,7 @@ const app = ({ superState, dispatcher }) => { | |
| ref={fileRef} | ||
| onClick={(e) => { e.target.value = null; }} | ||
| style={{ display: 'none' }} | ||
| accept=".graphml,.json" | ||
| accept=".graphml,.json,.png,.svg,.jpg,.jpeg" | ||
| onChange={(e) => readFile(superState, dispatcher, e.target.files[0])} | ||
| /> | ||
| <span className="arrow">⟶</span> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,8 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { saveAs } from 'file-saver'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from 'react-toastify'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import extractChunks from 'png-chunks-extract'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import encodeChunks from 'png-chunks-encode'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import textChunk from 'png-chunk-text'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import localStorageManager from '../local-storage-manager'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import graphmlBuilder from '../graphml/builder'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import BendingDistanceWeight from '../calculations/bending-dist-weight'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,8 +33,75 @@ class GraphLoadSave extends GraphUndoRedo { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloadImg(format) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.cy.emit('hide-bend'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.cy.$('.eh-handle').remove(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'PNG') saveAs(this.cy.png({ full: true }), `${this.getName()}-DHGWorkflow.png`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'JPG') saveAs(this.cy.jpg({ full: true }), `${this.getName()}-DHGWorkflow.jpg`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'JPG') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveAs(this.cy.jpg({ full: true }), `${this.getName()}-DHGWorkflow.jpg`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'PNG') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveAs(this.cy.png({ full: true }), `${this.getName()}-DHGWorkflow.png`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'PNG-EMBEDDED') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const b64Uri = this.cy.png({ full: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const b64Data = b64Uri.split(',')[1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buffer = new Uint8Array(window.atob(b64Data).split('').map((c) => c.charCodeAt(0))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chunks = extractChunks(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chunks.splice(-1, 0, textChunk.encode('graphml', this.getGraphML())); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newBuffer = new Uint8Array(encodeChunks(chunks)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+53
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const blob = new Blob([newBuffer], { type: 'image/png' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveAs(blob, `${this.getName()}.graphml.png`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'SVG') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const blob = new Blob([this.cy.svg({ full: true })], { type: 'image/svg+xml;charset=utf-8' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveAs(blob, `${this.getName()}-DHGWorkflow.svg`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'SVG-EMBEDDED') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const svgStr = this.cy.svg({ full: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parser = new DOMParser(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const doc = parser.parseFromString(svgStr, 'image/svg+xml'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const metadata = doc.createElementNS('http://www.w3.org/2000/svg', 'metadata'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata.setAttribute('data-graphml', this.getGraphML()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| doc.documentElement.insertBefore(metadata, doc.documentElement.firstChild); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newSvg = new XMLSerializer().serializeToString(doc); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const blob = new Blob([newSvg], { type: 'image/svg+xml;charset=utf-8' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveAs(blob, `${this.getName()}.graphml.svg`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (format === 'JPG-EMBEDDED') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const b64Uri = this.cy.jpg({ full: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const b64Data = b64Uri.split(',')[1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buffer = new Uint8Array(window.atob(b64Data).split('').map((c) => c.charCodeAt(0))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const graphMLStr = this.getGraphML(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+65
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const graphMLBytes = new TextEncoder().encode(graphMLStr); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const comSegments = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < graphMLBytes.length; i += 65533) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chunk = graphMLBytes.slice(i, i + 65533); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const segmentLen = chunk.length + 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const seg = new Uint8Array(4 + chunk.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seg[0] = 0xFF; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seg[1] = 0xFE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seg[2] = Math.floor(segmentLen / 256); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seg[3] = segmentLen % 256; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seg.set(chunk, 4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+68
to
+77
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const comSegments = []; | |
| for (let i = 0; i < graphMLBytes.length; i += 65533) { | |
| const chunk = graphMLBytes.slice(i, i + 65533); | |
| const segmentLen = chunk.length + 2; | |
| const seg = new Uint8Array(4 + chunk.length); | |
| seg[0] = 0xFF; | |
| seg[1] = 0xFE; | |
| seg[2] = Math.floor(segmentLen / 256); | |
| seg[3] = segmentLen % 256; | |
| seg.set(chunk, 4); | |
| // Prepare a recognizable header for each COM segment carrying GraphML. | |
| const headerBytes = new TextEncoder().encode('GRAPHMLCHUNK'); | |
| const perSegmentMetaLength = 4; // 2 bytes: total segment count, 2 bytes: segment index | |
| const headerLength = headerBytes.length + perSegmentMetaLength; | |
| // JPEG COM segment payload limit is 65533 bytes (length field is 2 bytes, max 65535). | |
| const maxChunkSize = 65533 - headerLength; | |
| const segmentCount = Math.ceil(graphMLBytes.length / maxChunkSize); | |
| const comSegments = []; | |
| for (let segIndex = 0, offset = 0; offset < graphMLBytes.length; segIndex += 1, offset += maxChunkSize) { | |
| const chunk = graphMLBytes.slice(offset, offset + maxChunkSize); | |
| const payloadLength = headerLength + chunk.length; | |
| const segmentLen = payloadLength + 2; // includes these 2 bytes for the length field | |
| const seg = new Uint8Array(4 + payloadLength); | |
| // JPEG COM marker | |
| seg[0] = 0xFF; | |
| seg[1] = 0xFE; | |
| // Big-endian segment length (includes these 2 bytes, excludes marker) | |
| seg[2] = Math.floor(segmentLen / 256); | |
| seg[3] = segmentLen % 256; | |
| // Header: magic string | |
| let writePos = 4; | |
| seg.set(headerBytes, writePos); | |
| writePos += headerBytes.length; | |
| // Header: total segment count (2 bytes, big-endian) | |
| seg[writePos] = Math.floor(segmentCount / 256); | |
| seg[writePos + 1] = segmentCount % 256; | |
| writePos += 2; | |
| // Header: current segment index (0-based, 2 bytes, big-endian) | |
| seg[writePos] = Math.floor(segIndex / 256); | |
| seg[writePos + 1] = segIndex % 256; | |
| writePos += 2; | |
| // Payload: chunk of GraphML bytes | |
| seg.set(chunk, writePos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably too much edge case thinking, the images should not break in normal sharing
No changes required here in my opinion, still will like to know Mentors suggestion!
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { saveAs } from 'file-saver'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from 'react-toastify'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import extractChunks from 'png-chunks-extract'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import textChunk from 'png-chunk-text'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import parser from '../graph-builder/graphml/parser'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { actionType as T } from '../reducer'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -150,7 +152,7 @@ const readFile = async (state, setState, file, fileHandle) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fr = new FileReader(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const projectName = file.name; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ext = file.name.split('.').pop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ext = file.name.split('.').pop()?.toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ext === 'graphml') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fr.onload = (x) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser(x.target.result).then(({ authorName }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -186,6 +188,106 @@ const readFile = async (state, setState, file, fileHandle) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fr.readAsText(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (ext === 'png') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fr.onload = (x) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buffer = new Uint8Array(x.target.result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chunks = extractChunks(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const textChunks = chunks.filter((c) => c.name === 'tEXt').map((c) => textChunk.decode(c)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const graphMLMeta = textChunks.find((c) => c.keyword === 'graphml'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (graphMLMeta && graphMLMeta.text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser(graphMLMeta.text).then(({ authorName }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: T.ADD_GRAPH, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graphML: graphMLMeta.text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileHandle: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileName: file.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authorName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.success('Imported embedded GraphML from Image!'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).catch(() => toast.error('Embedded GraphML inside PNG is invalid.')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('This PNG does not contain an embedded GraphML Workflow.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('Could not parse the PNG file.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileHandle) fr.readAsArrayBuffer(await fileHandle.getFile()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else fr.readAsArrayBuffer(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (ext === 'svg') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fr.onload = (x) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parserDOM = new DOMParser(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const svgDoc = parserDOM.parseFromString(x.target.result, 'image/svg+xml'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const metadata = svgDoc.getElementsByTagName('metadata')[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const graphML = metadata ? metadata.getAttribute('data-graphml') : null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (graphML) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser(graphML).then(({ authorName }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: T.ADD_GRAPH, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graphML, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileHandle: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileName: file.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authorName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.success('Imported embedded GraphML from SVG!'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).catch(() => toast.error('Embedded GraphML inside SVG is invalid.')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('This SVG does not contain an embedded GraphML Workflow.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('Could not parse the SVG file.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileHandle) fr.readAsText(await fileHandle.getFile()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else fr.readAsText(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (ext === 'jpg' || ext === 'jpeg') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fr.onload = (x) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buffer = new Uint8Array(x.target.result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pos = 2; // skip SOI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let graphMLData = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (pos < buffer.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (buffer[pos] !== 0xFF) break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const marker = buffer[pos + 1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (marker === 0xDA) break; // SOS - Start of Scan | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const len = (buffer[pos + 2] * 256) + buffer[pos + 3]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (marker === 0xFE) { // COM Comment segment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graphMLData += new TextDecoder().decode(buffer.slice(pos + 4, pos + 2 + len)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pos += 2 + len; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let graphMLData = ''; | |
| while (pos < buffer.length) { | |
| if (buffer[pos] !== 0xFF) break; | |
| const marker = buffer[pos + 1]; | |
| if (marker === 0xDA) break; // SOS - Start of Scan | |
| const len = (buffer[pos + 2] * 256) + buffer[pos + 3]; | |
| if (marker === 0xFE) { // COM Comment segment | |
| graphMLData += new TextDecoder().decode(buffer.slice(pos + 4, pos + 2 + len)); | |
| } | |
| pos += 2 + len; | |
| } | |
| const comSegments = []; | |
| while (pos < buffer.length) { | |
| if (buffer[pos] !== 0xFF) break; | |
| const marker = buffer[pos + 1]; | |
| if (marker === 0xDA) break; // SOS - Start of Scan | |
| const len = (buffer[pos + 2] * 256) + buffer[pos + 3]; | |
| if (marker === 0xFE) { // COM Comment segment | |
| comSegments.push(buffer.slice(pos + 4, pos + 2 + len)); | |
| } | |
| pos += 2 + len; | |
| } | |
| let graphMLData = ''; | |
| if (comSegments.length > 0) { | |
| const totalLength = comSegments.reduce((sum, seg) => sum + seg.length, 0); | |
| const allBytes = new Uint8Array(totalLength); | |
| let offset = 0; | |
| for (const seg of comSegments) { | |
| allBytes.set(seg, offset); | |
| offset += seg.length; | |
| } | |
| graphMLData = new TextDecoder().decode(allBytes); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed with second follow-up commit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New dependency
cytoscape-svgis GPLv3-licensed (seepackage-lock.jsonentry), while this repo is BSD-3-Clause (LICENSE). This can create a license incompatibility for distribution. Please confirm the project can legally take a GPLv3 dependency or switch to an SVG export approach/library with a permissive license compatible with BSD.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pradeeban Actually, excellent catch by copilot there.
I think the main problem is not cytoscape or even svg as entirety but just this specific plugin cytoscape-svg that I used to add the functionality for svg import export (svg support didn't exist prior to this PR).
cystoscape-svg is GPLv3 licensed and will not be ideal for open source, (we should only stick to MIT licensed and open source).
It's an excellent note to take for me as a contributor to verify the licensing for any new packages we add for some new functionality or some fix. Will keep this in mind for future!
I have fixed this with my first follow-up commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting (and important) catch, indeed!