From e1e56390b857d7da68bd92b7da42b4e5a89487da Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 14 Feb 2023 21:27:56 -0800 Subject: [PATCH] Drive web frontend using ontario (#53) * Drive web frontend using ontario Stop crashes Change default vals Fix upload_image breakage * It works, somewhat --- .gitignore | 2 +- backend/ontario-web/ontario_web/__init__.py | 1 - .../ontario-web/ontario_web/image_manager.py | 6 +- backend/ontario-web/ontario_web/nodes.py | 9 +- backend/ontario-web/ontario_web/processor.py | 4 +- frontend/pictonode-web/package.json | 2 + frontend/pictonode-web/src/api.ts | 30 + .../src/components/nodes/Flow.vue | 47 +- .../src/components/nodes/InnerSwitch.vue | 37 +- .../src/components/nodes/NodeTree.ts | 6 +- .../src/components/nodes/Templates.ts | 90 +- frontend/pictonode-web/src/consts.ts | 5 + .../GIMP 2.99/pictonode/manager.py | 34 +- .../GIMP 2.99/pictonode/pictonode.py | 13 +- .../GIMP 2.99/pictonode/window.py | 4 +- package-lock.json | 3650 ++--------------- 16 files changed, 601 insertions(+), 3339 deletions(-) create mode 100644 frontend/pictonode-web/src/api.ts create mode 100644 frontend/pictonode-web/src/consts.ts diff --git a/.gitignore b/.gitignore index 4b2b7b5..cdc7398 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ output/ node_modules lib build/ -**/__pycache__ \ No newline at end of file +**/__pycache__ diff --git a/backend/ontario-web/ontario_web/__init__.py b/backend/ontario-web/ontario_web/__init__.py index ae7379f..d4336a9 100644 --- a/backend/ontario-web/ontario_web/__init__.py +++ b/backend/ontario-web/ontario_web/__init__.py @@ -75,7 +75,6 @@ def upload_image(): # The body of the request should be an image image = request.files["image"] new_id = im.add_image(image.filename) - os.remove(image.filename) return {"id": new_id} # For /api/process, take the body of the request and process it diff --git a/backend/ontario-web/ontario_web/image_manager.py b/backend/ontario-web/ontario_web/image_manager.py index 1abaf2a..f9b2026 100644 --- a/backend/ontario-web/ontario_web/image_manager.py +++ b/backend/ontario-web/ontario_web/image_manager.py @@ -76,9 +76,6 @@ def add_image(self, path: str) -> int: Adds an image to the image manager. """ - # Get the image size. - image_size = os.path.getsize(path) - # Create a new image ID. image_id = self.__last_image_id self.__last_image_id += 1 @@ -91,6 +88,9 @@ def add_image(self, path: str) -> int: context = ontario.ImageContext() ontario.ImageBuilder(context).load_from_file( path).save_to_file(image_path).process() + + # get the image size + image_size = os.path.getsize(image_path) # Add the image to the image map. image_info = _ImageInfo(image_id, image_path, image_size) diff --git a/backend/ontario-web/ontario_web/nodes.py b/backend/ontario-web/ontario_web/nodes.py index 4cf5a4a..4b9cb14 100644 --- a/backend/ontario-web/ontario_web/nodes.py +++ b/backend/ontario-web/ontario_web/nodes.py @@ -622,6 +622,7 @@ def link( toId: int, toIndex: int, meta: M, + id = None, ) -> Link[T, M]: """ Link two nodes together and return the link object. @@ -637,6 +638,11 @@ def link( # raise ValueError("Input is occupied") link = to_node._linkFrom(from_node, fromIndex, toIndex, meta) + if not id: + id = self.__nextId + self.__nextId += 1 + link.setId(id) + #print(link.getId()) self.__links[link.getId()] = link return link @@ -749,7 +755,8 @@ def deserializePipeline( serializedLink["fromIndex"], serializedLink["to"], serializedLink["toIndex"], - serializedLink.get("metadata", None) + serializedLink.get("metadata", None), + serializedLink.get("id", None), ) if serializedLink.get("defaultValue") is not None: diff --git a/backend/ontario-web/ontario_web/processor.py b/backend/ontario-web/ontario_web/processor.py index 58e6f83..1cfb789 100644 --- a/backend/ontario-web/ontario_web/processor.py +++ b/backend/ontario-web/ontario_web/processor.py @@ -16,13 +16,13 @@ PipelineUnit = Union[ImageBuilder, int] -def process(pipeline: str, images: ImageManager, target: str) -> None: +def process(pipeline, images: ImageManager, target: str) -> None: """ Processes a pipeline. """ # Deserialize from JSON - pipeline = nodes.deserializePipeline(json.loads(pipeline), make_template_table()) + pipeline = nodes.deserializePipeline(pipeline, make_template_table()) # set metadata for all nodes context = ImageContext() diff --git a/frontend/pictonode-web/package.json b/frontend/pictonode-web/package.json index a114862..04c5dae 100644 --- a/frontend/pictonode-web/package.json +++ b/frontend/pictonode-web/package.json @@ -12,12 +12,14 @@ "@mdi/font": "^7.0.96", "@vue-flow/additional-components": "^1.2.4", "@vue-flow/core": "^1.4.1", + "axios": "^1.3.3", "libnode": "file:../../libraries/libnode", "vue": "^3.2.45", "vue-router": "^4.1.6", "vuetify": "^3.0.1" }, "devDependencies": { + "@types/axios": "^0.14.0", "css-loader": "^6.7.2", "sass": "^1.56.1", "sass-loader": "^13.2.0", diff --git a/frontend/pictonode-web/src/api.ts b/frontend/pictonode-web/src/api.ts new file mode 100644 index 0000000..4196a6f --- /dev/null +++ b/frontend/pictonode-web/src/api.ts @@ -0,0 +1,30 @@ +// GNU AGPL v3 License +// Written by John Nunley and Grace Meredith. + +import { API_URL } from "./consts"; + +import axios from "axios"; + +const API = axios.create({ + baseURL: API_URL, + timeout: 10000, +}); + +export function uploadImage(file: File): Promise { + const formData = new FormData(); + formData.append("image", file); + + // Response will be JSON with number field "id" + return API.post("/upload_image", formData, { responseType: "json" }).then( + (response) => response.data.id + ); +} + +export function processPipeline(pipeline: any): Promise { + // The body of the returning request will be an image file. + return API.post( + "/process", + pipeline, + { responseType: "blob" } + ).then((response) => response.data); +} diff --git a/frontend/pictonode-web/src/components/nodes/Flow.vue b/frontend/pictonode-web/src/components/nodes/Flow.vue index 1659483..95795e9 100644 --- a/frontend/pictonode-web/src/components/nodes/Flow.vue +++ b/frontend/pictonode-web/src/components/nodes/Flow.vue @@ -18,6 +18,8 @@ import { nodeToViewFlow, } from "./NodeTree"; import getTemplates from "./Templates"; +import { serializePipeline } from "libnode"; +import { processPipeline } from "../../api"; const pipeline = defaultPipeline(); const elements = pipelineToVueFlow(pipeline); @@ -112,17 +114,44 @@ export default defineComponent({ ctx.fillStyle = "#FFFFFF"; ctx.font = "30px Arial"; ctx.fillText("No output node", 10, 50); + + this.$emit("canvas-update", canvas); } else { - // Get the first input link and get the canvas from it. - const inputLink = outputNode.getInputs()[0]; - const data = inputLink.get(); - if (data.type !== NodeDataType.Image) { - throw new Error("Output node does not have an image input"); + // Parse the tree to a JSON format. + // @ts-ignore + const formatted = serializePipeline(this.pipeline); + + // Replace default values with IDs. + for (const link of formatted.links) { + if ("defaultValue" in link && link.defaultValue) { + // @ts-ignore + link.defaultValue = link.defaultValue.id; + } } - canvas = data.canvas; - } - - this.$emit("canvas-update", canvas); + + formatted.output = outputNode.getId(); + + // Run the API. + processPipeline(formatted).then((image_file) => { + // Conver the image_file `Blob` to a canvas + const img = new Image(); + img.src = URL.createObjectURL(image_file); + + img.onload = () => { + canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d"); + + if (!ctx) { + throw new Error("Could not get 2d context"); + } + + ctx.drawImage(img, 0, 0); + this.$emit("canvas-update", canvas); + }; + }); + } }, onConnect(connection: Connection | Edge) { diff --git a/frontend/pictonode-web/src/components/nodes/InnerSwitch.vue b/frontend/pictonode-web/src/components/nodes/InnerSwitch.vue index a52be5b..d8fa371 100644 --- a/frontend/pictonode-web/src/components/nodes/InnerSwitch.vue +++ b/frontend/pictonode-web/src/components/nodes/InnerSwitch.vue @@ -7,6 +7,7 @@