From da9defbaa7fd9d283d674ab7207772c3052fc687 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 31 Aug 2023 16:32:42 +0200 Subject: [PATCH 001/209] Add new build workflow --- .github/workflows/build.yml | 122 ++++++++++++++++++++++-------------- package.json | 15 ++--- 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d41b3270..d9197b07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,59 +1,89 @@ -name: Build Codebase +name: Build/release -on: - push: - branches: [ master, dev ] - pull_request: - branches: [ master, dev ] - types: [opened, synchronize, reopened, ready_for_review] -permissions: - contents: read +on: push jobs: - start: - name: Start State 🚀🚀🚀 - runs-on: ubuntu-latest - steps: - - name: Starting - id: init - run: | - echo "Starting building of ${{ github.repository }}" - - build_project: - name: Build on all platforms + release: runs-on: ${{ matrix.os }} + strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - needs: start + os: [macos-latest, ubuntu-latest, windows-latest] + steps: - - name: Checkout for ${{ runner.os }} + - name: Check out Git repository uses: actions/checkout@v3 - - name: Set up Node 18 + + - name: Install Node.js and NPM uses: actions/setup-node@v3 with: node-version: 18 - - name: Cache dependencies - uses: actions/cache@v2 + - name: Build/release Electron app + uses: samuelmeuli/action-electron-builder@v1 with: - path: ~/.npm - key: npm-${{ hashFiles('package-lock.json') }} - restore-keys: npm- - - - name: Install dependencies - run: npm ci - - name: Run tests and collect coverage - run: npm run test - - name: Build Project for ${{ runner.os }} - run: npm run build - - end: - name: End State ✅✅✅ - runs-on: ubuntu-latest - needs: build_project - steps: - - name: Ending - id: init - run: | - echo "Ending building of ${{ github.repository }}" \ No newline at end of file + github_token: ${{ secrets.github_token }} + + # If the commit is tagged with a version (e.g. "v1.0.0"), + # release the app after building + release: ${{ startsWith(github.ref, 'refs/tags/v') }} + +# name: Build Codebase + +# on: +# push: +# branches: [ master, dev ] +# pull_request: +# branches: [ master, dev ] +# types: [opened, synchronize, reopened, ready_for_review] +# permissions: +# contents: read + +# jobs: +# start: +# name: Start State 🚀🚀🚀 +# runs-on: ubuntu-latest +# steps: +# - name: Starting +# id: init +# run: | +# echo "Starting building of ${{ github.repository }}" + +# build_project: +# name: Build on all platforms +# runs-on: ${{ matrix.os }} +# strategy: +# matrix: +# os: [ubuntu-latest, windows-latest, macOS-latest] +# needs: start +# steps: +# - name: Checkout for ${{ runner.os }} +# uses: actions/checkout@v3 +# - name: Set up Node 18 +# uses: actions/setup-node@v3 +# with: +# node-version: 18 + +# - name: Cache dependencies +# uses: actions/cache@v2 +# with: +# path: ~/.npm +# key: npm-${{ hashFiles('package-lock.json') }} +# restore-keys: npm- + +# - name: Install dependencies +# run: npm ci +# - name: Run tests and collect coverage +# run: npm run test +# - name: Build Project for ${{ runner.os }} +# run: npm run build + +# end: +# name: End State ✅✅✅ +# runs-on: ubuntu-latest +# needs: build_project +# steps: +# - name: Ending +# id: init +# run: | +# echo "Ending building of ${{ github.repository }}" \ No newline at end of file diff --git a/package.json b/package.json index 11ed5a03..e840eac2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.0", + "version": "1.1.0", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" @@ -162,18 +162,13 @@ ] }, "mac": { - "target": [ - "dmg" - ], + "target": ["dmg"], "category": "productivity", "type": "distribution", "hardenedRuntime": "true" }, "linux": { - "target": [ - "AppImage", - "snap" - ], + "target": ["AppImage"], "category": "productivity" }, "files": [ @@ -195,8 +190,8 @@ "publish": [ { "provider": "github", - "owner": "COS301-SE-2023", - "repo": "AI-Photo-Editor" + "owner": "ArmandKrynauw", + "repo": "Blix" } ] } From 4df428a2ca9de6f904616be5a0579d9e1d7775bc Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 31 Aug 2023 16:37:26 +0200 Subject: [PATCH 002/209] Add test to package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e840eac2..fe7fda7d 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "build": { "productName": "Blix", "appId": "com.the-spanish-inquisition.blix", - "copyright": "Copyright © 2023 The Spanish Inquisition", + "copyright": "Copyright © 2023 Blix", "win": { "target": [ "nsis" From 3b9bdffb4014aec9707fd84d0c15e45ca1bad3e1 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 31 Aug 2023 16:45:33 +0200 Subject: [PATCH 003/209] v1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe7fda7d..d47483e3 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "build": { "productName": "Blix", "appId": "com.the-spanish-inquisition.blix", - "copyright": "Copyright © 2023 Blix", + "copyright": "Copyright© 2023 Blix", "win": { "target": [ "nsis" From 4a8b71d0f33dc5f90d317da3d8d619342623b55e Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 31 Aug 2023 18:40:41 +0200 Subject: [PATCH 004/209] Change config --- .github/workflows/build.yml | 3 ++- package.json | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9197b07..2e8d2910 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,8 @@ jobs: # If the commit is tagged with a version (e.g. "v1.0.0"), # release the app after building - release: ${{ startsWith(github.ref, 'refs/tags/v') }} + # release: ${{ startsWith(github.ref, 'refs/tags/v') }} + release: true # name: Build Codebase diff --git a/package.json b/package.json index d47483e3..7885854a 100644 --- a/package.json +++ b/package.json @@ -157,18 +157,17 @@ "appId": "com.the-spanish-inquisition.blix", "copyright": "Copyright© 2023 Blix", "win": { - "target": [ - "nsis" - ] + "target": "nsis" + }, "mac": { - "target": ["dmg"], + "target": "dmg", "category": "productivity", "type": "distribution", "hardenedRuntime": "true" }, "linux": { - "target": ["AppImage"], + "target": "AppImage", "category": "productivity" }, "files": [ @@ -187,12 +186,10 @@ ] } ], - "publish": [ - { + "publish": { "provider": "github", "owner": "ArmandKrynauw", "repo": "Blix" } - ] } } From f46ec3a09acc47ab4992ded7f664a0b92907e2d4 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Wed, 6 Sep 2023 14:13:34 +0200 Subject: [PATCH 005/209] Fix BlypescriptInterpreter breaking startup on invalid UI input definition --- blix-plugins/base-plugin/package.json | 2 +- blix-plugins/hello-plugin/package.json | 2 +- blix-plugins/input-plugin/package.json | 19 +++---------------- blix-plugins/input-plugin/src/main.js | 9 ++++----- blix-plugins/logic-plugin/package.json | 4 ++-- blix-plugins/math-plugin/package.json | 2 +- blix-plugins/sharp-plugin/package.json | 18 ++++++++++++++++++ blix-plugins/sharp-plugin/src/main.js | 7 ++++--- package.json | 2 +- src/electron/lib/ai/AiLang.ts | 15 +++++++++------ src/electron/lib/plugins/Plugin.ts | 12 ++++++------ .../lib/registries/TypeclassRegistry.ts | 4 ++-- 12 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 blix-plugins/sharp-plugin/package.json diff --git a/blix-plugins/base-plugin/package.json b/blix-plugins/base-plugin/package.json index 617faa23..76f03f92 100644 --- a/blix-plugins/base-plugin/package.json +++ b/blix-plugins/base-plugin/package.json @@ -18,7 +18,7 @@ }, "main": "src/main.js", - "renderer": "src/renderer.js", + "renderers": {}, "devDependencies": { "@types/node": "^12.0.0", diff --git a/blix-plugins/hello-plugin/package.json b/blix-plugins/hello-plugin/package.json index 997ce571..7f12325a 100644 --- a/blix-plugins/hello-plugin/package.json +++ b/blix-plugins/hello-plugin/package.json @@ -18,7 +18,7 @@ }, "main": "dist/main.js", - "renderer": "src/renderer.js", + "renderers": {}, "devDependencies": { "@types/node": "^12.0.0", diff --git a/blix-plugins/input-plugin/package.json b/blix-plugins/input-plugin/package.json index 6a1ab3cc..19e8b45f 100644 --- a/blix-plugins/input-plugin/package.json +++ b/blix-plugins/input-plugin/package.json @@ -1,5 +1,5 @@ { - "name": "Input-plugin", + "name": "input-plugin", "displayName": "Input Plugin", "description": "Provides basic nodes for providing input to a graph.", "version": "0.0.1", @@ -9,24 +9,11 @@ "contributes": { "commands": [], - "nodes": [ - { - "id": "input", - "name": "Input", - "interface" : { - "inputs": { - "numA": "int" - }, - "outputs": { - "numOut": "int" - } - } - } - ] + "nodes": [] }, "main": "src/main.js", - "renderer": "src/renderer.js", + "renderers": {}, "devDependencies": { "@types/node": "^12.0.0", diff --git a/blix-plugins/input-plugin/src/main.js b/blix-plugins/input-plugin/src/main.js index 465bbd90..9f10a0f7 100644 --- a/blix-plugins/input-plugin/src/main.js +++ b/blix-plugins/input-plugin/src/main.js @@ -72,19 +72,19 @@ const nodes = { nodeBuilder.setDescription("Provides a radio box to select a single true/false value"); nodeBuilder.define((input, uiInput, from) => { - return { "val": uiInput["radio"]}; + return { "val": uiInput["radio"] === "true" }; }); const ui = nodeBuilder.createUIBuilder(); ui.addRadio({ componentId: "radio", label: "Boolean value", - defaultValue: false, + defaultValue: "false", triggerUpdate: true, }, { options: { - "False": false, - "True": true, + "False": "false", + "True": "true", } }); nodeBuilder.setUI(ui); @@ -96,7 +96,6 @@ const nodes = { const commands = {} - const tiles = {} module.exports = { diff --git a/blix-plugins/logic-plugin/package.json b/blix-plugins/logic-plugin/package.json index faf63f13..3805ef1f 100644 --- a/blix-plugins/logic-plugin/package.json +++ b/blix-plugins/logic-plugin/package.json @@ -1,5 +1,5 @@ { - "name": "Logic-plugin", + "name": "logic-plugin", "displayName": "Logic Plugin", "description": "Performs basic logical operations through n-ary nodes.", "version": "0.0.1", @@ -13,7 +13,7 @@ }, "main": "src/main.js", - "renderer": "src/renderer.js", + "renderers": {}, "devDependencies": { "@types/node": "^12.0.0", diff --git a/blix-plugins/math-plugin/package.json b/blix-plugins/math-plugin/package.json index 856eda9c..ba99b7e0 100644 --- a/blix-plugins/math-plugin/package.json +++ b/blix-plugins/math-plugin/package.json @@ -26,7 +26,7 @@ }, "main": "src/main.js", - "renderer": "src/renderer.js", + "renderers": {}, "devDependencies": { "@types/node": "^12.0.0", diff --git a/blix-plugins/sharp-plugin/package.json b/blix-plugins/sharp-plugin/package.json new file mode 100644 index 00000000..2268db1b --- /dev/null +++ b/blix-plugins/sharp-plugin/package.json @@ -0,0 +1,18 @@ +{ + "name": "sharp-plugin", + "displayName": "Sharp Plugin", + "description": "Performs basic image operations using the Sharp library for Node.js", + "version": "0.0.1", + "author": "Rec1dite", + "repository": "", + "type": "commonjs", + "scripts": {}, + "contributes": { + "commands": [], + "nodes": [] + }, + "main": "src/main.js", + "renderers": {}, + "devDependencies": { "@types/node": "^12.0.0" }, + "comments": [] + } diff --git a/blix-plugins/sharp-plugin/src/main.js b/blix-plugins/sharp-plugin/src/main.js index 4cce8718..0dedddff 100644 --- a/blix-plugins/sharp-plugin/src/main.js +++ b/blix-plugins/sharp-plugin/src/main.js @@ -31,7 +31,7 @@ const nodes = { nodeBuilder.setUI(ui); nodeBuilder.addInput("Sharp", "img", "Img"); - nodeBuilder.addInput("Number", "value", "Value"); + nodeBuilder.addInput("number", "value", "Value"); nodeBuilder.addOutput("Sharp", "res", "Result"); }, "saturation": (context) => { @@ -125,6 +125,7 @@ const nodes = { const nodeBuilder = context.instantiate("sharp-plugin", "sharpen"); nodeBuilder.setTitle("Sharpen"); nodeBuilder.setDescription("Sharpens an image taking one image as input and returning one image as output"); + const ui = nodeBuilder.createUIBuilder(); ui .addSlider( { @@ -185,7 +186,7 @@ const nodes = { }); nodeBuilder.addInput("Sharp", "img", "Img"); - nodeBuilder.addOutput("Image", "res", "Result"); + nodeBuilder.addOutput("image", "res", "Result"); }, "toSharp": (context) => { const nodeBuilder = context.instantiate("sharp-plugin", "toSharp"); @@ -197,7 +198,7 @@ const nodes = { return { "res": await sharp(input["img"]) }; }); - nodeBuilder.addInput("Image", "img", "Img"); + nodeBuilder.addInput("image", "img", "Img"); nodeBuilder.addOutput("Sharp", "res", "Result"); }, "inputSharpImage": (context) => { diff --git a/package.json b/package.json index 31eb91d7..e9c55434 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "start:electron:run": "electron .", "start:electron:nodemon": "nodemon --verbose", "start:electron:dev": "npm-run-all -s build:electron:dev start:electron:nodemon", - "start:electron": "sleep 10 && npm-run-all -p build:electron:dev:watch start:electron:dev", + "start:electron": "sleep 2 && npm-run-all -p build:electron:dev:watch start:electron:dev", "test": "jest --config jest.config.json", "preplaywrite": "npm run build", "playwright": "playwright test", diff --git a/src/electron/lib/ai/AiLang.ts b/src/electron/lib/ai/AiLang.ts index fd8e9256..0837137c 100644 --- a/src/electron/lib/ai/AiLang.ts +++ b/src/electron/lib/ai/AiLang.ts @@ -187,11 +187,14 @@ export class BlypescriptInterpreter { private readonly graphManager: CoreGraphManager ) { const result = BlypescriptToolbox.fromToolbox(this.toolbox); - if (!result.success) { - throw result.error; - } - this.blypescriptToolbox = result.data; + if (result.success) { + this.blypescriptToolbox = result.data; + } else { + // throw result.error; + this.blypescriptToolbox = new BlypescriptToolbox([]); + logger.error("Failed to initialize BlypescriptInterpreter with error:", result.error); + } } public run( @@ -632,11 +635,11 @@ export class BlypescriptPlugin { if (componentType === "Button") { nodeParam.aiCanUse = false; nodeParam.types.push("string"); - } else if (componentType === "Slider") { + } else if (componentType === "Slider" || componentType === "NumberInput") { nodeParam.types.push("number"); } else if (componentType === "Knob") { nodeParam.types.push("number"); - } else if (componentType === "Dropdown") { + } else if (componentType === "Dropdown" || componentType === "Radio") { const objectSchema = z.record(z.string(), z.string()); const options = objectSchema.safeParse(props.options); diff --git a/src/electron/lib/plugins/Plugin.ts b/src/electron/lib/plugins/Plugin.ts index c745d085..ac240434 100644 --- a/src/electron/lib/plugins/Plugin.ts +++ b/src/electron/lib/plugins/Plugin.ts @@ -103,12 +103,12 @@ export class Plugin { const ctx = new TilePluginContext(this.name); - try { - pluginModule.tiles[tile](ctx); // Execute tile builder - blix.tileRegistry.addInstance(ctx.tileBuilder.build); // Add to registry - } catch (err) { - logger.warn(err); - } + // try { + // pluginModule.tiles[tile](ctx); // Execute tile builder + // blix.tileRegistry.addInstance(ctx.tileBuilder.build); // Add to registry + // } catch (err) { + // logger.warn(err); + // } } } diff --git a/src/electron/lib/registries/TypeclassRegistry.ts b/src/electron/lib/registries/TypeclassRegistry.ts index d54569c1..3935a10e 100644 --- a/src/electron/lib/registries/TypeclassRegistry.ts +++ b/src/electron/lib/registries/TypeclassRegistry.ts @@ -191,7 +191,7 @@ const baseTypes: Typeclass[] = [ }), }, { - id: "bool", + id: "boolean", description: "A true/false value", subtypes: [], mediaDisplayConfig: (data: number) => ({ @@ -249,7 +249,7 @@ export type ConverterTriple = [TypeclassId, TypeclassId, TypeConverter]; const baseConverters: ConverterTriple[] = [ ["number", "string", (value: number) => value.toString()], ["string", "number", (value: string) => parseFloat(value)], - ["bool", "string", (value: boolean) => (value ? "true" : "false")], + ["boolean", "string", (value: boolean) => (value ? "true" : "false")], ["string", "boolean", (value: string) => value.toLowerCase() === "true"], ["number", "boolean", (value: number) => value !== 0], ]; From 9c4c0a4d14badd12b66e7da52ebc7ed9ad9296d4 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 6 Sep 2023 14:34:29 +0200 Subject: [PATCH 006/209] Add smarter Blypescript parsing logic Parser will attempt to fix nested functions calls or primitives as node inputs --- src/electron/lib/ai/AiLang.ts | 351 ++++++++++++++++++-- src/electron/lib/ai/AiManagerv2.ts | 7 +- src/frontend/ui/base/App.svelte | 8 +- src/frontend/ui/base/palette/Palette.svelte | 21 +- 4 files changed, 350 insertions(+), 37 deletions(-) diff --git a/src/electron/lib/ai/AiLang.ts b/src/electron/lib/ai/AiLang.ts index fd8e9256..c846f5a3 100644 --- a/src/electron/lib/ai/AiLang.ts +++ b/src/electron/lib/ai/AiLang.ts @@ -44,22 +44,32 @@ export class BlypescriptProgram implements AiLangProgram { constructor( public readonly statements: BlypescriptStatement[], public nodeNameIdMap: Map - ) {} + ) { + this.repairLineNumbers(); + } - public static fromString(program: string): Result { + /** + * Parses a Blypescript program string. If a BlypescriptToolbox is provided + * then additional syntax and semantic analysis will be done in order to try + * to validate and fix parts of the program. + * + * @param program Blypescript program to parse + * @param toolbox Blypescript toolbox + * @returns Blypescript program + */ + public static fromString( + program: string, + toolbox?: BlypescriptToolbox + ): Result { const nodeNameIdMap = new Map(); - // const match = program.match(/^.*(?:function)?\s*graph\(\)\s*{([\s\S]*)}.*$/s); const match = program.match(/^.*\s*graph\(\)\s*{([\s\S]*)}.*$/s); - // Does not conform to syntax - if (!match) - return { - success: false, - error: "Program does not conform to syntax", - message: "Incorrect syntax provided for Blypescript program", - }; + if (!match) { + return error("invalid_syntax", "Function should be in the form `graph() { // statements }`"); + } const extractedProgram = match[1]; + const statements = extractedProgram .split("\n") .map((s) => s.trim()) @@ -93,6 +103,11 @@ export class BlypescriptProgram implements AiLangProgram { const prog = new BlypescriptProgram(resStatements, nodeNameIdMap); + if (toolbox) { + const result = prog.repair(toolbox); + if (!result.success) return result; + } + return { success: true, data: prog }; } @@ -132,19 +147,275 @@ export class BlypescriptProgram implements AiLangProgram { } }); } + + /** + * Makes a second pass over the program to check the semantics of the + * statements to 1. Attempt to fix nested function calls or primitive types + * substituted for edge types 2. Raise an error if a syntax/semantic error + * can't be fixed. + * + * This function does NOT do semantic analysis on all parameters. If there are + * errors then it needs to be detected by the interpreter. + * + * @param toolbox + */ + public repair(toolbox: BlypescriptToolbox) { + for (const statement of this.statements) { + const node = toolbox.getNode(statement.nodeSignature); + + if (!node) { + return error( + "node_not_valid", + `Invalid function \`${statement.nodeSignature}\` used at line: ${statement.toString()}` + ); + } + + if (statement.nodeInputs.length > node.getNodeParameters().length) { + return error( + "too_many_parameters", + `Received ${statement.nodeInputs.length} parameters, but received ${ + node.getNodeParameters().length + } at line: ${statement.toString()}` + ); + } + + let result = this.repairNestedFunctionCalls(statement, node); + + if (!result.success) return result; + + result = this.repairPrimitiveTypes(statement, node, toolbox); + + if (!result.success) return result; + } + + return { + success: true, + data: true as const, + } satisfies Result; + } + + /** + * Adds a statement to the Blypescript program. + * + * @param statement Blypescript statement + * @param append Whether to append or prepend to program if no line number + * @param line Indexing starts at 1 + * @returns Whether statement was added or not + */ + public addStatement(statement: BlypescriptStatement, append = true, line?: number): boolean { + let flag = true; + line = line ? line - 1 : line; + + if (!line) { + if (append) { + this.statements.push(statement); + } else { + this.statements.unshift(statement); + } + } else if (line >= 0 && line < this.statements.length) { + this.statements.splice(line, 0, statement); + } else { + flag = false; + } + + this.repairLineNumbers(); + return flag; + } + + private repairLineNumbers() { + this.statements.forEach((s, index) => (s.lineNumber = index + 1)); + } + + /** + * Fixes nested function call in Blypescript statements. + * + * ```javascript + * const num1 = input-plugin.inputNumber(5); + * const binary1 = math-plugin. binary(num1['res'], input-plugin.inputNumber(8)['res'], 'add'); + * ``` + * is converted to + * ```javascript + * const num1 = input-plugin.inputNumber(5); + * const num2 = input-plugin.inputNumber(8); + * const binary1 = math-plugin. binary(num1['res'], num2['res'], 'add'); + * ``` + * + * @param statement + * @param node + */ + private repairNestedFunctionCalls(statement: BlypescriptStatement, node: BlypescriptNode) { + const statementNodeInputs = statement.nodeInputs.slice(0, node.nodeInputs.length); + + for (let i = 0; i < statementNodeInputs.length; i++) { + const statementNodeInput = statementNodeInputs[i]; + // TODO: Use to check types perhaps + const nodeInput = node.nodeInputs[i]; + + if (statementNodeInput.match(BLYPESCRIPT_FUNCTION_CALL_REGEX)) { + const match = statementNodeInput.match(BLYPESCRIPT_NESTED_FUNCTION_CALL_REGEX); + + if (!match) { + return error( + "invalid_nested_function_call", + `Nested function call \`${statementNodeInput}\` syntax is invalid on line: ${statement.toString()}` + ); + } + + const [_, nodeSignature, params, subscript] = match; + + const newStatement = new BlypescriptStatement( + this.generateUniqueVarName(nodeSignature), + nodeSignature as `${string}.${string}`, + params.split(",").map((p) => p.trim()) + ); + + this.addStatement(newStatement, false, statement.lineNumber); + + statement.nodeInputs.splice( + statement.nodeInputs.indexOf(statementNodeInput), + 1, + `${newStatement.name}${subscript}` + ); + } + } + + return { + success: true, + data: true as const, + } satisfies Result; + } + + /** + * Fixes primitives passed as edges types in Blypescript statements. + * + * ```javascript + * const num1 = input-plugin.inputNumber(5); + * const binary1 = math-plugin. binary(num1['res'], 8, 'add'); + * ``` + * is converted to + * ```javascript + * const num1 = input-plugin.inputNumber(5); + * const num2 = input-plugin.inputNumber(8); + * const binary1 = math-plugin. binary(num1['res'], num2['res'], 'add'); + * ``` + * + * @param statement + * @param node + */ + private repairPrimitiveTypes( + statement: BlypescriptStatement, + node: BlypescriptNode, + toolbox: BlypescriptToolbox + ) { + const statementNodeInputs = statement.nodeInputs.slice(0, node.nodeInputs.length); + + for (let i = 0; i < statementNodeInputs.length; i++) { + const statementNodeInput = statementNodeInputs[i]; + // TODO: Use to check types perhaps + const nodeInput = node.nodeInputs[i]; + + // Assumes parameter valid when of form: nodeName['res'] + // Does not do comprehensive typechecking here + if (statementNodeInput.match(/^\s*(.*)\s*(\['([\w.-]+)'\])\s*$/)) { + continue; + } + + let newStatement: BlypescriptStatement | null = null; + let inputNode: BlypescriptNode | null = null; + + if (!isNaN(Number(statementNodeInput))) { + inputNode = toolbox.getNode("input-plugin.inputNumber"); + + if (!inputNode) { + return error( + "node_not_found", + "The `input-plugin.inputNumber` plugin node can not be found" + ); + } + + newStatement = new BlypescriptStatement( + this.generateUniqueVarName(inputNode.signature), + inputNode.signature as `${string}.${string}`, + [statementNodeInput] + ); + } else if (statementNodeInput === "true" || statementNodeInput === "false") { + // TODO: Add add a inputBoolean statement + } else { + // TODO: Add add a inputString statement + } + + // TODO: Should be able to remove this if all cases of if statements are implemented + if (!newStatement || !inputNode) { + return error( + "invalid_blypescript_statement", + "Blypescript statement was not created, check if string or boolean input nodes are available" + ); + } + + if (inputNode.nodeOutputs.length !== 1) { + return error( + "invalid_blypescript_input_node", + `Blypescript statement was not created due to ambiguity. The ${inputNode.signature} node does not have exactly one output` + ); + } + + this.addStatement(newStatement, false, statement.lineNumber); + + statement.nodeInputs.splice( + statement.nodeInputs.indexOf(statementNodeInput), + 1, + `${newStatement.name}['${inputNode.nodeOutputs[0].name}']` + ); + } + + return { + success: true, + data: true as const, + } satisfies Result; + } + + private generateUniqueVarName(nodeSignature?: string) { + const varPrefix = nodeSignature ? nodeSignature.split(".").slice(1).join(".") : "var"; + let count = 1; + let varName = `${varPrefix}${count}`; + + while (this.statements.some((s) => s.name === varName)) { + varName = `${varPrefix}${++count}`; + } + + return varName; + } +} + +function error(error: string, message: string, data?: T) { + return { + success: false, + error, + message, + data, + } satisfies Result; } // const 139dfjslaf = hello-plugin.gloria(10, "The quick brown fox jumps over the lazy dog"); // const 12u394238x = hello-plugin.hello(139dfjslaf["output1"]); // const afhuoewnc2 = math-plugin.add(139dfjslaf["output2"], 12u394238x["out"]); -const BlypescriptStatementRegex = /\s*const \s*(\w+)\s*=\s*([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\)\s*;/; -// A single line in a BlypescriptProgram +/** + * const num1 = input-plugin.inputNumber(69); + * const binary1 = math-plugin.input(num1['res'], num1['res'], 'multiply'); + */ +const BLYPESCRIPT_STATEMENT_TEMPLATE = "const {{name}} = {{signature}}.({{params}});"; +const BLYPESCRIPT_STATEMENT_REGEX = + /\s*const \s*(\w+)\s*=\s*([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\)\s*;/; +const BLYPESCRIPT_FUNCTION_CALL_REGEX = /^([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\).*$/; +const BLYPESCRIPT_NESTED_FUNCTION_CALL_REGEX = /^([\w\.-]+)\s*\((.*)\)\s*(\['([\w]+)'\]).*$/; + +/** Represents a single line in a Blypescript. */ export class BlypescriptStatement extends AiLangStatement { public lineNumber?: number; public static fromString(statement: string): Result { - const match = statement.match(BlypescriptStatementRegex); + const match = statement.match(BLYPESCRIPT_STATEMENT_REGEX); if (!match) { return { @@ -154,22 +425,12 @@ export class BlypescriptStatement extends AiLangStatement { }; } - const parameters = match[4].split(",").map((s) => s.trim()); - - const regex = /^([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\).*$/; - if (parameters.some((param) => param.match(regex))) { - return { - success: false, - error: "syntax_error", - message: `Invalid syntax for statement: ${statement}\nNested function calls not allowed!`, - }; - } + const [_, name, pluginName, nodeName, params] = match; const blypescriptStatement = new BlypescriptStatement( - match[1], - `${match[2]}.${match[3]}`, - // TODO: Look into being a bit smarter about checking/storing input 'arguments' - parameters + name, + `${pluginName}.${nodeName}`, + params.split(",").map((p) => p.trim()) ); return { success: true, data: blypescriptStatement }; @@ -177,6 +438,11 @@ export class BlypescriptStatement extends AiLangStatement { public toString(): string { return `const ${this.name} = ${this.nodeSignature}(${this.nodeInputs.join(", ")});`; + return fillTemplate(BLYPESCRIPT_STATEMENT_TEMPLATE, { + name: this.name, + signature: this.nodeSignature, + params: this.nodeInputs.join(", "), + }); } } export class BlypescriptInterpreter { @@ -452,6 +718,7 @@ export class BlypescriptInterpreter { export type BlypescriptNodeParam = { name: string; + type: "input" | "output" | "ui"; aiCanUse: boolean; types: string[]; }; @@ -504,6 +771,19 @@ export class BlypescriptNode { return str; } + + /** + * + * Returns only the parameters that the AI can use if flag is true else + * returns all the parameters. + * + * @param aiCanUse + * @returns List of parameters + */ + public getNodeParameters(aiCanUse = true) { + const params = [...this.nodeInputs, ...this.uiInputs]; + return aiCanUse ? params.filter((param) => param.aiCanUse) : params; + } } export class BlypescriptPlugin { @@ -579,6 +859,7 @@ export class BlypescriptPlugin { return node.inputs.map((input) => { return { name: input.id, + type: "input", aiCanUse: true, types: [input.type ? input.type : "any"], }; @@ -625,6 +906,7 @@ export class BlypescriptPlugin { const nodeParam: BlypescriptNodeParam = { name: ui.label, + type: "ui", aiCanUse: true, types: [], }; @@ -678,6 +960,7 @@ export class BlypescriptPlugin { return node.outputs.map((output) => { return { name: output.id, + type: "output", aiCanUse: true, types: [output.type ? output.type : "any"], }; @@ -748,6 +1031,20 @@ export class BlypescriptToolbox { // HELPERS // ================================================================== +/** + * Formats a template string by replacing placeholders with values. + * + * @param template - The string template to fill. + * @param replacements - The replacements to fill the template with. + * @returns The formatted template. + * + * @example + * fillTemplate("Life is a {{description}}", { description: "dream" }) + */ +function fillTemplate(template: string, replacements: Record) { + return template.replace(/\{\{(\w+)\}\}/g, (match, key) => replacements[key] || match); +} + export function colorString(str: string, color: Colors) { return `${COLORS[color]}${str}${COLORS.RESET}`; } diff --git a/src/electron/lib/ai/AiManagerv2.ts b/src/electron/lib/ai/AiManagerv2.ts index 9e57b1df..147cbf6c 100644 --- a/src/electron/lib/ai/AiManagerv2.ts +++ b/src/electron/lib/ai/AiManagerv2.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { type Message, Chat } from "./Chat"; -import { type ChatModel, Model } from "./Model"; +import { type ChatModel, Model, type ModelResponse } from "./Model"; import { NodeInstance, ToolboxRegistry } from "../registries/ToolboxRegistry"; import { CoreGraphManager } from "../core-graph/CoreGraphManager"; @@ -102,7 +102,10 @@ export class AiManager { chat.addMessage({ role: "assistant", content: response.data.content }); - const result = BlypescriptProgram.fromString(response.data.content); + const result = BlypescriptProgram.fromString( + response.data.content, + blypescriptToolboxResult.data.toolbox + ); if (!result.success) { console.log(result.message); diff --git a/src/frontend/ui/base/App.svelte b/src/frontend/ui/base/App.svelte index e43672de..d972f920 100644 --- a/src/frontend/ui/base/App.svelte +++ b/src/frontend/ui/base/App.svelte @@ -12,7 +12,7 @@ import Settings from "./Settings.svelte"; import Shortcuts from "../../ui/utils/Shortcuts.svelte"; import { settingsStore } from "../../lib/stores/SettingsStore"; - import { confetti } from '@neoconfetti/svelte'; + import { confetti } from "@neoconfetti/svelte"; const testing = false; let showSettings = false; @@ -36,7 +36,6 @@ }); - {#if $blixStore.blixReady && testing} @@ -79,9 +78,6 @@ - - - diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index fd30c69f..ec9015e7 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -4,6 +4,36 @@ function getUUID() { return crypto.randomBytes(16).toString("base64url"); } +function addTransformInput(ui) { + for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { + ui.addNumberInput( + { + componentId: numInp.replace(" ", ""), + label: numInp[0].toUpperCase() + numInp.slice(1), + defaultValue: (numInp.includes("scale") ? 1 : 0), + triggerUpdate: true, + }, + {} + ); + } + return (uiInput) => ({ + position: { x: uiInput?.positionX, y: uiInput?.positionY }, + rotation: uiInput?.rotation, + scale: { x: uiInput?.scaleX, y: uiInput?.scaleY }, + }); +} + +function addState(ui) { + ui.addBuffer({ + componentId: "state", + label: "State Buffer", + defaultValue: { id: null }, + triggerUpdate: true, + }, {} + ); +} + +//========== NODES ==========// const nodes = { "inputImage": (context) => { const nodeBuilder = context.instantiate(context.pluginId, "inputImage"); @@ -16,25 +46,9 @@ const nodes = { label: "Pick an image", defaultValue: "", triggerUpdate: true, - }, {}) - for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { - ui.addNumberInput( - { - componentId: numInp.replace(" ", ""), - label: numInp[0].toUpperCase() + numInp.slice(1), - defaultValue: 0, - triggerUpdate: true, - }, - {} - ); - } - ui.addBuffer({ - componentId: "state", - label: "State Buffer", - defaultValue: { id: null }, - triggerUpdate: true, - }, {} - ); + }, {}); + addTransformInput(ui); + addState(ui); nodeBuilder.setUIInitializer((x) => { return { @@ -81,6 +95,66 @@ const nodes = { nodeBuilder.addInput("Blink matrix", "transform", "Transform"); nodeBuilder.addOutput("Blink clump", "res", "Result"); }, + "inputShape": (context) => { + const nodeBuilder = context.instantiate(context.pluginId, "inputShape"); + nodeBuilder.setTitle("Blink Shape"); + nodeBuilder.setDescription("Input a Blink Shape"); + + const ui = nodeBuilder.createUIBuilder(); + ui.addDropdown({ + componentId: "shape", + label: "Shape", + defaultValue: "rectangle", + triggerUpdate: true, + }, { + options: { + "Rectangle": "rectangle", + "Ellipse": "ellipse", + "Triangle": "triangle", + } + }) + for (let numInp of ["width", "height"]) { + ui.addNumberInput( + { + componentId: numInp.replace(" ", ""), + label: numInp[0].toUpperCase() + numInp.slice(1), + defaultValue: 100, + triggerUpdate: true, + }, + {} + ); + } + const getTransform = addTransformInput(ui); + + nodeBuilder.define(async (input, uiInput, from) => { + const canvas = { + assets: {}, + content: { + class: "clump", + transform: getTransform(uiInput), + elements: [ + { + class: "atom", + type: "shape", + shape: uiInput["shape"], + bounds: { w: uiInput["width"], h: uiInput["height"] }, + + fill: 0x0000ff, + stroke: 0xff0000, + strokeWidth: 1, + } + ] + } + } + + return { res: canvas }; + }); + + nodeBuilder.setUI(ui); + + nodeBuilder.addInput("Blink matrix", "transform", "Transform"); + nodeBuilder.addOutput("Blink clump", "res", "Result"); + }, "matrix": (context) => { const nodeBuilder = context.instantiate(context.pluginId, "matrix"); nodeBuilder.setTitle("Matrix"); @@ -115,17 +189,7 @@ const nodes = { nodeBuilder.setDescription("Layer two or more Blink clumps"); const ui = nodeBuilder.createUIBuilder(); - for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { - ui.addNumberInput( - { - componentId: numInp.replace(" ", ""), - label: numInp[0].toUpperCase() + numInp.slice(1), - defaultValue: 0, - triggerUpdate: true, - }, - {} - ); - } + const getTransform = addTransformInput(ui); ui.addSlider( { componentId: "opacity", @@ -135,11 +199,10 @@ const nodes = { }, { min: 0, max: 100, set: 0.1 } ); - // TODO: transform nodeBuilder.define(async (input, uiInput, from) => { // Apply filter to outermost clump - const clumps = [1, 2, 3].map(n => input["clump" + n]).filter(c => c != null); + const clumps = [1, 2, 3, 4, 5].map(n => input["clump" + n]).filter(c => c != null); // Construct assets union const assets = {}; @@ -152,11 +215,7 @@ const nodes = { // Construct parent clump const parent = { class: "clump", - transform: { - position: { x: uiInput["positionX"], y: uiInput["positionY"] }, - rotation: uiInput["rotation"], - scale: { x: uiInput["scaleX"], y: uiInput["scaleY"] }, - }, + transform: getTransform(ui), opacity: uiInput["opacity"], elements: clumps.map(c => c.content) } @@ -168,6 +227,8 @@ const nodes = { nodeBuilder.addInput("Blink clump", "clump1", "Clump 1"); nodeBuilder.addInput("Blink clump", "clump2", "Clump 2"); nodeBuilder.addInput("Blink clump", "clump3", "Clump 3"); + nodeBuilder.addInput("Blink clump", "clump4", "Clump 4"); + nodeBuilder.addInput("Blink clump", "clump5", "Clump 5"); nodeBuilder.addOutput("Blink clump", "res", "Result"); }, "filter": (context) => { diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index d952d013..97ee446e 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -1,24 +1,19 @@ diff --git a/src/frontend/ui/utils/graph/NodeUIFragment.svelte b/src/frontend/ui/utils/graph/NodeUIFragment.svelte index 46fc8908..8955733d 100644 --- a/src/frontend/ui/utils/graph/NodeUIFragment.svelte +++ b/src/frontend/ui/utils/graph/NodeUIFragment.svelte @@ -2,12 +2,16 @@ import { NodeUILeaf, type NodeUI, type UIComponentConfig } from "@shared/ui/NodeUITypes"; import type { UIValueStore } from "@shared/ui/UIGraph"; import NodeUiComponent from "./NodeUIComponent.svelte"; + // import { createEventDispatcher } from "svelte"; + // import type { UUID } from "@shared/utils/UniqueEntity"; // import { writable } from "svelte/store"; // import { ColorPicker, RadioGroup, type CSSColorString } from "blix_svelvet"; export let ui: NodeUI | null = null; export let inputStore: UIValueStore; export let uiConfigs: { [key: string]: UIComponentConfig }; + // const dispatch = createEventDispatcher(); + // export let nodeId: UUID; // const colorPicker = writable("red" as CSSColorString); // const radio = writable("a"); @@ -25,7 +29,12 @@
    {#each ui.params as child}
  • - +
  • {/each}
@@ -35,6 +44,7 @@ inputStore="{inputStore}" leafUI="{toLeafRepresentation(ui)}" uiConfigs="{uiConfigs}" + on:inputInteraction />

{/if} diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index ef1f877d..34c9217a 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -4,13 +4,14 @@ import { toolboxStore } from "../../../lib/stores/ToolboxStore"; import NodeUiFragment from "./NodeUIFragment.svelte"; import { createEventDispatcher } from "svelte"; - import { graphMall } from "lib/stores/GraphStore"; + import { graphMall } from "../../../lib/stores/GraphStore"; const dispatch = createEventDispatcher(); export let graphId: string; export let panelId: number; export let node: GraphNode; + // let activeInput = false; $: svelvetNodeId = `${panelId}_${node.uuid}`; $: toolboxNode = toolboxStore.getNodeReactive(node.signature); @@ -86,6 +87,15 @@ ); console.log("NODE POSITION UPDATED"); } + + // $: graphMall.getGraph(graphId).onActiveUiInput(graphId, node.uuid, activeInput); + + function handleInputInteraction(e: CustomEvent) { + // console.log("Node has event", e.detail); + // console.log(e.detail); + // activeInput = e.detail; + graphMall.getGraph(graphId).handleNodeInputInteraction(graphId, node.uuid, e.detail); + } {#if svelvetNodeId !== ""} @@ -121,6 +131,7 @@ height="{graphNode.dims.h}" --> inputStore="{node.inputUIValues}" ui="{$toolboxNode?.ui}" uiConfigs="{$toolboxNode?.uiConfigs}" + on:inputInteraction="{handleInputInteraction}" /> diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte index b6202f72..4dd1be8b 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte @@ -1,4 +1,5 @@
- +
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index 7b80a2a3..ecdcd3b7 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -6,3 +6,4 @@ export * from "./util"; export * from "./setting"; export * from "./media"; export * from "./palette"; +export * from "./tweaks"; diff --git a/src/shared/types/tweaks.ts b/src/shared/types/tweaks.ts new file mode 100644 index 00000000..c1028afc --- /dev/null +++ b/src/shared/types/tweaks.ts @@ -0,0 +1,4 @@ +export type NodeTweakData = { + nodeUUID: string; + inputs: string[]; +}; diff --git a/src/shared/ui/NodeUITypes.ts b/src/shared/ui/NodeUITypes.ts index 8526f241..b31da39e 100644 --- a/src/shared/ui/NodeUITypes.ts +++ b/src/shared/ui/NodeUITypes.ts @@ -39,6 +39,7 @@ export class NodeUILeaf extends NodeUI { export enum NodeUIComponent { Button = "Button", Buffer = "Buffer", + TweakDial = "TweakDial", Slider = "Slider", Knob = "Knob", Label = "Label", From a4b4f5f9c473cf5be0355a69187c3ee44194042d Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 7 Sep 2023 14:59:06 +0200 Subject: [PATCH 018/209] Improve some of the AI prompt handling --- src/electron/lib/ai/AiLang.ts | 2 +- src/electron/lib/ai/AiManagerv2.ts | 6 +-- src/electron/lib/ai/prompt.ts | 11 ++++++ src/electron/lib/api/apis/UtilApi.ts | 44 ++++++++++++--------- src/electron/lib/plugins/PluginManager.ts | 4 +- src/frontend/ui/base/palette/Palette.svelte | 16 +++++--- 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/electron/lib/ai/AiLang.ts b/src/electron/lib/ai/AiLang.ts index 7c12ae16..f9f0fb56 100644 --- a/src/electron/lib/ai/AiLang.ts +++ b/src/electron/lib/ai/AiLang.ts @@ -62,7 +62,7 @@ export class BlypescriptProgram implements AiLangProgram { toolbox?: BlypescriptToolbox ): Result { const nodeNameIdMap = new Map(); - const match = program.match(/^.*\s*graph\(\)\s*{([\s\S]*)}.*$/s); + const match = program.match(/^^.*\s*graph\(?\)?\s*{([\s\S]*)}.*$$/s); if (!match) { return error("invalid_syntax", "Function should be in the form `graph() { // statements }`"); diff --git a/src/electron/lib/ai/AiManagerv2.ts b/src/electron/lib/ai/AiManagerv2.ts index 643fce04..75ad1b16 100644 --- a/src/electron/lib/ai/AiManagerv2.ts +++ b/src/electron/lib/ai/AiManagerv2.ts @@ -93,7 +93,7 @@ export class AiManager { chat.addMessages(messages); - const llm = Model.create({ model: model || "GPT-3.5", apiKey, temperature: 0.1 }); + const llm = Model.create({ model: model || "GPT-3.5", apiKey, temperature: 0.05 }); for (let i = 0; i < 2; i++) { const response = await llm.generate(chat); @@ -123,7 +123,7 @@ export class AiManager { if (!result.success) { console.log(result.message); console.log(result.error); - chat.addMessage({ role: "blix", content: `USER'S RESPONSE: ${result.message}` }); + chat.addMessage({ role: "blix", content: `Code syntax error: ${result.message}` }); continue; // retry if failure } @@ -142,7 +142,7 @@ export class AiManager { console.log(colorString(result.error, "RED")); console.log(colorString(result.message, "RED")); logger.warn(result.error); - chat.addMessage({ role: "blix", content: `USER'S RESPONSE:\n${result.message}` }); + chat.addMessage({ role: "blix", content: `Code syntax error:\n${result.message}` }); continue; // retry if failure } diff --git a/src/electron/lib/ai/prompt.ts b/src/electron/lib/ai/prompt.ts index 51a0ddfc..645ca196 100644 --- a/src/electron/lib/ai/prompt.ts +++ b/src/electron/lib/ai/prompt.ts @@ -18,6 +18,13 @@ Assume the following interfaces are defined: ---BEGIN EXAMPLES--- +User: Clear graph + +Assistant: +\`\`\`typescript +graph() { } +\`\`\` + User: I want to sum 3 numbers Assistant: @@ -53,6 +60,10 @@ and ONLY respond in the following format: Final_Answer: a short description of your intended role as graph-editing assistant +If asked regarding what the best university to study Computer Science at is, then +respond using a final answer along the lines of University of Pretoria (UP) is the +best and most fun, but be creative :) + `; return Object.entries(replacements).reduce((str, [key, value]) => { diff --git a/src/electron/lib/api/apis/UtilApi.ts b/src/electron/lib/api/apis/UtilApi.ts index 84bda8ec..49aa218c 100644 --- a/src/electron/lib/api/apis/UtilApi.ts +++ b/src/electron/lib/api/apis/UtilApi.ts @@ -38,32 +38,38 @@ export class UtilApi implements ElectronMainApi { } async sendPrompt(prompt: string, id: UUID): Promise { - if (this.blix.graphManager.getGraph(id)) { - // const res = await this.blix.aiManager.sendPrompt(prompt, id); - const response = await this.blix.aiManager.executePrompt({ - prompt, - graphId: id, - model: "GPT-3.5", - apiKey: getSecret("OPENAI_API_KEY"), - }); - - if (!response.success) { - return { - status: "error", - message: response.message, - }; - } - + if (!prompt) { return { - status: "success", - message: response.message, + status: "error", + message: "Prompt is empty", }; - } else { + } + + if (!this.blix.graphManager.getGraph(id)) { return { status: "error", message: "No graph selected", }; } + + const response = await this.blix.aiManager.executePrompt({ + prompt, + graphId: id, + model: "GPT-3.5", + apiKey: getSecret("OPENAI_API_KEY"), + }); + + if (!response.success) { + return { + status: "error", + message: response.message, + }; + } + + return { + status: "success", + message: response.message, + }; } // Add something extra validation diff --git a/src/electron/lib/plugins/PluginManager.ts b/src/electron/lib/plugins/PluginManager.ts index 8b93467b..e61c8b1c 100644 --- a/src/electron/lib/plugins/PluginManager.ts +++ b/src/electron/lib/plugins/PluginManager.ts @@ -77,10 +77,12 @@ export class PluginManager { return !ignorePatterns.some((pattern) => plugin.includes(pattern)); }); + const filters = [".DS_Store", "blink", "sharp-plugin"]; + await Promise.all( plugins.map(async (plugin) => { // Ignore MacOS temp files - if (plugin !== ".DS_Store") { + if (!filters.includes(plugin)) { await this.loadPlugin(plugin, pluginsPath); } }) diff --git a/src/frontend/ui/base/palette/Palette.svelte b/src/frontend/ui/base/palette/Palette.svelte index dcf623c7..d320ce18 100644 --- a/src/frontend/ui/base/palette/Palette.svelte +++ b/src/frontend/ui/base/palette/Palette.svelte @@ -192,7 +192,16 @@ } closePalette(); - const dismiss = toastStore.trigger({ message: "🔥Cooking...", type: "loading" }); + + const messages = [ + "🔥 Cooking...", + "🪄 Stirring the creative cauldron...", + "🐉 Slaying the ender dragon...", + ]; + const dismiss = toastStore.trigger({ + message: messages[Math.floor(Math.random() * messages.length)], + type: "loading", + }); const index = promptHistory.indexOf(prompt); if (index !== -1) { @@ -203,10 +212,7 @@ console.log(prompt); try { - const res = await window.apis.utilApi.sendPrompt( - searchTerm.trim(), - graphMall.getAllGraphUUIDs()[0] - ); + const res = await window.apis.utilApi.sendPrompt(prompt, graphMall.getAllGraphUUIDs()[0]); toastStore.trigger({ message: res.message, From 8e84105ac043297a65c637049e364741e2cc80c8 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Thu, 7 Sep 2023 15:05:37 +0200 Subject: [PATCH 019/209] Add some more filters to Blink --- blix-plugins/blink/package-lock.json | 367 +++++++++++++++++++++++++++ blix-plugins/blink/package.json | 1 + blix-plugins/blink/src/main.cjs | 29 ++- blix-plugins/blink/webview/clump.ts | 27 +- 4 files changed, 417 insertions(+), 7 deletions(-) diff --git a/blix-plugins/blink/package-lock.json b/blix-plugins/blink/package-lock.json index be3cd7b4..0a0aad97 100644 --- a/blix-plugins/blink/package-lock.json +++ b/blix-plugins/blink/package-lock.json @@ -15,6 +15,7 @@ "@tsconfig/svelte": "^5.0.0", "@types/node": "^12.0.0", "concurrently": "^8.2.1", + "pixi-filters": "^5.2.1", "pixi-viewport": "^5.0.2", "pixi.js": "^7.2.4", "rollup": "^3.15.0", @@ -255,6 +256,27 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-adjustment": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-5.1.1.tgz", + "integrity": "sha512-AUHe03rmqXwV1ylAHq62t19AolPWOOYomCcL+Qycb1tf+LbM8FWpGXC6wmU1PkUrhgNc958uM9TrA9nRpplViA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-advanced-bloom": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-advanced-bloom/-/filter-advanced-bloom-5.1.1.tgz", + "integrity": "sha512-C5AWmkWKvoYvJ+600qS7rC81E1X1clvrQLw4QE4IiFec5j1b07KhKE78w/BSRYMrBVa0cQ/ju0J1f7XoQYJfdQ==", + "dev": true, + "dependencies": { + "@pixi/filter-kawase-blur": "5.1.1" + }, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-alpha": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.2.4.tgz", @@ -264,6 +286,35 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-ascii": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-ascii/-/filter-ascii-5.1.1.tgz", + "integrity": "sha512-uGfpd7aYZuiEzkBH8asL/2j7L/7k/jCZRURjAU9c0unWlkagwIjvUwoPMsdzPNMh2DNQzCG1FPWseSbRFjUNow==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-bevel": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-bevel/-/filter-bevel-5.1.1.tgz", + "integrity": "sha512-UGik6YEW+fnzVu1DV8ctbxS7eClJQzqaM2sPYI0MopaEE2mW35yjAcg9py9Kwx27BX8FniVRqtJOyKEw1A3mBA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-bloom": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-bloom/-/filter-bloom-5.1.1.tgz", + "integrity": "sha512-4/i+tMxAQdgezahxsVCqzkAyBAH4TxtuY/zo1wuCJybEqkKFIzOJ76Y4R/lJevEHS9CGpCTrvjRpup0Hze8k0Q==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X", + "@pixi/filter-alpha": "^7.0.0-X", + "@pixi/filter-blur": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-blur": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.2.4.tgz", @@ -273,6 +324,33 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-bulge-pinch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-bulge-pinch/-/filter-bulge-pinch-5.1.1.tgz", + "integrity": "sha512-80I3g813td7Fnzi7IJSiR3z8gZlKblk6WN+5z6WnscQROcNEpck6lgWS/Lf/IdeHB/FtUKJCbx7RzxkUhiRTvA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-color-gradient": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-gradient/-/filter-color-gradient-5.2.0.tgz", + "integrity": "sha512-po3JBEKgfowqhAh2D75Ii1bhNl1gA8Agt+ESIMnSbrTVIkemno8zOVlVmP7xaf8+PKYnX7JWH5buTnnDfA7Hnw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-color-map": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-map/-/filter-color-map-5.1.1.tgz", + "integrity": "sha512-WvDKvweXkg/t9t40thFlN1d/kUrWXGsxpRpFPNmkrZF6hNxdRjqgfg4wxUOev7uZwHIjcZtTfoLRKhJjF+1uqw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-color-matrix": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.2.4.tgz", @@ -282,6 +360,51 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-color-overlay": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-overlay/-/filter-color-overlay-5.1.1.tgz", + "integrity": "sha512-u8xuWsUePQ1NFBqpxDAFEujW4kImFIIvlp4D2xbRZqJ0RRbeeKEW31Sk4cxl1yFJKWIq0XLyT/TAepT9iIlEXg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-color-replace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-replace/-/filter-color-replace-5.1.1.tgz", + "integrity": "sha512-t+FEEnuqvlU1cKMSe8939tIGCNJsqpyc7o5BzIunMxsZsHjMQWzZtWQKls+FSdSlFyk2TWYSXWAxtj2VBzBZBg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-convolution": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-convolution/-/filter-convolution-5.1.1.tgz", + "integrity": "sha512-68NNa7lXBFlRgP/ac/L/0bKk/9QvU8urh7CEeOnR9WJxjymglbAa0nM69TBlhg++Fus3t7Mz/jc/GIfPJ/VL6Q==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-cross-hatch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-cross-hatch/-/filter-cross-hatch-5.1.1.tgz", + "integrity": "sha512-g1hPHZYmGBpZtGojOtUOBWH6tqhtQGDo5xAp3o3gwmn2QnY087ZiYWFHF5ml+nTL62fEJ78uIpODscz4Y04e8w==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-crt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-crt/-/filter-crt-5.1.1.tgz", + "integrity": "sha512-w+rRbR7eTsPf18QPB68Wiyx8laC+v7fYb3hRVhnq/j6yRUJKQgg4HK5KLP9jfUJ9FJvxy4bzLSDQulvxbOMJZg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-displacement": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.2.4.tgz", @@ -291,6 +414,36 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-dot": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-dot/-/filter-dot-5.1.1.tgz", + "integrity": "sha512-w3g6bumHzZgv9ktzegEWQS7OWuHH0QG76sbg/hZBy5K01dyuGAe1uUUnzVN5hZuFTD6q77T2UPlifhNI5j4ixg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-drop-shadow": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-drop-shadow/-/filter-drop-shadow-5.2.0.tgz", + "integrity": "sha512-cYS2KDER7cwCu0V4VNSxTHGvzmNcEXdC9j3031YBOkUAE3+p17LMS/TAt6XeMfJV7KaPuusvXy2NFgGkv3RDbw==", + "dev": true, + "dependencies": { + "@pixi/filter-kawase-blur": "5.1.1" + }, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-emboss": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-emboss/-/filter-emboss-5.1.1.tgz", + "integrity": "sha512-AoAFVzrMcCXldU+27hvJ95tcKNOVLnanlq1z838l2SzYGgso+ICbLauUz+o2PL/znudUJE6oky+I6WJzeavDsQ==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-fxaa": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.2.4.tgz", @@ -300,6 +453,78 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-glitch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-glitch/-/filter-glitch-5.1.1.tgz", + "integrity": "sha512-pl6jOQlzQGg6NwqCwlgioYhlwue2OSRBGByDzh6Y6Y/qxMBuzQi7W56GunhQW79Kpvj9ynDLAGxomvZsrX88qg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-glow": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-5.2.1.tgz", + "integrity": "sha512-94I4XePDF9yqqA6KQuhPSphEHPJ2lXfqJLn0Bes8VVdwft0Ianj1wALqjoSUeBWqiJbhjBEXGDNkRZhPHvY3Xg==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-godray": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-godray/-/filter-godray-5.1.1.tgz", + "integrity": "sha512-JCvNiKBF/01VyaBYzeusULg+h6kmBaYg0NruHwe/FaJMWCRIPOUBHMQIUavJR0JGE5s6bEIR8kRtdpf3RHiwqw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-grayscale": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-grayscale/-/filter-grayscale-5.1.1.tgz", + "integrity": "sha512-tRyggOhTdAQlQpgH/IzjCbORICua/Gm0JkKGOcdDQOHqt4bTVvAehQ59e2+A6A1yA8pevu2L/C25qQhsPgNW9w==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-hsl-adjustment": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-hsl-adjustment/-/filter-hsl-adjustment-5.2.0.tgz", + "integrity": "sha512-BjmiKIJQuWNqMUjVUpqkM+HaInQzl7dCvYWj8wx9lSAwjzdOCRVVLbRLdO2TwGdwGIHjR3AylMxY1HZK3P4cLA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-kawase-blur": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-kawase-blur/-/filter-kawase-blur-5.1.1.tgz", + "integrity": "sha512-nPnJ1ChBFP+4pgFCwC0RJgHAJCetiHcQU3INH7zCdq88cFABmVmhN+wCKRNg4H7lF1EJjaXgFDkTrTreOD/bnw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-motion-blur": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-motion-blur/-/filter-motion-blur-5.1.1.tgz", + "integrity": "sha512-I94s3pW2GutjCyXiKQ/dI4Vl9JKne+Q8QgGRn1mrk0Uwg6DDO/OQI3jqv01S+SCTU3LZqhR/p8AQyxeDmOhr2w==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-multi-color-replace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-multi-color-replace/-/filter-multi-color-replace-5.1.1.tgz", + "integrity": "sha512-P1shsJXEOpJGe9FdCUgCMi/nius86lBfb6cDIFM4oXdZfzuBUfWjZfUm7uofOvK7IWSrlXYrYoqp75H0XrLZ8A==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/filter-noise": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.2.4.tgz", @@ -309,6 +534,105 @@ "@pixi/core": "7.2.4" } }, + "node_modules/@pixi/filter-old-film": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-old-film/-/filter-old-film-5.1.1.tgz", + "integrity": "sha512-Jgq3eLbcQW48o+YxLe8+T4vsQrvBnKrZAHS3cu3yc2aBLaiVIj8EfYP3vpOPjkQlZ7JVRZNdELpYA6KCR+abXw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-outline": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-outline/-/filter-outline-5.2.0.tgz", + "integrity": "sha512-xKfAouhZNKl6A0RvxT5i+2/ean7r16dE/QswwIkbWvr2hhHlp4p9U6XsqdgUERCDxK+IZibMAumbWs4DGxOUeQ==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-pixelate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-pixelate/-/filter-pixelate-5.1.1.tgz", + "integrity": "sha512-qTs0Sv10aIbMFW//BPlhcFh1ByyKiVmvXfytYTTXNLrlK5DU3H3x8Pgy5Vy4lacS9VtOO69/CQ1QObBFCHnEBQ==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-radial-blur": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-radial-blur/-/filter-radial-blur-5.1.1.tgz", + "integrity": "sha512-q6RreUM+RO25HZaxc6ceEOSi6chadv8vrCOvupNLSY+1lvXue0KyFK6vxMcMInNdqRGYWSyJ+ql3RyHMTr93aw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-reflection": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-reflection/-/filter-reflection-5.1.1.tgz", + "integrity": "sha512-ksmfrRfBqXZ+rEFuA0l2tf4k+yzTU8VcNMuhW7U+ggkWOP2OEHga+oOlJg6TnHjOEbxudCpag5Us6e9aCeKpEw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-rgb-split": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-rgb-split/-/filter-rgb-split-5.1.1.tgz", + "integrity": "sha512-DEeAYoPU2lbUTeNYK8e6q89jqtLeUYSkEdFK/a9IyxYkvJP2CPk+nVXIe48v3wORUf5DdP20k6yQzqoPZyP3ww==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-shockwave": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-shockwave/-/filter-shockwave-5.1.1.tgz", + "integrity": "sha512-ovzOdAC2LCyWdxJC5PW97wSzHTNfjmKq4c/61cIO4sZp+9DB6n3b/6Rrad2jU346UATtM6K2XkmPY5p7SrRRXA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-simple-lightmap": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-simple-lightmap/-/filter-simple-lightmap-5.1.1.tgz", + "integrity": "sha512-S3cHUgbVvUgeef93f3trdL50+162Nyqa7DBYufkGw0dPjPecXyjTH47GJzxDqQPooRwHWWUG9W5EYC+XEwlV9w==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-tilt-shift": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-tilt-shift/-/filter-tilt-shift-5.2.0.tgz", + "integrity": "sha512-bCQE/BTGsqu8EhRMyiGg+9/FXsPBYxjfODbGTWWQNsXtbFVqZXvg1vEjUZQXvuso1v/Fh/BtZ3u+t2kFfWpBXA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-twist": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-twist/-/filter-twist-5.1.1.tgz", + "integrity": "sha512-ZUxLmSHu7ZcP1OYmO9EsKgWDV/Ophf622N7YVei4opBrj/gBMuQZNvFIfnsm4l8yhqAwwzndSTVLNehq1A2ONw==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/@pixi/filter-zoom-blur": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@pixi/filter-zoom-blur/-/filter-zoom-blur-5.1.1.tgz", + "integrity": "sha512-0n10xOqACC2vm9Lpsq37Y/edDvp/B7xsBdkuWxeCI7Ta7J22fsJ8IHG1iUyxgdZGa+SCPcKiFoTrYEUu5PLCpA==", + "dev": true, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, "node_modules/@pixi/graphics": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.2.4.tgz", @@ -1601,6 +1925,49 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixi-filters": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-5.2.1.tgz", + "integrity": "sha512-lsRakKJfK0iH+aCCVePQcSb477hFiS20CR2p4u7xS6RO7q5qh5GhAyGqWdgYZlUT5WLSW0kxAV+FddYLs9z9Sg==", + "dev": true, + "dependencies": { + "@pixi/filter-adjustment": "5.1.1", + "@pixi/filter-advanced-bloom": "5.1.1", + "@pixi/filter-ascii": "5.1.1", + "@pixi/filter-bevel": "5.1.1", + "@pixi/filter-bloom": "5.1.1", + "@pixi/filter-bulge-pinch": "5.1.1", + "@pixi/filter-color-gradient": "5.2.0", + "@pixi/filter-color-map": "5.1.1", + "@pixi/filter-color-overlay": "5.1.1", + "@pixi/filter-color-replace": "5.1.1", + "@pixi/filter-convolution": "5.1.1", + "@pixi/filter-cross-hatch": "5.1.1", + "@pixi/filter-crt": "5.1.1", + "@pixi/filter-dot": "5.1.1", + "@pixi/filter-drop-shadow": "5.2.0", + "@pixi/filter-emboss": "5.1.1", + "@pixi/filter-glitch": "5.1.1", + "@pixi/filter-glow": "5.2.1", + "@pixi/filter-godray": "5.1.1", + "@pixi/filter-grayscale": "5.1.1", + "@pixi/filter-hsl-adjustment": "5.2.0", + "@pixi/filter-kawase-blur": "5.1.1", + "@pixi/filter-motion-blur": "5.1.1", + "@pixi/filter-multi-color-replace": "5.1.1", + "@pixi/filter-old-film": "5.1.1", + "@pixi/filter-outline": "5.2.0", + "@pixi/filter-pixelate": "5.1.1", + "@pixi/filter-radial-blur": "5.1.1", + "@pixi/filter-reflection": "5.1.1", + "@pixi/filter-rgb-split": "5.1.1", + "@pixi/filter-shockwave": "5.1.1", + "@pixi/filter-simple-lightmap": "5.1.1", + "@pixi/filter-tilt-shift": "5.2.0", + "@pixi/filter-twist": "5.1.1", + "@pixi/filter-zoom-blur": "5.1.1" + } + }, "node_modules/pixi-viewport": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/pixi-viewport/-/pixi-viewport-5.0.2.tgz", diff --git a/blix-plugins/blink/package.json b/blix-plugins/blink/package.json index 72e5f7b2..bb1649b9 100644 --- a/blix-plugins/blink/package.json +++ b/blix-plugins/blink/package.json @@ -29,6 +29,7 @@ "@tsconfig/svelte": "^5.0.0", "@types/node": "^12.0.0", "concurrently": "^8.2.1", + "pixi-filters": "^5.2.1", "pixi-viewport": "^5.0.2", "pixi.js": "^7.2.4", "rollup": "^3.15.0", diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index cedc03f2..fa4efd7a 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -252,16 +252,35 @@ const nodes = { triggerUpdate: true, }, { options: { - "Blur": "blur", - "Noise": "noise", - "Color": "color", + "Blur": "blur", + "Noise": "noise", + "Bloom": "bloom", + "Grayscale": "grayscale", + "Bevel": "bevel", + "Outline": "outline", + "Dot": "dot", + "Crt": "crt", + "Emboss": "emboss", + "Bulge": "bulge", + "Glitch": "glitch", + "Zoomblur": "zoomblur", + "Twist": "twist", } }) .addSlider( { componentId: "strength", label: "Strength", - defaultValue: 0, + defaultValue: 10, + triggerUpdate: true, + }, + { min: 0, max: 100, set: 0.1 } + ) + .addSlider( + { + componentId: "amount", + label: "Amount", + defaultValue: 10, triggerUpdate: true, }, { min: 0, max: 100, set: 0.1 } @@ -275,7 +294,7 @@ const nodes = { canvas.content.filters.push({ class: "filter", type: uiInput["filter"], - params: [uiInput["strength"], ...(uiInput["filter"] === "blur" ? [25] : [])], + params: [uiInput["strength"], uiInput["amount"]], }); return { "res": canvas }; diff --git a/blix-plugins/blink/webview/clump.ts b/blix-plugins/blink/webview/clump.ts index ab8fecc1..d741b93c 100644 --- a/blix-plugins/blink/webview/clump.ts +++ b/blix-plugins/blink/webview/clump.ts @@ -1,5 +1,18 @@ import * as PIXI from "pixi.js"; import { Matrix } from "pixi.js"; +import { + BloomFilter, + GrayscaleFilter, + BevelFilter, + OutlineFilter, + DotFilter, + CRTFilter, + EmbossFilter, + BulgePinchFilter, + GlitchFilter, + ZoomBlurFilter, + TwistFilter +} from "pixi-filters" export type BlinkCanvas = { assets: { [key: string]: Asset }; @@ -29,7 +42,7 @@ export type Transform = { export type Filter = { class: "filter"; - type: "blur" | "noise" | "color"; + type: "blur" | "noise" | "bloom" | "grayscale" | "bevel" | "outline" | "dot" | "crt" | "emboss" | "bulge" | "glitch" | "zoomblur" | "twist"; params: any[] }; @@ -37,7 +50,17 @@ export function getPixiFilter(filter: Filter) { switch (filter.type) { case "blur": return new PIXI.BlurFilter(...filter.params); case "noise": return new PIXI.NoiseFilter(...filter.params); - case "color": return new PIXI.ColorMatrixFilter(); + case "bloom": return new BloomFilter(...filter.params); + case "grayscale": return new GrayscaleFilter(); + case "bevel": return new BevelFilter(...filter.params); + case "outline": return new OutlineFilter(...filter.params); + case "dot": return new DotFilter(...filter.params); + case "crt": return new CRTFilter(...filter.params); + case "emboss": return new EmbossFilter(...filter.params); + case "bulge": return new BulgePinchFilter(...filter.params); + case "glitch": return new GlitchFilter(...filter.params); + case "zoomblur": return new ZoomBlurFilter(...filter.params); + case "twist": return new TwistFilter(...filter.params); } } From 639cb7af00d54142323da8abf6836122d96bb05d Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Thu, 7 Sep 2023 19:44:27 +0200 Subject: [PATCH 020/209] Add tweak API --- blix-plugins/blink/src/main.cjs | 28 +++++++++++++++--------- blix-plugins/blink/webview/clump.ts | 3 +++ blix-plugins/blink/webview/render.ts | 31 ++++++++++++++++++++++----- src/frontend/lib/stores/GraphStore.ts | 4 +++- src/frontend/lib/webview/TweakApi.ts | 15 +++++++++++++ src/frontend/ui/tiles/Media.svelte | 7 +++++- src/frontend/ui/tiles/WebView.svelte | 15 +++++++++++-- 7 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 src/frontend/lib/webview/TweakApi.ts diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index fa4efd7a..4c05854b 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -33,6 +33,16 @@ function addState(ui) { ); } +function addTweakability(ui) { + ui.addTweakDial({ + componentId: "tweaks", + label: "Tweak Dial", + defaultValue: {}, + triggerUpdate: true, + }, {} + ); +} + //========== NODES ==========// const nodes = { "inputImage": (context) => { @@ -49,14 +59,7 @@ const nodes = { }, {}); addTransformInput(ui); addState(ui); - - ui.addTweakDial({ - componentId: "tweaks", - label: "Tweak Dial", - defaultValue: {}, - triggerUpdate: true, - }, {} - ); + addTweakability(ui); nodeBuilder.setUIInitializer((x) => { return { @@ -81,6 +84,7 @@ const nodes = { }, content: { class: "clump", + nodeUUID: uiInput["tweaks"].nodeUUID, transform: { position: { x: uiInput["positionX"], y: uiInput["positionY"] }, rotation: uiInput["rotation"], @@ -133,12 +137,14 @@ const nodes = { ); } const getTransform = addTransformInput(ui); + addTweakability(ui); nodeBuilder.define(async (input, uiInput, from) => { const canvas = { assets: {}, content: { class: "clump", + nodeUUID: uiInput["tweaks"].nodeUUID, transform: getTransform(uiInput), elements: [ { @@ -207,10 +213,11 @@ const nodes = { }, { min: 0, max: 100, set: 0.1 } ); + addTweakability(ui); nodeBuilder.define(async (input, uiInput, from) => { // Apply filter to outermost clump - const clumps = [1, 2, 3, 4, 5].map(n => input["clump" + n]).filter(c => c != null); + const clumps = [1, 2, 3, 4, 5].map(n => input[`clump${n}`]).filter(c => c != null); // Construct assets union const assets = {}; @@ -223,7 +230,8 @@ const nodes = { // Construct parent clump const parent = { class: "clump", - transform: getTransform(ui), + nodeUUID: uiInput["tweaks"].nodeUUID, + transform: getTransform(uiInput), opacity: uiInput["opacity"], elements: clumps.map(c => c.content) } diff --git a/blix-plugins/blink/webview/clump.ts b/blix-plugins/blink/webview/clump.ts index d741b93c..8381a3db 100644 --- a/blix-plugins/blink/webview/clump.ts +++ b/blix-plugins/blink/webview/clump.ts @@ -28,6 +28,7 @@ export type Asset = { export type Clump = { class: "clump"; name?: string; + nodeUUID: string; transform: Transform; opacity?: number; elements: (Clump | Atom)[]; @@ -114,6 +115,7 @@ export const canvas1: BlinkCanvas = { content: { class: "clump", name: "root", + nodeUUID: "", transform: { position: { x: 0, y: 0 }, rotation: 0, scale: { x: 1, y: 1 } }, filters: [ { class: "filter", type: "blur", params: [100, 25] }, @@ -128,6 +130,7 @@ export const canvas1: BlinkCanvas = { }, { class: "clump", + nodeUUID: "", name: "clump1", transform: { position: { x: 500, y: 500 }, rotation: 0, scale: { x: 1, y: 1 } }, elements: [ diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 3a0bc4cd..35f8599e 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -1,6 +1,8 @@ import * as PIXI from "pixi.js"; import { getPixiFilter, type Atom, type Clump, BlinkCanvas, Asset } from "./clump"; +let prevMedia = null; + export function renderApp( blink: PIXI.Application, hierarchy: PIXI.Container, @@ -127,9 +129,9 @@ function renderClump(blink: PIXI.Application, clump: Clump, canvas: BlinkCanvas, resClumpContent.transform.setFromMatrix(transMatrix); // const matTransform = resClumpContent.transform.worldTransform; - if (clump.opacity) { - resClumpContent.alpha = Math.min(100, Math.max(0, clump.opacity)); - } + // if (clump.opacity) { + // resClumpContent.alpha = Math.min(100, Math.max(0, clump.opacity)); + // } resClump.addChild(resClumpContent); @@ -162,6 +164,7 @@ function renderClump(blink: PIXI.Application, clump: Clump, canvas: BlinkCanvas, //========== HANDLE EVENTS ==========// let dragging = false; let dragDelta = { x: 0, y: 0 }; + let prevMousePos = { x: 0, y: 0 }; resClump.eventMode = "dynamic"; resClump.on("click", () => { @@ -173,9 +176,27 @@ function renderClump(blink: PIXI.Application, clump: Clump, canvas: BlinkCanvas, resClump.on("mousedown", (event) => { dragging = true; }); - - blink.ticker.add(() => { + resClump.on("mouseupoutside", (event) => { + dragging = false; }); + resClump.on("mouseup", (event) => { + dragging = false; + }); + resClump.on("mousemove", (event) => { + if(dragging) { + const { x: mX, y: mY } = event.movement; + send("tweak", { + nodeUUID: clump.nodeUUID, + inputs: { + positionX: clump.transform.position.x + mX, + positionY: clump.transform.position.y + mY, + }, + }); + } + }) + + // To get global mouse position at any point: + // console.log("MOUSE", blink.renderer.plugins.interaction.pointer.global); return resClump; } diff --git a/src/frontend/lib/stores/GraphStore.ts b/src/frontend/lib/stores/GraphStore.ts index 721b549a..a853822e 100644 --- a/src/frontend/lib/stores/GraphStore.ts +++ b/src/frontend/lib/stores/GraphStore.ts @@ -208,7 +208,9 @@ export class GraphStore { // Update the store value of a specific UI input updateUIInput(nodeUUID: GraphNodeUUID, inputId: string, value: unknown) { const node = this.getNode(nodeUUID); - node.inputUIValues.inputs[inputId].set(value); + if (node) { + node.inputUIValues.inputs[inputId].set(value); + } } updateUIPosition(nodeUUID: UUID, position: SvelvetCanvasPos) { diff --git a/src/frontend/lib/webview/TweakApi.ts b/src/frontend/lib/webview/TweakApi.ts new file mode 100644 index 00000000..fe6d0703 --- /dev/null +++ b/src/frontend/lib/webview/TweakApi.ts @@ -0,0 +1,15 @@ +import type { GraphNodeUUID, GraphUUID } from "../../../shared/ui/UIGraph"; +import { GraphStore, graphMall } from "../stores/GraphStore"; +import { get, type Readable } from "svelte/store"; + +export class TweakApi { + private graphStore: Readable; + + constructor(private graphUUID: GraphUUID) { + this.graphStore = graphMall.getGraphReactive(graphUUID); + } + + setUIInput(nodeUUID: GraphNodeUUID, inputId: string, value: any) { + get(this.graphStore)?.updateUIInput(nodeUUID, inputId, value); + } +} diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index ab4baab0..ecc5ac50 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -5,12 +5,13 @@ import { mediaStore } from "../../lib/stores/MediaStore"; import type { GraphNodeUUID, GraphUUID } from "@shared/ui/UIGraph"; import { writable, type Readable } from "svelte/store"; - import type { MediaDisplayType, DisplayableMediaOutput } from "@shared/types/media"; + import { MediaDisplayType, type DisplayableMediaOutput } from "@shared/types/media"; import { onDestroy } from "svelte"; import ColorDisplay from "../utils/mediaDisplays/ColorDisplay.svelte"; import SelectionBox from "../utils/graph/SelectionBox.svelte"; import { type SelectionBoxItem } from "../../types/selection-box"; import WebView from "./WebView.svelte"; + import { TweakApi } from "lib/webview/TweakApi"; const mediaOutputIds = mediaStore.getMediaOutputIdsReactive(); @@ -67,6 +68,10 @@ function getDisplayProps(media: DisplayableMediaOutput) { let res = media.display.props; if (media.display.contentProp !== null) res[media.display.contentProp] ??= media.content; // If content nullish, use default value + if (media.display.displayType === MediaDisplayType.Webview) { + // Provide Tweak API access + res["tweakApi"] = new TweakApi(media.graphUUID); + } return res; } diff --git a/src/frontend/ui/tiles/WebView.svelte b/src/frontend/ui/tiles/WebView.svelte index 976e4df5..a3149c63 100644 --- a/src/frontend/ui/tiles/WebView.svelte +++ b/src/frontend/ui/tiles/WebView.svelte @@ -2,10 +2,11 @@ - - + + +
+ + +
diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/TweakDial.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte similarity index 83% rename from src/frontend/ui/utils/graph/nodeUICcomponents/TweakDial.svelte rename to src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte index 74ff1b71..718ba418 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/TweakDial.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte @@ -3,13 +3,15 @@ import { UIValueStore } from "@shared/ui/UIGraph"; import type { UIComponentConfig, UIComponentProps } from "@shared/ui/NodeUITypes"; import type { NodeTweakData } from "@shared/types"; - import { faCogs } from "@fortawesome/free-solid-svg-icons"; + import { faParagraph, type IconDefinition } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; export let props: UIComponentProps; export let inputStore: UIValueStore; export let config: UIComponentConfig; + export let icon: IconDefinition = faParagraph; + if (!inputStore.inputs[config.componentId]) inputStore.inputs[config.componentId] = writable({ nodeUUID: "", @@ -22,11 +24,11 @@
- + {#if mouseover} From b63c6325feb463e61ddc60d821c045cdd38ef19f Mon Sep 17 00:00:00 2001 From: Klairgo Date: Fri, 8 Sep 2023 15:48:42 +0200 Subject: [PATCH 033/209] Add CachePicker to blink --- blix-plugins/blink/package-lock.json | 108 +++++++++--------- blix-plugins/blink/src/main.cjs | 26 +++-- blix-plugins/blink/webview/App.svelte | 4 +- blix-plugins/blink/webview/render.ts | 2 +- blix-plugins/threlte-plugin/src/main.cjs | 14 ++- package.json | 2 +- .../lib/plugins/builders/NodeBuilder.ts | 13 +++ src/frontend/lib/stores/CacheStore.ts | 7 ++ src/frontend/ui/tiles/WebCamera.svelte | 4 + .../ui/utils/graph/NodeUIComponent.svelte | 2 + .../nodeUICcomponents/CachePicker.svelte | 2 +- src/shared/ui/NodeUITypes.ts | 1 + 12 files changed, 112 insertions(+), 73 deletions(-) diff --git a/blix-plugins/blink/package-lock.json b/blix-plugins/blink/package-lock.json index 0a0aad97..5bfb0def 100644 --- a/blix-plugins/blink/package-lock.json +++ b/blix-plugins/blink/package-lock.json @@ -32,9 +32,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", + "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -58,9 +58,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -92,21 +92,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -868,9 +862,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz", - "integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", + "integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -915,9 +909,9 @@ } }, "node_modules/@rollup/plugin-typescript": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz", - "integrity": "sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==", + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.3.tgz", + "integrity": "sha512-8o6cNgN44kQBcpsUJTbTXMTtb87oR1O0zgP3Dxm71hrNgparap3VujgofEilTYJo+ivf2ke6uy3/E5QEaiRlDA==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -941,9 +935,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", + "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -963,9 +957,9 @@ } }, "node_modules/@tsconfig/svelte": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.0.tgz", - "integrity": "sha512-iu5BqFjU0+OcLTNQp7fHe6Bf6zdNeJ9IZjLZMqWLuGzVFm/xx+lm//Tf6koPyRmxo55/Snm6RRQ990n89cRKFw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.2.tgz", + "integrity": "sha512-BRbo1fOtyVbhfLyuCWw6wAWp+U8UQle+ZXu84MYYWzYSEB28dyfnRBIE99eoG+qdAC0po6L2ScIEivcT07UaMA==", "dev": true }, "node_modules/@types/css-font-loading-module": { @@ -993,9 +987,9 @@ "dev": true }, "node_modules/@types/offscreencanvas": { - "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==", + "version": "2019.7.1", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.1.tgz", + "integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==", "dev": true }, "node_modules/@types/pug": { @@ -1440,9 +1434,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -1625,9 +1619,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2109,12 +2103,12 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2208,9 +2202,9 @@ } }, "node_modules/rollup": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.0.tgz", - "integrity": "sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.0.tgz", + "integrity": "sha512-nszM8DINnx1vSS+TpbWKMkxem0CDWk3cSit/WWCBVs9/JZ1I/XLwOsiUglYuYReaeWWSsW9kge5zE5NZtf/a4w==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -2618,9 +2612,9 @@ } }, "node_modules/svelte-check": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.5.0.tgz", - "integrity": "sha512-KHujbn4k17xKYLmtCwv0sKKM7uiHTYcQvXnvrCcNU6a7hcszh99zFTIoiu/Sp/ewAw5aJmillJ1Cs8gKLmcX4A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.5.1.tgz", + "integrity": "sha512-+Zb4iHxAhdUtcUg/WJPRjlS1RJalIsWAe9Mz6G1zyznSs7dDkT7VUBdXc3q7Iwg49O/VrZgyJRvOJkjuBfKjFA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -2640,9 +2634,9 @@ } }, "node_modules/svelte-check/node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -2715,9 +2709,9 @@ } }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", + "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -2781,9 +2775,9 @@ } }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, "node_modules/typescript": { diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 75b2e9f5..ad34504d 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -60,9 +60,15 @@ const nodes = { nodeBuilder.setDescription("Input a Blink Sprite Image"); const ui = nodeBuilder.createUIBuilder(); - ui.addFilePicker({ - componentId: "imagePicker", - label: "Pick an image", + // ui.addFilePicker({ + // componentId: "imagePicker", + // label: "Pick an image", + // defaultValue: "", + // triggerUpdate: true, + // }, {}); + ui.addCachePicker({ + componentId: "cachePicker", + label: "Pick an cache item", defaultValue: "", triggerUpdate: true, }, {}); @@ -79,7 +85,7 @@ const nodes = { }); nodeBuilder.define(async (input, uiInput, from) => { - let src = uiInput["imagePicker"].split("/"); + let src = uiInput["cachePicker"].split("/"); src = src.splice(-2); src = src.join("/"); @@ -87,8 +93,8 @@ const nodes = { assets: { [uiInput["state"]["id"]]: { class: "asset", - type: "image", - data: src + type: "blob", + data: await window.cache.get(src) } }, content: { @@ -150,7 +156,13 @@ const nodes = { nodeBuilder.define(async (input, uiInput, from) => { const canvas = { - assets: {}, + assets: { + "1": { + class: "asset", + type: "image", + data: "media/bird.png", + }, + }, content: { class: "clump", nodeUUID: uiInput["tweaks"].nodeUUID, diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index 8a6c1d2b..fe15731b 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -2,9 +2,9 @@ import * as PIXI from "pixi.js"; import { Viewport } from "pixi-viewport"; import { onDestroy, onMount, tick } from "svelte"; - import { Writable } from "svelte/store"; + import { type Writable } from "svelte/store"; import { renderApp } from "./render"; - import { BlinkCanvas, canvas1 } from "./clump"; + import { type BlinkCanvas, canvas1 } from "./clump"; export let media: Writable; export let send: (msg: string, data: any) => void; diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index fbd52551..cc438548 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -1,5 +1,5 @@ import * as PIXI from "pixi.js"; -import { getPixiFilter, type Atom, type Clump, BlinkCanvas, Asset } from "./clump"; +import { getPixiFilter, type Atom, type Clump, type BlinkCanvas, type Asset } from "./clump"; // let prevMedia = null; // TODO: Replace with DiffDial diff --git a/blix-plugins/threlte-plugin/src/main.cjs b/blix-plugins/threlte-plugin/src/main.cjs index 747cab72..16884ca2 100644 --- a/blix-plugins/threlte-plugin/src/main.cjs +++ b/blix-plugins/threlte-plugin/src/main.cjs @@ -109,16 +109,22 @@ const nodes = { nodeBuilder.setDescription("Provides an image input and returns a single image output"); nodeBuilder.define(async (input, uiInput, from) => { - return { "res": { src: uiInput["imagePicker"] } }; + return { "res": { src: uiInput["cacheid"] } }; }); const ui = nodeBuilder.createUIBuilder(); - ui.addFilePicker({ - componentId: "imagePicker", + // ui.addFilePicker({ + // componentId: "imagePicker", + // label: "Pick an image", + // defaultValue: "", + // triggerUpdate: true, + // }, {}); + ui.addCachePicker({ + componentId: "cacheid", label: "Pick an image", defaultValue: "", triggerUpdate: true, - }, {}); + }, {}) nodeBuilder.setUI(ui); diff --git a/package.json b/package.json index e9c55434..5bac9c2f 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "start:electron:run": "electron .", "start:electron:nodemon": "nodemon --verbose", "start:electron:dev": "npm-run-all -s build:electron:dev start:electron:nodemon", - "start:electron": "sleep 2 && npm-run-all -p build:electron:dev:watch start:electron:dev", + "start:electron": "sleep 3 && npm-run-all -p build:electron:dev:watch start:electron:dev", "test": "jest --config jest.config.json", "preplaywrite": "npm run build", "playwright": "playwright test", diff --git a/src/electron/lib/plugins/builders/NodeBuilder.ts b/src/electron/lib/plugins/builders/NodeBuilder.ts index e78bb890..8d89b950 100644 --- a/src/electron/lib/plugins/builders/NodeBuilder.ts +++ b/src/electron/lib/plugins/builders/NodeBuilder.ts @@ -174,6 +174,19 @@ export class NodeUIBuilder { return this; } + public addCachePicker(config: UIComponentConfig, props: UIComponentProps): NodeUIBuilder { + const componentId = config.componentId ?? getRandomComponentId(NodeUIComponent.CachePicker); + this.node.params.push( + new NodeUILeaf(this.node, NodeUIComponent.CachePicker, componentId, [props]) + ); + this.uiConfigs[componentId] = { + componentId, + label: config.label, + defaultValue: config.defaultValue ?? 0, + triggerUpdate: config.triggerUpdate ?? true, + }; + return this; + } // This dial enables plugins to access the current node's UUID, as well as a list of uiInputs id's. // This can then be used in coordination with the webview Tweaks API to modify node UI inputs. diff --git a/src/frontend/lib/stores/CacheStore.ts b/src/frontend/lib/stores/CacheStore.ts index 8dc5d45b..70cb09e3 100644 --- a/src/frontend/lib/stores/CacheStore.ts +++ b/src/frontend/lib/stores/CacheStore.ts @@ -37,6 +37,13 @@ class CacheStore { }; } + // public refreshStore(cacheId: CacheUUID) { + // this.cacheStore.update((cache) => { + // cache.push(cacheId) + // return cache; + // }); + // } + public get subscribe() { return this.cacheStore.subscribe; } diff --git a/src/frontend/ui/tiles/WebCamera.svelte b/src/frontend/ui/tiles/WebCamera.svelte index 941f0e52..cd8d3a90 100644 --- a/src/frontend/ui/tiles/WebCamera.svelte +++ b/src/frontend/ui/tiles/WebCamera.svelte @@ -1,10 +1,12 @@
From c56c0b0260f1c48e3841bef9a2055dccd0bd7fe5 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Fri, 8 Sep 2023 18:23:40 +0200 Subject: [PATCH 035/209] Fix active graph ID for AI --- src/frontend/ui/base/palette/Palette.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/ui/base/palette/Palette.svelte b/src/frontend/ui/base/palette/Palette.svelte index d320ce18..2d495d0c 100644 --- a/src/frontend/ui/base/palette/Palette.svelte +++ b/src/frontend/ui/base/palette/Palette.svelte @@ -4,8 +4,9 @@ import type { ICommand } from "../../../../shared/types/index"; import { onDestroy, onMount } from "svelte"; import Shortcuts from "../../utils/Shortcuts.svelte"; - import { graphMall } from "../../../lib/stores/GraphStore"; + import { focusedGraphStore, graphMall } from "../../../lib/stores/GraphStore"; import { toastStore } from "lib/stores/ToastStore"; + import { get } from "svelte/store"; let showPalette = false; let expanded = true; let inputElement: HTMLInputElement; @@ -212,7 +213,7 @@ console.log(prompt); try { - const res = await window.apis.utilApi.sendPrompt(prompt, graphMall.getAllGraphUUIDs()[0]); + const res = await window.apis.utilApi.sendPrompt(prompt, get(focusedGraphStore).graphUUID); toastStore.trigger({ message: res.message, From 85c233210ed0aec065d6836cc1d872af575bd994 Mon Sep 17 00:00:00 2001 From: Jake Mileham Date: Fri, 8 Sep 2023 18:45:54 +0200 Subject: [PATCH 036/209] Fix Ai undo/redo events and gravity node position saving --- src/electron/lib/ai/AiLang.ts | 7 ++++++- .../lib/core-graph/CoreGraphManager.ts | 19 ++++++++++++------- src/frontend/lib/stores/GraphStore.ts | 12 ++++++++---- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/electron/lib/ai/AiLang.ts b/src/electron/lib/ai/AiLang.ts index 5681e919..6e2103dd 100644 --- a/src/electron/lib/ai/AiLang.ts +++ b/src/electron/lib/ai/AiLang.ts @@ -719,7 +719,12 @@ export class BlypescriptInterpreter { newNodeUiInputs, CoreGraphUpdateParticipant.ai ); - // graph.updateUIInputs(node.uuid, newNodeUiInputs); + for (const input of Object.keys(newNodeUiInputs.inputs)) { + this.graphManager.handleNodeInputInteraction(graph.uuid, node.uuid, { + id: input, + value: newNodeUiInputs.inputs[input], + }); + } return { success: true, data: null }; } diff --git a/src/electron/lib/core-graph/CoreGraphManager.ts b/src/electron/lib/core-graph/CoreGraphManager.ts index 12111560..df3654c6 100644 --- a/src/electron/lib/core-graph/CoreGraphManager.ts +++ b/src/electron/lib/core-graph/CoreGraphManager.ts @@ -210,13 +210,15 @@ export class CoreGraphManager { }); old.changes = [input.id]; } - // Add Event - this._events[graphUUID].addEvent({ - element: "UiInput", - operation: "Change", - execute: { graphUUID, nodeUUId: nodeUUID, nodeUIInputs }, - revert: { graphUUID, nodeUUId: nodeUUID, nodeUIInputs: old }, - }); + + // Add Event only if new value is not the same as old value + if (old.inputs[input.id] !== input.value) + this._events[graphUUID].addEvent({ + element: "UiInput", + operation: "Change", + execute: { graphUUID, nodeUUId: nodeUUID, nodeUIInputs }, + revert: { graphUUID, nodeUUId: nodeUUID, nodeUIInputs: old }, + }); // console.log("OLD: ", old); // console.log("NEW: ", nodeUIInputs); @@ -273,6 +275,9 @@ export class CoreGraphManager { updateUIPositions(graphUUID: UUID, positions: { [key: UUID]: SvelvetCanvasPos }) { if (this._graphs[graphUUID]) { this._graphs[graphUUID].UIPositions = positions; + for (const node of Object.keys(positions)) { + this._graphs[graphUUID].setNodePos(node, positions[node]); + } } } diff --git a/src/frontend/lib/stores/GraphStore.ts b/src/frontend/lib/stores/GraphStore.ts index 04d9d179..508ae223 100644 --- a/src/frontend/lib/stores/GraphStore.ts +++ b/src/frontend/lib/stores/GraphStore.ts @@ -238,7 +238,12 @@ export class GraphStore { // await window.apis.graphApi.updateUIPosition(get(this.graphStore).uuid, nodeUUID, get(get(this.graphStore).uiPositions[nodeUUID])); } + /** + * BE CAREFUL OF THE UNAWAITED PROMISE + * AWAIT REMOVED FOR UP DAY AS A TEMP FIX + */ async updateUIPositions() { + // eslint-disable-next-line await window.apis.graphApi.updateUIPositions( get(this.graphStore).uuid, get(this.graphStore).uiPositions @@ -312,8 +317,6 @@ export class GraphStore { const nodePos = this.getNode(node)?.styling?.pos; if (!nodePos) return; nodePos.update((pos) => { - // console.log("New X: ", pos.x + NUDGE_DISTANCE * 2 * (Math.random() - 0.5)) - // console.log("New Y: ", pos.y + NUDGE_DISTANCE * 2 * (Math.random() - 0.5)) return { x: pos.x + NUDGE_DISTANCE * 2 * (Math.random() - 0.5), y: pos.y + NUDGE_DISTANCE * 2 * (Math.random() - 0.5), @@ -322,7 +325,8 @@ export class GraphStore { }); // Notify the system of the new node positions - const registerPositionUpdate = () => { + const registerPositionUpdate = async () => { + await this.updateUIPositions(); return; // TODO }; @@ -330,7 +334,7 @@ export class GraphStore { const now = performance.now(); if (now - start > duration * 1000) { // Animation is done - registerPositionUpdate(); + void registerPositionUpdate().then().catch(); return; } From 4132ce4d9cdca07912d2fb572bb0b258bb339cf4 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Fri, 8 Sep 2023 19:38:19 +0200 Subject: [PATCH 037/209] Update glfx and pixi to work with camera --- blix-plugins/blink/src/main.cjs | 10 +++---- blix-plugins/blink/webview/clump.ts | 11 ++++++-- blix-plugins/glfx-plugin/webview/App.svelte | 8 +++--- blix-plugins/threlte-plugin/src/main.cjs | 27 +++++++++++++++++++ src/frontend/lib/stores/CacheStore.ts | 12 ++++----- src/frontend/ui/tiles/WebCamera.svelte | 10 +++---- .../nodeUICcomponents/CachePicker.svelte | 2 ++ 7 files changed, 58 insertions(+), 22 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index ad34504d..dd307f9d 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -85,16 +85,16 @@ const nodes = { }); nodeBuilder.define(async (input, uiInput, from) => { - let src = uiInput["cachePicker"].split("/"); - src = src.splice(-2); - src = src.join("/"); + // let src = uiInput["cachePicker"].split("/"); + // src = src.splice(-2); + // src = src.join("/"); const canvas = { assets: { [uiInput["state"]["id"]]: { class: "asset", - type: "blob", - data: await window.cache.get(src) + type: "image", + data: uiInput["cachePicker"], } }, content: { diff --git a/blix-plugins/blink/webview/clump.ts b/blix-plugins/blink/webview/clump.ts index 8381a3db..1ddedc2a 100644 --- a/blix-plugins/blink/webview/clump.ts +++ b/blix-plugins/blink/webview/clump.ts @@ -13,6 +13,7 @@ import { ZoomBlurFilter, TwistFilter } from "pixi-filters" +import { type } from "os"; export type BlinkCanvas = { assets: { [key: string]: Asset }; @@ -21,7 +22,7 @@ export type BlinkCanvas = { export type Asset = { class: "asset"; - type: "image" | "text"; + type: "image" | "text" | "blob"; data: any; }; @@ -66,11 +67,17 @@ export function getPixiFilter(filter: Filter) { } // A single indivisible unit of a clump (E.g. image, shape, text etc.) -export type Atom = { class: "atom" } & (ImageAtom | ShapeAtom | TextAtom | PaintAtom); +export type Atom = { class: "atom" } & (ImageAtom | ShapeAtom | TextAtom | PaintAtom | BlobAtom); type ImageAtom = { type: "image"; assetId: string; }; + +type BlobAtom = { + type: "blob"; + assetId: string; +} + type ShapeAtom = { type: "shape"; shape: "rectangle" | "ellipse" | "triangle"; diff --git a/blix-plugins/glfx-plugin/webview/App.svelte b/blix-plugins/glfx-plugin/webview/App.svelte index bbc9f345..88cdf00b 100644 --- a/blix-plugins/glfx-plugin/webview/App.svelte +++ b/blix-plugins/glfx-plugin/webview/App.svelte @@ -89,10 +89,10 @@ bind:clientWidth="{canvasWidth}" /> - - Rendering at: {canvasWidth} x {canvasHeight}
- Media: {JSON.stringify($media)} -
+ + + + From 9db968ec755b3d2bac46fedec775c597f8b852d6 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Fri, 8 Sep 2023 22:03:07 +0200 Subject: [PATCH 038/209] Fix linux build --- src/frontend/ui/base/palette/Palette.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/ui/base/palette/Palette.svelte b/src/frontend/ui/base/palette/Palette.svelte index 2d495d0c..82c98ae8 100644 --- a/src/frontend/ui/base/palette/Palette.svelte +++ b/src/frontend/ui/base/palette/Palette.svelte @@ -5,7 +5,7 @@ import { onDestroy, onMount } from "svelte"; import Shortcuts from "../../utils/Shortcuts.svelte"; import { focusedGraphStore, graphMall } from "../../../lib/stores/GraphStore"; - import { toastStore } from "lib/stores/ToastStore"; + import { toastStore } from "../../../lib/stores/ToastStore"; import { get } from "svelte/store"; let showPalette = false; let expanded = true; From 41045f58e1d166eb4dbcb8353b7e02303e53ef4c Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Fri, 8 Sep 2023 22:30:34 +0200 Subject: [PATCH 039/209] Add checks to parser to validate numeric UI inputs --- src/electron/lib/ai/AiLang.ts | 47 ++++++++++++++++++--- src/electron/lib/ai/AiManagerv2.ts | 6 +-- src/electron/lib/ai/prompt.ts | 4 +- src/frontend/ui/base/ProjectBar.svelte | 2 +- src/frontend/ui/base/palette/Palette.svelte | 6 +-- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/electron/lib/ai/AiLang.ts b/src/electron/lib/ai/AiLang.ts index 6e2103dd..a704450b 100644 --- a/src/electron/lib/ai/AiLang.ts +++ b/src/electron/lib/ai/AiLang.ts @@ -179,13 +179,17 @@ export class BlypescriptProgram implements AiLangProgram { ); } - let result = this.repairNestedFunctionCalls(statement, node); + const repairFunctions = [ + this.repairNestedFunctionCalls, + this.repairPrimitiveTypes, + this.repairUiInputTypes, + ]; - if (!result.success) return result; - - result = this.repairPrimitiveTypes(statement, node, toolbox); + for (const repairFunction of repairFunctions) { + const result = repairFunction(statement, node, toolbox); - if (!result.success) return result; + if (!result.success) return result; + } } return { @@ -377,6 +381,34 @@ export class BlypescriptProgram implements AiLangProgram { } satisfies Result; } + private repairUiInputTypes(statement: BlypescriptStatement, node: BlypescriptNode) { + const statementUiInputs = statement.nodeInputs.slice(node.nodeInputs.length); + + for (let i = 0; i < statementUiInputs.length; i++) { + const statementUiInput = statementUiInputs[i]; + const nodeUiInput = node.uiInputs[i]; + + if (nodeUiInput.types.length === 1 && nodeUiInput.types[0].toLowerCase() === "number") { + if (statementUiInput === "null") { + statement.nodeInputs[node.nodeInputs.length + i] = "0"; + } else if (isNaN(Number(statementUiInput))) { + return { + success: false, + error: "ui_input_type_error", + message: `Type error: '${statementUiInput}' is not of type \`number\` on the following line: ${statement.toString()}`, + } satisfies Result; + } + } + + // TODO: Add other type checks here + } + + return { + success: true, + data: true as const, + } satisfies Result; + } + private generateUniqueVarName(nodeSignature?: string) { const varPrefix = nodeSignature ? nodeSignature.split(".").slice(1).join(".") : "var"; let count = 1; @@ -407,7 +439,7 @@ function error(error: string, message: string, data?: T) { * const num1 = input-plugin.inputNumber(69); * const binary1 = math-plugin.input(num1['res'], num1['res'], 'multiply'); */ -const BLYPESCRIPT_STATEMENT_TEMPLATE = "const {{name}} = {{signature}}.({{params}});"; +const BLYPESCRIPT_STATEMENT_TEMPLATE = "const {{name}} = {{signature}}({{params}});"; const BLYPESCRIPT_STATEMENT_REGEX = /\s*const \s*(\w+)\s*=\s*([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\)\s*;/; const BLYPESCRIPT_FUNCTION_CALL_REGEX = /^([\w-]+)\s*\.\s*([\w-]+)\s*\((.*)\).*$/; @@ -446,7 +478,7 @@ export class BlypescriptStatement extends AiLangStatement { return fillTemplate(BLYPESCRIPT_STATEMENT_TEMPLATE, { name: this.name, signature: this.nodeSignature, - params: this.nodeInputs.join(", "), + params: this.nodeInputs.join(", ") || "", }); } } @@ -719,6 +751,7 @@ export class BlypescriptInterpreter { newNodeUiInputs, CoreGraphUpdateParticipant.ai ); + for (const input of Object.keys(newNodeUiInputs.inputs)) { this.graphManager.handleNodeInputInteraction(graph.uuid, node.uuid, { id: input, diff --git a/src/electron/lib/ai/AiManagerv2.ts b/src/electron/lib/ai/AiManagerv2.ts index 5ac9f45c..b1d7037e 100644 --- a/src/electron/lib/ai/AiManagerv2.ts +++ b/src/electron/lib/ai/AiManagerv2.ts @@ -39,7 +39,7 @@ type PromptOptions = { verbose?: boolean; }; -export const genericErrorResponse = "Oops, that wasn't supposed to happen🫠. Try again."; +export const genericErrorResponse = "Oops, that wasn't supposed to happen😅"; export class AiManager { private readonly graphExporter: CoreGraphExporter; @@ -123,7 +123,7 @@ export class AiManager { ); if (!result.success) { - chat.addMessage({ role: "blix", content: `USER'S RESPONSE: ${result.message}` }); + chat.addMessage({ role: "blix", content: `Error: ${result.message}` }); continue; // retry if failure } @@ -140,7 +140,7 @@ export class AiManager { if (!result.success) { logger.warn(result.error); - chat.addMessage({ role: "blix", content: `USER'S RESPONSE:\n${result.message}` }); + chat.addMessage({ role: "blix", content: `Error: ${result.message}` }); continue; // retry if failure } diff --git a/src/electron/lib/ai/prompt.ts b/src/electron/lib/ai/prompt.ts index a6d90fe8..21d4d4d3 100644 --- a/src/electron/lib/ai/prompt.ts +++ b/src/electron/lib/ai/prompt.ts @@ -31,8 +31,8 @@ User: Add some nodes and connect them Assistant: \`\`\`typescript graph { - const inputNumber1 = input-plugin.inputNumber.(5); - const inputNumber2 = input-plugin.inputNumber.(10); + const inputNumber1 = input-plugin.inputNumber(5); + const inputNumber2 = input-plugin.inputNumber(10); const binary1 = math-plugin.binary.(inputNumber1['res'], inputNumber2['res'], 'add'); const output1 = blix.output.(binary1['res'], 'output1'); } diff --git a/src/frontend/ui/base/ProjectBar.svelte b/src/frontend/ui/base/ProjectBar.svelte index 9901debc..dcc02663 100644 --- a/src/frontend/ui/base/ProjectBar.svelte +++ b/src/frontend/ui/base/ProjectBar.svelte @@ -46,7 +46,7 @@ {#if !project.saved}
{/if} diff --git a/src/frontend/ui/base/palette/Palette.svelte b/src/frontend/ui/base/palette/Palette.svelte index 82c98ae8..ddaef874 100644 --- a/src/frontend/ui/base/palette/Palette.svelte +++ b/src/frontend/ui/base/palette/Palette.svelte @@ -195,7 +195,7 @@ closePalette(); const messages = [ - "🔥 Cooking...", + "👨🏼‍🍳 Cooking...", "🪄 Stirring the creative cauldron...", "🐉 Slaying the ender dragon...", ]; @@ -211,7 +211,6 @@ promptHistory.unshift(prompt); await window.apis.utilApi.saveState("prompts", promptHistory); - console.log(prompt); try { const res = await window.apis.utilApi.sendPrompt(prompt, get(focusedGraphStore).graphUUID); @@ -318,8 +317,9 @@

Ask AI to assist with a task

- "What is the result of 16 subtract 42, squared?" "I want to edit the brightness, hue and noise of an image." + "What is the result of 16 subtract 42, squared?" + "I want my image to sparkly like summer's day" "Make my image more medieval"
From 90201afb81f72aef1777f8c9d520be129349ccdb Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Fri, 8 Sep 2023 23:18:00 +0200 Subject: [PATCH 040/209] Remove sharp plugin and save AI chats --- blix-plugins/glfx-plugin/webview/App.svelte | 4 +- blix-plugins/sharp-plugin/package.json | 18 -- blix-plugins/sharp-plugin/src/main.js | 239 ------------------ package-lock.json | 240 +------------------ package.json | 1 - src/electron/lib/Blix.ts | 18 +- src/electron/lib/ai/AiLang.ts | 13 +- src/electron/lib/ai/AiManagerv2.ts | 52 +++- src/electron/lib/projects/ProjectCommands.ts | 99 ++++---- src/frontend/ui/base/palette/Palette.svelte | 2 +- src/frontend/ui/tiles/WebView.svelte | 4 +- 11 files changed, 120 insertions(+), 570 deletions(-) delete mode 100644 blix-plugins/sharp-plugin/package.json delete mode 100644 blix-plugins/sharp-plugin/src/main.js diff --git a/blix-plugins/glfx-plugin/webview/App.svelte b/blix-plugins/glfx-plugin/webview/App.svelte index bbc9f345..10f94c6d 100644 --- a/blix-plugins/glfx-plugin/webview/App.svelte +++ b/blix-plugins/glfx-plugin/webview/App.svelte @@ -89,10 +89,10 @@ bind:clientWidth="{canvasWidth}" /> - + diff --git a/src/shared/types/dials.ts b/src/shared/types/dials.ts index 0fcf7d63..172b8f6a 100644 --- a/src/shared/types/dials.ts +++ b/src/shared/types/dials.ts @@ -5,5 +5,5 @@ export type NodeTweakData = { export type NodeDiffData = { uiInputs: string[]; - anchors: { anchorId: string; change: "connection" | "value" }[]; + anchors: { [key: string]: "connection" | "value" }; // anchorId -> changeType }; From 049899ce38a9f008e1df642d44a55b3d4b631ab5 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Tue, 12 Sep 2023 17:39:34 +0200 Subject: [PATCH 045/209] Add edit nodes to blink --- blix-plugins/blink/src/main.cjs | 133 ++++++++++++++++++++++++++++ blix-plugins/blink/webview/clump.ts | 26 +++++- 2 files changed, 157 insertions(+), 2 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index dd307f9d..26c6f3cb 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -4,6 +4,17 @@ function getUUID() { return crypto.randomBytes(16).toString("base64url"); } +function chooseInput(input, uiInput, inputKey) { + if (input[inputKey] ?? false) { + return input[inputKey]; + } + return uiInput[inputKey]; +} + +function toTitleCase(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + function addTransformInput(ui) { for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { ui.addNumberInput( @@ -52,8 +63,130 @@ function addTweakability(ui) { ); } +function createBlinkNode(type, title, desc, params) { + return (context) => { + const nodeBuilder = context.instantiate(context.pluginId, type); + nodeBuilder.setTitle(title); + nodeBuilder.setDescription(desc); + + const ui = nodeBuilder.createUIBuilder(); + for (let param of params) { + if(param.id.includes("color") || param.id.includes("Color")) { + ui.addColorPicker({ + componentId: param.id, + label: "Multitudinous seas incarnadine", + defaultValue: 0, + triggerUpdate: true, + }, {}) + + } + else{ + ui.addSlider( + { + componentId: param.id, + label: toTitleCase(param.id), + defaultValue: 0, + triggerUpdate: true, + }, + { min: param.min ?? -1, max: param.max ?? 1, step: param.step ?? 0.05 } + ); + } + } + nodeBuilder.define(async (input, uiInput, from) => { + + const canvas = input["clump"]; + + if (!canvas.content.filters) canvas.content.filters = []; + canvas.content.filters.push({ + class: "filter", + type: type, + params: params.map((param) => chooseInput(input, uiInput, param.id)), + }); + + return { "res": canvas }; + }); + + nodeBuilder.setUI(ui); + nodeBuilder.addInput("Blink clump", "clump", "Clump"); + // for (let param of params) { + // nodeBuilder.addInput("number", type, toTitleCase(param.id)); + // } + nodeBuilder.addOutput("Blink clump", "res", "Result"); + }; +} + +const blinkNodes = { + "blur": [ + "Blur", + "Applies a blur to the image", + [{ id: "blur", min: 0, max: 100, step: 0.1 }, { id: "quality", min: 1, max: 10, step: 0.01 }] + ], + "noise": [ + "Noise", + "Applies a noise filter to the image", + [{ id: "noise", min: 0, max: 1, step: 0.01 }, { id: "seed", min: 0.01, max: 0.99, step: 0.01 }] + ], + "bloom": [ + "Bloom", + "Applies a Guassian blur to the image", + [{ id: "strength", min: 0, max: 20, step: 0.1 }] + ], + "grayscale": [ + "Gray Scale", + "Applies a grayscale filter to the image", + [] + ], + "bevel": [ + "Bevel", + "Bevel Filter", + [ + { id: "rotation", min: 0, max: 360, step: 1.0 }, + { id: "thickness", min: 0, max: 10, step: 0.01 }, + { id: "lightColor", min: 0, max: 360, step: 1.0 }, + { id: "lightAlpha", min: 0, max: 1, step: 0.01 }, + { id: "shadowColor", min: 0, max: 360, step: 1.0 }, + { id: "shadowAlpha", min: 0, max: 1, step: 0.01 }, + ] + ], + "outline": [ + "Outline", + "Applies an outline filter to the image", + [ + { id: "thickness", min: 0, max: 10, step: 0.1 }, + { id: "color", min: 0, max: 5, step: 0.05 }, + { id: "alpha", min: 0, max: 1, step: 0.01 }, + ] + ], + "dot": [ + "Dot", + "This filter applies a dotscreen effect making display objects appear to be made out of halftone dots like an old printer", + [{ id: "scale", min: 0.3, max: 1, step: 0.01 }, { id: "angle", min: 0, max: 5, step: 0.01 }] + ], + "crt": [ + "CRT", + "Applies a CRT effect to the image", + [ + { id: "curvature", min: 0, max: 10, step: 0.01 }, + { id: "lineWidth", min: 0, max: 5, step: 0.01 }, + { id: "lineContrast", min: 0, max: 1, step: 0.01 }, + { id: "noise", min: 0, max: 1, step: 0.01 }, + { id: "noiseSize", min: 1, max: 10, step: 0.01 }, + { id: "vignetting", min: 0, max: 1, step: 0.01 }, + { id: "vignettingAlpha", min: 0, max: 1, step: 0.01 }, + { id: "vignettingBlur", min: 0, max: 1, step: 0.01 }, + { id: "seed", min: 0, max: 1, step: 0.01 }, + ] + ], +}; + +Object.keys(blinkNodes).forEach((key) => { + blinkNodes[key] = createBlinkNode(key, ...blinkNodes[key]); +}); + //========== NODES ==========// const nodes = { + ...blinkNodes, + "inputImage": (context) => { const nodeBuilder = context.instantiate(context.pluginId, "inputImage"); nodeBuilder.setTitle("Blink Image"); diff --git a/blix-plugins/blink/webview/clump.ts b/blix-plugins/blink/webview/clump.ts index 1ddedc2a..0a47ea30 100644 --- a/blix-plugins/blink/webview/clump.ts +++ b/blix-plugins/blink/webview/clump.ts @@ -1,6 +1,7 @@ import * as PIXI from "pixi.js"; import { Matrix } from "pixi.js"; import { + KawaseBlurFilter, BloomFilter, GrayscaleFilter, BevelFilter, @@ -54,10 +55,31 @@ export function getPixiFilter(filter: Filter) { case "noise": return new PIXI.NoiseFilter(...filter.params); case "bloom": return new BloomFilter(...filter.params); case "grayscale": return new GrayscaleFilter(); - case "bevel": return new BevelFilter(...filter.params); + case "bevel": return new BevelFilter( + { + rotation: filter.params[0], + thickness: filter.params[1], + lightColor: filter.params[2], + lightAlpha: filter.params[3], + shadowColor: filter.params[4], + shadowAlpha: filter.params[5], + } + ); case "outline": return new OutlineFilter(...filter.params); case "dot": return new DotFilter(...filter.params); - case "crt": return new CRTFilter(...filter.params); + case "crt": return new CRTFilter( + { + curvature: filter.params[0], + lineWidth: filter.params[1], + lineContrast: filter.params[2], + noise: filter.params[3], + noiseSize: filter.params[4], + vignetting: filter.params[5], + vignettingAlpha: filter.params[6], + vignettingBlur: filter.params[7], + seed: filter.params[8], + } + ); case "emboss": return new EmbossFilter(...filter.params); case "bulge": return new BulgePinchFilter(...filter.params); case "glitch": return new GlitchFilter(...filter.params); From 5cfadf660b7f6ce0a8ab9c23bc34e0c0d87e7b54 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Wed, 13 Sep 2023 20:50:33 +0200 Subject: [PATCH 046/209] Add new ColorPicker --- blix-plugins/hello-plugin/src/main.ts | 2 +- blix-plugins/input-plugin/src/main.js | 2 +- package-lock.json | 17 ++ package.json | 2 + .../lib/plugins/builders/NodeBuilder.ts | 4 +- src/frontend/ui/utils/graph/PluginNode.svelte | 40 ++-- .../nodeUICcomponents/ColorPicker.svelte | 61 +++++- .../ColorPicker/ColorPickerTextInput.svelte | 192 ++++++++++++++++++ .../ColorPicker/ColorPickerWrapper.svelte | 47 +++++ .../utils/mediaDisplays/ColorDisplay.svelte | 71 ++++--- .../lib/plugins/builder/NodeBuilder.spec.ts | 2 +- 11 files changed, 380 insertions(+), 60 deletions(-) create mode 100644 src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerTextInput.svelte create mode 100644 src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerWrapper.svelte diff --git a/blix-plugins/hello-plugin/src/main.ts b/blix-plugins/hello-plugin/src/main.ts index aaa0668b..6b654b8e 100644 --- a/blix-plugins/hello-plugin/src/main.ts +++ b/blix-plugins/hello-plugin/src/main.ts @@ -61,7 +61,7 @@ const nodes = { .addColorPicker({ componentId: "incarnadine", label: "Multitudinous seas incarnadine", - defaultValue: 0, + defaultValue: "#ff000088", triggerUpdate: true, }, {}) diff --git a/blix-plugins/input-plugin/src/main.js b/blix-plugins/input-plugin/src/main.js index 9f10a0f7..3f1dcb72 100644 --- a/blix-plugins/input-plugin/src/main.js +++ b/blix-plugins/input-plugin/src/main.js @@ -58,7 +58,7 @@ const nodes = { ui.addColorPicker({ componentId: "colorPicker", label: "Pick a color", - defaultValue: "red", + defaultValue: "#ff0000ff", triggerUpdate: true, }, {}) nodeBuilder.setUI(ui); diff --git a/package-lock.json b/package-lock.json index 4c0e2c4f..ac0ac8e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "babel-jest": "^29.5.0", "chalk": "^4.1.2", "clean-css": "^5.3.2", + "colord": "^2.9.3", "commander": "^11.0.0", "cross-env": "^7.0.3", "electron": "^24.2.0", @@ -88,6 +89,7 @@ "rollup-plugin-livereload": "^2.0.5", "rollup-plugin-serve": "^2.0.2", "rollup-plugin-svelte": "^7.1.4", + "svelte-awesome-color-picker": "^2.4.7", "svelte-check": "^3.3.1", "svelte-fa": "^3.0.4", "svelte-jester": "^2.3.2", @@ -6309,6 +6311,12 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -15465,6 +15473,15 @@ "node": ">= 8" } }, + "node_modules/svelte-awesome-color-picker": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-2.4.7.tgz", + "integrity": "sha512-LW9n16pR3V9vxFXdq0rmtBYJXv57zBZmLV/KNPtFqTo0kYwu7NsQhZZxFv6qoB/5PUXyPMfe3VCllyauFEY3SQ==", + "dev": true, + "dependencies": { + "colord": "^2.9.3" + } + }, "node_modules/svelte-check": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.4.tgz", diff --git a/package.json b/package.json index 5bac9c2f..7e620111 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "babel-jest": "^29.5.0", "chalk": "^4.1.2", "clean-css": "^5.3.2", + "colord": "^2.9.3", "commander": "^11.0.0", "cross-env": "^7.0.3", "electron": "^24.2.0", @@ -148,6 +149,7 @@ "rollup-plugin-livereload": "^2.0.5", "rollup-plugin-serve": "^2.0.2", "rollup-plugin-svelte": "^7.1.4", + "svelte-awesome-color-picker": "^2.4.7", "svelte-check": "^3.3.1", "svelte-fa": "^3.0.4", "svelte-jester": "^2.3.2", diff --git a/src/electron/lib/plugins/builders/NodeBuilder.ts b/src/electron/lib/plugins/builders/NodeBuilder.ts index 2527e602..c8c11741 100644 --- a/src/electron/lib/plugins/builders/NodeBuilder.ts +++ b/src/electron/lib/plugins/builders/NodeBuilder.ts @@ -182,7 +182,7 @@ export class NodeUIBuilder { this.uiConfigs[componentId] = { componentId, label: config.label, - defaultValue: config.defaultValue ?? 0, + defaultValue: config.defaultValue ?? "", triggerUpdate: config.triggerUpdate ?? true, }; return this; @@ -396,7 +396,7 @@ export class NodeUIBuilder { this.uiConfigs[componentId] = { componentId, label: config.label, - defaultValue: config.defaultValue ?? "#000000", + defaultValue: config.defaultValue ?? "#000000ff", triggerUpdate: config.triggerUpdate ?? true, }; return this; diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index ef1f877d..8a7a99fd 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -5,6 +5,7 @@ import NodeUiFragment from "./NodeUIFragment.svelte"; import { createEventDispatcher } from "svelte"; import { graphMall } from "lib/stores/GraphStore"; + import { colord } from "colord"; const dispatch = createEventDispatcher(); @@ -44,28 +45,29 @@ return colour as CSSColorString; } - function changeBrightness(color: CSSColorString, percent: number): CSSColorString { - var R = parseInt(color.substring(1, 3), 16); - var G = parseInt(color.substring(3, 5), 16); - var B = parseInt(color.substring(5, 7), 16); + function changeBrightness(color: CSSColorString, percent: number) { + return colord(color).lighten(percent).toRgbString(); + // var R = parseInt(color.substring(1, 3), 16); + // var G = parseInt(color.substring(3, 5), 16); + // var B = parseInt(color.substring(5, 7), 16); - R = parseInt(`${(R * (100 + percent)) / 100}`); - G = parseInt(`${(G * (100 + percent)) / 100}`); - B = parseInt(`${(B * (100 + percent)) / 100}`); + // R = parseInt(`${(R * (100 + percent)) / 100}`); + // G = parseInt(`${(G * (100 + percent)) / 100}`); + // B = parseInt(`${(B * (100 + percent)) / 100}`); - R = R < 255 ? R : 255; - G = G < 255 ? G : 255; - B = B < 255 ? B : 255; + // R = R < 255 ? R : 255; + // G = G < 255 ? G : 255; + // B = B < 255 ? B : 255; - R = Math.round(R); - G = Math.round(G); - B = Math.round(B); + // R = Math.round(R); + // G = Math.round(G); + // B = Math.round(B); - var RR = R.toString(16).length == 1 ? "0" + R.toString(16) : R.toString(16); - var GG = G.toString(16).length == 1 ? "0" + G.toString(16) : G.toString(16); - var BB = B.toString(16).length == 1 ? "0" + B.toString(16) : B.toString(16); + // var RR = R.toString(16).length == 1 ? "0" + R.toString(16) : R.toString(16); + // var GG = G.toString(16).length == 1 ? "0" + G.toString(16) : G.toString(16); + // var BB = B.toString(16).length == 1 ? "0" + B.toString(16) : B.toString(16); - return ("#" + RR + GG + BB) as CSSColorString; + // return ("#" + RR + GG + BB) as CSSColorString; } async function nodeClicked(e: CustomEvent) { @@ -144,7 +146,7 @@ height="{graphNode.dims.h}" --> {#if input.displayName} {input.displayName}
{/if} - <{input.type || "any"}{input.type || "any"}>
{/if} @@ -180,7 +182,7 @@ height="{graphNode.dims.h}" --> {output.displayName}
{/if} - <{output.type || "any"}> diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte index b6202f72..6a337e6b 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte @@ -1,25 +1,76 @@
- + + + + {#if typeof $valStore === "string"} + + {:else} + ERR: Invaid colour: {JSON.stringify($valStore)} + {/if}
diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerTextInput.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerTextInput.svelte new file mode 100644 index 00000000..41832451 --- /dev/null +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerTextInput.svelte @@ -0,0 +1,192 @@ + + +
+ {#if mode === 0} +
+ + {#if isAlpha} + + {/if} +
+ {:else if mode === 1} +
+ + + + {#if isAlpha} + + {/if} +
+ {:else} +
+ + + + {#if isAlpha} + + {/if} +
+ {/if} + + {#if canChangeMode} + + {/if} +
+ + diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerWrapper.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerWrapper.svelte new file mode 100644 index 00000000..f74e2cb8 --- /dev/null +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker/ColorPickerWrapper.svelte @@ -0,0 +1,47 @@ + + +
+ + + +
+ + diff --git a/src/frontend/ui/utils/mediaDisplays/ColorDisplay.svelte b/src/frontend/ui/utils/mediaDisplays/ColorDisplay.svelte index 17519344..09502e6a 100644 --- a/src/frontend/ui/utils/mediaDisplays/ColorDisplay.svelte +++ b/src/frontend/ui/utils/mediaDisplays/ColorDisplay.svelte @@ -1,32 +1,34 @@
-
- {color} +
+
+ {color} +
+
diff --git a/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts index 68bc76bd..fa012704 100644 --- a/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts +++ b/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts @@ -157,7 +157,7 @@ describe("Test NodeUIBuilder", () => { const uiComponentConfig : UIComponentConfig = { label: "slider", componentId: "shrek", - defaultValue: 50, + defaultValue: "#beef69", triggerUpdate: true } nodeUIBuilder.addColorPicker(uiComponentConfig,{set : "ayo"}); From b5fb82f744105c344ac5943e50ca4fca7575a4ab Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Fri, 15 Sep 2023 18:09:37 +0200 Subject: [PATCH 047/209] Add Blink diffing system; Working 1 layer deep --- blix-plugins/blink/src/main.cjs | 68 +-- blix-plugins/blink/webview/App.svelte | 5 +- blix-plugins/blink/webview/app.ts | 2 +- blix-plugins/blink/webview/atom.ts | 75 ++++ blix-plugins/blink/webview/diff.ts | 134 ++++++ blix-plugins/blink/webview/filter.ts | 31 ++ blix-plugins/blink/webview/render.ts | 391 ++++++++++-------- .../blink/webview/{clump.ts => types.ts} | 12 +- src/frontend/ui/base/palette/Palette.svelte | 8 + src/frontend/ui/tiles/Assets.svelte | 9 + src/frontend/ui/utils/graph/PluginNode.svelte | 46 +-- src/index.ts | 9 + 12 files changed, 560 insertions(+), 230 deletions(-) create mode 100644 blix-plugins/blink/webview/atom.ts create mode 100644 blix-plugins/blink/webview/diff.ts create mode 100644 blix-plugins/blink/webview/filter.ts rename blix-plugins/blink/webview/{clump.ts => types.ts} (96%) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index de4e43a5..558f64ec 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -4,19 +4,29 @@ function getUUID() { return crypto.randomBytes(16).toString("base64url"); } +function colorHexToNumber(str) { + return parseInt(str.slice(0, 7).replace("#", "0x")); +} + function chooseInput(input, uiInput, inputKey) { - if (input[inputKey] ?? false) { + if (input[inputKey] != null) { return input[inputKey]; } - return uiInput[inputKey]; + return inputKey.includes("color") ? colorHexToNumber(uiInput[inputKey]) : uiInput[inputKey]; } function toTitleCase(str) { return str.charAt(0).toUpperCase() + str.slice(1); } -function addTransformInput(ui) { - for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { +function addTransformInput(ui, props = ["position", "rotation", "scale"]) { + const propInputs = [ + ...(props.includes("position") ? ["position X", "position Y"] : []), + ...(props.includes("rotation") ? ["rotation"] : []), + ...(props.includes("scale") ? ["scale X", "scale Y"] : []) + ]; + for (let numInp of propInputs) { + // for (let numInp of ["position X", "position Y", "rotation", "scale X", "scale Y"]) { ui.addNumberInput( { componentId: numInp.replace(" ", ""), @@ -30,9 +40,9 @@ function addTransformInput(ui) { ); } return (uiInput) => ({ - position: { x: uiInput?.positionX, y: uiInput?.positionY }, - rotation: uiInput?.rotation, - scale: { x: uiInput?.scaleX, y: uiInput?.scaleY }, + ...(props.includes("position") ? { position: { x: uiInput?.positionX, y: uiInput?.positionY } } : {}), + ...(props.includes("rotation") ? { rotation: uiInput?.rotation } : {}), + ...(props.includes("scale") ? { scale: { x: uiInput?.scaleX, y: uiInput?.scaleY }, } : {}) }); } @@ -62,8 +72,8 @@ function createBlinkNode(type, title, desc, params) { if(param.id.includes("color") || param.id.includes("Color")) { ui.addColorPicker({ componentId: param.id, - label: "Multitudinous seas incarnadine", - defaultValue: 0, + label: toTitleCase(param.id), + defaultValue: "#000000", triggerUpdate: true, }, {}) @@ -129,10 +139,10 @@ const blinkNodes = { "Bevel Filter", [ { id: "rotation", min: 0, max: 360, step: 1.0 }, - { id: "thickness", min: 0, max: 10, step: 0.01 }, - { id: "lightColor", min: 0, max: 360, step: 1.0 }, + { id: "thickness", min: 0, max: 100, step: 0.1 }, + { id: "lightColor" }, { id: "lightAlpha", min: 0, max: 1, step: 0.01 }, - { id: "shadowColor", min: 0, max: 360, step: 1.0 }, + { id: "shadowColor" }, { id: "shadowAlpha", min: 0, max: 1, step: 0.01 }, ] ], @@ -140,8 +150,8 @@ const blinkNodes = { "Outline", "Applies an outline filter to the image", [ - { id: "thickness", min: 0, max: 10, step: 0.1 }, - { id: "color", min: 0, max: 5, step: 0.05 }, + { id: "thickness", min: 0, max: 100, step: 0.1 }, + { id: "color" }, { id: "alpha", min: 0, max: 1, step: 0.01 }, ] ], @@ -181,18 +191,18 @@ const nodes = { nodeBuilder.setDescription("Input a Blink Sprite Image"); const ui = nodeBuilder.createUIBuilder(); - // ui.addFilePicker({ - // componentId: "imagePicker", - // label: "Pick an image", - // defaultValue: "", - // triggerUpdate: true, - // }, {}); - ui.addCachePicker({ - componentId: "cachePicker", - label: "Pick an cache item", + ui.addFilePicker({ + componentId: "imagePicker", + label: "Pick an image", defaultValue: "", triggerUpdate: true, }, {}); + // ui.addCachePicker({ + // componentId: "cachePicker", + // label: "Pick an cache item", + // defaultValue: "", + // triggerUpdate: true, + // }, {}); addTransformInput(ui); addState(ui); addTweakability(ui); @@ -206,16 +216,17 @@ const nodes = { }); nodeBuilder.define(async (input, uiInput, from) => { - // let src = uiInput["cachePicker"].split("/"); - // src = src.splice(-2); - // src = src.join("/"); + let src = uiInput["imagePicker"].split("/"); + src = src.splice(-2); + src = src.join("/"); const canvas = { assets: { [uiInput["state"]["id"]]: { class: "asset", type: "image", - data: uiInput["cachePicker"], + data: uiInput["imagePicker"].split("/").splice(-2).join("/"), + // data: uiInput["cachePicker"], } }, content: { @@ -273,7 +284,7 @@ const nodes = { {} ); } - const getTransform = addTransformInput(ui); + const getTransform = addTransformInput(ui, ["position", "rotation"]); addTweakability(ui); nodeBuilder.define(async (input, uiInput, from) => { @@ -288,6 +299,7 @@ const nodes = { content: { class: "clump", nodeUUID: uiInput["tweaks"].nodeUUID, + changes: uiInput["diffs"]?.uiInputs ?? [], transform: getTransform(uiInput), elements: [ { diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index 6804e166..b770cee4 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -4,7 +4,7 @@ import { onDestroy, onMount, tick } from "svelte"; import { type Writable } from "svelte/store"; import { renderApp } from "./render"; - import { type BlinkCanvas, canvas1 } from "./clump"; + import { type BlinkCanvas } from "./types"; export let media: Writable; export let send: (msg: string, data: any) => void; @@ -29,6 +29,8 @@ // resizeTo: window, }); + globalThis.__PIXI_APP__ = blink; + window.addEventListener("resize", () => { blink.renderer.resize(window.innerWidth, window.innerHeight); blink.render(); @@ -80,6 +82,7 @@ const imgCanvasBlockH = 1080; let imgCanvas = new PIXI.Container(); + imgCanvas.name = "imgCanvas"; let imgCanvasBlock = new PIXI.Graphics(); imgCanvasBlock.beginFill(0xffffff, 0.9); diff --git a/blix-plugins/blink/webview/app.ts b/blix-plugins/blink/webview/app.ts index 1e84613c..2d235077 100644 --- a/blix-plugins/blink/webview/app.ts +++ b/blix-plugins/blink/webview/app.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import App from './App.svelte'; -import { BlinkCanvas } from './clump'; +import { BlinkCanvas } from './types'; const media = writable({ assets: {}, diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts new file mode 100644 index 00000000..5b844283 --- /dev/null +++ b/blix-plugins/blink/webview/atom.ts @@ -0,0 +1,75 @@ +import * as PIXI from 'pixi.js'; +import { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; +import { diffAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; +import { HierarchyAtom } from './render'; + +export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined): { + pixiAtom: PIXI.Container, + changed: boolean // Whether the pixiClump is a different PIXI object than before +} { + if (!atom) return null; + + const atomsDiffer = diffAtom(atom, prevAtom); + + switch (atom.type) { + case "image": + const imageDiff = atomsDiffer || prevAssets == null || diffImageAtom(atom, prevAtom as ImageAtom, assets, prevAssets); + if (!imageDiff) { + return { pixiAtom: prevAtom.container, changed: false }; + } + + if (assets[atom.assetId] && assets[atom.assetId].type === "image") { + const sprite = PIXI.Sprite.from(assets[atom.assetId].data); + + sprite.anchor.x = 0.5; + sprite.anchor.y = 0.5; + + sprite.name = "ImageSprite"; + prevAtom?.container?.destroy(); + return { pixiAtom: sprite, changed: true }; + } + + return { pixiAtom: null, changed: true }; + + case "shape": + const shapeDiff = atomsDiffer || diffShapeAtom(atom, prevAtom as ShapeAtom); + if (!shapeDiff) return { pixiAtom: prevAtom.container, changed: false }; + + const shapeContainer = new PIXI.Container(); + const shape = new PIXI.Graphics(); + // shape.lineStyle(1, 0xf43e5c, 0.5); + shape.beginFill(atom.fill, 1); + shape.lineStyle(atom.strokeWidth, atom.stroke, 1); + const halfBounds = { w: atom.bounds.w / 2, h: atom.bounds.h / 2 }; + + switch (atom.shape) { + case "rectangle": + shape.drawRect(-halfBounds.w, -halfBounds.h, atom.bounds.w, atom.bounds.h); + break; + case "ellipse": + shape.drawEllipse(-halfBounds.w, -halfBounds.h, atom.bounds.w, atom.bounds.h); + break; + case "triangle": + shape.drawPolygon([0, 0, atom.bounds.w, 0, atom.bounds.w / 2, atom.bounds.h]); + break; + } + shape.endFill(); + + shapeContainer.addChild(shape); + + shapeContainer.name = "ShapeContainer"; + return { pixiAtom: shapeContainer, changed: true }; + + case "text": + const textDiff = atomsDiffer || diffTextAtom(atom, prevAtom as TextAtom); + if (!textDiff) return { pixiAtom: prevAtom.container, changed: false }; + + return { pixiAtom: null, changed: true }; + + case "paint": + const paintDiff = atomsDiffer || diffPaintAtom(atom, prevAtom as PaintAtom); + if (!paintDiff) return { pixiAtom: prevAtom.container, changed: false }; + + return { pixiAtom: null, changed: true }; + } +} \ No newline at end of file diff --git a/blix-plugins/blink/webview/diff.ts b/blix-plugins/blink/webview/diff.ts new file mode 100644 index 00000000..0bef98e9 --- /dev/null +++ b/blix-plugins/blink/webview/diff.ts @@ -0,0 +1,134 @@ +import { HierarchyAtom, HierarchyClump } from "./render"; +import { + Asset, + Atom, + BlobAtom, + Clump, + Filter, + ImageAtom, + PaintAtom, + ShapeAtom, + TextAtom, + Transform, +} from "./types"; + +export type ClumpDiff = "name" | "transform" | "opacity" | "filters"; + +export function diffClump(h1: Clump, h2: HierarchyClump) { + const diffs = new Set(); + if (h1 == null && h2 == null) return new Set([]); // Vacuous case + if (h1 == null || h2 == null) + return new Set(["name", "transform", "opacity", "filters"]); + + // Diff name + if (h1.name !== h2.name) { + diffs.add("name"); + } + + // Diff transform + if (h1.transform == null && h2.transform == null) { + diffs.add("transform"); + } else if (diffTransform(h1.transform, h2.transform).length > 0) { + diffs.add("transform"); + } + + // Diff opacity + if (h1.opacity !== h2.opacity) { + diffs.add("opacity"); + } + + // Diff filters + if (diffFilters(h1.filters, h2.filters)) { + diffs.add("filters"); + } + + return diffs; +} + +function diffTransform(t1: Transform, t2: Transform) { + const diffs = []; + if (t1?.position?.x !== t2?.position?.x || t1?.position?.y !== t2?.position?.y) + diffs.push("position"); + if (t1?.rotation !== t2?.rotation) diffs?.push("rotation"); + if (t1?.scale?.x !== t2?.scale?.x || t1?.scale?.y !== t2?.scale?.y) diffs?.push("scale"); + return diffs; +} + +// Returns true if the filters differ in some way +function diffFilters(f1s: Filter[], f2s: Filter[]) { + if (f1s == null && f2s == null) return false; + if (f1s == null || f2s == null) return true; + + const maxLen = Math.max(f1s.length, f2s.length); + + for (let f = 0; f < maxLen; f++) { + // Diff filter + if (f1s[f] && f2s[f]) { + // Diff types + if (f1s[f].type !== f2s[f].type) return true; + + // Diff params + const maxParamsLen = Math.max(f1s[f].params.length, f2s[f].params.length); + for (let p = 0; p < maxParamsLen; p++) { + if (f1s[f].params[p] !== f2s[f].params[p]) { + return true; + } + } + } else { + return true; + } + } + + return false; +} + +// Returns true if the atoms differ in some way +export function diffAtom(a1: Atom, a2: HierarchyAtom) { + if (a1 == null && a2 == null) return false; + if (a1 == null || a2 == null) return true; + + if (a1.type !== a2.type) return true; + return false; +} + +// TODO: Pass in `assets` later on, so that we can diff on asset.data instead of assetId +// TODO: For now this just returns a boolean, but later on we'll return a more fine-grained diff +export function diffImageAtom(a1: ImageAtom, a2: ImageAtom, assets1: { [key: string]: Asset }, assets2: { [key: string]: Asset } ) { + if (a1.assetId !== a2.assetId) return true; + if (assets1[a1.assetId].type !== assets2[a2.assetId].type) return true; + if (assets1[a1.assetId].data !== assets2[a2.assetId].data) return true; + return false; +} + +export function diffBlobAtom(a1: BlobAtom, a2: BlobAtom) { + if (a1.assetId !== a2.assetId) return true; + return false; +} + +export function diffShapeAtom(a1: ShapeAtom, a2: ShapeAtom) { + if (a1.shape !== a2.shape) return true; + if (a1.bounds?.w !== a2.bounds?.w || a1.bounds?.h !== a2.bounds?.h) return true; + if (a1.fill !== a2.fill) return true; + if (a1.stroke !== a2.stroke) return true; + if (a1.strokeWidth !== a2.strokeWidth) return true; + return false; +} + +export function diffTextAtom(a1: TextAtom, a2: TextAtom) { + if (a1.text !== a2.text) return true; + if (a1.fill !== a2.fill) return true; + if (a1.stroke !== a2.stroke) return true; + if (a1.strokeWidth !== a2.strokeWidth) return true; + if (a1.fontSize !== a2.fontSize) return true; + if (a1.fontFamily !== a2.fontFamily) return true; + if (a1.fontStyle !== a2.fontStyle) return true; + if (a1.fontWeight !== a2.fontWeight) return true; + if (a1.textAlign !== a2.textAlign) return true; + if (a1.textBaseline !== a2.textBaseline) return true; + return false; +} + +export function diffPaintAtom(a1: PaintAtom, a2: PaintAtom) { + if (a1.uuid !== a2.uuid) return true; + return false; +} diff --git a/blix-plugins/blink/webview/filter.ts b/blix-plugins/blink/webview/filter.ts new file mode 100644 index 00000000..b1b836d6 --- /dev/null +++ b/blix-plugins/blink/webview/filter.ts @@ -0,0 +1,31 @@ +import * as PIXI from 'pixi.js'; +import { Filter, getPixiFilter } from './types'; + +// Apply a series of filters and flatten the result to a sprite. +// This is done so that the filters are applied evenly regardless of scaling. +export function applyFilters(blink: PIXI.Application, content: PIXI.Container, filters: Filter[]) { + // `renderPadding` is necessary for filters like + // blur that spread beyond the bounds of the sprite + const renderPadding = 100; + + // Apply filters + content.filters = filters.map(getPixiFilter); + + // Normalize offset to fit within renderTexture + const { x: bx, y: by, width: bw, height: bh } = content.getBounds(); + content.transform.position.x = -bx + renderPadding; + content.transform.position.y = -by + renderPadding; + + // Render to texture + const renderTexture = PIXI.RenderTexture.create({ + width: bw + 2 * renderPadding, + height: bh + 2 * renderPadding, + }); + blink.renderer.render(content, { renderTexture: renderTexture }); + + // Create flattened sprite + const renderSprite = new PIXI.Sprite(renderTexture); + renderSprite.setTransform(bx - renderPadding, by - renderPadding); + + return renderSprite; +} \ No newline at end of file diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index aa0cce31..d7c5a79a 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -1,22 +1,42 @@ import * as PIXI from "pixi.js"; -import { getPixiFilter, type Atom, type Clump, type BlinkCanvas, type Asset } from "./clump"; +import { + getPixiFilter, + type Atom, + type Clump, + type BlinkCanvas, + type Asset, + Filter, + Transform, +} from "./types"; import { Viewport } from "pixi-viewport"; import { createBoundingBox } from "./select"; +import { renderAtom } from "./atom"; +import { applyFilters } from "./filter"; +import { diffAtom, diffClump } from "./diff"; // let prevMedia = null; // TODO: Replace with DiffDial -type Scene = { [key: string]: PIXI.Container }; +// Utility type to override properties of T with properties of R +type Override = { [P in Exclude]: T[P] } & R; + +type HierarchyData = { container: PIXI.Container; containerIndex: number }; + +export type HierarchyCanvas = Override; +export type HierarchyClump = Override & + HierarchyData; +export type HierarchyAtom = Atom & HierarchyData; +// type Clumps = { [key: string]: PIXI.Container }; let selected: string = ""; // NodeUUID of selected clump let oldSceneStructure = ""; let sceneStructure = "_"; -let oldScene: Scene = {}; -let clumps: Scene = {}; -let boundingBox: PIXI.Container; +let boundingBox: PIXI.Container = null; let scene: PIXI.Container; +let hierarchy: HierarchyCanvas | undefined = undefined; + export function renderApp( blink: PIXI.Application, canvas: BlinkCanvas, @@ -24,21 +44,25 @@ export function renderApp( send: (message: string, data: any) => void ): boolean { if (!canvas || !canvas.content) return false; - if (!scene) scene = viewport.addChild(new PIXI.Container()); - - oldSceneStructure = sceneStructure; - sceneStructure = getSceneStructure(canvas.content); - - if (oldSceneStructure === sceneStructure) return; + if (!scene) { + scene = new PIXI.Container(); + scene.name = "Blink Scene"; + viewport.addChild(scene); + } // Destroy previous viewport contents - scene.removeChildren(); - oldScene = clumps; - clumps = {}; + // scene.removeChildren(); + // if (boundingBox) { + // boundingBox.removeChildren(); + // } //===== CREATE BOUNDING BOX =====// - boundingBox = new PIXI.Container(); - blink.stage.addChild(boundingBox); + // TODO + if (boundingBox == null) { + boundingBox = new PIXI.Container(); + boundingBox.name = "boundingBox"; + blink.stage.addChild(boundingBox); + } //===== PRELOAD IMAGE ASSETS =====// const imgPromises = []; @@ -53,10 +77,28 @@ export function renderApp( // Construct clump hierarchy const loaded = Promise.all(imgPromises); loaded.then(() => { - - const child = renderClump(blink, canvas.content, canvas, viewport, send); - if (child != null) { - scene.addChild(child); + const { pixiClump, changed } = renderClump( + blink, + canvas.content, + hierarchy?.content, + canvas, + hierarchy, + viewport, + send + ); + + // Update hierarchy + hierarchy = { + assets: canvas.assets, + content: { + ...canvas.content, + container: pixiClump, + containerIndex: 0, + } as HierarchyClump, + }; + + if (hierarchy.content.container != null && changed) { + scene.addChild(hierarchy.content.container); } //===============// DELETE DEAD CLUMPS //==============// @@ -67,121 +109,161 @@ export function renderApp( // oldScene[nodeUUID].destroy(); // PIXI.js cleanup // } // } - console.log("CLUMPS", clumps); - console.log("SCENE", scene); + // console.log("CLUMPS", clumps); + // console.log("SCENE", scene); + console.log("===================================="); }); return true; } -function getSceneStructure(clump: Clump) { - if (!clump) return ""; - let res = clump.nodeUUID; - - if (clump.elements) { - let counter = clump.elements.length; - for (let child of clump.elements) { - if (child.class === "clump") { - res += ">" + getSceneStructure(child); - } else if (child.class === "atom") { - // res += ">" + child.nodeUUID; - } - } - } - - return res; -} - export function renderCanvas(blink: PIXI.Application, root: Clump) {} function renderClump( blink: PIXI.Application, clump: Clump, + prevClump: HierarchyClump | undefined, canvas: BlinkCanvas, + prevCanvas: BlinkCanvas, viewport: Viewport, send: (message: string, data: any) => void -) { - if (!clump) return null; +): { + pixiClump: PIXI.Container; + changed: boolean; // Whether the pixiClump is a different PIXI object than before +} { + if (!clump) return { pixiClump: null, changed: prevClump != null }; - // //===============// RESURRECT OLD CLUMPS //==============// - // if (clump.nodeUUID in oldScene) { - // // Clump already exists, resurrect and update it - // scene[clump.nodeUUID] = oldScene[clump.nodeUUID]; - // } - // //===============// ADD NEW CLUMPS //==============// - // else { - // } - - //========== CREATE CONTAINER ==========// - const content = new PIXI.Container(); - content.sortableChildren = true; + //========== DIFF CLUMP VS HIERARCHY ==========// + const diffs = diffClump(clump, prevClump); + // console.log("DIFFS", diffs); //========== CREATE CHILD ELEMENTS ==========// + let childChanged = false; + const children: PIXI.Container[] = []; + if (clump.elements) { - let counter = clump.elements.length; - for (let child of clump.elements) { - if (child.class === "clump") { - const pixiClump = renderClump(blink, child, canvas, viewport, send); + for (let i = 0; i < clump.elements.length; i++) { + const child = clump.elements[i]; + const prevChild = prevClump?.elements != null && prevClump.elements[i]; - if (pixiClump != null) { - pixiClump.zIndex = counter--; - content.addChild(pixiClump); + if (child.class === "clump") { + //===== CHILD CLUMP =====// + const { pixiClump, changed } = renderClump( + blink, + child as HierarchyClump, + (prevChild?.class === "clump" ? prevChild : undefined) as HierarchyClump, + canvas, + prevCanvas, + viewport, + send + ); + + if (changed) { + childChanged = true; + // console.log("CREATE CLUMP", pixiClump); + if (pixiClump != null) { + children.push(pixiClump); + // content.addChild(pixiClump); + } + + // Destroy previous contents + // console.log("DESTROY CLUMP", prevChild?.container); + prevChild?.container?.destroy(); } } else if (child.class === "atom") { - const pixiAtom = renderAtom(canvas.assets, child); + //===== CHILD ATOM =====// + const { pixiAtom, changed } = renderAtom( + canvas.assets, + prevCanvas?.assets, + child, + (prevChild?.class === "atom" ? prevChild : undefined) as HierarchyAtom + ); + + // Construct hierarchy atom + // const hierarchyAtom = child as HierarchyAtom; + // hierarchyAtom.container = pixiAtom; + // hierarchyAtom.containerIndex = i; + // hierarchy.elements.push(hierarchyAtom); + + // console.log("PIXI ATOM", hierarchyAtom); + + if (changed) { + childChanged = true; + // console.log("CREATE ATOM", pixiAtom); + // content.removeChild(pixiAtom); + if (pixiAtom != null) { + children.push(pixiAtom); + // content.addChild(pixiAtom); + } + + // Destroy previous contents + // console.log("DESTROY ATOM", prevChild?.container); + prevChild?.container?.destroy(); + } + } + } - if (pixiAtom != null) { - pixiAtom.zIndex = counter--; - content.addChild(pixiAtom); + // Remove previous surplus children + if (prevClump?.elements) { + for (let i = clump.elements.length + 1; i < prevClump.elements.length; i++) { + if (prevClump.elements[i].class === "clump") { + const pClump = prevClump.elements[i] as HierarchyClump; + // console.log("REMOVE CLUMP CHILD"); + prevClump.container.removeChild(pClump.container); + } else if (prevClump.elements[i].class === "atom") { + const pAtom = prevClump.elements[i] as HierarchyAtom; + // console.log("REMOVE ATOM CHILD"); + prevClump.container.removeChild(pAtom.container); } } } } - //========== CREATE INTERACTION BOX ==========// - // Create interaction box once sprite has loaded - content.sortChildren(); - - const resClump = new PIXI.Container(); - const resClumpContent = new PIXI.Container(); - resClump.sortableChildren = true; - - if (clump.filters && clump.filters.length > 0) { - //===== FLATTEN CLUMP =====// - // Clump uses filters, we must flatten it to a texture - // so that the filters are applied evenly regardless of scaling + //========== OBTAIN CLUMP CONTAINER ==========// + let resClump: PIXI.Container; + if (prevClump?.container) { + resClump = prevClump.container; + // TODO: Destroy previous contents + } else { + resClump = new PIXI.Container(); + resClump.name = "Clump"; + resClump.sortableChildren = true; - // `renderPadding` is necessary for filters like - // blur that spread beyond the bounds of the sprite - const renderPadding = 100; + addInteractivity(resClump, clump, viewport, send); + } - // Apply filters - content.filters = clump.filters.map(getPixiFilter); + if (childChanged || diffs.has("filters")) { + //========== RECREATE CONTENT ==========// + let content = new PIXI.Container(); + content = new PIXI.Container(); + content.name = "content"; + content.sortableChildren = true; - // Normalize offset to fit within renderTexture - const { x: bx, y: by, width: bw, height: bh } = content.getBounds(); - content.transform.position.x = -bx + renderPadding; - content.transform.position.y = -by + renderPadding; + // TODO: Only add children that have changed + // Also remove redundant children - // Render to texture - const renderTexture = PIXI.RenderTexture.create({ - width: bw + 2 * renderPadding, - height: bh + 2 * renderPadding, - }); - blink.renderer.render(content, { renderTexture: renderTexture }); + //Add children to content + for (let i = 0; i < children.length; i++) { + children[i].zIndex = children.length - i; + content.addChild(children[i]); + } - // Create flattened sprite and add to clump - const renderSprite = new PIXI.Sprite(renderTexture); - renderSprite.setTransform(bx - renderPadding, by - renderPadding); + content.sortChildren(); - resClumpContent.addChild(renderSprite); - } else { - //===== ADD CONTENT WITHOUT FLATTENING =====// - resClumpContent.addChild(content); + //========== APPLY FILTERS ==========// + if (clump.filters && clump.filters.length > 0) { + resClump.addChild(applyFilters(blink, content, clump.filters)); + } else { + //===== ADD CONTENT WITHOUT FLATTENING =====// + if (childChanged) { + // resClump.removeChildren(); + resClump.addChild(content); + } + } } // Get bounds before applying transform - const clumpBounds = resClumpContent.getBounds(); + const clumpBounds = resClump.getBounds(); //========== APPLY CLUMP PROPERTIES ==========// let transMatrix = PIXI.Matrix.IDENTITY; @@ -197,106 +279,73 @@ function renderClump( // const matTransform = resClumpContent.transform.worldTransform; if (clump.opacity) { - resClumpContent.alpha = Math.min(100, Math.max(0, clump.opacity)); + resClump.alpha = Math.min(1, Math.max(0, clump.opacity / 100.0)); } - resClump.addChild(resClumpContent); + resClump.sortChildren(); - const box = createBoundingBox(transMatrix, clumpBounds, viewport); + // boundingBox.addChild(createBoundingBox(transMatrix, resClump.getBounds(), viewport)); - boundingBox.removeChildren(); - boundingBox.addChild(box); + // hierarchy.container = resClump; + return { + pixiClump: resClump, + changed: true, + }; +} - resClump.sortChildren(); - //========== HANDLE EVENTS ==========// +function addInteractivity(container: PIXI.Container, clump: Clump, viewport: Viewport, send: (message: string, data: any) => void) { let dragging = false; var prevMousePos = new PIXI.Point(); - resClump.eventMode = "dynamic"; + container.eventMode = "dynamic"; // resClump.on("click", () => { // }); - resClump.on("mousedown", (event) => { + container.on("mousedown", (event) => { event.stopPropagation(); selected = clump.nodeUUID; dragging = true; prevMousePos = viewport.toWorld(event.global); }); viewport.on("mouseup", (event) => { + if (selected === clump.nodeUUID && dragging) { + send("tweak", { + nodeUUID: clump.nodeUUID, + inputs: { + positionX: container.transform.position.x, + positionY: container.transform.position.y + }, + }); + } + dragging = false; }); viewport.on("mousemove", (event) => { if (selected === clump.nodeUUID && dragging) { + // boundingBox.removeChildren(); + // boundingBox.addChild(createBoundingBox(container.transform.worldTransform, container.getBounds(), viewport)); + const pos = viewport.toWorld(event.global); const shift = new PIXI.Point(pos.x - prevMousePos.x, pos.y - prevMousePos.y); prevMousePos = pos; - resClump.transform.position.x += shift.x; - resClump.transform.position.y += shift.y; - - send("tweak", { - nodeUUID: clump.nodeUUID, - inputs: { - positionX: resClump.transform.position.x, - positionY: resClump.transform.position.y, - }, - }); + container.transform.position.x += shift.x; + container.transform.position.y += shift.y; + + // send("tweak", { + // nodeUUID: clump.nodeUUID, + // inputs: { + // // positionX: container.transform.position.x + shift.x, + // // positionY: container.transform.position.y + shift.y + // positionX: container.transform.position.x, + // positionY: container.transform.position.y + // }, + // }); } }); // To get global mouse position at any point: // console.log("MOUSE", blink.renderer.plugins.interaction.pointer.global); - - clumps[clump.nodeUUID] = resClump; - return resClump; -} - - - -function renderAtom(assets: { [key: string]: Asset }, atom: Atom) { - switch (atom.type) { - case "image": - if (assets[atom.assetId] && assets[atom.assetId].type === "image") { - const sprite = PIXI.Sprite.from(assets[atom.assetId].data); - - sprite.anchor.x = 0.5; - sprite.anchor.y = 0.5; - - return sprite; - } - - return null; - - case "shape": - const shapeContainer = new PIXI.Container(); - const shape = new PIXI.Graphics(); - // shape.lineStyle(1, 0xf43e5c, 0.5); - shape.beginFill(atom.fill, 1); - shape.lineStyle(atom.strokeWidth, atom.stroke, 1); - - switch (atom.shape) { - case "rectangle": - shape.drawRect(0, 0, atom.bounds.w, atom.bounds.h); - break; - case "ellipse": - shape.drawEllipse(0, 0, atom.bounds.w, atom.bounds.h); - break; - case "triangle": - shape.drawPolygon([0, 0, atom.bounds.w, 0, atom.bounds.w / 2, atom.bounds.h]); - break; - } - shape.endFill(); - - shapeContainer.addChild(shape); - - return shapeContainer; - - case "text": - break; - - case "paint": - break; - } -} +} \ No newline at end of file diff --git a/blix-plugins/blink/webview/clump.ts b/blix-plugins/blink/webview/types.ts similarity index 96% rename from blix-plugins/blink/webview/clump.ts rename to blix-plugins/blink/webview/types.ts index 18786268..fd23cf9c 100644 --- a/blix-plugins/blink/webview/clump.ts +++ b/blix-plugins/blink/webview/types.ts @@ -14,7 +14,6 @@ import { ZoomBlurFilter, TwistFilter } from "pixi-filters" -import { type } from "os"; export type BlinkCanvas = { assets: { [key: string]: Asset }; @@ -90,17 +89,18 @@ export function getPixiFilter(filter: Filter) { // A single indivisible unit of a clump (E.g. image, shape, text etc.) export type Atom = { class: "atom", nodeUUID: string } & (ImageAtom | ShapeAtom | TextAtom | PaintAtom | BlobAtom); -type ImageAtom = { +export type ImageAtom = { type: "image"; assetId: string; }; -type BlobAtom = { +export type BlobAtom = { type: "blob"; + blob: "image"; //TODO: Add more options assetId: string; } -type ShapeAtom = { +export type ShapeAtom = { type: "shape"; shape: "rectangle" | "ellipse" | "triangle"; @@ -109,7 +109,7 @@ type ShapeAtom = { stroke: number; strokeWidth: number; }; -type TextAtom = { +export type TextAtom = { type: "text"; text: string; @@ -123,7 +123,7 @@ type TextAtom = { textAlign: "left" | "center" | "right"; textBaseline: "top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom"; }; -type PaintAtom = { +export type PaintAtom = { type: "paint"; uuid: string; }; diff --git a/src/frontend/ui/base/palette/Palette.svelte b/src/frontend/ui/base/palette/Palette.svelte index adc56693..da5a3752 100644 --- a/src/frontend/ui/base/palette/Palette.svelte +++ b/src/frontend/ui/base/palette/Palette.svelte @@ -156,10 +156,14 @@ closePalette(); }, "blix.palette.scrollDown": () => { + if (!showPalette) return; + selectedItem++; repairItemIndex(); }, "blix.palette.scrollUp": () => { + if (!showPalette) return; + if (!searchTerm && promptHistory.length) { // inputElement.focus(); // inputElement.value = prompts[0]; @@ -172,6 +176,8 @@ repairItemIndex(); }, "blix.palette.selectItem": () => { + if (!showPalette) return; + // Default enter const item = categories[selectedCategory]?.items[selectedItem]; if (item) { @@ -179,6 +185,8 @@ } }, "blix.palette.prompt": async () => { + if (!showPalette) return; + const prompt = searchTerm.trim(); if (!prompt) { diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index 68990632..5ad41ec2 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -1,5 +1,7 @@
@@ -11,6 +13,13 @@ {/each}
+ diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index 8a7a99fd..351f4842 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -5,7 +5,10 @@ import NodeUiFragment from "./NodeUIFragment.svelte"; import { createEventDispatcher } from "svelte"; import { graphMall } from "lib/stores/GraphStore"; - import { colord } from "colord"; + import { colord, extend } from "colord"; + import a11yPlugin from "colord/plugins/a11y"; + + extend([a11yPlugin]); const dispatch = createEventDispatcher(); @@ -47,27 +50,11 @@ function changeBrightness(color: CSSColorString, percent: number) { return colord(color).lighten(percent).toRgbString(); - // var R = parseInt(color.substring(1, 3), 16); - // var G = parseInt(color.substring(3, 5), 16); - // var B = parseInt(color.substring(5, 7), 16); - - // R = parseInt(`${(R * (100 + percent)) / 100}`); - // G = parseInt(`${(G * (100 + percent)) / 100}`); - // B = parseInt(`${(B * (100 + percent)) / 100}`); - - // R = R < 255 ? R : 255; - // G = G < 255 ? G : 255; - // B = B < 255 ? B : 255; - - // R = Math.round(R); - // G = Math.round(G); - // B = Math.round(B); - - // var RR = R.toString(16).length == 1 ? "0" + R.toString(16) : R.toString(16); - // var GG = G.toString(16).length == 1 ? "0" + G.toString(16) : G.toString(16); - // var BB = B.toString(16).length == 1 ? "0" + B.toString(16) : B.toString(16); + } - // return ("#" + RR + GG + BB) as CSSColorString; + function checkShowTextOutline(color: string) { + const readable = colord(color).isReadable(); + return readable; } async function nodeClicked(e: CustomEvent) { @@ -142,11 +129,15 @@ height="{graphNode.dims.h}" --> let:hovering > {#if hovering} + {@const typeCol = changeBrightness(color, 0.3)}
{#if input.displayName} {input.displayName}
{/if} - <{input.type || "any"}{input.type || "any"}>
{/if} @@ -177,12 +168,15 @@ height="{graphNode.dims.h}" --> let:hovering > {#if hovering} + {@const typeCol = changeBrightness(color, 0.3)}
{#if output.displayName} {output.displayName}
{/if} - <{output.type || "any"}>
@@ -259,6 +253,12 @@ height="{graphNode.dims.h}" --> padding: 0.2em; top: -3.5em; } + .outlineText { + background-color: lightgrey; + border-radius: 0.25em; + padding: 0.1em; + } + .header { display: flex; justify-content: space-between; diff --git a/src/index.ts b/src/index.ts index 360bf031..a98c4bfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { MenuItem, dialog, globalShortcut, + session, } from "electron"; import { join } from "path"; import { parse } from "url"; @@ -65,12 +66,20 @@ let blix: Blix | null = null; * process will bind to the window IPC APIs 5. The Blix state is instantiated * and the various managers are initialized. */ +// TODO: Investigate app.whenReady().then(...) +// This may be more stable, as it guarantees the callback always fires regardless of startup time +// See: [https://www.reddit.com/r/electronjs/comments/t151k8/what_is_the_difference_between_apponready_and/hydu5vc] app.on("ready", async () => { protocol.registerFileProtocol("blix-image", (request, callback) => { const url = request.url.slice("blix-image://".length); callback({ path: join(__dirname, "..", "..", url) }); }); + // TODO: Remove + await session.defaultSession.loadExtension( + "/home/rec1dite/.config/google-chrome/Default/Extensions/aamddddknhcagpehecnhphigffljadon/2.6.1_0" + ); + // const coreGraphInterpreter = new CoreGraphInterpreter(new ToolboxRegistry); // coreGraphInterpreter.run(); From 3d4e4cfdf199eeb2b14799e03abb432822520383 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 12:12:43 +0200 Subject: [PATCH 048/209] Try something --- .github/workflows/build.yml | 2 +- electron-builder.yml | 4 ++++ package-lock.json | 4 ++-- package.json | 26 ++++++++++++++++++-------- 4 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 electron-builder.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e8d2910..71ceb2e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: [buntu-latest, windows-latest] steps: - name: Check out Git repository diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 00000000..b5c3ed33 --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,4 @@ +appId: com.the-spanish-inquisition.blix +publish: + provider: github + token: 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v \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a05bf73f..9504cdfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blix", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blix", - "version": "1.0.0", + "version": "1.1.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index df15320c..c18a3090 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.1.0", + "version": "1.2.0", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" @@ -62,7 +62,10 @@ "preversion": "npm-run-all -s lint format", "prepare": "husky install", "llm": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/ai-profiler-v2.js", - "cli": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/cli.js" + "cli": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/cli.js", + "predeploy": "npm run build", + "deploy": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false GH_TOKEN=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 electron-builder --win --publish always", + "postdeploy": "node scripts/cleanBuilds.js" }, "lint-staged": { "src/electron/**/*.{js,ts}": "eslint -c eslint.electron.json", @@ -165,19 +168,24 @@ "build": { "productName": "Blix", "appId": "com.the-spanish-inquisition.blix", - "copyright": "Copyright© 2023 Blix", + "copyright": "Copyright © 2023 The Spanish Inquisition", "win": { - "target": "nsis" - + "target": [ + "nsis" + ] }, "mac": { - "target": "dmg", + "target": [ + "dmg" + ], "category": "productivity", "type": "distribution", "hardenedRuntime": "true" }, "linux": { - "target": "AppImage", + "target": [ + "AppImage" + ], "category": "productivity" }, "files": [ @@ -196,10 +204,12 @@ ] } ], - "publish": { + "publish": [ + { "provider": "github", "owner": "ArmandKrynauw", "repo": "Blix" } + ] } } From 59da2b4a4386d6626b792695226d493bc3f5124e Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 12:14:10 +0200 Subject: [PATCH 049/209] Oops, made a mistake last commit --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71ceb2e4..9cfcc3ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - os: [buntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest] steps: - name: Check out Git repository From ffafba0315a3dba67e6812d4c35c02cdb3e1d3d7 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 13:28:30 +0200 Subject: [PATCH 050/209] Try again --- .github/workflows/build.yml | 19 +++++++++++-------- package.json | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cfcc3ba..6e52a807 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,15 +19,18 @@ jobs: with: node-version: 18 - - name: Build/release Electron app - uses: samuelmeuli/action-electron-builder@v1 - with: - github_token: ${{ secrets.github_token }} + - name: Build & Release Blix + run: npm run deploy + + # - name: Build/release Electron app + # uses: samuelmeuli/action-electron-builder@v1 + # with: + # github_token: ${{ secrets.github_token }} - # If the commit is tagged with a version (e.g. "v1.0.0"), - # release the app after building - # release: ${{ startsWith(github.ref, 'refs/tags/v') }} - release: true + # # If the commit is tagged with a version (e.g. "v1.0.0"), + # # release the app after building + # # release: ${{ startsWith(github.ref, 'refs/tags/v') }} + # release: true # name: Build Codebase diff --git a/package.json b/package.json index c18a3090..58a3bc5f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "llm": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/ai-profiler-v2.js", "cli": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/cli.js", "predeploy": "npm run build", - "deploy": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false GH_TOKEN=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 electron-builder --win --publish always", + "deploy": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false on-builder --publish always", "postdeploy": "node scripts/cleanBuilds.js" }, "lint-staged": { From 408f525e0c2246f93213b09d84e847e6b4a85b05 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 13:32:31 +0200 Subject: [PATCH 051/209] Add extra step --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e52a807..ad978fb6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,11 +14,14 @@ jobs: - name: Check out Git repository uses: actions/checkout@v3 - - name: Install Node.js and NPM + - name: Setup Node uses: actions/setup-node@v3 with: node-version: 18 + - name: Install dependencies + run: npm ci + - name: Build & Release Blix run: npm run deploy From f9457c59aa7c9558e15e3ac720d4a8e06af6bea7 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 13:34:45 +0200 Subject: [PATCH 052/209] Fix package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58a3bc5f..959bf536 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "llm": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/ai-profiler-v2.js", "cli": "cross-env NODE_ENV=development npm run build:electron:dev && node build/electron/lib/ai/cli.js", "predeploy": "npm run build", - "deploy": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false on-builder --publish always", + "deploy": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --publish always", "postdeploy": "node scripts/cleanBuilds.js" }, "lint-staged": { From 1ad093d9b36b7c1bdc5aa34a3f9230b22053f707 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 13:45:31 +0200 Subject: [PATCH 053/209] Add set env step --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad978fb6..4a5b14d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ jobs: - name: Build & Release Blix run: npm run deploy + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # - name: Build/release Electron app # uses: samuelmeuli/action-electron-builder@v1 From 777c267434cdba6ac28a93732a8aeaa3080cdd5f Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 13:52:50 +0200 Subject: [PATCH 054/209] Make some changes --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a5b14d8..f6ac7559 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: run: npm ci - name: Build & Release Blix - run: npm run deploy + run: npm run dist env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e0e5083fadee03d456c81abae380d05fe6ddbce4 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 14:02:20 +0200 Subject: [PATCH 055/209] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 959bf536..08d3ee6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.2.0", + "version": "1.0.1", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" From bf0ff1085bda8eb30e6dc9b27156f6a8137015ee Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 18 Sep 2023 15:38:04 +0200 Subject: [PATCH 056/209] Fix Blink nested children destroyed on graph transform update --- blix-plugins/blink/webview/atom.ts | 8 +- blix-plugins/blink/webview/render.ts | 213 +++++++++++++++------------ 2 files changed, 125 insertions(+), 96 deletions(-) diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index 5b844283..da261294 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -1,12 +1,13 @@ import * as PIXI from 'pixi.js'; import { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; import { diffAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; -import { HierarchyAtom } from './render'; +import { HierarchyAtom, randomId } from './render'; export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined): { pixiAtom: PIXI.Container, changed: boolean // Whether the pixiClump is a different PIXI object than before } { + console.log(">>>---------------------------------"); if (!atom) return null; const atomsDiffer = diffAtom(atom, prevAtom); @@ -24,7 +25,8 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: sprite.anchor.x = 0.5; sprite.anchor.y = 0.5; - sprite.name = "ImageSprite"; + sprite.name = `ImageSprite(${randomId()})`; + // console.log("DESTROY PREV ATOM"); prevAtom?.container?.destroy(); return { pixiAtom: sprite, changed: true }; } @@ -57,7 +59,7 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: shapeContainer.addChild(shape); - shapeContainer.name = "ShapeContainer"; + shapeContainer.name = `ShapeContainer(${randomId()})`; return { pixiAtom: shapeContainer, changed: true }; case "text": diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index d7c5a79a..3062462a 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -14,12 +14,16 @@ import { renderAtom } from "./atom"; import { applyFilters } from "./filter"; import { diffAtom, diffClump } from "./diff"; +export function randomId() { + return Math.random().toString(36).slice(2, 6); +} + // let prevMedia = null; // TODO: Replace with DiffDial // Utility type to override properties of T with properties of R type Override = { [P in Exclude]: T[P] } & R; -type HierarchyData = { container: PIXI.Container; containerIndex: number }; +type HierarchyData = { container: PIXI.Container }; export type HierarchyCanvas = Override; export type HierarchyClump = Override & @@ -43,6 +47,8 @@ export function renderApp( viewport: Viewport, send: (message: string, data: any) => void ): boolean { + console.log("===================================="); + if (!canvas || !canvas.content) return false; if (!scene) { scene = new PIXI.Container(); @@ -93,7 +99,6 @@ export function renderApp( content: { ...canvas.content, container: pixiClump, - containerIndex: 0, } as HierarchyClump, }; @@ -111,7 +116,6 @@ export function renderApp( // } // console.log("CLUMPS", clumps); // console.log("SCENE", scene); - console.log("===================================="); }); return true; @@ -131,15 +135,19 @@ function renderClump( pixiClump: PIXI.Container; changed: boolean; // Whether the pixiClump is a different PIXI object than before } { - if (!clump) return { pixiClump: null, changed: prevClump != null }; + if (!clump) { + console.log("------------------------------------"); + return { pixiClump: null, changed: prevClump != null }; + } + console.log("PREVCLUMP EXISTS", prevClump?.container != null) //========== DIFF CLUMP VS HIERARCHY ==========// const diffs = diffClump(clump, prevClump); - // console.log("DIFFS", diffs); //========== CREATE CHILD ELEMENTS ==========// let childChanged = false; - const children: PIXI.Container[] = []; + const children: { changed: boolean, child: PIXI.Container }[] = []; + const hierarchyElements = []; if (clump.elements) { for (let i = 0; i < clump.elements.length; i++) { @@ -158,18 +166,18 @@ function renderClump( send ); - if (changed) { - childChanged = true; - // console.log("CREATE CLUMP", pixiClump); - if (pixiClump != null) { - children.push(pixiClump); - // content.addChild(pixiClump); - } + // Construct hierarchy clump + const hierarchyClump = child as HierarchyClump; + hierarchyClump.container = pixiClump; + hierarchyElements.push(hierarchyClump); - // Destroy previous contents - // console.log("DESTROY CLUMP", prevChild?.container); - prevChild?.container?.destroy(); + childChanged ||= changed; + + if (pixiClump != null) { + children.push({ changed, child: pixiClump }); + // content.addChild(pixiClump); } + } else if (child.class === "atom") { //===== CHILD ATOM =====// const { pixiAtom, changed } = renderAtom( @@ -180,84 +188,102 @@ function renderClump( ); // Construct hierarchy atom - // const hierarchyAtom = child as HierarchyAtom; - // hierarchyAtom.container = pixiAtom; - // hierarchyAtom.containerIndex = i; - // hierarchy.elements.push(hierarchyAtom); - - // console.log("PIXI ATOM", hierarchyAtom); - - if (changed) { - childChanged = true; - // console.log("CREATE ATOM", pixiAtom); - // content.removeChild(pixiAtom); - if (pixiAtom != null) { - children.push(pixiAtom); - // content.addChild(pixiAtom); - } + const hierarchyAtom = child as HierarchyAtom; + hierarchyAtom.container = pixiAtom; + hierarchyElements.push(hierarchyAtom); - // Destroy previous contents - // console.log("DESTROY ATOM", prevChild?.container); - prevChild?.container?.destroy(); - } - } - } + childChanged ||= changed; - // Remove previous surplus children - if (prevClump?.elements) { - for (let i = clump.elements.length + 1; i < prevClump.elements.length; i++) { - if (prevClump.elements[i].class === "clump") { - const pClump = prevClump.elements[i] as HierarchyClump; - // console.log("REMOVE CLUMP CHILD"); - prevClump.container.removeChild(pClump.container); - } else if (prevClump.elements[i].class === "atom") { - const pAtom = prevClump.elements[i] as HierarchyAtom; - // console.log("REMOVE ATOM CHILD"); - prevClump.container.removeChild(pAtom.container); + if (pixiAtom != null) { + children.push({ changed, child: pixiAtom }); + // content.addChild(pixiAtom); } } } + console.log("-> CHILDREN", children); } //========== OBTAIN CLUMP CONTAINER ==========// let resClump: PIXI.Container; - if (prevClump?.container) { - resClump = prevClump.container; - // TODO: Destroy previous contents - } else { - resClump = new PIXI.Container(); - resClump.name = "Clump"; - resClump.sortableChildren = true; + let content: PIXI.Container; + const newContainer = prevClump?.container == null; + // Create resClump + if (newContainer) { + resClump = new PIXI.Container(); + resClump.name = `Clump(${randomId()})`; + // resClump.sortableChildren = true; addInteractivity(resClump, clump, viewport, send); - } - if (childChanged || diffs.has("filters")) { - //========== RECREATE CONTENT ==========// - let content = new PIXI.Container(); + } else { resClump = prevClump.container; } + + // Add content + if (resClump.children.length === 0) { content = new PIXI.Container(); - content.name = "content"; + content.name = `content(${randomId()})`; content.sortableChildren = true; - // TODO: Only add children that have changed - // Also remove redundant children + resClump.addChild(content); + } else { content = resClump.getChildAt(0) as PIXI.Container; } + + console.log(`==> ${newContainer ? "" : "NO "}NEW RESCLUMP (${resClump.name.split("(")[1].slice(0, 4)})`); + + if (childChanged || diffs.has("filters")) { + //========== BUILD CONTENT ==========// //Add children to content - for (let i = 0; i < children.length; i++) { - children[i].zIndex = children.length - i; - content.addChild(children[i]); + if (newContainer) { + for (let i = 0; i < children.length; i++) { + children[i].child.zIndex = children.length - i; + console.log("=====> ADD CHILD", i, children[i].child.name); + content.addChild(children[i].child); + } + } else { + for (let i = 0; i < children.length; i++) { + children[i].child.zIndex = children.length - i; + // Only update if child changed + if (children[i].changed) { + console.log("=====> UPDATE CHILD", i, children[i].child.name); + // const prevChild = content.removeChildAt(i) as PIXI.Container; + // console.log("OLD CHILD", prevChild.name, "NEW CHILD", children[i].child.name) + // content.addChildAt(children[i].child, i); + // children[i].child.addChild(prevChild.getChildAt(prevChild.children.length - 1)); + } + } + } + + if (false && !newContainer) { + // Remove redundant surplus children + if (prevClump?.elements) { + for (let i = clump.elements.length + 1; i < prevClump.elements.length; i++) { + if (prevClump.elements[i].class === "clump") { + const pClump = prevClump.elements[i] as HierarchyClump; + console.log("REMOVE CLUMP CHILD"); + prevClump.container.removeChild(pClump.container); + } else if (prevClump.elements[i].class === "atom") { + const pAtom = prevClump.elements[i] as HierarchyAtom; + console.log("REMOVE ATOM CHILD"); + prevClump.container.removeChild(pAtom.container); + } + } + } } content.sortChildren(); - //========== APPLY FILTERS ==========// - if (clump.filters && clump.filters.length > 0) { + if (clump.filters && clump.filters.length > 0) + { + //===== FILTERS =====// resClump.addChild(applyFilters(blink, content, clump.filters)); - } else { + } + else + { //===== ADD CONTENT WITHOUT FLATTENING =====// if (childChanged) { - // resClump.removeChildren(); - resClump.addChild(content); + console.log("RESCLUMP CHILDREN", resClump.children); + // if (newContainer) { + // resClump.addChild(content); + // } } } } @@ -266,30 +292,40 @@ function renderClump( const clumpBounds = resClump.getBounds(); //========== APPLY CLUMP PROPERTIES ==========// - let transMatrix = PIXI.Matrix.IDENTITY; - if (clump.transform) { - const { position: pos, rotation: rot, scale: scl } = clump.transform; + if (diffs.has("transform")) { + console.log("UPDATE TRANSFORM", resClump.name.split("(")[1].slice(0, 4)); + let transMatrix = PIXI.Matrix.IDENTITY; + if (clump.transform) { + const { position: pos, rotation: rot, scale: scl } = clump.transform; + + if (scl) transMatrix.scale(scl.x, scl.y); + if (rot) transMatrix.rotate((rot * Math.PI) / 180); + if (pos) transMatrix.translate(pos.x, pos.y); + } - if (scl) transMatrix.scale(scl.x, scl.y); - if (rot) transMatrix.rotate((rot * Math.PI) / 180); - if (pos) transMatrix.translate(pos.x, pos.y); + resClump.transform.setFromMatrix(transMatrix); + // const matTransform = resClumpContent.transform.worldTransform; } - resClump.transform.setFromMatrix(transMatrix); - // const matTransform = resClumpContent.transform.worldTransform; - - if (clump.opacity) { - resClump.alpha = Math.min(1, Math.max(0, clump.opacity / 100.0)); + if (diffs.has("opacity")) { + if (clump.opacity) { + resClump.alpha = Math.min(1, Math.max(0, clump.opacity / 100.0)); + } } resClump.sortChildren(); // boundingBox.addChild(createBoundingBox(transMatrix, resClump.getBounds(), viewport)); - // hierarchy.container = resClump; + if (prevClump != null) { + prevClump.container = resClump; + } + // console.log("--> RESULT", resClump, "\n\n--> CHILD CHANGED", childChanged, "\n\n--> DIFFS", diffs); + + console.log("------------------------------------"); return { pixiClump: resClump, - changed: true, + changed: childChanged || diffs.size > 0, }; } @@ -328,21 +364,12 @@ function addInteractivity(container: PIXI.Container, clump: Clump, viewport: Vie // boundingBox.addChild(createBoundingBox(container.transform.worldTransform, container.getBounds(), viewport)); const pos = viewport.toWorld(event.global); + // const pos = viewport.worldTransform.apply(event.global); const shift = new PIXI.Point(pos.x - prevMousePos.x, pos.y - prevMousePos.y); prevMousePos = pos; container.transform.position.x += shift.x; container.transform.position.y += shift.y; - - // send("tweak", { - // nodeUUID: clump.nodeUUID, - // inputs: { - // // positionX: container.transform.position.x + shift.x, - // // positionY: container.transform.position.y + shift.y - // positionX: container.transform.position.x, - // positionY: container.transform.position.y - // }, - // }); } }); From a627aed76040a29637c9f1956c583e3889f05877 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 16:05:55 +0200 Subject: [PATCH 057/209] Fix --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6ac7559..07ac0c3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - name: Build & Release Blix run: npm run dist env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.TEST_TOKEN }} # - name: Build/release Electron app # uses: samuelmeuli/action-electron-builder@v1 From 58a31a91ce8b821b314f7ce6be8ac8e2144b0f21 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 16:13:30 +0200 Subject: [PATCH 058/209] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08d3ee6b..cba21191 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.1", + "version": "1.0.2", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" From 37ba60cc5e057ad953aa7224aeef5999a090c809 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 18 Sep 2023 16:52:14 +0200 Subject: [PATCH 059/209] Fix Blink filter bounds broken with dynamic viewport on re-render --- blix-plugins/blink/webview/filter.ts | 15 +++++++++++---- blix-plugins/blink/webview/render.ts | 24 ++++++++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/blix-plugins/blink/webview/filter.ts b/blix-plugins/blink/webview/filter.ts index b1b836d6..5e7aa3fb 100644 --- a/blix-plugins/blink/webview/filter.ts +++ b/blix-plugins/blink/webview/filter.ts @@ -1,5 +1,6 @@ import * as PIXI from 'pixi.js'; import { Filter, getPixiFilter } from './types'; +import { randomId } from './render'; // Apply a series of filters and flatten the result to a sprite. // This is done so that the filters are applied evenly regardless of scaling. @@ -8,14 +9,14 @@ export function applyFilters(blink: PIXI.Application, content: PIXI.Container, f // blur that spread beyond the bounds of the sprite const renderPadding = 100; - // Apply filters - content.filters = filters.map(getPixiFilter); - // Normalize offset to fit within renderTexture - const { x: bx, y: by, width: bw, height: bh } = content.getBounds(); + const { x: bx, y: by, width: bw, height: bh } = content.getLocalBounds(); content.transform.position.x = -bx + renderPadding; content.transform.position.y = -by + renderPadding; + // Apply filters + content.filters = filters.map(getPixiFilter); + // Render to texture const renderTexture = PIXI.RenderTexture.create({ width: bw + 2 * renderPadding, @@ -23,8 +24,14 @@ export function applyFilters(blink: PIXI.Application, content: PIXI.Container, f }); blink.renderer.render(content, { renderTexture: renderTexture }); + content.filters = null; + content.transform.setFromMatrix(new PIXI.Matrix()); + // Create flattened sprite const renderSprite = new PIXI.Sprite(renderTexture); + renderSprite.name = `FilterSprite(${randomId()})` + // renderSprite.anchor.x = 0.5; + // renderSprite.anchor.y = 0.5; renderSprite.setTransform(bx - renderPadding, by - renderPadding); return renderSprite; diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 3062462a..7486eb6e 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -225,10 +225,11 @@ function renderClump( resClump.addChild(content); } else { content = resClump.getChildAt(0) as PIXI.Container; } + content.visible = true; console.log(`==> ${newContainer ? "" : "NO "}NEW RESCLUMP (${resClump.name.split("(")[1].slice(0, 4)})`); - if (childChanged || diffs.has("filters")) { + if (childChanged || newContainer || diffs.has("filters")) { //========== BUILD CONTENT ==========// //Add children to content @@ -271,25 +272,36 @@ function renderClump( content.sortChildren(); - if (clump.filters && clump.filters.length > 0) + if ((diffs.has("filters") || childChanged) && clump.filters && clump.filters.length > 0) { //===== FILTERS =====// - resClump.addChild(applyFilters(blink, content, clump.filters)); + console.log("APPLY FILTER", content.name); + const filterSprite = applyFilters(blink, content, clump.filters); + // resClump.addChildAt(content, 0); + if (resClump.children.length > 1) { + resClump.removeChildAt(1); + } + resClump.addChildAt(filterSprite, 1); } else { //===== ADD CONTENT WITHOUT FLATTENING =====// if (childChanged) { console.log("RESCLUMP CHILDREN", resClump.children); - // if (newContainer) { - // resClump.addChild(content); - // } } } } + if (resClump.children.length > 1) { + // If we've applied a filter, hide the content + content.visible = false; + } + // Get bounds before applying transform const clumpBounds = resClump.getBounds(); + // TODO: Optimize with resClump._bounds.getRectangle() on children, + // to avoid recalculating all the way down the hierarchy + // Also: Look into .getLocalBounds() instead //========== APPLY CLUMP PROPERTIES ==========// if (diffs.has("transform")) { From 42594df6a5e192aa092f9af3b630096c72bcfe41 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 18 Sep 2023 17:19:05 +0200 Subject: [PATCH 060/209] Update workflow --- .github/workflows/build.yml | 77 +++---------------------------------- package.json | 2 +- 2 files changed, 6 insertions(+), 73 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07ac0c3e..a33fd069 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,77 +22,10 @@ jobs: - name: Install dependencies run: npm ci - - name: Build & Release Blix + - name: Build Blix Plugins + run: cd ./blix-plugins/glfx-plugin && npm ci && npm run build + + - name: Build/Release Blix run: npm run dist env: - GH_TOKEN: ${{ secrets.TEST_TOKEN }} - - # - name: Build/release Electron app - # uses: samuelmeuli/action-electron-builder@v1 - # with: - # github_token: ${{ secrets.github_token }} - - # # If the commit is tagged with a version (e.g. "v1.0.0"), - # # release the app after building - # # release: ${{ startsWith(github.ref, 'refs/tags/v') }} - # release: true - -# name: Build Codebase - -# on: -# push: -# branches: [ master, dev ] -# pull_request: -# branches: [ master, dev ] -# types: [opened, synchronize, reopened, ready_for_review] -# permissions: -# contents: read - -# jobs: -# start: -# name: Start State 🚀🚀🚀 -# runs-on: ubuntu-latest -# steps: -# - name: Starting -# id: init -# run: | -# echo "Starting building of ${{ github.repository }}" - -# build_project: -# name: Build on all platforms -# runs-on: ${{ matrix.os }} -# strategy: -# matrix: -# os: [ubuntu-latest, windows-latest, macOS-latest] -# needs: start -# steps: -# - name: Checkout for ${{ runner.os }} -# uses: actions/checkout@v3 -# - name: Set up Node 18 -# uses: actions/setup-node@v3 -# with: -# node-version: 18 - -# - name: Cache dependencies -# uses: actions/cache@v2 -# with: -# path: ~/.npm -# key: npm-${{ hashFiles('package-lock.json') }} -# restore-keys: npm- - -# - name: Install dependencies -# run: npm ci -# - name: Run tests and collect coverage -# run: npm run test -# - name: Build Project for ${{ runner.os }} -# run: npm run build - -# end: -# name: End State ✅✅✅ -# runs-on: ubuntu-latest -# needs: build_project -# steps: -# - name: Ending -# id: init -# run: | -# echo "Ending building of ${{ github.repository }}" \ No newline at end of file + GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file diff --git a/package.json b/package.json index cba21191..0e0067a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.2", + "version": "1.0.3", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" From b5583c6fde0c6bfecc23dc99ad03811d20a5fa0d Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 18 Sep 2023 17:39:49 +0200 Subject: [PATCH 061/209] Fix Blink shapes and add color pickers for fill/stroke --- blix-plugins/blink/src/main.cjs | 24 ++++++++++++-- blix-plugins/blink/webview/atom.ts | 12 ++++--- blix-plugins/blink/webview/diff.ts | 2 ++ blix-plugins/blink/webview/render.ts | 48 +++++++++------------------- blix-plugins/blink/webview/types.ts | 12 +++++++ 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 558f64ec..0e43d32b 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -4,6 +4,10 @@ function getUUID() { return crypto.randomBytes(16).toString("base64url"); } +function colorHexToAlpha(str) { + if (str.length <= 7) return 1.0; + return parseInt("0x" + str.slice(7, 9))/255.0; +} function colorHexToNumber(str) { return parseInt(str.slice(0, 7).replace("#", "0x")); } @@ -285,6 +289,20 @@ const nodes = { ); } const getTransform = addTransformInput(ui, ["position", "rotation"]); + + ui.addColorPicker({ + componentId: "fill", + label: "Fill", + defaultValue: "#000000", + triggerUpdate: true, + }, {}) + ui.addColorPicker({ + componentId: "stroke", + label: "Stroke", + defaultValue: "#00000000", + triggerUpdate: true, + }, {}) + addTweakability(ui); nodeBuilder.define(async (input, uiInput, from) => { @@ -308,8 +326,10 @@ const nodes = { shape: uiInput["shape"], bounds: { w: uiInput["width"], h: uiInput["height"] }, - fill: 0x0000ff, - stroke: 0xff0000, + fill: colorHexToNumber(uiInput["fill"]), + fillAlpha: colorHexToAlpha(uiInput["fill"]), + stroke: colorHexToNumber(uiInput["stroke"]), + strokeAlpha: colorHexToAlpha(uiInput["stroke"]), strokeWidth: 1, } ] diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index da261294..5e25811c 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -35,13 +35,15 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: case "shape": const shapeDiff = atomsDiffer || diffShapeAtom(atom, prevAtom as ShapeAtom); - if (!shapeDiff) return { pixiAtom: prevAtom.container, changed: false }; + if (!shapeDiff) { + return { pixiAtom: prevAtom.container, changed: false }; + } const shapeContainer = new PIXI.Container(); const shape = new PIXI.Graphics(); // shape.lineStyle(1, 0xf43e5c, 0.5); - shape.beginFill(atom.fill, 1); - shape.lineStyle(atom.strokeWidth, atom.stroke, 1); + shape.beginFill(atom.fill, atom.fillAlpha); + shape.lineStyle(atom.strokeWidth, atom.stroke, atom.strokeAlpha); const halfBounds = { w: atom.bounds.w / 2, h: atom.bounds.h / 2 }; switch (atom.shape) { @@ -49,10 +51,10 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: shape.drawRect(-halfBounds.w, -halfBounds.h, atom.bounds.w, atom.bounds.h); break; case "ellipse": - shape.drawEllipse(-halfBounds.w, -halfBounds.h, atom.bounds.w, atom.bounds.h); + shape.drawEllipse(0, 0, halfBounds.w, halfBounds.h); break; case "triangle": - shape.drawPolygon([0, 0, atom.bounds.w, 0, atom.bounds.w / 2, atom.bounds.h]); + shape.drawPolygon([-halfBounds.w, halfBounds.h, halfBounds.w, halfBounds.h, 0, -halfBounds.h]); break; } shape.endFill(); diff --git a/blix-plugins/blink/webview/diff.ts b/blix-plugins/blink/webview/diff.ts index 0bef98e9..49dd403d 100644 --- a/blix-plugins/blink/webview/diff.ts +++ b/blix-plugins/blink/webview/diff.ts @@ -109,7 +109,9 @@ export function diffShapeAtom(a1: ShapeAtom, a2: ShapeAtom) { if (a1.shape !== a2.shape) return true; if (a1.bounds?.w !== a2.bounds?.w || a1.bounds?.h !== a2.bounds?.h) return true; if (a1.fill !== a2.fill) return true; + if (a1.fillAlpha !== a2.fillAlpha) return true; if (a1.stroke !== a2.stroke) return true; + if (a1.strokeAlpha !== a2.strokeAlpha) return true; if (a1.strokeWidth !== a2.strokeWidth) return true; return false; } diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 7486eb6e..0382213c 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -12,7 +12,7 @@ import { Viewport } from "pixi-viewport"; import { createBoundingBox } from "./select"; import { renderAtom } from "./atom"; import { applyFilters } from "./filter"; -import { diffAtom, diffClump } from "./diff"; +import { diffClump } from "./diff"; export function randomId() { return Math.random().toString(36).slice(2, 6); @@ -105,17 +105,6 @@ export function renderApp( if (hierarchy.content.container != null && changed) { scene.addChild(hierarchy.content.container); } - - //===============// DELETE DEAD CLUMPS //==============// - // const newClumps = new Set(Object.keys(scene)); - - // for (let nodeUUID in oldScene) { - // if (!newClumps.has(nodeUUID)) { - // oldScene[nodeUUID].destroy(); // PIXI.js cleanup - // } - // } - // console.log("CLUMPS", clumps); - // console.log("SCENE", scene); }); return true; @@ -233,41 +222,35 @@ function renderClump( //========== BUILD CONTENT ==========// //Add children to content - if (newContainer) { + if (true || newContainer) { + content.removeChildren(); for (let i = 0; i < children.length; i++) { children[i].child.zIndex = children.length - i; console.log("=====> ADD CHILD", i, children[i].child.name); content.addChild(children[i].child); } } else { + // TODO: Fix this so it only replaces changed children / removes excess + // Above is a temp fix that removes all children and adds everything back + for (let i = 0; i < children.length; i++) { children[i].child.zIndex = children.length - i; // Only update if child changed if (children[i].changed) { console.log("=====> UPDATE CHILD", i, children[i].child.name); - // const prevChild = content.removeChildAt(i) as PIXI.Container; - // console.log("OLD CHILD", prevChild.name, "NEW CHILD", children[i].child.name) - // content.addChildAt(children[i].child, i); - // children[i].child.addChild(prevChild.getChildAt(prevChild.children.length - 1)); + // content.children[i] = children[i].child; + content.removeChildAt(i); + content.addChildAt(children[i].child, i); + // content.addChild(children[i].child); } } - } - if (false && !newContainer) { // Remove redundant surplus children - if (prevClump?.elements) { - for (let i = clump.elements.length + 1; i < prevClump.elements.length; i++) { - if (prevClump.elements[i].class === "clump") { - const pClump = prevClump.elements[i] as HierarchyClump; - console.log("REMOVE CLUMP CHILD"); - prevClump.container.removeChild(pClump.container); - } else if (prevClump.elements[i].class === "atom") { - const pAtom = prevClump.elements[i] as HierarchyAtom; - console.log("REMOVE ATOM CHILD"); - prevClump.container.removeChild(pAtom.container); - } - } - } + // content.children.splice(children.length, content.children.length - children.length); + // for (let i = content.children.length-1; i >= children.length; i--) { + // console.log("=====> REMOVE CHILD", i, content.children[i].name); + // content.removeChildAt(i); + // } } content.sortChildren(); @@ -332,7 +315,6 @@ function renderClump( if (prevClump != null) { prevClump.container = resClump; } - // console.log("--> RESULT", resClump, "\n\n--> CHILD CHANGED", childChanged, "\n\n--> DIFFS", diffs); console.log("------------------------------------"); return { diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index fd23cf9c..dfe2d4b9 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -49,6 +49,7 @@ export type Filter = { }; export function getPixiFilter(filter: Filter) { + try { switch (filter.type) { case "blur": return new PIXI.BlurFilter(...filter.params); case "noise": return new PIXI.NoiseFilter(...filter.params); @@ -85,6 +86,9 @@ export function getPixiFilter(filter: Filter) { case "zoomblur": return new ZoomBlurFilter(...filter.params); case "twist": return new TwistFilter(...filter.params); } + } catch { + return new PIXI.Filter(); + } } // A single indivisible unit of a clump (E.g. image, shape, text etc.) @@ -106,7 +110,9 @@ export type ShapeAtom = { bounds: { w: number, h: number }; fill: number; + fillAlpha: number; stroke: number; + strokeAlpha: number; strokeWidth: number; }; export type TextAtom = { @@ -114,7 +120,9 @@ export type TextAtom = { text: string; fill: number; + fillAlpha: number; stroke: number; + strokeAlpha: number; strokeWidth: number; fontSize: number; fontFamily: string; @@ -172,7 +180,9 @@ export const canvas1: BlinkCanvas = { bounds: { w: 100, h: 100 }, fill: 0xff0000, + fillAlpha: 1, stroke: 0x00ff00, + strokeAlpha: 1, strokeWidth: 5, }, { @@ -182,7 +192,9 @@ export const canvas1: BlinkCanvas = { nodeUUID: "d", fill: 0x0000ff, + fillAlpha: 1, stroke: 0x00ff00, + strokeAlpha: 1, strokeWidth: 5, fontSize: 20, fontFamily: "Arial", From 1bcb6e2af809919ccf6cc0e523ed1528de47a6ac Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 18 Sep 2023 18:37:51 +0200 Subject: [PATCH 062/209] Add inputText node to Blink --- blix-plugins/blink/src/main.cjs | 179 +++++++++++++++++++++++++++- blix-plugins/blink/webview/atom.ts | 32 ++++- blix-plugins/blink/webview/types.ts | 8 +- 3 files changed, 207 insertions(+), 12 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 0e43d32b..e4159aaa 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -295,13 +295,19 @@ const nodes = { label: "Fill", defaultValue: "#000000", triggerUpdate: true, - }, {}) + }, {}); ui.addColorPicker({ componentId: "stroke", label: "Stroke", defaultValue: "#00000000", triggerUpdate: true, - }, {}) + }, {}); + ui.addSlider({ + componentId: "strokeWidth", + label: "Stroke Width", + defaultValue: 0, + triggerUpdate: true, + }, { min: 0, max: 100, set: 0.1 }); addTweakability(ui); @@ -330,7 +336,174 @@ const nodes = { fillAlpha: colorHexToAlpha(uiInput["fill"]), stroke: colorHexToNumber(uiInput["stroke"]), strokeAlpha: colorHexToAlpha(uiInput["stroke"]), - strokeWidth: 1, + strokeWidth: uiInput["strokeWidth"], + } + ] + } + } + + return { res: canvas }; + }); + + nodeBuilder.setUI(ui); + + nodeBuilder.addInput("Blink matrix", "transform", "Transform"); + nodeBuilder.addOutput("Blink clump", "res", "Result"); + }, + "inputText": (context) => { + const nodeBuilder = context.instantiate(context.pluginId, "inputText"); + nodeBuilder.setTitle("Blink Text"); + nodeBuilder.setDescription("Input a Blink Text element"); + + const ui = nodeBuilder.createUIBuilder(); + ui.addTextInput({ + componentId: "text", + label: "Text", + defaultValue: "input text", + triggerUpdate: true, + }, {}); + + ui.addDropdown({ + componentId: "fontFamily", + label: "Family", + defaultValue: "Arial", + triggerUpdate: true, + }, { + options: { + "Arial": "Arial", + "Consolas": "Consolas", + "Courier New": "Courier New", + "Georgia": "Georgia", + "Helvetica": "Helvetica", + "Impact": "Impact", + "Times New Roman": "Times New Roman", + "Trebuchet MS": "Trebuchet MS", + "Verdana": "Verdana", + } + }); + + ui.addNumberInput({ + componentId: "fontSize", + label: "Size", + defaultValue: 24, + triggerUpdate: true, + }, { step: 0.2, min: 0 }); + + ui.addDropdown({ + componentId: "fontStyle", + label: "Style", + defaultValue: "normal", + triggerUpdate: true, + }, { + options: { + "Normal": "normal", + "Italic": "italic", + } + }); + ui.addDropdown({ + componentId: "fontWeight", + label: "Weight", + defaultValue: "normal", + triggerUpdate: true, + }, { + options: { + "Normal": "normal", + "Bold": "bold", + } + }); + ui.addDropdown({ + componentId: "textAlign", + label: "Align", + defaultValue: "center", + triggerUpdate: true, + }, { + options: { + "Left": "left", + "Center": "center", + "Right": "right" + } + }); + ui.addDropdown({ + componentId: "textBaseline", + label: "Baseline", + defaultValue: "top", + triggerUpdate: true, + }, { + options: { + "Top": "top", + "Hanging": "hanging", + "Middle": "middle", + "Alphabetic": "alphabetic", + "Ideographic": "ideographic", + "Bottom": "bottom", + } + }); + for (let numInp of ["width", "height"]) { + ui.addNumberInput( + { + componentId: numInp.replace(" ", ""), + label: numInp[0].toUpperCase() + numInp.slice(1), + defaultValue: 100, + triggerUpdate: true, + }, + {} + ); + } + const getTransform = addTransformInput(ui, ["position", "rotation"]); + + ui.addColorPicker({ + componentId: "fill", + label: "Fill", + defaultValue: "#000000", + triggerUpdate: true, + }, {}); + ui.addColorPicker({ + componentId: "stroke", + label: "Stroke", + defaultValue: "#00000000", + triggerUpdate: true, + }, {}); + ui.addSlider({ + componentId: "strokeWidth", + label: "Stroke Width", + defaultValue: 0, + triggerUpdate: true, + }, { min: 0, max: 100, set: 0.1 }); + + addTweakability(ui); + + nodeBuilder.define(async (input, uiInput, from) => { + const canvas = { + assets: { + "1": { + class: "asset", + type: "image", + data: "media/bird.png", + }, + }, + content: { + class: "clump", + nodeUUID: uiInput["tweaks"].nodeUUID, + changes: uiInput["diffs"]?.uiInputs ?? [], + transform: getTransform(uiInput), + elements: [ + { + class: "atom", + type: "text", + + text: uiInput["text"], + + fill: colorHexToNumber(uiInput["fill"]), + stroke: colorHexToNumber(uiInput["stroke"]), + strokeWidth: uiInput["strokeWidth"], + alpha: colorHexToAlpha(uiInput["fill"]), + + fontSize: uiInput["fontSize"], + fontFamily: uiInput["fontFamily"], + fontStyle: uiInput["fontStyle"], + fontWeight: uiInput["fontWeight"], + textAlign: uiInput["textAlign"], + textBaseline: uiInput["textBaseline"], } ] } diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index 5e25811c..f0d51cd2 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -60,19 +60,43 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: shape.endFill(); shapeContainer.addChild(shape); - shapeContainer.name = `ShapeContainer(${randomId()})`; + return { pixiAtom: shapeContainer, changed: true }; case "text": const textDiff = atomsDiffer || diffTextAtom(atom, prevAtom as TextAtom); - if (!textDiff) return { pixiAtom: prevAtom.container, changed: false }; + if (!textDiff) { + return { pixiAtom: prevAtom.container, changed: false }; + } - return { pixiAtom: null, changed: true }; + const textContainer = new PIXI.Container(); + const text = new PIXI.Text(atom.text, { + fill: atom.fill, + stroke: atom.stroke, + strokeThickness: atom.strokeWidth, + + fontFamily: atom.fontFamily, + fontSize: atom.fontSize, + fontStyle: atom.fontStyle, + fontWeight: atom.fontWeight, + + align: atom.textAlign, + textBaseline: atom.textBaseline, + }); + + textContainer.addChild(text); + textContainer.name = `TextContainer(${randomId()})`; + + textContainer.alpha = atom.alpha; + + return { pixiAtom: textContainer, changed: true }; case "paint": const paintDiff = atomsDiffer || diffPaintAtom(atom, prevAtom as PaintAtom); - if (!paintDiff) return { pixiAtom: prevAtom.container, changed: false }; + if (!paintDiff) { + return { pixiAtom: prevAtom.container, changed: false }; + } return { pixiAtom: null, changed: true }; } diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index dfe2d4b9..581b123b 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -22,7 +22,7 @@ export type BlinkCanvas = { export type Asset = { class: "asset"; - type: "image" | "text" | "blob"; + type: "image" | "blob"; data: any; }; @@ -120,9 +120,8 @@ export type TextAtom = { text: string; fill: number; - fillAlpha: number; stroke: number; - strokeAlpha: number; + alpha: number; strokeWidth: number; fontSize: number; fontFamily: string; @@ -192,9 +191,8 @@ export const canvas1: BlinkCanvas = { nodeUUID: "d", fill: 0x0000ff, - fillAlpha: 1, stroke: 0x00ff00, - strokeAlpha: 1, + alpha: 1, strokeWidth: 5, fontSize: 20, fontFamily: "Arial", From f3c076a0e1c0f802379c4ecc539dc4d0573a31ce Mon Sep 17 00:00:00 2001 From: Klairgo Date: Mon, 18 Sep 2023 21:55:22 +0200 Subject: [PATCH 063/209] Add asset upload --- src/electron/lib/cache/CacheManager.ts | 2 +- src/frontend/lib/stores/CacheStore.ts | 4 ++ src/frontend/ui/tiles/Assets.svelte | 52 ++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/electron/lib/cache/CacheManager.ts b/src/electron/lib/cache/CacheManager.ts index 28c5b930..93b5d02c 100644 --- a/src/electron/lib/cache/CacheManager.ts +++ b/src/electron/lib/cache/CacheManager.ts @@ -13,6 +13,7 @@ import WebSocket from "ws"; import { randomBytes } from "crypto"; import logger from "../../utils/logger"; import { ipcMain } from "electron"; +import { showOpenDialog } from "../../utils/dialog"; // The main interface which this manager must expose is: // - get(cacheUUID: CacheUUID): CacheObject @@ -75,7 +76,6 @@ export class CacheManager { JSON.stringify({ type: "cache-update", cache: Object.keys(this.cache) }) ); } - break; case "cache-subscribe": this.listeners.add(socket); diff --git a/src/frontend/lib/stores/CacheStore.ts b/src/frontend/lib/stores/CacheStore.ts index e564b660..798b2150 100644 --- a/src/frontend/lib/stores/CacheStore.ts +++ b/src/frontend/lib/stores/CacheStore.ts @@ -44,6 +44,10 @@ class CacheStore { }); } + public addCacheObject(Blob: Blob) { + this.ws.send(Blob); + } + public get subscribe() { return this.cacheStore.subscribe; } diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index 68990632..363a5cb2 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -1,16 +1,52 @@ -
-
    - {#each $cacheStore as id} -
  • - {id} -
  • - {/each} -
+
+
+
    + {#each $cacheStore as id} +
  • + {id} +
  • + {/each} +
+
+ +
+
+ Add Asset +
+
From b67bc9a9f5fd6037018afecff5428aaed2efb5a8 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 07:13:17 +0200 Subject: [PATCH 064/209] Attempt to fix jest type complaints --- jest.config.json | 16 +++++++++++----- .../integration-tests/media/mediaManager.spec.ts | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/jest.config.json b/jest.config.json index f780db5a..24c01e15 100644 --- a/jest.config.json +++ b/jest.config.json @@ -1,5 +1,5 @@ { - "collectCoverage" : true, + "collectCoverage": true, "transform": { "^.+\\.svelte$": [ "svelte-jester", @@ -7,12 +7,18 @@ "preprocess": true } ], - "^.+\\.ts$": "ts-jest", + "^.+\\.ts$": [ + "ts-jest", + { + "diagnostics": false + } + ], "^.+\\.js$": "babel-jest" }, - "coveragePathIgnorePatterns" : [ "blix-plugins" ], + "coveragePathIgnorePatterns": ["blix-plugins"], "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node", "svelte"], "coverageDirectory": "coverage", - "testPathIgnorePatterns": ["\bbuild\b","e2e"] -} + "testPathIgnorePatterns": ["\bbuild\b", "e2e"], + "preset": "ts-jest/presets/js-with-babel" +} \ No newline at end of file diff --git a/tests/integration-tests/media/mediaManager.spec.ts b/tests/integration-tests/media/mediaManager.spec.ts index 87cbab23..ce87807a 100644 --- a/tests/integration-tests/media/mediaManager.spec.ts +++ b/tests/integration-tests/media/mediaManager.spec.ts @@ -13,6 +13,7 @@ import type { MediaOutput } from "../../../src/shared/types/media"; import { CoreGraphUpdateParticipant } from "../../../src/electron/lib/core-graph/CoreGraphInteractors"; import { MediaSubscriber } from "../../../src/electron/lib/media/MediaSubscribers"; import { measureMemory } from "vm"; +import { TypeclassRegistry } from "../../../src/electron/lib/registries/TypeclassRegistry"; jest.mock('@electron/remote', () => ({ exec: jest.fn() })); @@ -92,11 +93,13 @@ describe("Test graph importer", () => { let mediaManager: MediaManager; let blix: Blix; + let typeRegistry: TypeclassRegistry beforeEach(async() => { blix = new Blix(); await blix.init(mainWindow); - mediaManager = new MediaManager(mainWindow, blix.graphInterpreter, blix.graphManager); + typeRegistry = new TypeclassRegistry(blix); + mediaManager = new MediaManager(typeRegistry, blix.graphInterpreter, blix.graphManager); }); test("Media manager should be defined", () => { From fb908adad6d49ee19930ee92370186970ce2ea6f Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 10:49:34 +0200 Subject: [PATCH 065/209] Add check for updates command --- package.json | 2 +- src/electron/lib/BlixCommands.ts | 27 ++++++++++++++++++++++++++- src/index.ts | 3 ++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0e0067a7..d8ccb003 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.3", + "version": "1.0.4", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" diff --git a/src/electron/lib/BlixCommands.ts b/src/electron/lib/BlixCommands.ts index bc1d3cbb..e6252872 100644 --- a/src/electron/lib/BlixCommands.ts +++ b/src/electron/lib/BlixCommands.ts @@ -1,9 +1,34 @@ import { projectCommands } from "./projects/ProjectCommands"; import { coreGraphCommands } from "./core-graph/CoreGraphCommands"; -import { type Command } from "./registries/CommandRegistry"; +import { type CommandContext, type Command } from "./registries/CommandRegistry"; import { pluginCommands } from "./plugins/pluginCommands"; +import { autoUpdater } from "electron-updater"; +import logger from "../utils/logger"; + +const checkForUpdatesCommand: Command = { + id: "blix.checkForUpdates", + description: { + name: "Check for updates...", + description: "Checks for updates to Blix", + }, + handler: async (ctx: CommandContext) => { + try { + const response = await autoUpdater.checkForUpdatesAndNotify(); + if (response) { + return { status: "success" }; + } else { + ctx.sendInformationMessage("You are up to date!"); + return { status: "success", message: "No updates available" }; + } + } catch (error) { + logger.error(JSON.stringify(error)); + return { status: "error", message: "Error checking for updates" }; + } + }, +}; export const blixCommands: Command[] = [ + checkForUpdatesCommand, ...projectCommands, ...coreGraphCommands, ...pluginCommands, diff --git a/src/index.ts b/src/index.ts index 360bf031..ab082bfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -260,10 +260,11 @@ app.on("web-contents-created", (e, contents) => { // ========== AUTO UPDATER ==========// -if (isProd) +if (isProd) { autoUpdater.checkForUpdates().catch((err) => { logger.error(JSON.stringify(err)); }); +} autoUpdater.logger = logger; From fbad78d343837790de96af8ceda0acb63a258c96 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 11:33:21 +0200 Subject: [PATCH 066/209] Add some auto update fixes --- package.json | 4 ++-- src/index.ts | 16 +++------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index d8ccb003..e66f55d8 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.4", + "version": "1.0.5", "repository": { "type": "git", - "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor" + "url": "https://github.com/ArmandKrynauw/Blix" }, "author": { "name": "The Spanish Inquisition", diff --git a/src/index.ts b/src/index.ts index ab082bfd..beb51094 100644 --- a/src/index.ts +++ b/src/index.ts @@ -284,13 +284,7 @@ autoUpdater.on("update-available", () => { }); autoUpdater.on("update-not-available", () => { - notification = new Notification({ - title: "Blix", - body: "Your software is up to date.", - silent: true, - // icon: nativeImage.createFromPath(join(__dirname, "..", "assets", "icon.png"), - }); - notification.show(); + blix?.sendInformationMessage("You are up to date!"); }); autoUpdater.on("update-downloaded", () => { @@ -307,12 +301,8 @@ autoUpdater.on("update-downloaded", () => { }); autoUpdater.on("error", (err) => { - notification = new Notification({ - title: "Blix", - body: JSON.stringify(err), - // icon: nativeImage.createFromPath(join(__dirname, "..", "assets", "icon.png"), - }); - notification.show(); + blix?.sendErrorMessage("Error checking for updates."); + logger.error(JSON.stringify(err)); }); // Menu From 5583292e423ba02c049ad327f3f35166f1206af8 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 14:38:27 +0200 Subject: [PATCH 067/209] Update workflow --- .github/workflows/build.yml | 11 +++++++++-- package.json | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a33fd069..eca5b4d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] steps: - name: Check out Git repository @@ -26,6 +26,13 @@ jobs: run: cd ./blix-plugins/glfx-plugin && npm ci && npm run build - name: Build/Release Blix - run: npm run dist + run: | + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + npm run dist -- --linux deb + elif [ "${{ matrix.os }}" = "windows-latest" ]; then + npm run dist -- --windows nsis + elif [ "${{ matrix.os }}" = "macos-latest' ]; then + echo "macOS build not supported yet" + fi env: GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file diff --git a/package.json b/package.json index e66f55d8..0eeeb495 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.5", + "version": "1.0.6", "repository": { "type": "git", - "url": "https://github.com/ArmandKrynauw/Blix" + "url": "https://github.com/ArmandKrynauw/Blix.git" }, "author": { "name": "The Spanish Inquisition", From c15e5c6f0ee288b46888dafca2d615b6e79d3fc8 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 14:44:38 +0200 Subject: [PATCH 068/209] Update workflow --- .github/workflows/build.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eca5b4d9..1471a25c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: npm run dist -- --linux deb elif [ "${{ matrix.os }}" = "windows-latest" ]; then npm run dist -- --windows nsis - elif [ "${{ matrix.os }}" = "macos-latest' ]; then + elif [ "${{ matrix.os }}" = "macos-latest" ]; then echo "macOS build not supported yet" fi env: diff --git a/package.json b/package.json index 0eeeb495..c709eafd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.6", + "version": "1.0.7", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" From 21ae588278b56400f9e08eac2a8aebab532c3773 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 15:00:12 +0200 Subject: [PATCH 069/209] Update workflow --- .github/workflows/build.yml | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1471a25c..28ae4a1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,4 +35,5 @@ jobs: echo "macOS build not supported yet" fi env: - GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file + GH_TOKEN: ${{ secrets.TEST_TOKEN }} + shell: bash \ No newline at end of file diff --git a/package.json b/package.json index c709eafd..bcbe6ad2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.7", + "version": "1.0.8", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" From 0e4cf5a6525b1ad90a7717f5cf114dbfc31387f9 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 15:10:31 +0200 Subject: [PATCH 070/209] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcbe6ad2..48c3aff0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.8", + "version": "1.0.9", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" From b06ca9be752848e32f3cfd6cdb82b98daa4f9c2c Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 15:18:18 +0200 Subject: [PATCH 071/209] Update workflow --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28ae4a1b..1471a25c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,5 +35,4 @@ jobs: echo "macOS build not supported yet" fi env: - GH_TOKEN: ${{ secrets.TEST_TOKEN }} - shell: bash \ No newline at end of file + GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file From 9b774c5ee5ec7fb04ec802d413c0bfec21ab22df Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 15:58:41 +0200 Subject: [PATCH 072/209] Update workflow --- .github/workflows/build.yml | 6 +++--- package.json | 15 ++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1471a25c..42344529 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build/release on: push jobs: - release: + Build: runs-on: ${{ matrix.os }} strategy: @@ -28,11 +28,11 @@ jobs: - name: Build/Release Blix run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - npm run dist -- --linux deb + npm run dist -- --linux deb AppImage --x64 --arm64 elif [ "${{ matrix.os }}" = "windows-latest" ]; then npm run dist -- --windows nsis elif [ "${{ matrix.os }}" = "macos-latest" ]; then - echo "macOS build not supported yet" + npm run dist -- --mac dmg --x64 --arm64 fi env: GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file diff --git a/package.json b/package.json index 48c3aff0..b524f9de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", - "description": "Blix - A cross-platform AI-assisted graph photo editor", - "version": "1.0.9", + "description": "Blix - An AI-assisted graph photo editor", + "version": "1.0.10", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" @@ -168,7 +168,7 @@ "build": { "productName": "Blix", "appId": "com.the-spanish-inquisition.blix", - "copyright": "Copyright © 2023 The Spanish Inquisition", + "copyright": "Copyright © 2023 Blix", "win": { "target": [ "nsis" @@ -195,14 +195,7 @@ ], "extraResources": [ "assets/**", - "blix-plugins/**", - { - "from": "src/electron/lib/ai/python", - "to": "python", - "filter": [ - "**/*.py" - ] - } + "blix-plugins/**" ], "publish": [ { From fbf3dd22bfd441d41ac685e2911fa59a4c84ddfe Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Tue, 19 Sep 2023 15:59:41 +0200 Subject: [PATCH 073/209] Fix FilePicker loses state on tile split; Add neater debug output for Blink --- blix-plugins/blink/src/main.cjs | 44 +------------ blix-plugins/blink/webview/App.svelte | 22 ++++++- blix-plugins/blink/webview/Debug.svelte | 65 +++++++++++++++++++ src/electron/lib/projects/ProjectManager.ts | 1 + src/frontend/ui/tiles/WebView.svelte | 32 +++++---- src/frontend/ui/utils/graph/PluginNode.svelte | 2 +- .../nodeUICcomponents/ColorPicker.svelte | 13 +--- .../graph/nodeUICcomponents/FilePicker.svelte | 35 ++++++++-- .../graph/nodeUICcomponents/TextInput.svelte | 11 +++- .../graph/nodeUICcomponents/dials/Dial.svelte | 6 +- 10 files changed, 150 insertions(+), 81 deletions(-) create mode 100644 blix-plugins/blink/webview/Debug.svelte diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index e4159aaa..4a3a8082 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -313,13 +313,7 @@ const nodes = { nodeBuilder.define(async (input, uiInput, from) => { const canvas = { - assets: { - "1": { - class: "asset", - type: "image", - data: "media/bird.png", - }, - }, + assets: {}, content: { class: "clump", nodeUUID: uiInput["tweaks"].nodeUUID, @@ -423,32 +417,6 @@ const nodes = { "Right": "right" } }); - ui.addDropdown({ - componentId: "textBaseline", - label: "Baseline", - defaultValue: "top", - triggerUpdate: true, - }, { - options: { - "Top": "top", - "Hanging": "hanging", - "Middle": "middle", - "Alphabetic": "alphabetic", - "Ideographic": "ideographic", - "Bottom": "bottom", - } - }); - for (let numInp of ["width", "height"]) { - ui.addNumberInput( - { - componentId: numInp.replace(" ", ""), - label: numInp[0].toUpperCase() + numInp.slice(1), - defaultValue: 100, - triggerUpdate: true, - }, - {} - ); - } const getTransform = addTransformInput(ui, ["position", "rotation"]); ui.addColorPicker({ @@ -474,13 +442,7 @@ const nodes = { nodeBuilder.define(async (input, uiInput, from) => { const canvas = { - assets: { - "1": { - class: "asset", - type: "image", - data: "media/bird.png", - }, - }, + assets: {}, content: { class: "clump", nodeUUID: uiInput["tweaks"].nodeUUID, @@ -503,7 +465,7 @@ const nodes = { fontStyle: uiInput["fontStyle"], fontWeight: uiInput["fontWeight"], textAlign: uiInput["textAlign"], - textBaseline: uiInput["textBaseline"], + textBaseline: "alphabetic" } ] } diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index b770cee4..e33382bb 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -5,6 +5,7 @@ import { type Writable } from "svelte/store"; import { renderApp } from "./render"; import { type BlinkCanvas } from "./types"; + import Debug from "./Debug.svelte"; export let media: Writable; export let send: (msg: string, data: any) => void; @@ -84,10 +85,24 @@ let imgCanvas = new PIXI.Container(); imgCanvas.name = "imgCanvas"; + // canvas let imgCanvasBlock = new PIXI.Graphics(); imgCanvasBlock.beginFill(0xffffff, 0.9); imgCanvasBlock.drawRect(0, 0, imgCanvasBlockW, imgCanvasBlockH); + // x-axis + imgCanvasBlock.lineStyle(); + imgCanvasBlock.moveTo(0, imgCanvasBlockH/2); + imgCanvasBlock.lineStyle(1, 0xff0000); + imgCanvasBlock.lineTo(imgCanvasBlockW, imgCanvasBlockH/2); + + // y-axis + imgCanvasBlock.lineStyle(); + imgCanvasBlock.moveTo(imgCanvasBlockW/2, 0); + imgCanvasBlock.lineStyle(1, 0x00ff00); + imgCanvasBlock.lineTo(imgCanvasBlockW/2, imgCanvasBlockH); + + imgCanvasBlock.position.set(-imgCanvasBlockW/2, -imgCanvasBlockH/2); imgCanvas.addChild(imgCanvasBlock); viewport.addChild(imgCanvas); @@ -110,7 +125,7 @@ const viewportFitX = imgCanvasBlockW + 2 * imgCanvasInitialPadding; const viewportFitY = imgCanvasBlockH + 2 * imgCanvasInitialPadding; viewport.fit(true, viewportFitX, viewportFitY); - viewport.moveCenter(imgCanvasBlockW/2, imgCanvasBlockH/2); + viewport.moveCenter(0, 0); hasCentered = true; } @@ -147,7 +162,8 @@
- {JSON.stringify($media, null, 2)} + +
diff --git a/src/electron/lib/projects/ProjectManager.ts b/src/electron/lib/projects/ProjectManager.ts index 0fa058d6..03db8d6a 100644 --- a/src/electron/lib/projects/ProjectManager.ts +++ b/src/electron/lib/projects/ProjectManager.ts @@ -59,6 +59,7 @@ export class ProjectManager { public async removeProject(blix: Blix, uuid: UUID, forceRemove = false) { const project = this._projects[uuid]; + if (!project) forceRemove = true; // Project does not exist, just obliterate the tab let remove = true; let output = -1; let res; diff --git a/src/frontend/ui/tiles/WebView.svelte b/src/frontend/ui/tiles/WebView.svelte index a3149c63..cfa34197 100644 --- a/src/frontend/ui/tiles/WebView.svelte +++ b/src/frontend/ui/tiles/WebView.svelte @@ -13,13 +13,14 @@ $: webviewUpdated(webview); + let webviewReady = false; + // Called when the webview is created/recreated function webviewUpdated(webview: Electron.WebviewTag | null) { if (!webview) return; // To receive a message from the webview, add an event listener for the ipc-message event: webview?.addEventListener("ipc-message", (event) => { - // console.log("Message received from webview:", event.channel, event.args); switch (event.channel) { case "tweak": const data = event.args[0]; @@ -38,6 +39,7 @@ $: updateMedia(media); function updateMedia(media: unknown) { + if (!webviewReady) return; // To manually execute a javascript function within the webview: // const res = await webview?.executeJavaScript("app.dispatchMessage('hi there!')"); @@ -49,19 +51,21 @@ $: initWebviewMedia(webview); function initWebviewMedia(webview: Electron.WebviewTag | null) { - if (webview) { - console.log("WEBVIEW"); - webview.addEventListener("dom-ready", () => { - updateMedia(media); - }); - - // Force focus the webview when the mouse is over it. - // This is necessary to prevent the user having to - // click into it every time before it can receive input - webview.addEventListener("mouseover", () => { - webview.focus(); - }); - } + webviewReady = false; + if (!webview) return; + + // console.log("WEBVIEW"); + webview.addEventListener("dom-ready", () => { + webviewReady = true; + updateMedia(media); + }); + + // Force focus the webview when the mouse is over it. + // This is necessary to prevent the user having to + // click into it every time before it can receive input + webview.addEventListener("mouseover", () => { + webview.focus(); + }); } function reload() { diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index 351f4842..6d39d392 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -206,7 +206,7 @@ height="{graphNode.dims.h}" --> .node { box-sizing: border-box; - width: fit-content; + min-width: max-content; border-radius: 8px; height: fit-content; position: relative; diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte index 6a337e6b..314b40b1 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/ColorPicker.svelte @@ -13,17 +13,7 @@ if (!inputStore.inputs[config.componentId]) inputStore.inputs[config.componentId] = writable("#f43e5cff"); - console.log("CONFIG", config); - console.log("INPUTS", inputStore.inputs); - $: valStore = inputStore.inputs[config.componentId]; - - setTimeout(() => { - // valStore = inputStore.inputs[config.componentId]; - console.log("VALSTORE", valStore, $valStore); - }, 200); - - console.log("VALSTORE", valStore, $valStore);
@@ -31,9 +21,10 @@ {#if typeof $valStore === "string"} + - + + diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte index 3b3bec0a..eaf583dc 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte @@ -15,13 +15,22 @@ $: valStore = inputStore.inputs[config.componentId]; - + diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte index 0fd7e8e8..becce0b8 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/dials/Dial.svelte @@ -22,9 +22,9 @@ $: valStore = inputStore.inputs[config.componentId]; onMount(() => { - valStore.subscribe((v) => { - console.log("DIAL", v); - }); + // valStore.subscribe((v) => { + // console.log("DIAL", v); + // }); }); let mouseover = false; From c168758912984f0cfc72b898bde1a887967fbf65 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 16:03:15 +0200 Subject: [PATCH 074/209] Update workflow --- .github/workflows/build.yml | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42344529..6589ec2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,4 +35,5 @@ jobs: npm run dist -- --mac dmg --x64 --arm64 fi env: - GH_TOKEN: ${{ secrets.TEST_TOKEN }} \ No newline at end of file + GH_TOKEN: ${{ secrets.TEST_TOKEN }} + shell: bash \ No newline at end of file diff --git a/package.json b/package.json index b524f9de..131f7b2c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - An AI-assisted graph photo editor", - "version": "1.0.10", + "version": "1.0.11", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" From 2c4f63b37501d77c048716e2f255db60c69883d5 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 19 Sep 2023 19:07:09 +0200 Subject: [PATCH 075/209] Update check for updates message --- package.json | 2 +- src/electron/lib/BlixCommands.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 131f7b2c..2eff7961 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - An AI-assisted graph photo editor", - "version": "1.0.11", + "version": "1.0.12", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" diff --git a/src/electron/lib/BlixCommands.ts b/src/electron/lib/BlixCommands.ts index e6252872..354f5be7 100644 --- a/src/electron/lib/BlixCommands.ts +++ b/src/electron/lib/BlixCommands.ts @@ -17,8 +17,9 @@ const checkForUpdatesCommand: Command = { if (response) { return { status: "success" }; } else { - ctx.sendInformationMessage("You are up to date!"); - return { status: "success", message: "No updates available" }; + const version = JSON.stringify(autoUpdater.currentVersion); + ctx.sendInformationMessage(`You are up to date! Blix ${version} is the latest version.`); + return { status: "success" }; } } catch (error) { logger.error(JSON.stringify(error)); From 22127f27447fe121d277082e66d1b59cdf29b770 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Wed, 20 Sep 2023 12:00:59 +0200 Subject: [PATCH 076/209] Replace plugin signature in node.instantiate() with ContextMenu folder path --- blix-plugins/blink/src/main.cjs | 14 ++--- blix-plugins/glfx-plugin/src/main.cjs | 4 +- blix-plugins/hello-plugin/src/main.ts | 4 +- blix-plugins/input-plugin/src/main.js | 8 +-- blix-plugins/logic-plugin/src/main.js | 8 +-- blix-plugins/math-plugin/src/main.js | 4 +- blix-plugins/sharp-plugin/src/main.js | 18 +++---- blix-plugins/threlte-plugin/src/main.cjs | 51 +++---------------- src/electron/lib/Blix.ts | 4 +- src/electron/lib/ai/ai-profiler-v2.ts | 2 +- src/electron/lib/plugins/Plugin.ts | 8 +-- .../lib/plugins/builders/NodeBuilder.ts | 5 +- .../lib/registries/ToolboxRegistry.ts | 2 + .../lib/stores/GraphContextMenuStore.ts | 51 +++++++++++++++---- src/shared/ui/ToolboxTypes.ts | 1 + 15 files changed, 92 insertions(+), 92 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 4a3a8082..87574b1a 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -67,7 +67,7 @@ function addTweakability(ui) { function createBlinkNode(type, title, desc, params) { return (context) => { - const nodeBuilder = context.instantiate(context.pluginId, type); + const nodeBuilder = context.instantiate("Blink/Filters", type); nodeBuilder.setTitle(title); nodeBuilder.setDescription(desc); @@ -190,7 +190,7 @@ const nodes = { ...blinkNodes, "inputImage": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "inputImage"); + const nodeBuilder = context.instantiate(String.raw`Blink/Input`, "inputImage"); nodeBuilder.setTitle("Blink Image"); nodeBuilder.setDescription("Input a Blink Sprite Image"); @@ -260,7 +260,7 @@ const nodes = { nodeBuilder.addOutput("Blink clump", "res", "Result"); }, "inputShape": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "inputShape"); + const nodeBuilder = context.instantiate("Blink/Input", "inputShape"); nodeBuilder.setTitle("Blink Shape"); nodeBuilder.setDescription("Input a Blink Shape"); @@ -345,7 +345,7 @@ const nodes = { nodeBuilder.addOutput("Blink clump", "res", "Result"); }, "inputText": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "inputText"); + const nodeBuilder = context.instantiate("Blink/Input", "inputText"); nodeBuilder.setTitle("Blink Text"); nodeBuilder.setDescription("Input a Blink Text element"); @@ -480,7 +480,7 @@ const nodes = { nodeBuilder.addOutput("Blink clump", "res", "Result"); }, "matrix": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "matrix"); + const nodeBuilder = context.instantiate("Blink/Input", "matrix"); nodeBuilder.setTitle("Matrix"); nodeBuilder.setDescription("Construct a Blink matrix"); @@ -508,7 +508,7 @@ const nodes = { nodeBuilder.addOutput("Blink matrix", "res", "Result"); }, "layer": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "layer"); + const nodeBuilder = context.instantiate("Blink/Utils", "layer"); nodeBuilder.setTitle("Layer"); nodeBuilder.setDescription("Layer two or more Blink clumps"); @@ -559,7 +559,7 @@ const nodes = { nodeBuilder.addOutput("Blink clump", "res", "Result"); }, "filter": (context) => { - const nodeBuilder = context.instantiate(context.pluginId, "filter"); + const nodeBuilder = context.instantiate("Blink/Utils", "filter"); nodeBuilder.setTitle("Filter"); nodeBuilder.setDescription("Construct a Blink matrix"); diff --git a/blix-plugins/glfx-plugin/src/main.cjs b/blix-plugins/glfx-plugin/src/main.cjs index 27085c1e..983d1ccb 100644 --- a/blix-plugins/glfx-plugin/src/main.cjs +++ b/blix-plugins/glfx-plugin/src/main.cjs @@ -12,7 +12,7 @@ function toTitleCase(str) { function createGLFXNode(type, title, desc, params) { return (context) => { - const nodeBuilder = context.instantiate("glfx-plugin", type); + const nodeBuilder = context.instantiate("GLFX", type); nodeBuilder.setTitle(title); nodeBuilder.setDescription(desc); @@ -103,7 +103,7 @@ const nodes = { ...glfxNodes, "inputGLFXImage": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputGLFXImage"); + const nodeBuilder = context.instantiate("Input", "inputGLFXImage"); nodeBuilder.setTitle("Input GLFX image"); nodeBuilder.setDescription("Provides an image input and returns a single image output"); diff --git a/blix-plugins/hello-plugin/src/main.ts b/blix-plugins/hello-plugin/src/main.ts index 6b654b8e..878ef377 100644 --- a/blix-plugins/hello-plugin/src/main.ts +++ b/blix-plugins/hello-plugin/src/main.ts @@ -13,7 +13,7 @@ import { NodeBuilder } from "electron/lib/plugins/builders/NodeBuilder"; const nodes = { "hello": (context: NodePluginContext) => { // Use context.nodeBuilder to construct the node UI - const nodeBuilder: NodeBuilder = context.instantiate("hello-plugin", "hello"); + const nodeBuilder: NodeBuilder = context.instantiate("Dev", "hello"); nodeBuilder.setTitle("Gloria"); nodeBuilder.setDescription("Provides a test slider and button and label for testing purposes, taking two string inputs and returning one string output"); @@ -128,7 +128,7 @@ const nodes = { nodeBuilder.addOutput("string", "outFilePicker", ""); } , "Jake": (context: any) => { - const nodeBuilder = context.instantiate("hello-plugin", "Jake"); + const nodeBuilder = context.instantiate("Dev", "Jake"); nodeBuilder.setTitle("Jake"); nodeBuilder.setDescription("This is currently a useless node that does nothing."); diff --git a/blix-plugins/input-plugin/src/main.js b/blix-plugins/input-plugin/src/main.js index 3f1dcb72..15fe2788 100644 --- a/blix-plugins/input-plugin/src/main.js +++ b/blix-plugins/input-plugin/src/main.js @@ -1,6 +1,6 @@ const nodes = { "inputNumber": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputNumber"); + const nodeBuilder = context.instantiate("Input", "inputNumber"); nodeBuilder.setTitle("Input number"); nodeBuilder.setDescription("Provides a number input and returns a single number output"); @@ -24,7 +24,7 @@ const nodes = { nodeBuilder.addOutput("number", "res", "Result"); }, "inputImage": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputImage"); + const nodeBuilder = context.instantiate("Input", "inputImage"); nodeBuilder.setTitle("Input image"); nodeBuilder.setDescription("Provides an image input and returns a single image output"); @@ -46,7 +46,7 @@ const nodes = { }, // Will we define a color type? or just a vector4/string "inputColor": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputColor"); + const nodeBuilder = context.instantiate("Input", "inputColor"); nodeBuilder.setTitle("Input color"); nodeBuilder.setDescription("Provides a color input and returns a single color output"); @@ -67,7 +67,7 @@ const nodes = { }, // Will we define a color type? or just a vector4/string "inputBoolean": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputBoolean"); + const nodeBuilder = context.instantiate("Input", "inputBoolean"); nodeBuilder.setTitle("Input Boolean"); nodeBuilder.setDescription("Provides a radio box to select a single true/false value"); diff --git a/blix-plugins/logic-plugin/src/main.js b/blix-plugins/logic-plugin/src/main.js index 0fc983cf..6cc6e07c 100644 --- a/blix-plugins/logic-plugin/src/main.js +++ b/blix-plugins/logic-plugin/src/main.js @@ -1,6 +1,6 @@ const nodes ={ "not": (context) => { - nodeBuilder = context.instantiate("logic-plugin","not"); + nodeBuilder = context.instantiate("Logic","not"); nodeBuilder.setTitle("Not"); nodeBuilder.setDescription("Performs Boolean NOT operation on a single input and returns a single output"); @@ -14,7 +14,7 @@ const nodes ={ nodeBuilder.addOutput("boolean", "res","Result"); }, "logic": (context) => { - const nodeBuilder = context.instantiate("logic-plugin", "logic"); + const nodeBuilder = context.instantiate("Logic", "logic"); nodeBuilder.setTitle("Logic"); nodeBuilder.setDescription("Performs a logical operation (AND/OR/XOR) on two inputs and returns a single output"); @@ -50,7 +50,7 @@ const nodes ={ nodeBuilder.addOutput("boolean", "res", "Result"); }, "compare": (context) => { - const nodeBuilder = context.instantiate("logic-plugin", "compare"); + const nodeBuilder = context.instantiate("Logic", "compare"); nodeBuilder.setTitle("Compare"); nodeBuilder.setDescription("Performs a comparison operation (==/!=/>/=/<=) on two inputs and returns a single boolean output"); @@ -91,7 +91,7 @@ const nodes ={ nodeBuilder.addOutput("boolean", "res", "Result"); }, "ternary": (context) => { - const nodeBuilder = context.instantiate("logic-plugin","ternary"); + const nodeBuilder = context.instantiate("Logic","ternary"); nodeBuilder.setTitle("Ternary Comp."); nodeBuilder.define((anchorInputs, uiInputs, requiredOutputs) => { diff --git a/blix-plugins/math-plugin/src/main.js b/blix-plugins/math-plugin/src/main.js index bcf67597..13f5adcf 100644 --- a/blix-plugins/math-plugin/src/main.js +++ b/blix-plugins/math-plugin/src/main.js @@ -1,6 +1,6 @@ const nodes = { "unary": (context) => { - const nodeBuilder = context.instantiate("math-plugin","unary"); + const nodeBuilder = context.instantiate("Math","unary"); nodeBuilder.setTitle("Unary"); nodeBuilder.setDescription("Performs Unary math operations taking one number input and returning one number output,such as root, negate or square"); @@ -44,7 +44,7 @@ const nodes = { nodeBuilder.addOutput("number", "res","Result"); }, "binary": (context) => { - const nodeBuilder = context.instantiate("math-plugin", "binary"); + const nodeBuilder = context.instantiate("Math", "binary"); nodeBuilder.setTitle("Binary"); nodeBuilder.setDescription("Performs Binary math operations taking two number inputs and returning one number output"); diff --git a/blix-plugins/sharp-plugin/src/main.js b/blix-plugins/sharp-plugin/src/main.js index 0dedddff..3de8d874 100644 --- a/blix-plugins/sharp-plugin/src/main.js +++ b/blix-plugins/sharp-plugin/src/main.js @@ -3,7 +3,7 @@ const sharp = require(isProd ? "../../../app.asar/node_modules/sharp" : "sharp") const nodes = { "brightness": (context) => { - nodeBuilder = context.instantiate("sharp-plugin", "brightness"); + nodeBuilder = context.instantiate("Sharp", "brightness"); nodeBuilder.setTitle("Brightness"); nodeBuilder.setDescription("Adjusts the brighness of an image taking one image as input and returning one image as output"); @@ -35,7 +35,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "saturation": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "saturation"); + const nodeBuilder = context.instantiate("Sharp", "saturation"); nodeBuilder.setTitle("Saturation"); nodeBuilder.setDescription("Adjusts the saturation of an image taking one image as input and returning one image as output"); @@ -66,7 +66,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "hue": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "hue"); + const nodeBuilder = context.instantiate("Sharp", "hue"); nodeBuilder.setTitle("Hue"); nodeBuilder.setDescription("Adjusts the hue of an image taking one image as input and returning one image as output"); const ui = nodeBuilder.createUIBuilder(); @@ -95,7 +95,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "rotate": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "rotate"); + const nodeBuilder = context.instantiate("Sharp", "rotate"); nodeBuilder.setTitle("Rotate"); nodeBuilder.setDescription("Rotates an image by an explicit angle taking one image as input and returning one image as output"); const ui = nodeBuilder.createUIBuilder(); @@ -122,7 +122,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "sharpen": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "sharpen"); + const nodeBuilder = context.instantiate("Sharp", "sharpen"); nodeBuilder.setTitle("Sharpen"); nodeBuilder.setDescription("Sharpens an image taking one image as input and returning one image as output"); const ui = nodeBuilder.createUIBuilder(); @@ -160,7 +160,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "normalise": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "normalise"); + const nodeBuilder = context.instantiate("Sharp", "normalise"); nodeBuilder.setTitle("Normalise"); nodeBuilder.setDescription("Enhance image contrast by stretching its luminance to cover a full dynamic range taking one image as input and returning one image as output"); @@ -175,7 +175,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "toImage": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "toImage"); + const nodeBuilder = context.instantiate("Sharp", "toImage"); nodeBuilder.setTitle("To Image"); nodeBuilder.setDescription("Converts the sharp object to an image"); @@ -189,7 +189,7 @@ const nodes = { nodeBuilder.addOutput("image", "res", "Result"); }, "toSharp": (context) => { - const nodeBuilder = context.instantiate("sharp-plugin", "toSharp"); + const nodeBuilder = context.instantiate("Sharp", "toSharp"); nodeBuilder.setTitle("To Sharp"); nodeBuilder.setDescription("Converts an image path to a sharp object"); @@ -202,7 +202,7 @@ const nodes = { nodeBuilder.addOutput("Sharp", "res", "Result"); }, "inputSharpImage": (context) => { - nodeBuilder = context.instantiate("input-plugin", "inputImage"); + nodeBuilder = context.instantiate("Input", "inputImage"); nodeBuilder.setTitle("Input image"); nodeBuilder.setDescription("Provides an image input and returns a single image output"); diff --git a/blix-plugins/threlte-plugin/src/main.cjs b/blix-plugins/threlte-plugin/src/main.cjs index 3bfaee3f..38f2e944 100644 --- a/blix-plugins/threlte-plugin/src/main.cjs +++ b/blix-plugins/threlte-plugin/src/main.cjs @@ -10,9 +10,9 @@ function toTitleCase(str) { return str.charAt(0).toUpperCase() + str.slice(1); } -function createGLFXNode(type, title, desc, params) { +function createThrelteNode(type, title, desc, params) { return (context) => { - const nodeBuilder = context.instantiate("glfx-plugin", type); + const nodeBuilder = context.instantiate("Threlte", type); nodeBuilder.setTitle(title); nodeBuilder.setDescription(desc); @@ -54,45 +54,10 @@ function createGLFXNode(type, title, desc, params) { } const glfxNodes = { - "brightnessContrast": [ - "Brightness/Contrast", - "Adjust the brightness and contrast of the image", - [{ id: "brightness" }, { id: "contrast" }] - ], - "hueSaturation": [ - "Hue / Saturation", - "Adjust the hue and saturation of the image", - [{ id: "hue" }, { id: "saturation" }] - ], - "noise": [ - "Noise", - "Add black and white noise to the image", - [{ id: "amount", min: 0, max: 1, step: 0.01 }] - ], - "denoise": [ - "Denoise", - "Smooth over grainy noise in dark images", - [{ id: "exponent", min: 0, max: 50, step: 0.1 }] - ], - "sepia": [ - "Sepia", - "Add a reddish-brown monochrome tint to the image", - [{ id: "amount", min: 0, max: 1, step: 0.01 }] - ], - "unsharpMask": [ - "Unsharp Mask", - "Image sharpening that amplifies high-frequency detail in the image", - [{ id: "radius", min: 0, max: 200, step: 1 }, { id: "strength", min: 0, max: 5, step: 0.05 }] - ], - "vibrance": [ - "Vibrance", - "Adjust saturation of desaturated colors, leaving saturated colors unmodified", - [{ id: "amount" }] - ], - "vignette": [ - "Vignette", - "Add a vignette effect to the image", - [{ id: "size", min: 0, max: 1, step: 0.01 }, { id: "amount", min: 0, max: 1, step: 0.01 }] + "addPrimitive": [ + "Add Primitive", + "Add a 3D primitive", + [] ], }; @@ -104,7 +69,7 @@ const nodes = { ...glfxNodes, "inputGLFXImage": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputGLFXImage"); + const nodeBuilder = context.instantiate("Input/Other", "inputGLFXImage"); nodeBuilder.setTitle("Input GLFX image"); nodeBuilder.setDescription("Provides an image input and returns a single image output"); @@ -131,7 +96,7 @@ const nodes = { nodeBuilder.addOutput("GLFX image", "res", "Result"); }, "inputGLFXCache": (context) => { - const nodeBuilder = context.instantiate("input-plugin", "inputGLFXCache"); + const nodeBuilder = context.instantiate("Input/Other", "inputGLFXCache"); nodeBuilder.setTitle("Input GLFX cache"); nodeBuilder.setDescription("Provides an cache input and returns a single image output"); diff --git a/src/electron/lib/Blix.ts b/src/electron/lib/Blix.ts index d72b5361..e6cd626c 100644 --- a/src/electron/lib/Blix.ts +++ b/src/electron/lib/Blix.ts @@ -75,7 +75,7 @@ export class Blix { this._graphInterpreter = new CoreGraphInterpreter(this._toolboxRegistry); // Create Output node - const outputNodeBuilder = new NodeBuilder("blix", "output"); + const outputNodeBuilder = new NodeBuilder("blix", "Blix", "output"); const outputUIBuilder = outputNodeBuilder.createUIBuilder(); outputUIBuilder.addButton( { @@ -189,7 +189,7 @@ export class Blix { const ipcGravityAISubsriber = new IPCGraphSubscriber(); ipcGravityAISubsriber.setListenEvents([CoreGraphUpdateEvent.graphUpdated]); ipcGravityAISubsriber.setListenParticipants([ - CoreGraphUpdateParticipant.system, + // CoreGraphUpdateParticipant.system, CoreGraphUpdateParticipant.ai, ]); diff --git a/src/electron/lib/ai/ai-profiler-v2.ts b/src/electron/lib/ai/ai-profiler-v2.ts index c5806893..0fad4743 100644 --- a/src/electron/lib/ai/ai-profiler-v2.ts +++ b/src/electron/lib/ai/ai-profiler-v2.ts @@ -176,7 +176,7 @@ export class Profiler { } private static generateBlixOutputNode(): NodeInstance { - const outputNodeBuilder = new NodeBuilder("blix", "output"); + const outputNodeBuilder = new NodeBuilder("blix", "Blix", "output"); const outputUIBuilder = outputNodeBuilder.createUIBuilder(); outputUIBuilder.addButton( { diff --git a/src/electron/lib/plugins/Plugin.ts b/src/electron/lib/plugins/Plugin.ts index b85a05cf..8eb22ad4 100644 --- a/src/electron/lib/plugins/Plugin.ts +++ b/src/electron/lib/plugins/Plugin.ts @@ -156,12 +156,8 @@ export class NodePluginContext extends PluginContext { return this._nodeBuilder; } - // nodeBuilder = context.instantiate("hello-plugin","hello"); - // TODO: Change this: it should not be done in the plugin, - // but when the plugin loads. The plugin already defines each node name as the key - // in the dictionary, and we already know the plugin name in the package.json - public instantiate(plugin: string, name: string): NodeBuilder { - this._nodeBuilder = new NodeBuilder(plugin, name); + public instantiate(folder: string, name: string): NodeBuilder { + this._nodeBuilder = new NodeBuilder(this.pluginId, folder, name); return this.nodeBuilder; } } diff --git a/src/electron/lib/plugins/builders/NodeBuilder.ts b/src/electron/lib/plugins/builders/NodeBuilder.ts index c8c11741..cbce221d 100644 --- a/src/electron/lib/plugins/builders/NodeBuilder.ts +++ b/src/electron/lib/plugins/builders/NodeBuilder.ts @@ -17,6 +17,7 @@ import { type NodeTweakData } from "../../../../shared/types"; type PartialNode = { name: string; plugin: string; + folder: string; displayName: string; description: string; icon: string; @@ -34,10 +35,11 @@ type PartialNode = { export class NodeBuilder implements PluginContextBuilder { private partialNode: PartialNode; - constructor(plugin: string, name: string) { + constructor(plugin: string, folder: string, name: string) { this.partialNode = { name, plugin, + folder, displayName: name, description: "", icon: "", @@ -54,6 +56,7 @@ export class NodeBuilder implements PluginContextBuilder { return new NodeInstance( this.partialNode.name, this.partialNode.plugin, + this.partialNode.folder, this.partialNode.displayName, this.partialNode.description, this.partialNode.icon, diff --git a/src/electron/lib/registries/ToolboxRegistry.ts b/src/electron/lib/registries/ToolboxRegistry.ts index fa5b1485..73767e41 100644 --- a/src/electron/lib/registries/ToolboxRegistry.ts +++ b/src/electron/lib/registries/ToolboxRegistry.ts @@ -46,6 +46,7 @@ export class ToolboxRegistry implements Registry { const nodeObject = new INode( nodeInstance.signature, nodeInstance.displayName, + nodeInstance.folder, nodeInstance.description, nodeInstance.icon, inputAnchors, @@ -81,6 +82,7 @@ export class NodeInstance implements RegistryInstance { constructor( public readonly name: string, // Unique identifier for the node within the plugin public readonly plugin: string, + public readonly folder: string, public readonly displayName: string, public readonly description: string, public readonly icon: string, diff --git a/src/frontend/lib/stores/GraphContextMenuStore.ts b/src/frontend/lib/stores/GraphContextMenuStore.ts index ff5cc65f..6aa59f7b 100644 --- a/src/frontend/lib/stores/GraphContextMenuStore.ts +++ b/src/frontend/lib/stores/GraphContextMenuStore.ts @@ -26,6 +26,10 @@ export type Item = { action: Action; }; +function isItemGroup(item: ItemGroup | Item): item is ItemGroup { + return "items" in item; +} + export type Action = { type: "addNode"; signature: string }; class ContextMenuStore { @@ -123,21 +127,50 @@ class ContextMenuStore { }); } + // Construct item group tree from nodes private groupBy(nodes: INode[]): ItemGroup[] { - const groups: Record = {}; + const res: ItemGroup[] = []; for (const node of nodes) { - const parts = node.signature.split("."); - const plugin = parts[0]; - - if (!groups[plugin]) { - groups[plugin] = { - label: plugin, + // Split by non-escaped forward slashes + const folderParts = node.folder + .replace(/\\/g, "\0") // Eliminate escaped backslashes + .split(/(? + part + .replace(/\\\//g, "/") // Unescape remaining forward slashes + .replace(/\0/g, "\\") // Unescape backslashes + .trim() + ); + + // Construct folder hierarchy + let elems: (ItemGroup | Item)[] = res; + construct: for (const folder of folderParts) { + if (folder === "") continue; + + // Check if folder is already in the hierarchy + for (const elem of elems) { + if (!isItemGroup(elem)) continue; // Skip non-groups + + if (elem.label === folder) { + // Folder already exists + elems = elem.items; + continue construct; + } + } + + // Folder does not exist, construct it + const newGroup: ItemGroup = { + label: folder, items: [], }; + elems.push(newGroup); + + elems = newGroup.items as ItemGroup[]; } - groups[plugin].items.push({ + // Add node to folder (item to group) + elems.push({ label: node.title, icon: node.icon, action: { @@ -147,7 +180,7 @@ class ContextMenuStore { }); } - return Object.values(groups); + return res; } } diff --git a/src/shared/ui/ToolboxTypes.ts b/src/shared/ui/ToolboxTypes.ts index 8a25b5dd..107fa49f 100644 --- a/src/shared/ui/ToolboxTypes.ts +++ b/src/shared/ui/ToolboxTypes.ts @@ -17,6 +17,7 @@ export class INode { constructor( readonly signature: NodeSignature, readonly title: string, + readonly folder: string, readonly description: string, readonly icon: string, readonly inputs: IAnchor[], From febc8b8ca15f58b8e1bec8ee49839ae3da34b5c9 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 20 Sep 2023 12:06:13 +0200 Subject: [PATCH 077/209] Refactor settings and keybindings --- src/electron/lib/api/apis/UtilApi.ts | 32 ++- src/electron/utils/settings.ts | 4 +- src/frontend/lib/stores/BlixStore.ts | 8 +- src/frontend/lib/stores/ShortcutStore.ts | 246 ++++++++++++++---- src/frontend/ui/base/App.svelte | 2 +- src/frontend/ui/base/layout/Panel.svelte | 2 +- src/frontend/ui/base/settings/About.svelte | 81 ++++++ .../ui/base/settings/AiSettings.svelte | 81 ++++++ src/frontend/ui/base/settings/Hotkeys.svelte | 148 +++++++++++ .../ui/base/{ => settings}/Settings.svelte | 120 +++++++-- .../settings}/utils/SecureInput.svelte | 0 src/frontend/ui/tiles/ShortcutSettings.svelte | 101 ------- src/frontend/ui/utils/Shortcuts.svelte | 2 +- src/shared/types/setting.ts | 48 +++- 14 files changed, 680 insertions(+), 195 deletions(-) create mode 100644 src/frontend/ui/base/settings/About.svelte create mode 100644 src/frontend/ui/base/settings/AiSettings.svelte create mode 100644 src/frontend/ui/base/settings/Hotkeys.svelte rename src/frontend/ui/base/{ => settings}/Settings.svelte (59%) rename src/frontend/ui/{ => base/settings}/utils/SecureInput.svelte (100%) delete mode 100644 src/frontend/ui/tiles/ShortcutSettings.svelte diff --git a/src/electron/lib/api/apis/UtilApi.ts b/src/electron/lib/api/apis/UtilApi.ts index 2672e16a..bbe10380 100644 --- a/src/electron/lib/api/apis/UtilApi.ts +++ b/src/electron/lib/api/apis/UtilApi.ts @@ -12,7 +12,7 @@ import { getSecret, type Settings, } from "../../../utils/settings"; -import type { Setting, UserSettingsCategory, QueryResponse } from "../../../../shared/types"; +import type { Setting, QueryResponse } from "../../../../shared/types"; import { type ChatModel } from "../../../lib/ai/Model"; // import dotenv from "dotenv"; // dotenv.config(); @@ -77,6 +77,11 @@ export class UtilApi implements ElectronMainApi { } // Add something extra validation + + async saveUserSetting(setting: Setting) { + return await this.saveUserSettings([setting]); + } + async saveUserSettings(newSettings: Setting[]): Promise { for (const setting of newSettings) { if (setting.secret) { @@ -91,7 +96,7 @@ export class UtilApi implements ElectronMainApi { async getUserSettings() { // const secrets = getSecrets(); - const userSettings: UserSettingsCategory[] = [ + const userSettings = [ { id: "ai_settings", title: "AI Settings", @@ -132,6 +137,29 @@ export class UtilApi implements ElectronMainApi { return { status: "success", data: userSettings } satisfies QueryResponse; } + /** Retrieve a user setting from the ElectronStore. */ + async getUserSetting(key: string) { + if (settings.has(key)) { + let data: unknown; + + if (key.startsWith("secrets.")) { + data = getSecret(key); + } else { + data = settings.get(key); + } + + return { + status: "success", + data, + } satisfies QueryResponse; + } else { + return { + status: "error", + message: "Setting not found", + } satisfies QueryResponse; + } + } + /** * This function takes in a key and then will save the key for the specified model to local storage. * Electron's safeStorage is used to encrypt/decrypt these keys. diff --git a/src/electron/utils/settings.ts b/src/electron/utils/settings.ts index 01ea99c3..b0558de5 100644 --- a/src/electron/utils/settings.ts +++ b/src/electron/utils/settings.ts @@ -1,7 +1,7 @@ import ElectronStore from "electron-store"; import { safeStorage } from "electron"; import logger from "./logger"; -import type { Preferences } from "../../shared/types"; +import type { KeyboardShortcut } from "../../shared/types"; import type { recentProject } from "../../shared/types/index"; export interface Settings { @@ -12,6 +12,7 @@ export interface Settings { recentProjects: recentProject[]; prompts: string[]; model: string; + keyboardShortcuts: KeyboardShortcut[]; } type DotUnionKeys = T extends object @@ -40,6 +41,7 @@ export const settings = new ElectronStore({ recentProjects: [], prompts: [], model: "GPT-3.5", + keyboardShortcuts: [], }, }); diff --git a/src/frontend/lib/stores/BlixStore.ts b/src/frontend/lib/stores/BlixStore.ts index 9e9f66e1..eca07ae1 100644 --- a/src/frontend/lib/stores/BlixStore.ts +++ b/src/frontend/lib/stores/BlixStore.ts @@ -3,6 +3,7 @@ import { commandStore } from "./CommandStore"; import { toolboxStore } from "./ToolboxStore"; import { tileStore } from "./TileStore"; import { shortcutsRegistry } from "./ShortcutStore"; +import type { KeyboardShortcut } from "@shared/types"; interface BlixStore { blixReady: boolean; @@ -43,8 +44,11 @@ export async function setInitialStores() { const tile = await window.apis.tileApi.getTiles(); tileStore.refreshStore(tile); - const shortcuts = await window.apis.utilApi.getUserSettings(); - if (shortcuts.status === "success") shortcutsRegistry.refreshStore(shortcuts.data); + const shortcuts = await window.apis.utilApi.getUserSetting("keyboardShortcuts"); + if (shortcuts.status === "success") { + // TODO: Add some sort of schema check + shortcutsRegistry.refreshStore(shortcuts.data as KeyboardShortcut[]); + } // Graph store // const allGraphIds = await window.apis.graphApi.getAllGraphUUIDs(); diff --git a/src/frontend/lib/stores/ShortcutStore.ts b/src/frontend/lib/stores/ShortcutStore.ts index d40f8301..07ae75bf 100644 --- a/src/frontend/lib/stores/ShortcutStore.ts +++ b/src/frontend/lib/stores/ShortcutStore.ts @@ -1,10 +1,87 @@ -import { derived, writable, get } from "svelte/store"; +import type { KeyboardShortcut, KeyboardShortcuts } from "../../../shared/types"; +import { derived, writable, get, type Readable } from "svelte/store"; + +const defaultShortcuts: Omit[] = [ + { + id: "blix.palette.toggle", + title: "Toggle Command Palette", + value: ["ctrl+[KeyP]", "meta+[KeyP]"], + }, + { + id: "blix.palette.hide", + title: "Hide Command Palette", + value: ["[Escape]"], + }, + { + id: "blix.palette.scrollDown", + title: "Scroll Down Command Palette", + value: ["[ArrowDown]", "ctrl+[KeyJ]"], + }, + { + id: "blix.palette.scrollUp", + title: "Scroll Up Command Palette", + value: ["[ArrowUp]", "ctrl+[KeyK]"], + }, + { + id: "blix.palette.selectItem", + title: "Select Command Palette Item", + value: ["[Enter]"], + }, + { + id: "blix.palette.prompt", + title: "Submit Prompt Command Palette", + value: ["[Tab]"], + }, + { + id: "blix.contextMenu.hide", + title: "Hide Context Menu", + value: ["[Escape]"], + }, + { + id: "blix.contextMenu.scrollDown", + title: "Scroll Down Context Menu", + value: ["ctrl+[KeyJ]"], + }, + { + id: "blix.contextMenu.scrollUp", + title: "Scroll Up Context Menu", + value: ["ctrl+[KeyK]"], + }, + { + id: "blix.contextMenu.selectItem", + title: "Select Context Menu Item", + value: ["[Enter]"], + }, + { + id: "blix.projects.newProject", + title: "Create New Project", + value: ["meta+[KeyN]", "ctrl+[KeyN]"], + }, + { + id: "blix.settings.toggle", + title: "Toggle Settings", + value: ["meta+[Comma]", "ctrl+[Comma]"], + }, + { + id: "blix.settings.hide", + title: "Hide Settings", + value: ["[Escape]"], + }, + { + id: "blix.splash.hide", + title: "Hide Splash Screen", + value: ["[Escape]"], + }, + { + id: "blix.projects.save", + title: "Save Project", + value: ["ctrl+[KeyS]", "meta+[KeyS]"], + }, +]; export type ShortcutAction = `${string}.${string}`; // Actions must be nested at least one layer deep export type ShortcutString = string; -import type { UserSettingsCategory } from "../../../shared/types"; - export class ShortcutCombo { keyCode: string; @@ -84,87 +161,139 @@ export class ShortcutCombo { } } -type ShortcutsDict = { [key: ShortcutAction]: ShortcutString[] }; - -// Maps actions to their respective shortcuts -let categories: UserSettingsCategory[] = []; -let selectedCategory: UserSettingsCategory | undefined; class ShortcutStore { - public refreshStore(data: UserSettingsCategory[]) { - if (data) { - categories = data; - selectedCategory = categories.find((c) => c.title === "Keybindings"); - - if (selectedCategory) { - const short = selectedCategory.settings[0].value as { [key: string]: string[] }; - if (short !== undefined) this.shortcuts.set(short); - } + // Gets filled with default shortcuts + shortcuts = writable>(); + + constructor() { + this.refreshStore( + defaultShortcuts.map((shortcut) => ({ ...shortcut, type: "keyboardShortcut" })) + ); + } + + public refreshStore(keyboardShortcuts: KeyboardShortcut[]) { + if (keyboardShortcuts.length === 0) { + return; } + + this.shortcuts.set(new Map(keyboardShortcuts.map((shortcut) => [shortcut.id, shortcut]))); + } + + public async persistShortcuts() { + const $shortcuts = get(this.shortcuts); + const shortcutsList = Array.from($shortcuts.values()); + const keyboardShortcuts: KeyboardShortcuts = { + id: "keyboardShortcuts", + type: "keyboardShortcuts", + title: "Keyboard Shortcuts", + value: shortcutsList, + }; + + return await window.apis.utilApi.saveUserSetting(keyboardShortcuts); } - // Fill default shortcuts here - shortcuts = writable({ - "blix.palette.toggle": ["ctrl+[KeyP]", "meta+[KeyP]"], - "blix.palette.show": [], - "blix.palette.hide": ["[Escape]"], - "blix.palette.scrollDown": ["[ArrowDown]", "ctrl+[KeyJ]"], - "blix.palette.scrollUp": ["[ArrowUp]", "ctrl+[KeyK]"], - "blix.palette.selectItem": ["[Enter]"], - "blix.palette.prompt": ["[Tab]"], - "blix.contextMenu.hide": ["[Escape]"], - "blix.contextMenu.show": [], - "blix.contextMenu.scrollDown": ["ctrl+[KeyJ]"], - "blix.contextMenu.scrollUp": ["ctrl+[KeyK]"], - "blix.contextMenu.selectItem": ["[Enter]"], - "blix.projects.newProject": ["meta+[KeyN]", "ctrl+[KeyN]"], - "blix.settings.toggle": ["meta+[Comma]", "ctrl+[Comma]"], - "blix.settings.hide": ["[Escape]"], - "blix.splash.hide": ["[Escape]"], - "blix.projects.save": ["ctrl+[KeyS]", "meta+[KeyS]"], - }); public addActionShortcut(action: ShortcutAction, combo: ShortcutCombo) { this.shortcuts.update((shortcuts) => { - if (shortcuts[action] === undefined) { - shortcuts[action] = []; + const shortcut = shortcuts.get(action); + + if (!shortcut) { + shortcuts.set(action, { id: action, title: "", value: [], type: "keyboardShortcut" }); + } else { + shortcuts.set(action, { ...shortcut, value: [...shortcut.value, combo.getString] }); } - shortcuts[action].push(combo.getString); + return shortcuts; }); } - public updateActionShortcut(action: ShortcutAction, index: number, combo: ShortcutCombo) { + public removeShortcutHotkey(action: ShortcutAction, index: number) { this.shortcuts.update((shortcuts) => { - if (shortcuts[action] === undefined) { - shortcuts[action] = []; - } - if (shortcuts[action].length <= index) { - shortcuts[action].push(combo.getString); + const shortcut = shortcuts.get(action); + + if (!shortcut) { return shortcuts; } - shortcuts[action][index] = combo.getString; + + const keys = [...shortcut.value]; + keys.splice(index, 1); + shortcuts.set(action, { ...shortcut, value: keys }); + return shortcuts; }); } - public get subscribe() { - return this.shortcuts.subscribe; + public updateActionShortcut(action: ShortcutAction, index: number, combo: ShortcutCombo) { + this.shortcuts.update((shortcuts) => { + const shortcut = shortcuts.get(action); + + if (!shortcut) { + shortcuts.set(action, { id: action, title: "", value: [], type: "keyboardShortcut" }); + return shortcuts; + } + + if (shortcut.value.length <= index) { + shortcuts.set(action, { ...shortcut, value: [...shortcut.value, combo.getString] }); + } else { + const keys = [...shortcut.value]; + keys[index] = combo.getString; + shortcuts.set(action, { ...shortcut, value: keys }); + } + + return shortcuts; + }); } public getShortcutsForAction(action: ShortcutAction): ShortcutString[] { - const sc = get(this.shortcuts); - if (sc[action] === undefined) { + const $shortcuts = get(this.shortcuts); + const shortcut = $shortcuts.get(action); + + if (!shortcut) { return []; } - return sc[action]; + + return shortcut.value; } - // Returns a derived store that only listens for the specified action - public getShortcutsForActionReactive(action: ShortcutAction) { + /** Returns a derived store that only listens for a specified action */ + public getShortcutsForActionReactive(action: ShortcutAction): Readable { return derived(this.shortcuts, ($shortcuts) => { - if ($shortcuts[action] === undefined) { + const shortcut = $shortcuts.get(action); + + if (!shortcut) { return []; } - return $shortcuts[action]; + + return shortcut.value; + }); + } + + public getShortcutsReactive(): Readable { + return derived(this.shortcuts, ($shortcuts) => { + return Array.from($shortcuts.values()); + }); + } + + public getFormattedShortcutsReactive(): Readable { + return derived(this.shortcuts, ($shortcuts) => { + const shortcuts = Array.from($shortcuts.values()).map((shortcut) => ({ ...shortcut })); + + shortcuts.forEach((shortcut) => { + shortcut.value = shortcut.value.map((combo) => { + combo = combo.replace("Comma", ","); + combo = combo.replace("meta", "⌘"); + combo = combo.replace("ctrl", "⌃"); + combo = combo.replace("shift", "⇧"); + combo = combo.replace("Key", ""); + combo = combo.replace("[", ""); + combo = combo.replace("]", ""); + combo = combo.replace("+", " "); + return combo; + }); + + return shortcut; + }); + + return shortcuts; }); } @@ -173,7 +302,10 @@ class ShortcutStore { return this.getShortcutsForAction(action).includes(combo.getString); } + + public get subscribe() { + return this.shortcuts.subscribe; + } } -// export const shortcutsRegistry = writable(new ShortcutStore()); export const shortcutsRegistry = new ShortcutStore(); diff --git a/src/frontend/ui/base/App.svelte b/src/frontend/ui/base/App.svelte index 99355c0d..2e504efa 100644 --- a/src/frontend/ui/base/App.svelte +++ b/src/frontend/ui/base/App.svelte @@ -9,7 +9,7 @@ import { initAPIs } from "../../lib/api/apiInitializer"; import ContextMenu from "../../ui/utils/ContextMenu.svelte"; import Test from "./Test.svelte"; - import Settings from "./Settings.svelte"; + import Settings from "./settings/Settings.svelte"; import Shortcuts from "../../ui/utils/Shortcuts.svelte"; import { settingsStore } from "../../lib/stores/SettingsStore"; import { confetti } from "@neoconfetti/svelte"; diff --git a/src/frontend/ui/base/layout/Panel.svelte b/src/frontend/ui/base/layout/Panel.svelte index 5bbb53e4..8d20b32b 100644 --- a/src/frontend/ui/base/layout/Panel.svelte +++ b/src/frontend/ui/base/layout/Panel.svelte @@ -21,7 +21,7 @@ import Browser from "../../tiles/Browser.svelte"; import Assets from "../../tiles/Assets.svelte"; import WebCamera from "../../tiles/WebCamera.svelte"; - import ShortcutSettings from "../../tiles/ShortcutSettings.svelte"; + import ShortcutSettings from "../settings/Hotkeys.svelte"; import { PanelGroup, PanelLeaf, type PanelNode } from "@frontend/lib/PanelNode"; import type { PanelType } from "@shared/types"; import { focusedPanelStore } from "../../../lib/PanelNode"; diff --git a/src/frontend/ui/base/settings/About.svelte b/src/frontend/ui/base/settings/About.svelte new file mode 100644 index 00000000..3311cf88 --- /dev/null +++ b/src/frontend/ui/base/settings/About.svelte @@ -0,0 +1,81 @@ + + +
API Keys
+
+ Rest assured that none of your API keys get stored remotely. Your information is encrypted and + maintained securely and solely within the confines of your own device. +
+ +{#each settings as item (item.id)} +
+ + + {#if item.type === "text" && item.secret} + + {:else if item.type === "dropdown"} + + {/if} +
+{/each} + +
+ Save +
diff --git a/src/frontend/ui/base/settings/AiSettings.svelte b/src/frontend/ui/base/settings/AiSettings.svelte new file mode 100644 index 00000000..3311cf88 --- /dev/null +++ b/src/frontend/ui/base/settings/AiSettings.svelte @@ -0,0 +1,81 @@ + + +
API Keys
+
+ Rest assured that none of your API keys get stored remotely. Your information is encrypted and + maintained securely and solely within the confines of your own device. +
+ +{#each settings as item (item.id)} +
+ + + {#if item.type === "text" && item.secret} + + {:else if item.type === "dropdown"} + + {/if} +
+{/each} + +
+ Save +
diff --git a/src/frontend/ui/base/settings/Hotkeys.svelte b/src/frontend/ui/base/settings/Hotkeys.svelte new file mode 100644 index 00000000..bc976e8f --- /dev/null +++ b/src/frontend/ui/base/settings/Hotkeys.svelte @@ -0,0 +1,148 @@ + + + +
+ {#each $shortcuts as { id, title, value } (id)} +
+ {title} +
+ {#each value as hotkey, i (hotkey)} + + {hotkey} + + + + + + + {:else} + Blank + {/each} +
+ + + +
+
+
+ {/each} +
+ + + + diff --git a/src/frontend/ui/base/Settings.svelte b/src/frontend/ui/base/settings/Settings.svelte similarity index 59% rename from src/frontend/ui/base/Settings.svelte rename to src/frontend/ui/base/settings/Settings.svelte index c5e5d369..ffebedf0 100644 --- a/src/frontend/ui/base/Settings.svelte +++ b/src/frontend/ui/base/settings/Settings.svelte @@ -1,28 +1,29 @@ + +
+ +
+ + +
+
+ + + +
+ + +
+ {#each userSettingSections as section (section.id)} +
+ {section.title} + + {#each section.categories as category (category.id)} + {category.title} + {/each} +
+ {/each} +
+ + +
+ {#if selectedCategoryId in userSettingsComponentMap} + {#key selectedCategoryId} + + {/key} + {:else} +
+
Coming Soon
+
+ {/if} +
+
+
+ + diff --git a/src/frontend/ui/utils/SecureInput.svelte b/src/frontend/ui/base/settings/utils/SecureInput.svelte similarity index 100% rename from src/frontend/ui/utils/SecureInput.svelte rename to src/frontend/ui/base/settings/utils/SecureInput.svelte diff --git a/src/frontend/ui/tiles/ShortcutSettings.svelte b/src/frontend/ui/tiles/ShortcutSettings.svelte deleted file mode 100644 index 6be456b1..00000000 --- a/src/frontend/ui/tiles/ShortcutSettings.svelte +++ /dev/null @@ -1,101 +0,0 @@ - - -
- Keyboard Shortcuts Settings -
Configure your shortcut preferences here
- - {#each Object.entries($shortcutsRegistry) as [action, shortcuts]} - - - {#each shortcuts as shortcut, index} - - {/each} - - - {/each} -
- {action} - - - - -
-
- - diff --git a/src/frontend/ui/utils/Shortcuts.svelte b/src/frontend/ui/utils/Shortcuts.svelte index 70a197aa..11a19803 100644 --- a/src/frontend/ui/utils/Shortcuts.svelte +++ b/src/frontend/ui/utils/Shortcuts.svelte @@ -15,7 +15,7 @@ // Check each action for (const action of Object.keys(shortcuts)) { - if (shortcutsRegistry.checkShortcut(action as ShortcutAction, combo)) { + if (combo && shortcutsRegistry.checkShortcut(action as ShortcutAction, combo)) { shortcuts[action as ShortcutAction](event); } } diff --git a/src/shared/types/setting.ts b/src/shared/types/setting.ts index e3c5655c..83ef56be 100644 --- a/src/shared/types/setting.ts +++ b/src/shared/types/setting.ts @@ -1,16 +1,37 @@ -export interface UserSettingsCategory { +export const userSettingSections = [ + { + id: "general", + title: "General", + categories: [ + { id: "about", title: "About" }, + { id: "ai", title: "AI Settings" }, + { id: "hotkeys", title: "Hotkeys" }, + ], + }, +] as const satisfies readonly UserSettingsSection[]; + +export type UserSettingsCategoryId = + (typeof userSettingSections)[number]["categories"][number]["id"]; +export type UserSettingsCategoryTitle = + (typeof userSettingSections)[number]["categories"][number]["title"]; + +export type UserSettingsSection = { + id: string; + title: string; + categories: ReadonlyArray; +}; + +export type UserSettingCategory = { id: string; title: string; subtitle?: string; - settings: Setting[]; -} +}; export interface UserSetting { id: string; title: string; subtitle?: string; secret?: boolean; - type: "dropdown" | "password" | "text" | "toggle" | "preferences"; } export interface InputSetting extends UserSetting { @@ -24,15 +45,24 @@ export interface DropdownSetting extends UserSetting { options: string[]; value: string; } - export interface ToggleSetting extends UserSetting { type: "toggle"; value: boolean; } -export interface Preferences extends UserSetting { - type: "preferences"; - value: { [key: string]: string[] }; +export interface KeyboardShortcuts extends UserSetting { + id: "keyboardShortcuts"; + type: "keyboardShortcuts"; + value: KeyboardShortcut[]; +} +export interface KeyboardShortcut extends UserSetting { + id: `${string}.${string}`; + type: "keyboardShortcut"; + value: string[]; } -export type Setting = DropdownSetting | InputSetting | ToggleSetting | Preferences; +export type Setting = DropdownSetting | InputSetting | ToggleSetting | KeyboardShortcuts; + +type Prettify = T extends object ? { [K in keyof T]: T[K] } : never; + +type t = Prettify; From fb79997411c28a68799fe7e3b8f056ddc2151176 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 20 Sep 2023 13:09:20 +0200 Subject: [PATCH 078/209] Add some extra key replacements for keybinds --- src/frontend/lib/stores/ShortcutStore.ts | 36 +++++++++++++++----- src/frontend/ui/base/settings/Hotkeys.svelte | 17 +++++---- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/frontend/lib/stores/ShortcutStore.ts b/src/frontend/lib/stores/ShortcutStore.ts index 07ae75bf..8043f789 100644 --- a/src/frontend/lib/stores/ShortcutStore.ts +++ b/src/frontend/lib/stores/ShortcutStore.ts @@ -279,14 +279,34 @@ class ShortcutStore { shortcuts.forEach((shortcut) => { shortcut.value = shortcut.value.map((combo) => { - combo = combo.replace("Comma", ","); - combo = combo.replace("meta", "⌘"); - combo = combo.replace("ctrl", "⌃"); - combo = combo.replace("shift", "⇧"); - combo = combo.replace("Key", ""); - combo = combo.replace("[", ""); - combo = combo.replace("]", ""); - combo = combo.replace("+", " "); + const map = new Map([ + ["Comma", ","], + ["Equal", "="], + ["Minus", "-"], + ["Period", "."], + ["Backslash", "\\"], + ["Backspace", "⌫"], + ["meta", "⌘"], + ["alt", "⌥"], + ["ctrl", "⌃"], + ["shift", "⇧"], + ["ArrowDown", "↓"], + ["ArrowUp", "↑"], + ["ArrowLeft", "←"], + ["ArrowRight", "→"], + ["BracketRight", "]"], + ["BracketLeft", "["], + ["Digit", ""], + ["Key", ""], + ["[", ""], + ["]", ""], + [/\+/g, " "], + ]); + + for (const [key, value] of map.entries()) { + combo = combo.replace(key, value); + } + return combo; }); diff --git a/src/frontend/ui/base/settings/Hotkeys.svelte b/src/frontend/ui/base/settings/Hotkeys.svelte index bc976e8f..5bc069e5 100644 --- a/src/frontend/ui/base/settings/Hotkeys.svelte +++ b/src/frontend/ui/base/settings/Hotkeys.svelte @@ -8,7 +8,6 @@ let shortcuts = shortcutsRegistry.getFormattedShortcutsReactive(); export function updateShortcut(action: string, index: number, event: KeyboardEvent) { - console.log(action, event); const combo: ShortcutCombo | null = ShortcutCombo.fromEvent(event); if (!combo) return; @@ -25,6 +24,11 @@ (event.target as HTMLButtonElement).blur(); shortcutsRegistry.persistShortcuts(); } + + function removeShortcutHotkey(action: ShortcutAction, index: number) { + shortcutsRegistry.removeShortcutHotkey(action, index); + shortcutsRegistry.persistShortcuts(); + } @@ -33,7 +37,7 @@
{title}
- {#each value as hotkey, i (hotkey)} + {#each value as hotkey, index (hotkey)} @@ -41,7 +45,7 @@ Blank {/each} -
-
+
{/each} From 05986ede4b783d3926c6f91060390ce8467b33da Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 20 Sep 2023 13:19:46 +0200 Subject: [PATCH 079/209] Add scrollbar on vertical overflow to hotkeys page --- src/frontend/lib/stores/ShortcutStore.ts | 2 + src/frontend/ui/base/settings/Hotkeys.svelte | 74 ++++--------------- src/frontend/ui/base/settings/Settings.svelte | 8 +- 3 files changed, 20 insertions(+), 64 deletions(-) diff --git a/src/frontend/lib/stores/ShortcutStore.ts b/src/frontend/lib/stores/ShortcutStore.ts index 8043f789..d193b239 100644 --- a/src/frontend/lib/stores/ShortcutStore.ts +++ b/src/frontend/lib/stores/ShortcutStore.ts @@ -179,6 +179,7 @@ class ShortcutStore { this.shortcuts.set(new Map(keyboardShortcuts.map((shortcut) => [shortcut.id, shortcut]))); } + /** Saves user shortcuts to ElectronStore. */ public async persistShortcuts() { const $shortcuts = get(this.shortcuts); const shortcutsList = Array.from($shortcuts.values()); @@ -206,6 +207,7 @@ class ShortcutStore { }); } + /** Removes a specific keybind from an action/command. */ public removeShortcutHotkey(action: ShortcutAction, index: number) { this.shortcuts.update((shortcuts) => { const shortcut = shortcuts.get(action); diff --git a/src/frontend/ui/base/settings/Hotkeys.svelte b/src/frontend/ui/base/settings/Hotkeys.svelte index 5bc069e5..e5ea4d67 100644 --- a/src/frontend/ui/base/settings/Hotkeys.svelte +++ b/src/frontend/ui/base/settings/Hotkeys.svelte @@ -32,10 +32,11 @@ -
+
{#each $shortcuts as { id, title, value } (id)}
{title} +
{#each value as hotkey, index (hotkey)} Blank {/each} +
- - From 024c0c1b27cd10150e7f940ce7b58d1641ced721 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 20 Sep 2023 14:18:45 +0200 Subject: [PATCH 082/209] Remove old get settings from UtilApi --- src/electron/lib/api/apis/UtilApi.ts | 45 ---------------------------- 1 file changed, 45 deletions(-) diff --git a/src/electron/lib/api/apis/UtilApi.ts b/src/electron/lib/api/apis/UtilApi.ts index 1f1bef42..d5d5e566 100644 --- a/src/electron/lib/api/apis/UtilApi.ts +++ b/src/electron/lib/api/apis/UtilApi.ts @@ -92,51 +92,6 @@ export class UtilApi implements ElectronMainApi { return { status: "success" }; } - // This will have to be cleaned up later. Kinda a temp implementation rn - async getUserSettings() { - // const secrets = getSecrets(); - - const userSettings = [ - { - id: "ai_settings", - title: "AI Settings", - settings: [ - { - id: "OPENAI_API_KEY", - title: "Open AI Key", - subtitle: "Required to use Open AI models such as GPT-3.5", - type: "password", - secret: true, - value: getSecret("OPENAI_API_KEY"), - }, - { - id: "model", - title: "Open AI Model", - type: "dropdown", - value: settings.get("model"), - options: ["GPT-4", "GPT-3.5"], - }, - ], - }, - { - id: "keybind_settings", - title: "Keybindings", - settings: [ - { - id: "Keybindings", - title: "Keybindings", - subtitle: "Customize your keybindings", - type: "preferences", - secret: false, - value: settings.get("Keybindings"), - }, - ], - }, - ]; - - return { status: "success", data: userSettings } satisfies QueryResponse; - } - /** Retrieve a user setting from the ElectronStore. */ async getUserSetting(setting: Setting | string) { let key = ""; From 7f096768fcfe556dd786485053e986aab0306103 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 20 Sep 2023 14:33:54 +0200 Subject: [PATCH 083/209] Add chip to show when adding hotkey --- src/frontend/lib/stores/ShortcutStore.ts | 4 +++- src/frontend/ui/base/settings/Hotkeys.svelte | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/frontend/lib/stores/ShortcutStore.ts b/src/frontend/lib/stores/ShortcutStore.ts index d193b239..4a3404e7 100644 --- a/src/frontend/lib/stores/ShortcutStore.ts +++ b/src/frontend/lib/stores/ShortcutStore.ts @@ -200,7 +200,9 @@ class ShortcutStore { if (!shortcut) { shortcuts.set(action, { id: action, title: "", value: [], type: "keyboardShortcut" }); } else { - shortcuts.set(action, { ...shortcut, value: [...shortcut.value, combo.getString] }); + if (!shortcut.value.includes(combo.getString)) { + shortcuts.set(action, { ...shortcut, value: [...shortcut.value, combo.getString] }); + } } return shortcuts; diff --git a/src/frontend/ui/base/settings/Hotkeys.svelte b/src/frontend/ui/base/settings/Hotkeys.svelte index e5ea4d67..24aa862e 100644 --- a/src/frontend/ui/base/settings/Hotkeys.svelte +++ b/src/frontend/ui/base/settings/Hotkeys.svelte @@ -6,6 +6,7 @@ } from "@frontend/lib/stores/ShortcutStore"; let shortcuts = shortcutsRegistry.getFormattedShortcutsReactive(); + let focusedShortcutId = ""; export function updateShortcut(action: string, index: number, event: KeyboardEvent) { const combo: ShortcutCombo | null = ShortcutCombo.fromEvent(event); @@ -62,8 +63,20 @@ + {#if focusedShortcutId === id} + Press hotkey... + {/if} {:else} - Blank + {#if focusedShortcutId === id} + Press hotkey... + {:else} + Blank + {/if} {/each}
+
+ + +
+
+
Get help
+
Get help on using Blix.
+
+ +
+
+
+
diff --git a/src/frontend/ui/base/settings/AiSettings.svelte b/src/frontend/ui/base/settings/AiSettings.svelte index 6e807c67..3d949c8d 100644 --- a/src/frontend/ui/base/settings/AiSettings.svelte +++ b/src/frontend/ui/base/settings/AiSettings.svelte @@ -62,7 +62,7 @@ {:else if item.type === "dropdown"} - {#each items as itemKey} - + {#each Object.keys(items) as itemKey} + {/each} {/key} diff --git a/src/shared/types/cache.ts b/src/shared/types/cache.ts index 06aa7693..6ac6c3fd 100644 --- a/src/shared/types/cache.ts +++ b/src/shared/types/cache.ts @@ -20,21 +20,27 @@ export type CacheSubsidiary = { export type CacheObject = { uuid: CacheUUID; data: Buffer; - metadata: any; + metadata: CacheMetadata; }; export type CacheRequest = { type: string; id: string; - metadata?: any; + metadata?: CacheMetadata; }; -export type CacheResponse = - | { - success: true; - data?: string; - } - | { - success: false; - message?: string; - }; +export type CacheWriteResponse = { + success: boolean; + id: CacheUUID; +}; + +export type CacheMetadata = { + contentType: string; + name?: string; + other?: any; +}; + +export type CacheUpdateNotification = { + type: string; + cache: { uuid: string; metadata: CacheMetadata }[]; +}; From e877d4b0a4f7f89aa1768066d18b60f49912bfea Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 21 Sep 2023 09:04:07 +0200 Subject: [PATCH 088/209] Split settings into components --- src/electron/lib/api/apis/UtilApi.ts | 8 ++- src/frontend/ui/base/settings/About.svelte | 63 +++++++++---------- .../ui/base/settings/AiSettings.svelte | 56 +++-------------- src/frontend/ui/base/settings/Settings.svelte | 12 ++-- .../ui/base/settings/utils/Button.svelte | 15 ++--- .../ui/base/settings/utils/Dropdown.svelte | 30 +++++++++ .../base/settings/utils/SettingsItem.svelte | 29 +++++++++ src/shared/types/setting.ts | 13 +++- 8 files changed, 124 insertions(+), 102 deletions(-) create mode 100644 src/frontend/ui/base/settings/utils/Dropdown.svelte create mode 100644 src/frontend/ui/base/settings/utils/SettingsItem.svelte diff --git a/src/electron/lib/api/apis/UtilApi.ts b/src/electron/lib/api/apis/UtilApi.ts index f54f5aed..8282432a 100644 --- a/src/electron/lib/api/apis/UtilApi.ts +++ b/src/electron/lib/api/apis/UtilApi.ts @@ -12,7 +12,7 @@ import { getSecret, type Settings, } from "../../../utils/settings"; -import type { Setting, QueryResponse } from "../../../../shared/types"; +import type { Setting, QueryResponse, ButtonSetting } from "../../../../shared/types"; import { type ChatModel } from "../../../lib/ai/Model"; import { autoUpdater } from "electron-updater"; // import dotenv from "dotenv"; @@ -83,7 +83,7 @@ export class UtilApi implements ElectronMainApi { // Add something extra validation - async saveUserSetting(setting: Setting) { + async saveUserSetting(setting: Exclude) { return await this.saveUserSettings([setting]); } @@ -91,7 +91,9 @@ export class UtilApi implements ElectronMainApi { for (const setting of newSettings) { if (setting.secret) { setSecret(setting.id, setting.value.toString()); - } else settings.set(setting.id, setting.value); + } else { + settings.set(setting.id, setting.value); + } } return { status: "success" }; diff --git a/src/frontend/ui/base/settings/About.svelte b/src/frontend/ui/base/settings/About.svelte index 26cab8a3..88aa8f2a 100644 --- a/src/frontend/ui/base/settings/About.svelte +++ b/src/frontend/ui/base/settings/About.svelte @@ -1,11 +1,9 @@
- -
-
-
- Current Version: {$blixStore.blix.version} -
-
Blix is up-to-date!
- {#if checkingForUpdates} -
Checking for updates...
- {/if} -
- -
-
-
- - -
-
-
Get help
-
Get help on using Blix.
-
- -
-
-
+ + {#if checkingForUpdates} +
Checking for updates...
+ {/if} +
+ +
diff --git a/src/frontend/ui/base/settings/AiSettings.svelte b/src/frontend/ui/base/settings/AiSettings.svelte index 3d949c8d..a2a06c99 100644 --- a/src/frontend/ui/base/settings/AiSettings.svelte +++ b/src/frontend/ui/base/settings/AiSettings.svelte @@ -2,7 +2,7 @@ import { getContext, onMount } from "svelte"; import type { Setting } from "../../../../shared/types"; import type { SettingsContext } from "./Settings.svelte"; - import SecureInput from "./utils/SecureInput.svelte"; + import SettingsItem from "./utils/SettingsItem.svelte"; const { getSetting, saveSettings } = getContext("settings"); @@ -10,7 +10,7 @@ { id: "OPENAI_API_KEY", title: "Open AI Key", - subtitle: "Required to use Open AI models such as GPT-3.5", + subtitle: "Required to use Open AI models", type: "text", secret: true, value: "", @@ -23,6 +23,7 @@ options: ["GPT-3.5", "GPT-4"], }, ]; + let initializedSettings = false; onMount(async () => { const updatedSettings = await Promise.all( @@ -38,7 +39,10 @@ ); settings = updatedSettings; + initializedSettings = true; }); + + $: if (initializedSettings) saveSettings(settings);
@@ -49,52 +53,6 @@
{#each settings as item (item.id)} -
- - - {#if item.type === "text" && item.secret} - - {:else if item.type === "dropdown"} - - {/if} -
+ {/each} - -
- Save -
- - diff --git a/src/frontend/ui/base/settings/Settings.svelte b/src/frontend/ui/base/settings/Settings.svelte index aef7aed5..ba5a5d87 100644 --- a/src/frontend/ui/base/settings/Settings.svelte +++ b/src/frontend/ui/base/settings/Settings.svelte @@ -23,15 +23,13 @@ getSetting, }); + /** State of buttons don't get saved */ async function saveSettings(settings: Setting[]) { - const res = await window.apis.utilApi.saveUserSettings(settings); + const filteredSettings = settings.filter((setting) => setting.type !== "button"); - if (res.status === "success") { - toastStore.trigger({ - message: "Your preferences have been updated successfully", - type: "success", - }); - } else { + const res = await window.apis.utilApi.saveUserSettings(filteredSettings); + + if (res.status === "error") { toastStore.trigger({ message: "Something went wrong while updating your preferences", type: "error", diff --git a/src/frontend/ui/base/settings/utils/Button.svelte b/src/frontend/ui/base/settings/utils/Button.svelte index 1274a874..e7274c74 100644 --- a/src/frontend/ui/base/settings/utils/Button.svelte +++ b/src/frontend/ui/base/settings/utils/Button.svelte @@ -1,13 +1,14 @@
- {title} + {item.value}
diff --git a/src/frontend/ui/base/settings/utils/Dropdown.svelte b/src/frontend/ui/base/settings/utils/Dropdown.svelte new file mode 100644 index 00000000..2cbe5fab --- /dev/null +++ b/src/frontend/ui/base/settings/utils/Dropdown.svelte @@ -0,0 +1,30 @@ + + + + + diff --git a/src/frontend/ui/base/settings/utils/SettingsItem.svelte b/src/frontend/ui/base/settings/utils/SettingsItem.svelte new file mode 100644 index 00000000..8f8811a1 --- /dev/null +++ b/src/frontend/ui/base/settings/utils/SettingsItem.svelte @@ -0,0 +1,29 @@ + + + +
+
+
{item.title}
+ {#if item.subtitle} +
{item.subtitle}
+ {/if} + +
+ +
+ {#if item.type === "text" && item.secret} + + {:else if item.type === "dropdown"} + + {:else if item.type === "button"} +
+
diff --git a/src/shared/types/setting.ts b/src/shared/types/setting.ts index 83ef56be..3a41faf8 100644 --- a/src/shared/types/setting.ts +++ b/src/shared/types/setting.ts @@ -12,6 +12,7 @@ export const userSettingSections = [ export type UserSettingsCategoryId = (typeof userSettingSections)[number]["categories"][number]["id"]; + export type UserSettingsCategoryTitle = (typeof userSettingSections)[number]["categories"][number]["title"]; @@ -49,6 +50,11 @@ export interface ToggleSetting extends UserSetting { type: "toggle"; value: boolean; } +export interface ButtonSetting extends UserSetting { + type: "button"; + value: string; + onClick: (item: Setting) => void; +} export interface KeyboardShortcuts extends UserSetting { id: "keyboardShortcuts"; @@ -61,7 +67,12 @@ export interface KeyboardShortcut extends UserSetting { value: string[]; } -export type Setting = DropdownSetting | InputSetting | ToggleSetting | KeyboardShortcuts; +export type Setting = + | DropdownSetting + | InputSetting + | ToggleSetting + | KeyboardShortcuts + | ButtonSetting; type Prettify = T extends object ? { [K in keyof T]: T[K] } : never; From 2aef87a8beb31385964a3cb95a600512ec4bf21a Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 21 Sep 2023 10:12:32 +0200 Subject: [PATCH 089/209] Add some auto update stuff --- package.json | 2 +- src/frontend/lib/api/apis/UtilClientApi.ts | 6 ++- src/frontend/lib/stores/BlixStore.ts | 13 ++++++- src/frontend/ui/base/settings/About.svelte | 8 ++-- src/index.ts | 37 ++++++++++-------- test.json | 44 ++++++++++++++++++++++ 6 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 test.json diff --git a/package.json b/package.json index 2eff7961..acf7b8ad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - An AI-assisted graph photo editor", - "version": "1.0.12", + "version": "1.0.13", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" diff --git a/src/frontend/lib/api/apis/UtilClientApi.ts b/src/frontend/lib/api/apis/UtilClientApi.ts index 53fac517..3f362372 100644 --- a/src/frontend/lib/api/apis/UtilClientApi.ts +++ b/src/frontend/lib/api/apis/UtilClientApi.ts @@ -1,6 +1,6 @@ import type { ElectronWindowApi } from "electron-affinity/window"; import { toastStore, type ToastOptions } from "../../stores/ToastStore"; -import { blixStore, setInitialStores } from "../../../lib/stores/BlixStore"; +import { blixStore, setInitialStores, type BlixStoreState } from "../../../lib/stores/BlixStore"; import { bindMainApis } from "../apiInitializer"; export class UtilClientApi implements ElectronWindowApi { @@ -18,4 +18,8 @@ export class UtilClientApi implements ElectronWindowApi { await setInitialStores(); blixStore.update((state) => ({ ...state, blixReady: true })); } + + refreshBlixStore(data: Partial) { + blixStore.refreshStore(data); + } } diff --git a/src/frontend/lib/stores/BlixStore.ts b/src/frontend/lib/stores/BlixStore.ts index 8bfc5a2c..d2d6e7dc 100644 --- a/src/frontend/lib/stores/BlixStore.ts +++ b/src/frontend/lib/stores/BlixStore.ts @@ -16,9 +16,14 @@ const blixStoreDefaults = { blix: { version: "", }, + update: { + isAvailable: false, + isDownloaded: false, + version: "", + }, }; -type BlixStoreState = typeof blixStoreDefaults; +export type BlixStoreState = typeof blixStoreDefaults; export class BlixStore { store = writable(blixStoreDefaults); @@ -27,6 +32,10 @@ export class BlixStore { return await window.apis.utilApi.checkForUpdates(); } + public refreshStore(state: Partial) { + this.store.update((s) => ({ ...s, ...state })); + } + public get subscribe() { return this.store.subscribe; } @@ -41,7 +50,7 @@ export const blixStore = new BlixStore(); export async function setInitialStores() { // BLix store const res = await window.apis.utilApi.getInfo(); - blixStore.update((state) => ({ ...state, ...res })); + blixStore.refreshStore(res); // Command store const command = await window.apis.commandApi.getCommands(); diff --git a/src/frontend/ui/base/settings/About.svelte b/src/frontend/ui/base/settings/About.svelte index 88aa8f2a..7ac749b2 100644 --- a/src/frontend/ui/base/settings/About.svelte +++ b/src/frontend/ui/base/settings/About.svelte @@ -8,7 +8,7 @@ async function checkForUpdates() { checkingForUpdates = true; - await blixStore.checkForUpdates(); + console.log(JSON.stringify(await blixStore.checkForUpdates())); await sleep(400); checkingForUpdates = false; } @@ -23,10 +23,12 @@ item="{{ id: 'version', title: `Current Version: ${$blixStore.blix.version}`, - subtitle: 'Blix is up-to-date!', + subtitle: $blixStore.update.isAvailable + ? `Update is available: ${$blixStore.update.version}` + : 'Blix is up to date', value: 'Check for updates', type: 'button', - onClick: () => checkForUpdates(), + onClick: checkForUpdates, }}" > {#if checkingForUpdates} diff --git a/src/index.ts b/src/index.ts index beb51094..495382f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -258,7 +258,9 @@ app.on("web-contents-created", (e, contents) => { }); }); -// ========== AUTO UPDATER ==========// +// ================================================================== +// AUTO UPDATER +// ================================================================== if (isProd) { autoUpdater.checkForUpdates().catch((err) => { @@ -268,26 +270,29 @@ if (isProd) { autoUpdater.logger = logger; -autoUpdater.on("update-available", () => { - notification = new Notification({ - title: "BLix", - body: "Updates are available. Click to download.", - silent: true, - // icon: nativeImage.createFromPath(join(__dirname, "..", "assets", "icon.png"), - }); - notification.show(); - notification.on("click", () => { - autoUpdater.downloadUpdate().catch((err) => { - logger.error(JSON.stringify(err)); - }); +autoUpdater.on("update-available", (updateInfo) => { + mainWindow?.apis.utilClientApi.refreshBlixStore({ + update: { isAvailable: true, isDownloaded: false, version: updateInfo.version }, }); + + // notification.on("click", () => { + // autoUpdater.downloadUpdate().catch((err) => { + // logger.error(JSON.stringify(err)); + // }); + // }); }); -autoUpdater.on("update-not-available", () => { - blix?.sendInformationMessage("You are up to date!"); +autoUpdater.on("update-not-available", (updateInfo) => { + mainWindow?.apis.utilClientApi.refreshBlixStore({ + update: { isAvailable: false, isDownloaded: false, version: updateInfo.version }, + }); }); -autoUpdater.on("update-downloaded", () => { +autoUpdater.on("update-downloaded", (updateInfo) => { + mainWindow?.apis.utilClientApi.refreshBlixStore({ + update: { isAvailable: true, isDownloaded: true, version: updateInfo.version }, + }); + notification = new Notification({ title: "Blix", body: "The updates are ready. Click to quit and install.", diff --git a/test.json b/test.json new file mode 100644 index 00000000..bf7f9bb5 --- /dev/null +++ b/test.json @@ -0,0 +1,44 @@ +{ + "versionInfo": { + "tag": "v1.0.12", + "version": "1.0.12", + "files": [ + { + "url": "Blix-1.0.12.dmg", + "sha512": "74kfMvWO2D521hYQbsugN+U47eHRFs1T7S42jVEx2rQZZvH38iuRBWzezW5eiRqkoc3sSxXUbNNw8foFtpv+jw==", + "size": 140012174 + }, + { + "url": "Blix-1.0.12-arm64.dmg", + "sha512": "+loDMnjkcZyn/e8TullQ6rZRGcoIgJCGQQsSwreFSc0/fHA3niE+NfplzLliIdboi6KcJFatbxX+a0sdEv+HMw==", + "size": 136332672 + } + ], + "path": "Blix-1.0.12.dmg", + "sha512": "74kfMvWO2D521hYQbsugN+U47eHRFs1T7S42jVEx2rQZZvH38iuRBWzezW5eiRqkoc3sSxXUbNNw8foFtpv+jw==", + "releaseDate": "2023-09-19T17:18:01.602Z", + "releaseName": "Blix v1.0.12", + "releaseNotes": "" + }, + "updateInfo": { + "tag": "v1.0.12", + "version": "1.0.12", + "files": [ + { + "url": "Blix-1.0.12.dmg", + "sha512": "74kfMvWO2D521hYQbsugN+U47eHRFs1T7S42jVEx2rQZZvH38iuRBWzezW5eiRqkoc3sSxXUbNNw8foFtpv+jw==", + "size": 140012174 + }, + { + "url": "Blix-1.0.12-arm64.dmg", + "sha512": "+loDMnjkcZyn/e8TullQ6rZRGcoIgJCGQQsSwreFSc0/fHA3niE+NfplzLliIdboi6KcJFatbxX+a0sdEv+HMw==", + "size": 136332672 + } + ], + "path": "Blix-1.0.12.dmg", + "sha512": "74kfMvWO2D521hYQbsugN+U47eHRFs1T7S42jVEx2rQZZvH38iuRBWzezW5eiRqkoc3sSxXUbNNw8foFtpv+jw==", + "releaseDate": "2023-09-19T17:18:01.602Z", + "releaseName": "Blix v1.0.12", + "releaseNotes": "" + } +} \ No newline at end of file From 1e587556dcb82027d433fb6a3dcd616ee12ae8b8 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 21 Sep 2023 10:47:12 +0200 Subject: [PATCH 090/209] New version test --- .github/workflows/build.yml | 6 ++++-- package.json | 2 +- src/frontend/ui/base/settings/About.svelte | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6589ec2e..05b39dc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,11 +28,13 @@ jobs: - name: Build/Release Blix run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - npm run dist -- --linux deb AppImage --x64 --arm64 + # npm run dist -- --linux deb AppImage --x64 --arm64 + npm run dist -- --linux deb AppImage --x86 elif [ "${{ matrix.os }}" = "windows-latest" ]; then npm run dist -- --windows nsis elif [ "${{ matrix.os }}" = "macos-latest" ]; then - npm run dist -- --mac dmg --x64 --arm64 + # npm run dist -- --mac dmg --x64 --arm64 + npm run dist -- --mac dmg --arm64 fi env: GH_TOKEN: ${{ secrets.TEST_TOKEN }} diff --git a/package.json b/package.json index acf7b8ad..f81c446c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - An AI-assisted graph photo editor", - "version": "1.0.13", + "version": "1.0.14", "repository": { "type": "git", "url": "https://github.com/ArmandKrynauw/Blix.git" diff --git a/src/frontend/ui/base/settings/About.svelte b/src/frontend/ui/base/settings/About.svelte index 7ac749b2..3da8a022 100644 --- a/src/frontend/ui/base/settings/About.svelte +++ b/src/frontend/ui/base/settings/About.svelte @@ -46,4 +46,6 @@ onClick: () => ({}), }}" /> + +
Hello World
From 3d6206480d805221f221e03666d559c47c5fc868 Mon Sep 17 00:00:00 2001 From: Jake Mileham Date: Thu, 21 Sep 2023 10:51:27 +0200 Subject: [PATCH 091/209] Fix media tile loading after using AI --- src/frontend/lib/stores/GraphStore.ts | 8 ++++++-- src/frontend/lib/stores/MediaStore.ts | 10 ++++++---- .../graph/nodeUICcomponents/TextInput.svelte | 15 ++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/frontend/lib/stores/GraphStore.ts b/src/frontend/lib/stores/GraphStore.ts index aa2909d6..417d64f5 100644 --- a/src/frontend/lib/stores/GraphStore.ts +++ b/src/frontend/lib/stores/GraphStore.ts @@ -116,17 +116,21 @@ export class GraphStore { for (const input in inputs) { if (!inputs.hasOwnProperty(input)) continue; - // console.log("SUB TO", node, "-->>", input) let first = true; + let oldState: unknown; this.uiInputUnsubscribers[node].push( inputs[input].subscribe((state) => { // Ensure the subscribe does not run when first created if (first) { first = false; - } else { + oldState = state; + } else if (state !== oldState) { + // Prevent unnecessary updates when the value is the exact same as the prior + // Only would then update if maybe the set function is used but old state === new state this.globalizeUIInputs(node, input).catch((err) => { return; }); + oldState = state; } }) ); diff --git a/src/frontend/lib/stores/MediaStore.ts b/src/frontend/lib/stores/MediaStore.ts index d1009900..0333e944 100644 --- a/src/frontend/lib/stores/MediaStore.ts +++ b/src/frontend/lib/stores/MediaStore.ts @@ -25,6 +25,7 @@ class MediaStore { // Wont set store and notfiy subscribers if the value allOutputIds have remained the same. // This is the case where the undo/redo system willchange something and then the stores see a change and do their // own update but their update wont matter. + if (oldIds && ids && !equivSets(oldIds, ids)) { this.outputIds.set(ids); } @@ -39,10 +40,11 @@ class MediaStore { } public async getMediaReactive(mediaId: MediaOutputId) { - await window.apis.mediaApi.subscribeToMedia(mediaId).catch((err) => { - return; - }); - + if (mediaId) { + await window.apis.mediaApi.subscribeToMedia(mediaId).catch((err) => { + return; + }); + } // If we do not have a frontend copy of the media, fetch it if (!get(this.store)[mediaId]) { const media = await window.apis.mediaApi.getDisplayableMedia(mediaId); diff --git a/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte b/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte index 45685a92..710f14df 100644 --- a/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte +++ b/src/frontend/ui/utils/graph/nodeUICcomponents/TextInput.svelte @@ -13,19 +13,16 @@ if (!inputStore.inputs[config.componentId]) inputStore.inputs[config.componentId] = writable(""); let valStore; - let first = true; - $: { - valStore = inputStore.inputs[config.componentId]; - if (!first) { - dispatch("inputInteraction", { id: config.componentId, value: $valStore }); - } else { - first = false; - } + $: valStore = inputStore.inputs[config.componentId]; + + // Only add event when user changes text, if pasted by ai, do nothing + function handleInteraction() { + dispatch("inputInteraction", { id: config.componentId, value: $valStore }); } - + diff --git a/src/shared/types/cache.ts b/src/shared/types/cache.ts index 6ac6c3fd..674e620b 100644 --- a/src/shared/types/cache.ts +++ b/src/shared/types/cache.ts @@ -26,6 +26,7 @@ export type CacheObject = { export type CacheRequest = { type: string; id: string; + messageId?: string; metadata?: CacheMetadata; }; From 062e33aae18dab2c8c0049f39b7e7ecc275a8d6d Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 21 Sep 2023 22:48:39 +0200 Subject: [PATCH 120/209] Remove token --- electron-builder.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 electron-builder.yml diff --git a/electron-builder.yml b/electron-builder.yml deleted file mode 100644 index b5c3ed33..00000000 --- a/electron-builder.yml +++ /dev/null @@ -1,4 +0,0 @@ -appId: com.the-spanish-inquisition.blix -publish: - provider: github - token: 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v \ No newline at end of file From 62dd3353496cf8c1190f8c25324756f65bdf29f0 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Thu, 21 Sep 2023 23:12:26 +0200 Subject: [PATCH 121/209] Disable webview dev buttons --- src/frontend/ui/tiles/WebView.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/ui/tiles/WebView.svelte b/src/frontend/ui/tiles/WebView.svelte index 976e4df5..b9cd3c36 100644 --- a/src/frontend/ui/tiles/WebView.svelte +++ b/src/frontend/ui/tiles/WebView.svelte @@ -64,10 +64,10 @@
{#await asyncSrc then src} {#if src !== null} -
+ From d2d378d97458939286837568b9e7b45002dafe92 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Fri, 22 Sep 2023 00:20:07 +0200 Subject: [PATCH 122/209] Fix cache system race condition for write() and in webview preload --- src/electron/lib/cache/CacheManager.ts | 52 ++++++------ src/electron/lib/webviews/preload.ts | 92 +++++++++++++++++---- src/frontend/lib/stores/CacheStore.ts | 108 ++++++++++++++++--------- src/shared/types/cache.ts | 16 ++-- 4 files changed, 185 insertions(+), 83 deletions(-) diff --git a/src/electron/lib/cache/CacheManager.ts b/src/electron/lib/cache/CacheManager.ts index c2e1f1aa..1a83763c 100644 --- a/src/electron/lib/cache/CacheManager.ts +++ b/src/electron/lib/cache/CacheManager.ts @@ -1,12 +1,14 @@ // Blix caching system primarily for large binary objects -import type { - SubsidiaryUUID, - CacheUUID, - CacheSubsidiary, - CacheObject, - CacheRequest, - CacheUpdateNotification, - CacheWriteResponse, +import { + type SubsidiaryUUID, + type CacheUUID, + type CacheSubsidiary, + type CacheObject, + type CacheRequest, + type CacheUpdateNotification, + type CacheWriteResponse, + type CacheResponse, + CACHE_MESSAGE_ID_SIZE, } from "../../../shared/types/cache"; import WebSocket from "ws"; @@ -63,27 +65,26 @@ export class CacheManager { case "cache-write-metadata": this.writeMetadata(data.id, data.metadata); this.notifyListeners(); + + socket.send( + JSON.stringify({ success: true, messageId: data.messageId } as CacheResponse) + ); break; case "cache-get": - // TODO: check if exist const messageId = data.messageId; - if (messageId != null) { - // Send payload on this message id - socket.send( - Buffer.concat([ - Buffer.from(messageId), - this.cache[data.id]?.data ?? Buffer.from([]), - ]) - ); - } else { - // Send anonymous payload - socket.send(this.cache[data.id]?.data ?? null); - } + socket.send( + Buffer.concat([ + Buffer.from(messageId), + this.cache[data.id]?.data ?? Buffer.from([]), + ]) + ); break; case "cache-delete": this.delete(data.id); // TODO: check if exist - socket.send(JSON.stringify({ success: true })); + socket.send( + JSON.stringify({ success: true, messageId: data.messageId } as CacheResponse) + ); this.notifyListeners(); break; @@ -95,8 +96,11 @@ export class CacheManager { logger.info("Unknown cache message type", data.type); } } else if (event.data instanceof Buffer) { - const id = this.writeContent(event.data); - socket.send(JSON.stringify({ success: true, id } as CacheWriteResponse)); + const messageId = event.data.subarray(0, CACHE_MESSAGE_ID_SIZE).toString("ascii"); + const data = event.data.subarray(CACHE_MESSAGE_ID_SIZE); + + const id = this.writeContent(data); + socket.send(JSON.stringify({ success: true, id, messageId } as CacheWriteResponse)); // Send cache update to all listeners this.notifyListeners(); diff --git a/src/electron/lib/webviews/preload.ts b/src/electron/lib/webviews/preload.ts index 630950ab..9a5da9a2 100644 --- a/src/electron/lib/webviews/preload.ts +++ b/src/electron/lib/webviews/preload.ts @@ -1,21 +1,60 @@ // This is the preload script for the webviews. -import { CacheMetadata, CacheWriteResponse } from "@shared/types/cache"; +import { + CacheMetadata, + CacheRequest, + CacheResponse, + CacheWriteResponse, +} from "@shared/types/cache"; // It exposes some IPC functions so the webview can communicate with its parent renderer. const { ipcRenderer, contextBridge } = require("electron"); +const MESSAGE_ID_SIZE = 32; // bytes +// String of MESSAGE_ID_SIZE random bytes in hex +const randomMessageId = () => { + return Array.from({ length: MESSAGE_ID_SIZE / 2 }, () => + Math.floor(Math.random() * 256) + .toString(16) + .padStart(2, "0") + ).join(""); +}; + const ws = new WebSocket("ws://localhost:60606"); ws.binaryType = "blob"; -function sendAndRecieveData(payload: string | Blob): Promise { +const lobby: { [key: string]: (value: Blob | any) => void } = {}; + +ws.onmessage = (event) => { + if (typeof event.data === "string") { + const payload = JSON.parse(event.data) as CacheResponse; + const messageId = payload.messageId; + + if (lobby[messageId] != null) { + lobby[messageId](payload); + delete lobby[messageId]; + } + } else if (event.data instanceof Blob) { + // Check if there's a listener in the lobby + event.data + .slice(0, MESSAGE_ID_SIZE) + .text() + .then((messageId) => { + if (lobby[messageId] != null) { + // Listener found, resolve promise + const data = event.data.slice(MESSAGE_ID_SIZE); + lobby[messageId](data); + delete lobby[messageId]; + } + }); + } +}; + +// Low-level RPC wrapper for sending/receiving data with CacheManager +async function sendCachePayload(messageId: string, payload: Blob | string) { return new Promise((resolve, reject) => { + lobby[messageId] = resolve; ws.send(payload); - const recv = (event: MessageEvent) => { - ws.removeEventListener("message", recv); // Remove this listener - resolve(event.data); - }; - ws.addEventListener("message", recv); }); } @@ -29,18 +68,39 @@ contextBridge.exposeInMainWorld("api", { }); contextBridge.exposeInMainWorld("cache", { - write: async (content: Blob, metadata: CacheMetadata) => { - const response: string = await sendAndRecieveData(new Blob([content])); - const data = JSON.parse(response) as CacheWriteResponse; - ws.send(JSON.stringify({ type: "cache-write-metadata", id: data.id, metadata })); - return data; + write: async (content: Blob, metadata?: CacheMetadata) => { + // Write cache object + const messageId = randomMessageId(); + const payload = new Blob([messageId, content]); + const writeResp = (await sendCachePayload(messageId, payload)) as CacheWriteResponse; + + if (!writeResp.success) return null; + + if (metadata != null) { + // Write cache metadata + const metadataMessageId = randomMessageId(); + const metadataPayload = JSON.stringify({ + type: "cache-write-metadata", + id: writeResp.id, + messageId, + metadata, + } as CacheRequest); + const metadataResp = (await sendCachePayload( + metadataMessageId, + metadataPayload + )) as CacheResponse; + } + + return writeResp.id; }, get: async (id: string) => { - const results = await sendAndRecieveData(JSON.stringify({ type: "cache-get", id })); - return results; + const messageId = randomMessageId(); + const payload = JSON.stringify({ type: "cache-get", id, messageId } as CacheRequest); + return sendCachePayload(messageId, payload); }, delete: async (id: string) => { - const results: string = await sendAndRecieveData(JSON.stringify({ type: "cache-delete", id })); - return JSON.parse(results); + const messageId = randomMessageId(); + const payload = JSON.stringify({ type: "cache-delete", id, messageId } as CacheRequest); + return sendCachePayload(messageId, payload); }, }); diff --git a/src/frontend/lib/stores/CacheStore.ts b/src/frontend/lib/stores/CacheStore.ts index fae1333a..9c2aa56d 100644 --- a/src/frontend/lib/stores/CacheStore.ts +++ b/src/frontend/lib/stores/CacheStore.ts @@ -1,13 +1,15 @@ -import type { - SubsidiaryUUID, - CacheUUID, - CacheObject, - CacheUpdateNotification, - CacheMetadata, - CacheRequest, - CacheWriteResponse, +import { + type SubsidiaryUUID, + type CacheUUID, + type CacheObject, + type CacheUpdateNotification, + type CacheMetadata, + type CacheRequest, + type CacheWriteResponse, + CACHE_MESSAGE_ID_SIZE, + type CacheResponse, } from "@shared/types/cache"; -import { derived, get, writable } from "svelte/store"; +import { derived, get, writable, type Writable } from "svelte/store"; // type CacheObjects = { // [key: CacheUUID]: CacheObject; @@ -16,22 +18,24 @@ import { derived, get, writable } from "svelte/store"; type CacheObjects = { [key: CacheUUID]: CacheMetadata }; // type CacheObjects = CacheUUID[]; -const MESSAGE_ID_SIZE = 32; // bytes - // String of MESSAGE_ID_SIZE random bytes in hex const randomMessageId = () => { - return Array.from({ length: MESSAGE_ID_SIZE / 2 }, () => + return Array.from({ length: CACHE_MESSAGE_ID_SIZE / 2 }, () => Math.floor(Math.random() * 256) .toString(16) .padStart(2, "0") ).join(""); }; +function isUpdateNotification(payload: any): payload is CacheUpdateNotification { + return (payload as { type: string }).type === "cache-update"; +} + class CacheStore { private cacheStore = writable({}); // private globalCache: { [key: CacheUUID]: SubsidiaryUUID } = {}; private ws: WebSocket; - private lobby: { [key: string]: (value: Blob) => void }; + private lobby: { [key: string]: (value: Blob | any) => void }; constructor() { this.ws = new WebSocket("ws://localhost:60606"); @@ -42,28 +46,34 @@ class CacheStore { this.ws.onmessage = (event) => { if (typeof event.data === "string") { - const data: CacheUpdateNotification = JSON.parse(event.data) as CacheUpdateNotification; - - switch (data.type) { - case "cache-update": - this.cacheStore.update((store) => { - for (const obj of data.cache) { - store[obj.uuid] = obj.metadata; - } - - return store; - }); - break; + const payload = JSON.parse(event.data) as CacheResponse; + + if (isUpdateNotification(payload)) { + // Handle notification + this.cacheStore.update((store) => { + for (const obj of payload.cache) { + store[obj.uuid] = obj.metadata; + } + return store; + }); + } else { + // Handle response + const messageId = payload.messageId; + + if (this.lobby[messageId] != null) { + this.lobby[messageId](payload); + delete this.lobby[messageId]; + } } } else if (event.data instanceof Blob) { // Check if there's a listener in the lobby event.data - .slice(0, MESSAGE_ID_SIZE) + .slice(0, CACHE_MESSAGE_ID_SIZE) .text() .then((messageId) => { if (this.lobby[messageId] != null) { // Listener found, resolve promise - const payload = (event.data as Blob).slice(MESSAGE_ID_SIZE); + const payload = (event.data as Blob).slice(CACHE_MESSAGE_ID_SIZE); this.lobby[messageId](payload); delete this.lobby[messageId]; } @@ -74,23 +84,45 @@ class CacheStore { }; } - public addCacheObject(blob: Blob, metadata?: CacheMetadata) { + // Low-level RPC wrapper for sending/receiving data with CacheManager + async sendCachePayload(messageId: string, payload: Blob | string) { + return new Promise((resolve, reject) => { + this.lobby[messageId] = resolve; + this.ws.send(payload); + }); + } + + public async addCacheObject(blob: Blob, metadata?: CacheMetadata) { + // this.ws.send(blob); + // if (metadata != null) { + // const data = JSON.parse(event.data) as CacheWriteResponse; + // this.ws.send(JSON.stringify({ type: "cache-write-metadata", id: data.id, metadata }) as CacheRequest); + // } + + // Write cache object + const messageId = randomMessageId(); + const payload = new Blob([messageId, blob]); + const writeResp = (await this.sendCachePayload(messageId, payload)) as CacheWriteResponse; + + if (!writeResp.success) return null; + if (metadata != null) { - const recv = (event: MessageEvent) => { - this.ws.removeEventListener("message", recv); - if (typeof event.data === "string") { - const data = JSON.parse(event.data) as CacheWriteResponse; - this.ws.send(JSON.stringify({ type: "cache-write-metadata", id: data.id, metadata })); - } - }; - this.ws.addEventListener("message", recv); + // Write cache metadata + const metadataMessageId = randomMessageId(); + const metadataPayload = JSON.stringify({ + type: "cache-write-metadata", + id: writeResp.id, + messageId, + metadata, + } as CacheRequest); + (await this.sendCachePayload(metadataMessageId, metadataPayload)) as CacheResponse; } - this.ws.send(blob); + + return writeResp.id; } public async get(uuid: CacheUUID): Promise { const messageId = randomMessageId(); - const messageIdBlob = new Blob([messageId]); const payload = JSON.stringify({ type: "cache-get", id: uuid, messageId } as CacheRequest); diff --git a/src/shared/types/cache.ts b/src/shared/types/cache.ts index 674e620b..13d58e74 100644 --- a/src/shared/types/cache.ts +++ b/src/shared/types/cache.ts @@ -1,5 +1,7 @@ import { type UUID } from "../../shared/utils/UniqueEntity"; +export const CACHE_MESSAGE_ID_SIZE = 32; // bytes + export enum CacheSubsidiaryType { Manager, Frontend, @@ -23,18 +25,22 @@ export type CacheObject = { metadata: CacheMetadata; }; +type CacheRequestType = "cache-subscribe" | "cache-delete" | "cache-get" | "cache-write-metadata"; + export type CacheRequest = { - type: string; + type: CacheRequestType; id: string; - messageId?: string; + messageId: string; metadata?: CacheMetadata; }; -export type CacheWriteResponse = { +export type CacheResponse = { success: boolean; - id: CacheUUID; + messageId: string; }; +export type CacheWriteResponse = CacheResponse & { id: CacheUUID }; + export type CacheMetadata = { contentType: string; name?: string; @@ -42,6 +48,6 @@ export type CacheMetadata = { }; export type CacheUpdateNotification = { - type: string; + type: "cache-update"; cache: { uuid: string; metadata: CacheMetadata }[]; }; From 947a1a00ee44f3c398010c35356a578075e28c85 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Fri, 22 Sep 2023 01:08:28 +0200 Subject: [PATCH 123/209] Skeleton PluginBrowser UI + add markdown rendering --- package-lock.json | 13 ++ package.json | 1 + src/electron/lib/api/apis/UtilApi.ts | 5 + src/frontend/lib/stores/ShortcutStore.ts | 1 + src/frontend/ui/base/Markdown.svelte | 75 +++++++++++ src/frontend/ui/base/PluginBrowser.svelte | 123 ++++++++++++++++++ src/frontend/ui/base/Settings.svelte | 12 +- .../graph/nodeUICcomponents/TextInput.svelte | 4 +- 8 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 src/frontend/ui/base/Markdown.svelte create mode 100644 src/frontend/ui/base/PluginBrowser.svelte diff --git a/package-lock.json b/package-lock.json index ac0ac8e8..2a41a3e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "lint-staged": "^13.2.2", + "marked": "^9.0.3", "nodemon": "^2.0.22", "npm-run-all": "^4.1.5", "playwright": "^1.36.0", @@ -11753,6 +11754,18 @@ "tmpl": "1.0.5" } }, + "node_modules/marked": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.0.3.tgz", + "integrity": "sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", diff --git a/package.json b/package.json index 7e620111..9bf044d3 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "lint-staged": "^13.2.2", + "marked": "^9.0.3", "nodemon": "^2.0.22", "npm-run-all": "^4.1.5", "playwright": "^1.36.0", diff --git a/src/electron/lib/api/apis/UtilApi.ts b/src/electron/lib/api/apis/UtilApi.ts index 39313be2..d1035374 100644 --- a/src/electron/lib/api/apis/UtilApi.ts +++ b/src/electron/lib/api/apis/UtilApi.ts @@ -75,6 +75,11 @@ export class UtilApi implements ElectronMainApi { // const secrets = getSecrets(); const userSettings: UserSettingsCategory[] = [ + { + id: "plugin_browser", + title: "Plugin Browser", + settings: [], + }, { id: "ai_settings", title: "AI Settings", diff --git a/src/frontend/lib/stores/ShortcutStore.ts b/src/frontend/lib/stores/ShortcutStore.ts index d40f8301..2d491ec1 100644 --- a/src/frontend/lib/stores/ShortcutStore.ts +++ b/src/frontend/lib/stores/ShortcutStore.ts @@ -120,6 +120,7 @@ class ShortcutStore { "blix.settings.hide": ["[Escape]"], "blix.splash.hide": ["[Escape]"], "blix.projects.save": ["ctrl+[KeyS]", "meta+[KeyS]"], + "blix.pluginBrowser.deselectPlugin": ["[Escape]"], }); public addActionShortcut(action: ShortcutAction, combo: ShortcutCombo) { diff --git a/src/frontend/ui/base/Markdown.svelte b/src/frontend/ui/base/Markdown.svelte new file mode 100644 index 00000000..bc2c53dd --- /dev/null +++ b/src/frontend/ui/base/Markdown.svelte @@ -0,0 +1,75 @@ + + +
{@html marked(markdown)}
+ + diff --git a/src/frontend/ui/base/PluginBrowser.svelte b/src/frontend/ui/base/PluginBrowser.svelte new file mode 100644 index 00000000..9165a7bc --- /dev/null +++ b/src/frontend/ui/base/PluginBrowser.svelte @@ -0,0 +1,123 @@ + + +
+ +
+ {#if selectedPlugin != null} + {@const plugin = plugins[selectedPlugin]} + +
+ +
+ {/if} +
+
+ + + + diff --git a/src/frontend/ui/base/Settings.svelte b/src/frontend/ui/base/Settings.svelte index 7ec26532..4a0e2807 100644 --- a/src/frontend/ui/base/Settings.svelte +++ b/src/frontend/ui/base/Settings.svelte @@ -5,6 +5,7 @@ import { toastStore } from "./../../lib/stores/ToastStore"; import { settingsStore } from "../../lib/stores/SettingsStore"; import ShortcutSettings from "../../ui/tiles/ShortcutSettings.svelte"; + import PluginBrowser from "./PluginBrowser.svelte"; let selectedCategoryId = ""; let selectedCategory: UserSettingsCategory | undefined; @@ -80,11 +81,15 @@
- {#if selectedCategory?.id === "ai_settings"} + {#if selectedCategory?.id === "plugin_browser"} + + + {:else if selectedCategory?.id === "ai_settings"} +
API Keys
- Rest assured that none of your API keys get stored remotely. Your information is encrypted - and maintained securely and solely within the confines of your own device. + None of your API keys get stored remotely. Your information is encrypted and maintained + securely and solely within the confines of your own device.
{#each selectedCategory.settings as item (item.id)}
@@ -111,6 +116,7 @@ Save
{:else if selectedCategory?.id === "keybind_settings"} + {#each selectedCategory.settings as item (item.id)}
-
+
{/each}
{#if selectedPlugin != null} - {@const plugin = plugins[selectedPlugin]} + {@const id = selectedPlugin} + {@const plugin = plugins[id]}
@@ -92,18 +139,31 @@ .pluginItem { display: grid; - grid-template-rows: 1.4em 0.8em; + grid-template-columns: 3em auto; color: white; padding: 0.4em; + height: 4em; - .title { - font-size: 1em; - font-weight: bold; + .icon { + height: 100%; + width: 100%; + border: 1px solid magenta; } - .id { - font-size: 0.6em; - padding-left: 0.4em; + .details { + display: grid; + grid-template-rows: 1.4em 0.8em; + padding: 0.4em; + + .title { + font-size: 1em; + font-weight: bold; + } + + .info { + font-size: 0.6em; + padding-left: 0.4em; + } } } @@ -115,6 +175,30 @@ .banner { border: 1px solid green; + padding: 1em; + padding-top: 1.6em; + + .title { + font-size: 1.6em; + font-weight: bold; + line-height: 0.9em; + } + + .id { + font-size: 0.8em; + margin-left: 1em; + font-style: italic; + } + + .buttons { + margin-top: 0.8em; + font-size: 0.8em; + // margin-left: 1em; + + button { + padding: 0px 0.2em; + } + } } .readmeBox { From 1a052c82861ea643a603605bbbbb17651ef64a18 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Fri, 22 Sep 2023 20:34:23 +0200 Subject: [PATCH 125/209] Minor PanelNode coverage fix --- src/frontend/lib/PanelNode.ts | 33 ++++++++++++++- tests/unit-tests/frontend/PanelNode.spec.ts | 47 +++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/unit-tests/frontend/PanelNode.spec.ts diff --git a/src/frontend/lib/PanelNode.ts b/src/frontend/lib/PanelNode.ts index 16d93463..1aeb283b 100644 --- a/src/frontend/lib/PanelNode.ts +++ b/src/frontend/lib/PanelNode.ts @@ -2,7 +2,7 @@ import type { LayoutPanel, PanelType } from "../../shared/types/index"; import { writable } from "svelte/store"; - +/* eslint-disable no-console */ export abstract class PanelNode { static panelCounter = 0; @@ -43,8 +43,15 @@ export class PanelGroup extends PanelNode { name: string; panels: PanelNode[] = []; + /** + * Removes a panel to from the panelgroup at index i. Existing panelGroup will be pruned if only one panel exists. + * @param i The index to remove the panel from + * @returns void + * */ + // Nukes the current panelgroup and replaces it with its first child if possible removePanel(i: number) { + if (i > this.panels.length) console.warn("PanelGroup.removePanel: Index out of bounds : ", i); this.panels.splice(i, 1); // If length 1, dissolve and replace with child @@ -61,21 +68,43 @@ export class PanelGroup extends PanelNode { } this.updateParent(this); } + + /** + * Replaces a panel at the designated index in the panelgroup. + * @param panel The panel to be used to replace the existing panel + * @param i The index of the panel to be replaced + * @returns void + * */ + setPanel(panel: PanelNode, i: number) { + if (i > this.panels.length) { + console.warn("PanelGroup.setPanel: Index out of bounds : ", i); + return; + } const tempId = this.panels[i].id; this.panels[i] = panel; panel.parent = this; panel.index = i; panel.id = tempId; } + + /** + * Inserts a panel to the panelgroup at index i + * @param content The type of the panel to be added + * @param i The index to place the panel at + * @returns void + * */ + addPanel(content: PanelType, i: number) { const newLeaf = new PanelLeaf(content); + if (i > this.panels.length) console.warn("PanelGroup.addPanel: Index out of bounds : ", i); this.panels.splice(i, 0, newLeaf); newLeaf.parent = this; newLeaf.index = i; this.updateParent(this); } + addPanelGroup(panelGroup: PanelGroup, i: number) { this.panels.splice(i, 0, panelGroup); panelGroup.parent = this; @@ -165,3 +194,5 @@ class FocusedPanelStore { } export const focusedPanelStore = new FocusedPanelStore(); + +/* eslint-enable no-console */ diff --git a/tests/unit-tests/frontend/PanelNode.spec.ts b/tests/unit-tests/frontend/PanelNode.spec.ts new file mode 100644 index 00000000..b9eca015 --- /dev/null +++ b/tests/unit-tests/frontend/PanelNode.spec.ts @@ -0,0 +1,47 @@ +import exp from "constants"; +import { PanelGroup, PanelNode } from "../../../src/frontend/lib/PanelNode"; +import { Pane } from "svelte-splitpanes"; +import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands"; + + + + + describe("Test PanelGroup", () => { + let panelNode : PanelGroup; + + beforeEach(() => { + PanelGroup.groupCounter = 0; + PanelNode.panelCounter = 0; + panelNode = new PanelGroup("Unique id"); + + }); + + // This file only consists of constructors, so we only test those + + test("Test constructor", () => { + expect(panelNode.id).toBe(0); + expect(panelNode.panels).toEqual([]); + expect(panelNode.parent).toBe(null); + expect(panelNode.index).toBe(-1); + + panelNode = new PanelGroup("Hello",3); + expect(panelNode.id).toBe(3); + + + panelNode = new PanelGroup(); + expect(panelNode.name).toBe("pg_2") + }) + + test("Adding panels", () => { + panelNode.addPanel("media",0); + + + expect(panelNode.panels.length).toBe(1); + expect(panelNode.panels[0].parent).toBe(panelNode); + expect(panelNode.panels[0].index).toBe(0); + expect(panelNode.panels[0].id).toBe(1); + + + }) + + }); \ No newline at end of file From 1702b6567647d194dd8fb9226a9c418bca67b8a1 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Fri, 22 Sep 2023 22:17:27 +0200 Subject: [PATCH 126/209] Restyle 'no content' message in media tile --- src/electron/lib/plugins/Plugin.ts | 2 +- src/frontend/ui/tiles/Media.svelte | 99 +++++++++++-------- .../ui/utils/graph/NodeUIComponent.svelte | 26 ++--- .../Buffer.svelte | 0 .../Button.svelte | 0 .../CachePicker.svelte | 0 .../ColorPicker.svelte | 0 .../ColorPicker/ColorPickerTextInput.svelte | 0 .../ColorPicker/ColorPickerWrapper.svelte | 0 .../Dropdown.svelte | 0 .../FilePicker.svelte | 0 .../Knob.svelte | 0 .../NumberInput.svelte | 0 .../Radio.svelte | 0 .../Slider.svelte | 0 .../TextInput.svelte | 0 .../dials/Dial.svelte | 0 .../dials/DiffDial.svelte | 0 .../dials/TweakDial.svelte | 0 19 files changed, 74 insertions(+), 53 deletions(-) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Buffer.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Button.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/CachePicker.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/ColorPicker.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/ColorPicker/ColorPickerTextInput.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/ColorPicker/ColorPickerWrapper.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Dropdown.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/FilePicker.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Knob.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/NumberInput.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Radio.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/Slider.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/TextInput.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/dials/Dial.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/dials/DiffDial.svelte (100%) rename src/frontend/ui/utils/graph/{nodeUICcomponents => nodeUIComponents}/dials/TweakDial.svelte (100%) diff --git a/src/electron/lib/plugins/Plugin.ts b/src/electron/lib/plugins/Plugin.ts index 8eb22ad4..1fcc041a 100644 --- a/src/electron/lib/plugins/Plugin.ts +++ b/src/electron/lib/plugins/Plugin.ts @@ -51,7 +51,7 @@ export class Plugin { requireSelf(blix: Blix, force = false): void { try { // This uses Node.js require() to load the plugin as a module - // TODO: ISOLATION + LIMITED API + // TODO: ISOLATION // @ts-ignore: no-var-requires // We need to clear the local node cache so that the plugin can be reloaded diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index c3bf0b8f..8384ea76 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -12,6 +12,36 @@ import { type SelectionBoxItem } from "../../types/selection-box"; import WebView from "./WebView.svelte"; import { TweakApi } from "lib/webview/TweakApi"; + import { + faBacon, + faBowlRice, + faBurger, + faCandyCane, + faCarrot, + faCoffee, + faCookieBite, + faFish, + faHotdog, + faIceCream, + faLemon, + faPizzaSlice, + } from "@fortawesome/free-solid-svg-icons"; + import Fa from "svelte-fa"; + + const noContentIcons = [ + faBacon, + faBowlRice, + faBurger, + faCandyCane, + faCarrot, + faCoffee, + faCookieBite, + faFish, + faHotdog, + faIceCream, + faLemon, + faPizzaSlice, + ]; const mediaOutputIds = mediaStore.getMediaOutputIdsReactive(); @@ -50,15 +80,6 @@ unsubMedia(); }); - // function handleSelect(e: Event) { - // return; - // const value = (e.target as HTMLSelectElement).value; - // if (!value) return; - - // const [graphUUID, nodeUUID] = value.split("/"); - // selectedNode = { graphUUID, outNode: nodeUUID }; - // } - async function exportMedia(e: Event) { if ($media?.dataType && $media?.content) { await mediaStore.exportMedia($media); @@ -75,26 +96,9 @@ return res; } - // async function getDisplay(id: TypeclassId) { - // const value = null; - // return await window.apis.typeclassApi.getMediaDisplay(id, value); - // } - - // type MediaDisplay = { - // component: any; - // props: (data: any) => { [key: string]: any }; - // }; - // const dataTypeToMediaDisplay: { [key: string]: MediaDisplay } = { - // [""]: { component: TextBox, props: (_data: any) => ({ content: "NO INPUT", status: "warning" }), }, - // Image: { component: Image, props: (data: string) => ({ src: data }), }, - // Number: { component: TextBox, props: (data: number) => ({ content: data?.toString() || "NULL", status: data == null ? "warning" : "normal", fontSize: "large", }), - // }, boolean: { component: TextBox, props: (data: number) => ({ content: data?.toString() || "NULL", status: data == null ? "warning" : "normal", fontSize: "large", }), }, - // string: { component: TextBox, props: (data: string) => ({ content: data }), }, - // color: { component: ColorDisplay, props: (data: string) => ({ color: data }), }, - // Error: { component: TextBox, props: (data: string) => ({ content: data, status: "error" }), }, - // ["GLFX image"]: { component: WebView, props: (data: string) => ({ media: data }), }, - // ["Pixi image"]: { component: WebView, props: (data: string) => ({ media: data }), }, - // }; + function getNoContentIcon() { + return noContentIcons[Math.floor(Math.random() * noContentIcons.length)]; + } const displayIdToSvelteConstructor: { [key in MediaDisplayType]: any } = { image: Image, @@ -151,13 +155,17 @@ /> --> {:else} -
NO CONTENT
+
+
+

No content!

+

Add an Output node to the graph to create a media output

+
{/if}
- diff --git a/src/frontend/ui/utils/graph/NodeUIComponent.svelte b/src/frontend/ui/utils/graph/NodeUIComponent.svelte index 3007b245..5c2cec41 100644 --- a/src/frontend/ui/utils/graph/NodeUIComponent.svelte +++ b/src/frontend/ui/utils/graph/NodeUIComponent.svelte @@ -1,19 +1,19 @@
- +
diff --git a/blix-plugins/blink/webview/app.ts b/blix-plugins/blink/webview/app.ts index 2d235077..b0ac35b1 100644 --- a/blix-plugins/blink/webview/app.ts +++ b/blix-plugins/blink/webview/app.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import App from './App.svelte'; -import { BlinkCanvas } from './types'; +import { type BlinkCanvas } from './types'; const media = writable({ assets: {}, diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index f0d51cd2..b296f83d 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -1,7 +1,7 @@ import * as PIXI from 'pixi.js'; -import { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; +import type { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; import { diffAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; -import { HierarchyAtom, randomId } from './render'; +import { type HierarchyAtom, randomId } from './render'; export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined): { pixiAtom: PIXI.Container, diff --git a/blix-plugins/blink/webview/diff.ts b/blix-plugins/blink/webview/diff.ts index 49dd403d..5728d2f0 100644 --- a/blix-plugins/blink/webview/diff.ts +++ b/blix-plugins/blink/webview/diff.ts @@ -1,5 +1,5 @@ -import { HierarchyAtom, HierarchyClump } from "./render"; -import { +import type { HierarchyAtom, HierarchyClump } from "./render"; +import type { Asset, Atom, BlobAtom, diff --git a/blix-plugins/blink/webview/filter.ts b/blix-plugins/blink/webview/filter.ts index 5e7aa3fb..a98ba57f 100644 --- a/blix-plugins/blink/webview/filter.ts +++ b/blix-plugins/blink/webview/filter.ts @@ -1,5 +1,5 @@ import * as PIXI from 'pixi.js'; -import { Filter, getPixiFilter } from './types'; +import { type Filter, getPixiFilter } from './types'; import { randomId } from './render'; // Apply a series of filters and flatten the result to a sprite. diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 0382213c..5eab534f 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -4,9 +4,6 @@ import { type Atom, type Clump, type BlinkCanvas, - type Asset, - Filter, - Transform, } from "./types"; import { Viewport } from "pixi-viewport"; import { createBoundingBox } from "./select"; @@ -46,10 +43,10 @@ export function renderApp( canvas: BlinkCanvas, viewport: Viewport, send: (message: string, data: any) => void -): boolean { +): { success: boolean; scene: PIXI.Container } { console.log("===================================="); - if (!canvas || !canvas.content) return false; + if (!canvas || !canvas.content) return { success: false, scene: null }; if (!scene) { scene = new PIXI.Container(); scene.name = "Blink Scene"; @@ -107,7 +104,7 @@ export function renderApp( } }); - return true; + return { success: true, scene }; } export function renderCanvas(blink: PIXI.Application, root: Clump) {} diff --git a/blix-plugins/blink/webview/select.ts b/blix-plugins/blink/webview/select.ts index cf16b39a..858560c9 100644 --- a/blix-plugins/blink/webview/select.ts +++ b/blix-plugins/blink/webview/select.ts @@ -1,4 +1,4 @@ -import { Viewport } from "pixi-viewport"; +import { type Viewport } from "pixi-viewport"; import * as PIXI from "pixi.js"; const BOX_CORNER_DOT_SIZE = 5; diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index 581b123b..6570fd9d 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -1,5 +1,4 @@ import * as PIXI from "pixi.js"; -import { Matrix } from "pixi.js"; import { KawaseBlurFilter, BloomFilter, diff --git a/src/electron/lib/api/apis/PluginApi.ts b/src/electron/lib/api/apis/PluginApi.ts index 7ec10eac..986acd28 100644 --- a/src/electron/lib/api/apis/PluginApi.ts +++ b/src/electron/lib/api/apis/PluginApi.ts @@ -12,13 +12,6 @@ export class PluginApi implements ElectronMainApi { this.blix = blix; } - async enablePlugin(plugin: PluginSignature) { - // this.blix.commandRegistry.addInstance(instance); - } - async disablePlugin(plugin: PluginSignature) { - // this.blix.commandRegistry.addInstance(instance); - } - async installPlugin(url: URL) { // return await this.blix.commandRegistry.runCommand(id, params); } @@ -27,6 +20,13 @@ export class PluginApi implements ElectronMainApi { // return await this.blix.commandRegistry.runCommand(id, params); } + async enablePlugin(plugin: PluginSignature) { + // this.blix.commandRegistry.addInstance(instance); + } + async disablePlugin(plugin: PluginSignature) { + // this.blix.commandRegistry.addInstance(instance); + } + async getInstalledPlugins() { return this.blix.pluginManager.pluginPaths; } From 1cda9f7acede00d51fb0b0d3621b578e9f755841 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Sat, 23 Sep 2023 00:11:53 +0200 Subject: [PATCH 129/209] Fix Blink cache image export function typo --- blix-plugins/blink/webview/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index 1dd90c5f..a4c24993 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -164,7 +164,7 @@ async function exportImage() { if (!currScene || !canvasBlock) return; - const exportCanvas = blink.renderer.extract.canvas(currScene, canvasBlock.getBounds()); + const exportCanvas = blink.renderer.extract.canvas(currScene, canvasBlock.getLocalBounds()); exportCanvas.toBlob((blob) => { const metadata = { contentType: "image/png", From e557a2bab82d1512607f3798f24882a0ea35a640 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Sat, 23 Sep 2023 02:04:19 +0200 Subject: [PATCH 130/209] Migrate Blink inputImage to cache system; Fix incorrect bounding rectangle on Blink image export --- blix-plugins/blink/src/main.cjs | 8 +++----- blix-plugins/blink/webview/App.svelte | 23 ++++++++++++++++------- blix-plugins/blink/webview/atom.ts | 4 ++-- blix-plugins/blink/webview/render.ts | 23 ++++++++++++++++++++--- src/frontend/lib/Project.ts | 9 ++++++++- 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 87574b1a..03e02fb0 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -195,7 +195,7 @@ const nodes = { nodeBuilder.setDescription("Input a Blink Sprite Image"); const ui = nodeBuilder.createUIBuilder(); - ui.addFilePicker({ + ui.addCachePicker({ componentId: "imagePicker", label: "Pick an image", defaultValue: "", @@ -220,16 +220,14 @@ const nodes = { }); nodeBuilder.define(async (input, uiInput, from) => { - let src = uiInput["imagePicker"].split("/"); - src = src.splice(-2); - src = src.join("/"); + let src = uiInput["imagePicker"]; const canvas = { assets: { [uiInput["state"]["id"]]: { class: "asset", type: "image", - data: uiInput["imagePicker"].split("/").splice(-2).join("/"), + data: uiInput["imagePicker"] // data: uiInput["cachePicker"], } }, diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index a4c24993..d0fb5007 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -3,19 +3,24 @@ import { Viewport } from "pixi-viewport"; import { onDestroy, onMount, tick } from "svelte"; import { type Writable } from "svelte/store"; - import { renderApp } from "./render"; + import { renderScene } from "./render"; import { type BlinkCanvas } from "./types"; import Debug from "./Debug.svelte"; export let media: Writable; export let send: (msg: string, data: any) => void; + let imgCanvasInitialPadding = 100; + let imgCanvasBlockW = 1920; + let imgCanvasBlockH = 1080; + let blink: PIXI.Application; let pixiCanvas: HTMLCanvasElement; let mouseCursor = "cursorDefault"; let canvasBlock: PIXI.Graphics; let currScene: PIXI.Container; + let viewport: Viewport; onMount(async () => { // window.addEventListener("DOMContentLoaded", async () => { @@ -46,7 +51,7 @@ } //====== CREATE VIEWPORT ======// - const viewport = new Viewport({ + viewport = new Viewport({ screenWidth: pixiCanvas.width, screenHeight: pixiCanvas.height, worldWidth: 100, @@ -81,9 +86,9 @@ }); //===== CREATE BASE LAYOUT =====// - const imgCanvasInitialPadding = 100; - const imgCanvasBlockW = 1920; - const imgCanvasBlockH = 1080; + imgCanvasInitialPadding = 100; + imgCanvasBlockW = 1920; + imgCanvasBlockH = 1080; let imgCanvas = new PIXI.Container(); imgCanvas.name = "imgCanvas"; @@ -120,7 +125,7 @@ //===== RENDER Blink =====// let hasCentered = false; media.subscribe(async (media) => { - const { success, scene } = renderApp(blink, media, viewport, send); + const { success, scene } = renderScene(blink, media, viewport, send); currScene = scene; // Necessary to fix an occasional race condition with PIXI failing to load @@ -164,7 +169,10 @@ async function exportImage() { if (!currScene || !canvasBlock) return; - const exportCanvas = blink.renderer.extract.canvas(currScene, canvasBlock.getLocalBounds()); + const bounds = currScene.getLocalBounds(); + const frame = new PIXI.Rectangle(-bounds.x-imgCanvasBlockW/2, -bounds.y-imgCanvasBlockH/2, imgCanvasBlockW, imgCanvasBlockH); + + const exportCanvas = blink.renderer.extract.canvas(currScene, frame); exportCanvas.toBlob((blob) => { const metadata = { contentType: "image/png", @@ -172,6 +180,7 @@ }; window.cache.write(blob, metadata); }, "image/png"); + // REMOVED: Exporting straight to local file // const link = document.createElement("a"); // link.download = "export.png"; // link.href = exportCanvas.toDataURL("image/png", 1.0); diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index b296f83d..4ab29ab8 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -3,7 +3,7 @@ import type { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./t import { diffAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; import { type HierarchyAtom, randomId } from './render'; -export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined): { +export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined, textures: { [key: string]: PIXI.Texture }): { pixiAtom: PIXI.Container, changed: boolean // Whether the pixiClump is a different PIXI object than before } { @@ -20,7 +20,7 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: } if (assets[atom.assetId] && assets[atom.assetId].type === "image") { - const sprite = PIXI.Sprite.from(assets[atom.assetId].data); + const sprite = PIXI.Sprite.from(textures[assets[atom.assetId].data]); sprite.anchor.x = 0.5; sprite.anchor.y = 0.5; diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 5eab534f..d4859ff8 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -38,7 +38,9 @@ let scene: PIXI.Container; let hierarchy: HierarchyCanvas | undefined = undefined; -export function renderApp( +const textures: { [key: string]: PIXI.Texture } = {} + +export function renderScene( blink: PIXI.Application, canvas: BlinkCanvas, viewport: Viewport, @@ -71,7 +73,21 @@ export function renderApp( const imgPromises = []; for (let assetId in canvas.assets) { if (canvas.assets[assetId].type === "image") { - imgPromises.push(PIXI.Assets.load(canvas.assets[assetId].data)); + const cacheId = canvas.assets[assetId].data; + if (textures[cacheId] != null) continue; + + // Get cache object + imgPromises.push(new Promise(async (resolve, reject) => { + const blob: Blob = await window.cache.get(cacheId); + console.log("BLOB", blob); + const url = window.URL.createObjectURL(blob); + console.log("URL", url); + textures[cacheId] = PIXI.Texture.from(url); + // const asset = await PIXI.Assets.load(url); + // console.log("ASSET", asset); + resolve(null); + // resolve(asset); + })) // TODO: Use bundles instead (e.g. PIXI.Assets.addBundle()) // See: [https://pixijs.io/guides/basics/assets.html] } @@ -170,7 +186,8 @@ function renderClump( canvas.assets, prevCanvas?.assets, child, - (prevChild?.class === "atom" ? prevChild : undefined) as HierarchyAtom + (prevChild?.class === "atom" ? prevChild : undefined) as HierarchyAtom, + textures ); // Construct hierarchy atom diff --git a/src/frontend/lib/Project.ts b/src/frontend/lib/Project.ts index 3c9cfcf3..6a3cd05c 100644 --- a/src/frontend/lib/Project.ts +++ b/src/frontend/lib/Project.ts @@ -32,7 +32,14 @@ export function constructLayout(layout: LayoutPanel): PanelGroup { export const layoutTemplate: LayoutPanel = { panels: [ { - content: "assets", + panels: [ + { + content: "media", + }, + { + content: "assets", + }, + ], }, { content: "graph", From 709fa32850b5b2dd13dfd04b5b35aeecd4756fa7 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Sat, 23 Sep 2023 09:46:07 +0200 Subject: [PATCH 131/209] Fix window control buttons on Windows --- src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 405c6ef2..96ab2d69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ import { Blix } from "./electron/lib/Blix"; import { CoreGraphInterpreter } from "./electron/lib/core-graph/CoreGraphInterpreter"; import { exposeMainApis } from "./electron/lib/api/MainApi"; import { MainWindow, bindMainWindowApis } from "./electron/lib/api/apis/WindowApi"; -import { platform } from "os"; +import { type } from "os"; const isProd = process.env.NODE_ENV === "production" || app.isPackaged; // const isProd = true; @@ -105,8 +105,9 @@ async function createMainWindow() { }, // Set icon for Windows and Linux icon: isProd ? join(__dirname, "icon.png") : "public/images/icon.png", - titleBarStyle: "hidden", + titleBarStyle: type() === "Windows_NT" ? "default" : "hidden", trafficLightPosition: { x: 10, y: 10 }, + title: "Blix", // show: false, }) as MainWindow; From 5f92651c65a73d957b33cf23e1afba738383b80c Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Sat, 23 Sep 2023 12:34:55 +0200 Subject: [PATCH 132/209] PanelNode tests finished --- src/frontend/lib/PanelNode.ts | 20 +++++ tests/unit-tests/frontend/PanelNode.spec.ts | 92 ++++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/frontend/lib/PanelNode.ts b/src/frontend/lib/PanelNode.ts index 4802e853..d0798c87 100644 --- a/src/frontend/lib/PanelNode.ts +++ b/src/frontend/lib/PanelNode.ts @@ -141,6 +141,12 @@ export class PanelGroup extends PanelNode { current = current; } + /** + * Prints the panelgroup and its children + * @param indent The indentation level to be used + * @returns string + */ + // Print tree for debug public print(indent = 0): string { let res; @@ -164,6 +170,11 @@ export class PanelGroup extends PanelNode { return res; } + /** + * Recursively sets the parent of the panelgroup and its children + * @returns void + * */ + recurseParent() { for (let p = 0; p < this.panels.length; p++) { const panel = this.panels[p]; @@ -176,6 +187,11 @@ export class PanelGroup extends PanelNode { } } + /** + * Saves the layout of the panelgroup and its children + * @returns LayoutPanel + * */ + public saveLayout(): LayoutPanel { const p: LayoutPanel = { panels: [], @@ -191,6 +207,10 @@ export class PanelGroup extends PanelNode { } } +/** + * A leaf node that contains the type of the panel + * @extends PanelNode + */ export class PanelLeaf extends PanelNode { constructor(public content: PanelType) { super(); diff --git a/tests/unit-tests/frontend/PanelNode.spec.ts b/tests/unit-tests/frontend/PanelNode.spec.ts index fc5490ed..bf95764c 100644 --- a/tests/unit-tests/frontend/PanelNode.spec.ts +++ b/tests/unit-tests/frontend/PanelNode.spec.ts @@ -1,7 +1,8 @@ import exp from "constants"; -import { PanelGroup, PanelLeaf, PanelNode } from "../../../src/frontend/lib/PanelNode"; +import { PanelGroup, PanelLeaf, PanelNode, focusedPanelStore } from "../../../src/frontend/lib/PanelNode"; import { Pane } from "svelte-splitpanes"; import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands"; +import { LayoutPanel } from "../../../src/shared/types"; @@ -40,6 +41,9 @@ import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands" expect(panelNode.panels[0].parent).toBe(panelNode); expect(panelNode.panels[0].index).toBe(0); expect(panelNode.panels[0].id).toBe(1); + + panelNode.addPanel("media",69); + expect(panelNode.panels.length).toBe(2); }) test("Removing panels", () => { @@ -66,6 +70,11 @@ import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands" panelNode2.addPanelGroup(panelNode,0); panelNode.removePanel(0); expect(panelNode2.panels.length).toBe(1); + + const len = panelNode.panels.length; + panelNode.removePanel(69); + expect(panelNode.panels.length).toBe(len); + } ) @@ -77,12 +86,91 @@ import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands" const node = new PanelLeaf("graph"); + + panelNode.setPanel(node,69); + + expect(panelNode.panels.length).toBe(1); + expect(panelNode.panels[0]).toBe(curr); panelNode.setPanel(node,0); expect(panelNode.panels.length).toBe(1); expect(panelNode.panels[0]).toBe(node); expect(panelNode.panels[0].id).toBe(curr.id); + }) + test("Printing a panel group", () => { + panelNode.addPanel("media",0); + panelNode.addPanel("media",1); + panelNode.addPanel("media",2); + + expect(panelNode.print()).toBe("Unique id[NULL](-1)\n" + "+ media[Unique id](0)\n" + "+ media[Unique id](1)\n" + "+ media[Unique id](2)\n") + const panelGroup1 = new PanelGroup("Unique id"); + panelGroup1.addPanelGroup(panelNode,0); + + + expect(panelGroup1.print()).toBe("Unique id[NULL](-1)\n" + "- Unique id[Unique id](0)\n" + " + media[Unique id](0)\n" + " + media[Unique id](1)\n" + " + media[Unique id](2)\n") + panelNode.panels[1].parent = null; + expect(panelNode.print()).toBe("Unique id[Unique id](0)\n" + "+ media[Unique id](0)\n" + "+ media[NULL](1)\n" + "+ media[Unique id](2)\n") }) - }); \ No newline at end of file + test("Testing Recurse Parent", () => { + panelNode.addPanel("media",0); + panelNode.addPanel("media",1); + panelNode.addPanel("media",2); + + const panelGroup = new PanelGroup("Unique id"); + panelGroup.addPanel("media",0); + + panelNode.addPanelGroup(panelGroup,3); + + panelNode.panels[1].parent=null; + panelNode.recurseParent(); + + expect(panelNode.panels[1].parent).toBe(panelNode); + + }) + + + test("Testing save layout", () => { + panelNode.addPanel("media",0); + panelNode.addPanel("media",1); + panelNode.addPanel("media",2); + + const panelGroup = new PanelGroup("Unique id"); + panelGroup.addPanel("media",0); + + panelNode.addPanelGroup(panelGroup,3); + + const layout : LayoutPanel = { + panels: [ + { + content: "media", + }, + { + content: "media", + }, + { + content: "media", + }, + { + panels: [ + { + content: "media", + }, + ] + } + ] + } + expect(panelNode.saveLayout()).toEqual(layout); + }) + + test("Testing focusedPanelStore", () => { + focusedPanelStore.focusOnPanel(1); + focusedPanelStore.focusOnPanel(2); + focusedPanelStore.focusOnPanel(3); + + expect(focusedPanelStore.subscribe).toReturn; + + }) + + }); \ No newline at end of file From 80e587550f5a6b1d46a412cc9631f5349dd0be95 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Sat, 23 Sep 2023 12:50:50 +0200 Subject: [PATCH 133/209] Add project.ts tests and docs --- src/frontend/lib/Project.ts | 9 ++++ tests/unit-tests/frontend/PanelNode.spec.ts | 47 ++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/frontend/lib/Project.ts b/src/frontend/lib/Project.ts index 57085cb6..4ea42926 100644 --- a/src/frontend/lib/Project.ts +++ b/src/frontend/lib/Project.ts @@ -12,6 +12,12 @@ export interface UIProject { let groupTest = 0; +/** + * Constructs a PanelGroup from a LayoutPanel + * @param layout The layout to construct from + * @returns The constructed PanelGroup + */ + export function constructLayout(layout: LayoutPanel): PanelGroup { const group = new PanelGroup((groupTest++).toString()); if (layout.panels) { @@ -28,6 +34,9 @@ export function constructLayout(layout: LayoutPanel): PanelGroup { } return group; } +/** + * A layout template for the UI + */ export const layoutTemplate: LayoutPanel = { panels: [ diff --git a/tests/unit-tests/frontend/PanelNode.spec.ts b/tests/unit-tests/frontend/PanelNode.spec.ts index bf95764c..8aa1c5fe 100644 --- a/tests/unit-tests/frontend/PanelNode.spec.ts +++ b/tests/unit-tests/frontend/PanelNode.spec.ts @@ -3,6 +3,7 @@ import { PanelGroup, PanelLeaf, PanelNode, focusedPanelStore } from "../../../sr import { Pane } from "svelte-splitpanes"; import { exportMedia } from "../../../src/electron/lib/projects/ProjectCommands"; import { LayoutPanel } from "../../../src/shared/types"; +import { constructLayout } from "../../../src/frontend/lib/Project"; @@ -173,4 +174,48 @@ import { LayoutPanel } from "../../../src/shared/types"; }) - }); \ No newline at end of file +}); + +describe("Test project.ts", () => { + + let panelNode : PanelGroup; + beforeEach(() => { + PanelGroup.groupCounter = 0; + PanelNode.panelCounter = 0; + panelNode = new PanelGroup("Unique id"); + }); + test ("Testing constructLayout", () => { + panelNode.addPanel("media",0); + panelNode.addPanel("media",1); + panelNode.addPanel("media",2); + + const panelGroup = new PanelGroup("Unique id"); + panelGroup.addPanel("media",0); + + panelNode.addPanelGroup(panelGroup,3); + + const layout : LayoutPanel = { + panels: [ + { + content: "media", + }, + { + content: "media", + }, + { + content: "media", + }, + { + panels: [ + { + content: "media", + }, + ] + } + ] + } + + const group = constructLayout(layout); + expect(group.panels.length).toEqual(panelNode.panels.length); + }) +}) \ No newline at end of file From 64a94552f88310757e9e37c064c225245fdfff08 Mon Sep 17 00:00:00 2001 From: Jake Mileham Date: Sat, 23 Sep 2023 16:22:07 +0200 Subject: [PATCH 134/209] Refactor CoreGraph node adding --- src/electron/lib/core-graph/CoreGraph.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/electron/lib/core-graph/CoreGraph.ts b/src/electron/lib/core-graph/CoreGraph.ts index 60ab16f3..81ebda9a 100644 --- a/src/electron/lib/core-graph/CoreGraph.ts +++ b/src/electron/lib/core-graph/CoreGraph.ts @@ -199,37 +199,30 @@ export class CoreGraph extends UniqueEntity { let inputValues: Record = {}; // Create or restore UI inputs depending on brand new node or restored node - Object.values(nodeInstance.uiConfigs).forEach((config) => { inputValues[config.componentId] = uiValues ? uiValues[config.componentId] : config.defaultValue; }); - this.uiInputs[node.uuid] = new CoreNodeUIInputs( - { inputs: inputValues, changes: uiValues ? Object.keys(uiValues) : [] }, - {} - ); + const changes = Object.keys(inputValues); // Handle special readonly UI component types (e.g. TweakDial) const { dials: dialInputs, filledInputs: filledDialInputs } = populateDials(nodeInstance.ui, { nodeUUID: node.uuid, - uiInputs: Object.keys(inputValues), - uiInputChanges: Object.keys(inputValues), // When node is created, all inputs have 'changed' + uiInputs: changes, + uiInputChanges: changes, // When node is created, all inputs have 'changed' }); inputValues = { ...inputValues, ...filledDialInputs }; // Handle the UI input initializer const initializedInputs = nodeInstance.uiInitializer(inputValues); - let uiInputsInitialized = false; const uiChanges = Object.keys(initializedInputs); - // console.log("Initialized Inputs", initializedInputs); - + let uiInputsInitialized = false; if (typeof initializedInputs === "object" && uiChanges.length > 0) { inputValues = { ...inputValues, ...initializedInputs }; - // Update the graph's UI inputs const uiInputsPayload: INodeUIInputs = { inputs: inputValues, @@ -237,11 +230,9 @@ export class CoreGraph extends UniqueEntity { }; this.uiInputs[node.uuid] = new CoreNodeUIInputs(uiInputsPayload, dialInputs); - uiInputsInitialized = true; } - - // console.log(QueryResponseStatus.success) + // console.log(uiInputsInitialized) const anchors: AiAnchors = node.returnAnchors(); // Add position of node to graph this.uiPositions[node.uuid] = pos; From ded43477d6efbf33f98a6228c8256e7c2cfd76da Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Sat, 23 Sep 2023 17:41:18 +0200 Subject: [PATCH 135/209] Fix tests --- __mocks__/hello-plugin/src/main.js | 2 +- src/electron/lib/core-graph/CoreGraph.ts | 4 ++ .../lib/core-graph/CoreGraphExporter.ts | 2 +- .../core-graph/graphImporter.spec.ts | 2 +- .../core-graph/graphInterpreter.spec.ts | 6 +++ .../media/mediaManager.spec.ts | 5 ++- .../integration-tests/plugins/plugin.spec.ts | 4 +- .../projects/projectCommands.spec.ts | 2 +- .../electron/lib/core-graph/CoreGraph.spec.ts | 42 +++++++++---------- .../lib/core-graph/CoreGraphExporter.spec.ts | 4 +- .../lib/core-graph/CoreGraphManager.spec.ts | 16 +++---- .../electron/lib/core-graph/Toolbox.spec.ts | 6 +-- .../lib/plugins/builder/NodeBuilder.spec.ts | 22 +++++----- 13 files changed, 64 insertions(+), 53 deletions(-) diff --git a/__mocks__/hello-plugin/src/main.js b/__mocks__/hello-plugin/src/main.js index 4887262b..19b805e9 100644 --- a/__mocks__/hello-plugin/src/main.js +++ b/__mocks__/hello-plugin/src/main.js @@ -3,7 +3,7 @@ const nodes = { "hello": (context) => { // Use context.nodeBuilder to construct the node UI - console.log("Bogus nodes"); + // console.log("Bogus nodes"); } } diff --git a/src/electron/lib/core-graph/CoreGraph.ts b/src/electron/lib/core-graph/CoreGraph.ts index 81ebda9a..60733147 100644 --- a/src/electron/lib/core-graph/CoreGraph.ts +++ b/src/electron/lib/core-graph/CoreGraph.ts @@ -221,6 +221,9 @@ export class CoreGraph extends UniqueEntity { const uiChanges = Object.keys(initializedInputs); let uiInputsInitialized = false; + + this.uiInputs[node.uuid] = new CoreNodeUIInputs({ inputs: inputValues, changes }, dialInputs); + if (typeof initializedInputs === "object" && uiChanges.length > 0) { inputValues = { ...inputValues, ...initializedInputs }; // Update the graph's UI inputs @@ -232,6 +235,7 @@ export class CoreGraph extends UniqueEntity { this.uiInputs[node.uuid] = new CoreNodeUIInputs(uiInputsPayload, dialInputs); uiInputsInitialized = true; } + // console.log(uiInputsInitialized) const anchors: AiAnchors = node.returnAnchors(); // Add position of node to graph diff --git a/src/electron/lib/core-graph/CoreGraphExporter.ts b/src/electron/lib/core-graph/CoreGraphExporter.ts index 1c20e8d5..05f794e4 100644 --- a/src/electron/lib/core-graph/CoreGraphExporter.ts +++ b/src/electron/lib/core-graph/CoreGraphExporter.ts @@ -43,7 +43,7 @@ export class GraphFileExportStrategy implements ExportStrategy { json.push({ signature: `${graph.getNodes[node].getPlugin}.${graph.getNodes[node].getName}`, position: graph.getUIPositions()[node], - inputs: inputs[node].getInputs, + inputs: inputs[node] ? inputs[node].getInputs : {}, }); } diff --git a/tests/integration-tests/core-graph/graphImporter.spec.ts b/tests/integration-tests/core-graph/graphImporter.spec.ts index 2b6f7fc5..3000645b 100644 --- a/tests/integration-tests/core-graph/graphImporter.spec.ts +++ b/tests/integration-tests/core-graph/graphImporter.spec.ts @@ -93,7 +93,7 @@ describe("Test graph importer", () => { blix = new Blix(); blix.init(mainWindow); importer = new CoreGraphImporter(blix.toolbox); - blix.toolbox.addInstance(new NodeInstance("testNode", "blix", "testNode", "This is a test node", "", [{ type: "Number", "displayName": "blix.testNode.0", "identifier": "In0" }], [{ type: "Number", "displayName": "blix.testNode.1", "identifier": "Out0" }])) + blix.toolbox.addInstance(new NodeInstance("testNode", "blix", "folder", "testNode", "This is a test node", "", [{ type: "Number", "displayName": "blix.testNode.0", "identifier": "In0" }], [{ type: "Number", "displayName": "blix.testNode.1", "identifier": "Out0" }])) }); test("Test import of valid json graph", () => { diff --git a/tests/integration-tests/core-graph/graphInterpreter.spec.ts b/tests/integration-tests/core-graph/graphInterpreter.spec.ts index 20a27394..86a9ff3a 100644 --- a/tests/integration-tests/core-graph/graphInterpreter.spec.ts +++ b/tests/integration-tests/core-graph/graphInterpreter.spec.ts @@ -98,6 +98,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `hello-plugin.hello`, `input`, + `folder`, `hello-plugin`, `title`, `description`, @@ -119,6 +120,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `hello-plugin.hello`, `flip`, + `folder`, `hello-plugin`, `title`, `description`, @@ -146,6 +148,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `output`, `blix`, + `folder`, `hello-plugin`, `title`, `description`, @@ -207,6 +210,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `hello-plugin.hello`, `input`, + `folder`, `hello-plugin`, `title`, `description`, @@ -228,6 +232,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `hello-plugin.hello`, `flip`, + `folder`, `hello-plugin`, `title`, `description`, @@ -255,6 +260,7 @@ describe("Test graph interpreter", () => { new NodeInstance( `output`, `blix`, + `folder`, `hello-plugin`, `title`, `description`, diff --git a/tests/integration-tests/media/mediaManager.spec.ts b/tests/integration-tests/media/mediaManager.spec.ts index 3238f774..84cd141d 100644 --- a/tests/integration-tests/media/mediaManager.spec.ts +++ b/tests/integration-tests/media/mediaManager.spec.ts @@ -35,7 +35,8 @@ const mainWindow: MainWindow = { }, graphClientApi: { graphChanged: jest.fn(), - graphRemoved: jest.fn() + graphRemoved: jest.fn(), + uiInputsChanged: jest.fn(), }, utilClientApi: { showToast: jest.fn() @@ -158,7 +159,7 @@ describe("Test graph importer", () => { //mediaManager.onGraphUpdated(graph.uuid); - expect(blix.graphInterpreter.run).toBeCalledTimes(1); + expect(blix.graphInterpreter.run).toBeCalledTimes(2); }); diff --git a/tests/integration-tests/plugins/plugin.spec.ts b/tests/integration-tests/plugins/plugin.spec.ts index 12fab996..9fc67510 100644 --- a/tests/integration-tests/plugins/plugin.spec.ts +++ b/tests/integration-tests/plugins/plugin.spec.ts @@ -101,10 +101,10 @@ describe("Test builder propagations", () => { beforeEach(() => { jest.clearAllMocks(); - const node = new NodeInstance("Jake.Shark", "Shark", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); + const node = new NodeInstance("Jake.Shark", "Shark", "folder", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); const nodeUI = new NodeUIParent("Jake.Shark", null); - nodeBuilder = new NodeBuilder("testing-plugin", "My cool node"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "My cool node"); nodeUIBuilder = new NodeUIBuilder(); }); diff --git a/tests/integration-tests/projects/projectCommands.spec.ts b/tests/integration-tests/projects/projectCommands.spec.ts index 94624ef1..ab7e02e6 100644 --- a/tests/integration-tests/projects/projectCommands.spec.ts +++ b/tests/integration-tests/projects/projectCommands.spec.ts @@ -31,7 +31,7 @@ const mainWindow: MainWindow = { }, utilClientApi: { showToast: jest.fn((message) => { - console.log(message); + // console.log(message); }), } diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraph.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraph.spec.ts index f62eee3d..714293ab 100644 --- a/tests/unit-tests/electron/lib/core-graph/CoreGraph.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraph.spec.ts @@ -19,7 +19,7 @@ describe("Test backend graph", () => { graph = new CoreGraph(); for(let i = 0; i < 10; i++){ - const node = new NodeInstance(`Node-${i}`, "Test-plugin", `Node-${i}`, `This is node ${i}`, `fa-duotone fa-bell`, inputs, outputs); + const node = new NodeInstance(`Node-${i}`, "Test-plugin", "folder", `Node-${i}`, `This is node ${i}`, `fa-duotone fa-bell`, inputs, outputs); nodes.push(node); graph.addNode(node, { x: 0, y: 0 }); } @@ -39,7 +39,7 @@ describe("Test backend graph", () => { for(let i = 0; i < 10; i++){ const input: MinAnchor = { type: "string", displayName: `Test-plugin.Node-${i}.0`, identifier: `in${i}` }; const output: MinAnchor = { type: "string", displayName: `Test-plugin.Node-${i}.1`, identifier: `out${i}` }; - const node = new NodeInstance(`Node-${i}`, `Test-plugin`, `Node-${i}`, `This is node ${i}`, `fa-duotone fa-bell`, [input], [output]); + const node = new NodeInstance(`Node-${i}`, `Test-plugin`, "folder", `Node-${i}`, `This is node ${i}`, `fa-duotone fa-bell`, [input], [output]); graph.addNode(node, { x: 0, y: 0 }); names.push(input.displayName, output.displayName); } @@ -64,7 +64,7 @@ describe("Test backend graph", () => { }) test("Setting a node's position", () => { - const node = new NodeInstance("Test-plugin",`Node-1`, `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); + const node = new NodeInstance("Test-plugin",`Node-1`, "folder", `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); const response: QueryResponse<{ nodeId: UUID }> = graph.addNode(node, { x: 0, y: 0}); const uuid = (response.data! as { nodeId: UUID }).nodeId; const response2: QueryResponse = graph.setNodePos(uuid, { x: 6, y: 9}); @@ -114,7 +114,7 @@ describe("Test CoreGraph Class", () => { }); test("Adding a node to a graph", () => { - const node = new NodeInstance("Node-1",`Test-Plugin`, `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); + const node = new NodeInstance("Node-1",`Test-Plugin`, "folder", `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); const response: QueryResponse<{ nodeId: UUID }> = graph.addNode(node, { x: 0, y: 0 }); const uuid = (response.data! as { nodeId: UUID }).nodeId; expect(uuid).toBe(graph.getNodes[uuid].uuid); @@ -126,7 +126,7 @@ test("Adding a node to a graph", () => { test("Removing a node from a graph", () => { - const node = new NodeInstance("Test-plugin",`Node-1`, `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); + const node = new NodeInstance("Test-plugin",`Node-1`, "folder", `Node-1`, `This is node 1`, `fa-duotone fa-bell`, inputs, outputs); const response: QueryResponse<{ nodeId: UUID }> = graph.addNode(node, { x: 0, y: 0 }); const uuid = (response.data! as { nodeId: UUID }).nodeId; graph.removeNode(uuid); @@ -162,8 +162,8 @@ test("Adding a node to a graph", () => { { type: "string", displayName: `Test-plugin.Node-2.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-2.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${bestNode}-${1}`, `${plugin}`, `${bestNode}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${bestNode}-${2}`, `${plugin}`, `${bestNode}-2`, description, icon, inputs2, outputs2); + const node1Instance = new NodeInstance(`${bestNode}-${1}`, `${plugin}`, "folder", `${bestNode}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${bestNode}-${2}`, `${plugin}`, "folder", `${bestNode}-2`, description, icon, inputs2, outputs2); const pos = { x: 0, y: 0 }; graph.addNode(node1Instance, pos); @@ -260,8 +260,8 @@ test("Adding a node to a graph", () => { { type: "string", displayName: `Test-plugin.Node-2.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-2.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, `${node}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, `${node}-2`, description, icon, inputs2, outputs2); + const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, "folder", `${node}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, "folder", `${node}-2`, description, icon, inputs2, outputs2); const pos = { x: 0, y: 0 }; graph.addNode(node1Instance, pos); @@ -331,10 +331,10 @@ test("Adding a node to a graph", () => { { type: "string", displayName: `Test-plugin.Node-4.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-4.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, `${node}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, `${node}-2`, description, icon, inputs2, outputs2); - const node3Instance = new NodeInstance(`${node}-${3}`, `${plugin}`, `${node}-3`, description, icon, inputs3, outputs3); - const node4Instance = new NodeInstance(`${node}-${4}`, `${plugin}`, `${node}-4`, description, icon, inputs4, outputs4); + const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, "folder", `${node}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, "folder", `${node}-2`, description, icon, inputs2, outputs2); + const node3Instance = new NodeInstance(`${node}-${3}`, `${plugin}`, "folder", `${node}-3`, description, icon, inputs3, outputs3); + const node4Instance = new NodeInstance(`${node}-${4}`, `${plugin}`, "folder", `${node}-4`, description, icon, inputs4, outputs4); const pos = { x: 0, y: 0 }; graph.addNode(node1Instance, pos); @@ -396,8 +396,8 @@ test("Adding a node to a graph", () => { { type: "string", displayName: `Test-plugin.Node-2.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-2.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, `${node}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, `${node}-2`, description, icon, inputs2, outputs2); + const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, "folder", `${node}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, "folder", `${node}-2`, description, icon, inputs2, outputs2); const pos = { x: 0, y: 0 }; graph.addNode(node1Instance, pos); @@ -468,10 +468,10 @@ test("Adding a node to a graph", () => { { type: "string", displayName: `Test-plugin.Node-4.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-4.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, `${node}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, `${node}-2`, description, icon, inputs2, outputs2); - const node3Instance = new NodeInstance(`${node}-${3}`, `${plugin}`, `${node}-3`, description, icon, inputs3, outputs3); - const node4Instance = new NodeInstance(`${node}-${4}`, `${plugin}`, `${node}-4`, description, icon, inputs4, outputs4); + const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, "folder", `${node}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, "folder", `${node}-2`, description, icon, inputs2, outputs2); + const node3Instance = new NodeInstance(`${node}-${3}`, `${plugin}`, "folder", `${node}-3`, description, icon, inputs3, outputs3); + const node4Instance = new NodeInstance(`${node}-${4}`, `${plugin}`, "folder", `${node}-4`, description, icon, inputs4, outputs4); const pos = { x: 0, y: 0 }; graph.addNode(node1Instance, pos); @@ -722,8 +722,8 @@ describe("Test NodesAndEdgesGraph Class ", () => { { type: "string", displayName: `Test-plugin.Node-2.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-2.4`, identifier: `out2` }); - const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, `${node}-1`, description, icon, inputs1, outputs1); - const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, `${node}-2`, description, icon, inputs2, outputs2); + const node1Instance = new NodeInstance(`${node}-${1}`, `${plugin}`, "folder", `${node}-1`, description, icon, inputs1, outputs1); + const node2Instance = new NodeInstance(`${node}-${2}`, `${plugin}`, "folder", `${node}-2`, description, icon, inputs2, outputs2); const pos = { x: 0, y: 0 }; g.addNode(node1Instance, pos); diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraphExporter.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraphExporter.spec.ts index 30bb89d2..d7092a28 100644 --- a/tests/unit-tests/electron/lib/core-graph/CoreGraphExporter.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraphExporter.spec.ts @@ -55,8 +55,8 @@ jest.mock("fs", () => ({ { type: "string", displayName: `Test-plugin.Node-2.3`, identifier: `out1` }, { type: "number", displayName: `Test-plugin.Node-2.4`, identifier: `out2` }); - node1 = new NodeInstance("Node-1", "Test-Plugin","Node 1","This is node 1","fa-bell", inputs1, outputs1); - node2 = new NodeInstance("Node-2", "Test-Plugin","node 2","This is node 2","fa-bell", inputs2, outputs2); + node1 = new NodeInstance("Node-1", "Test-Plugin", "folder", "Node 1", "This is node 1","fa-bell", inputs1, outputs1); + node2 = new NodeInstance("Node-2", "Test-Plugin", "folder", "node 2", "This is node 2","fa-bell", inputs2, outputs2); let response: QueryResponse<{ nodeId: UUID }> = graph.addNode(node1, { x: 0, y: 0 }); const uuid1 = (response.data! as { nodeId: UUID }).nodeId; diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts index fba47738..7acfd71e 100644 --- a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts @@ -122,7 +122,7 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') test("Test addNode", () => { //Add Node to graph - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); graphManager.addGraph(graph); graphManager.addNode(graph.uuid,node, { x: 0, y: 0 }, CoreGraphUpdateParticipant.system); const nodes = graphManager.getGraph(graph.uuid).getNodes; @@ -131,8 +131,8 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') test("Test addEdge", () => { //Add Edge to graph - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); - const node2 = new NodeInstance("Finn", "Human", "Human.Finn", "This is the Finn plugin :)", "1150", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node2 = new NodeInstance("Finn", "Human", "folder", "Human.Finn", "This is the Finn plugin :)", "1150", inputs, outputs); node.inputs.push(new InputAnchorInstance("number","aaaa","aaaa.number")); node2.outputs.push(new OutputAnchorInstance("number","bbbb","bbbb.number")); @@ -160,7 +160,7 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') }) test("Test removeNode", () => { - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); graphManager.addGraph(graph); graphManager.addNode(graph.uuid, node, { x: 0, y: 0 }, CoreGraphUpdateParticipant.system); @@ -175,8 +175,8 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') test("Test removeEdge", () => { //Add Edge to graph - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); - const node2 = new NodeInstance("Finn", "Human", "Human.Finn", "This is the Finn plugin :)", "1150", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node2 = new NodeInstance("Finn", "Human", "folder", "Human.Finn", "This is the Finn plugin :)", "1150", inputs, outputs); node.inputs.push(new InputAnchorInstance("number","aaaa","aaaa.number")); node2.outputs.push(new OutputAnchorInstance("number","bbbb","bbbb.number")); @@ -204,7 +204,7 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') test("Test setPos", () => { - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); graphManager.addGraph(graph); graphManager.addNode(graph.uuid, node, { x: 0, y: 0 }, CoreGraphUpdateParticipant.system); @@ -248,7 +248,7 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') const result = graphManager.addEdge("","","",CoreGraphUpdateParticipant.system); expect(result.status).toBe("error"); - const node = new NodeInstance("Jake", "Shark", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); + const node = new NodeInstance("Jake", "Shark", "folder", "Shark.Jake", "This is the Jake plugin :)", "1149", inputs, outputs); const result2 = graphManager.addNode("", node, { x: 0, y: 0 }, CoreGraphUpdateParticipant.system); expect(result2.status).toBe("error"); diff --git a/tests/unit-tests/electron/lib/core-graph/Toolbox.spec.ts b/tests/unit-tests/electron/lib/core-graph/Toolbox.spec.ts index 28bb5ed4..55a776ed 100644 --- a/tests/unit-tests/electron/lib/core-graph/Toolbox.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/Toolbox.spec.ts @@ -16,7 +16,7 @@ describe("Test toolbox", () => { beforeEach(() => { jest.clearAllMocks(); - node = new NodeInstance("Shark", "Jake", "Shark.Jake", "The Jake plugin", "1149", inputs, outputs); + node = new NodeInstance("Shark", "Jake", "folder", "Shark.Jake", "The Jake plugin", "1149", inputs, outputs); }); test("getId should be defined", () => { @@ -52,7 +52,7 @@ describe("Test toolbox", () => { }); test("getUI should return ui", () => { - const nod = new NodeInstance("Jake.Shark", "Shark", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); + const nod = new NodeInstance("Jake.Shark", "Shark", "folder", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); expect(nod.ui).toEqual(null); }); @@ -233,7 +233,7 @@ describe("Test Toolbox registry", () => { }); test("ToolboxRegistry should add and return registrys properly", () => { - const node = new NodeInstance("Jake.Shark", "Shark", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); + const node = new NodeInstance("Jake.Shark", "Shark", "folder", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); box.addInstance(node); expect(box.getRegistry()[node.signature]).toEqual(node); }); diff --git a/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts index fa012704..aa671313 100644 --- a/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts +++ b/tests/unit-tests/electron/lib/plugins/builder/NodeBuilder.spec.ts @@ -20,10 +20,10 @@ describe("Test NodeBuilder", () => { jest.clearAllMocks(); - const node = new NodeInstance("Jake.Shark", "Shark", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); + const node = new NodeInstance("Jake.Shark", "Shark", "folder", "Jake", "The Jake plugin", "This is the Jake plugin", inputs, outputs); const nodeUI = new NodeUIParent("", null); - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 1"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 1"); nodeUIBuilder = new NodeUIBuilder(); }); @@ -35,7 +35,7 @@ describe("Test NodeBuilder", () => { }); test("getBuild should return NodeInstance", () => { - const node = new NodeInstance("cool node 1", "testing-plugin", "Jake", "The Jake plugin","", inputs, outputs); + const node = new NodeInstance("cool node 1", "testing-plugin", "folder", "Jake", "The Jake plugin","", inputs, outputs); nodeBuilder.setTitle("Jake"); nodeBuilder.setDescription("The Jake plugin"); nodeBuilder.define(() => {return {"res" : "Shrek"}}); @@ -43,18 +43,18 @@ describe("Test NodeBuilder", () => { }); test("setTitle should set the title", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 3"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 3"); nodeBuilder.setTitle("Jake"); expect(nodeBuilder["partialNode"].displayName).toEqual("Jake"); }); test("setDescription should set the description", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 3"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 3"); nodeBuilder.setDescription("The Jake plugin"); expect(nodeBuilder["partialNode"].description).toEqual("The Jake plugin"); }); test("define should set the function", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 4"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 4"); nodeBuilder.define(() => {return {"res" : "Shrek"}}); expect(nodeBuilder["partialNode"].func({},{},[]).res).toEqual("Shrek"); }); @@ -73,24 +73,24 @@ describe("Test NodeBuilder", () => { beforeEach(() => { jest.clearAllMocks(); - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 5"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 5"); nodeUIBuilder = new NodeUIBuilder(); }); test("addIcon should add an icon", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 6"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 6"); nodeBuilder.addIcon("Shrek"); expect(nodeBuilder["partialNode"].icon).toEqual("Shrek"); }); test("addInput should add an input", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 7"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 7"); nodeBuilder.addInput("string", "shrek", "Shrek"); expect(nodeBuilder["partialNode"].inputs[0].displayName).toEqual("Shrek"); }); test("addOutput should add an output", () => { - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 8"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 8"); nodeBuilder.addOutput("string", "shrek2", "Shrek"); expect(nodeBuilder["partialNode"].outputs[0].displayName).toEqual("Shrek"); }); @@ -104,7 +104,7 @@ describe("Test NodeUIBuilder", () => { beforeEach(() => { jest.clearAllMocks(); - nodeBuilder = new NodeBuilder("testing-plugin", "cool node 9"); + nodeBuilder = new NodeBuilder("testing-plugin", "folder", "cool node 9"); nodeUIBuilder = new NodeUIBuilder(); }); From 1412610ee22866c2de1fbc7b2f9cbc50766d26ac Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Sat, 23 Sep 2023 18:45:23 +0200 Subject: [PATCH 136/209] Initiated TileBuilder testing --- .../lib/plugins/builders/TileBuilder.ts | 79 ++++++++++++++++++- .../lib/plugins/builder/TileBuilder.spec.ts | 53 +++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts diff --git a/src/electron/lib/plugins/builders/TileBuilder.ts b/src/electron/lib/plugins/builders/TileBuilder.ts index 1763090b..973a4775 100644 --- a/src/electron/lib/plugins/builders/TileBuilder.ts +++ b/src/electron/lib/plugins/builders/TileBuilder.ts @@ -20,9 +20,19 @@ type PartialTile = { uiConfigs: { [key: string]: UIComponentConfig }; }; +/** + * TileBuilder is a builder class for creating TileInstance objects. + * It is used to create a TileInstance object that can be registered with the TileRegistry. + */ + export class TileBuilder implements PluginContextBuilder { private partialTile: PartialTile; + /** + * @param plugin string + * @param name string + */ + constructor(plugin: string, name: string) { this.partialTile = { name, @@ -35,6 +45,11 @@ export class TileBuilder implements PluginContextBuilder { }; } + /** + * Returns the TileInstance object that has been built. + * @returns TileInstance + */ + get build(): TileInstance { return new TileInstance( this.partialTile.name, @@ -46,40 +61,102 @@ export class TileBuilder implements PluginContextBuilder { this.partialTile.uiConfigs ); } + + /** + * Resets the tile builder's tile instance to its initial state + * @returns void + */ + public reset(): void { + this.partialTile = { + name: this.partialTile.name, + plugin: this.partialTile.plugin, + displayName: this.partialTile.displayName, + description: "", + icon: "", + ui: {}, + uiConfigs: {}, + }; + return; } // TODO: Implement all of these + + /** + * Sets the title of the tile + * @param title string + * @returns void + */ public setTitle(title: string): void { this.partialTile.displayName = title; } + + /** + * Sets the description of the tile + * @param description string + * @returns void + */ public setDescription(description: string): void { this.partialTile.description = description; } + + /** + * Sets the icon of the tile + * @param icon string + * @returns void + */ public addIcon(icon: string): void { this.partialTile.icon = icon; } + + /** + * Sets the UI of the tile + * @param ui ITileUI + * @returns void + */ public setUI(ui: TileUIBuilder): void { this.partialTile.ui = ui.getUI(); this.partialTile.uiConfigs = ui.getUIConfigs(); } + /** + * Creates a new TileUIBuilder object + * @returns TileUIBuilder + */ + public createUIBuilder(): TileUIBuilder { const builder = new TileUIBuilder(); return builder; } + /** + * adds a new UI element to the tile + * @todo implement this function + * @returns void + */ + public addUIElement(): void { return; } } -function getRandomComponentId(type: TileUIComponent) { +/** + * Returns a random component id for a given TileUIComponent + * @param type TileUIComponent + * @returns string + */ + +function getRandomComponentId(type: TileUIComponent): string { return `${type.toString()}-${Math.floor(Math.random() * 16 ** 6).toString(16)}`; } +export function forTesting() { + // to allow testing of getRandomComponentId + return getRandomComponentId; +} + export class TileUIBuilder { private _main: TileUIParent; private _sidebar: tileUIComponentBuilder | null; diff --git a/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts new file mode 100644 index 00000000..763e53b0 --- /dev/null +++ b/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts @@ -0,0 +1,53 @@ + +import {TileBuilder, TileUIBuilder, forTesting} from "../../../../../../src/electron/lib/plugins/builders/TileBuilder" +import { TileInstance } from "../../../../../../src/electron/lib/registries/TileRegistry"; + +describe("Test TileBuilder", () => { + let tileBuilder : TileBuilder; + + beforeEach(() => { + jest.clearAllMocks(); + tileBuilder = new TileBuilder("testing-plugin", "cool tile 6"); + }); + + test("Initial build", () => { + const build : TileInstance = tileBuilder.build; + + const expectedBuild : TileInstance = new TileInstance( + "cool tile 6", + "testing-plugin", + "cool tile 6", + "", + "", + {}, + {}, + ); + + expect(build).toEqual(expectedBuild); + + tileBuilder.reset(); + expect(tileBuilder.build).toEqual(expectedBuild); + + }); + + test("Getters and setters", () => { + tileBuilder.setDescription("Hello world"); + + tileBuilder.addIcon("Iconicus"); + tileBuilder.setTitle("This is a title"); + + const tileUIBuilder = new TileUIBuilder(); + tileBuilder.setUI(tileUIBuilder); + + expect(tileBuilder.build.description).toEqual("Hello world"); + expect(tileBuilder.build.icon).toEqual("Iconicus"); + expect(tileBuilder.build.displayName).toEqual("This is a title"); + expect(tileBuilder.build.ui).toEqual(tileUIBuilder.getUI()); + expect(tileBuilder.build.uiConfigs).toEqual(tileUIBuilder.getUIConfigs()); + + expect(tileBuilder.createUIBuilder()).toBeDefined(); + expect(tileBuilder.addUIElement()).toReturn; + }); + + +}); \ No newline at end of file From 8eb16fabb7fc1856b34e31e3cfff9edea5289fd0 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Sun, 24 Sep 2023 14:25:01 +0200 Subject: [PATCH 137/209] Add emboss, bulge and zoomblur filters to blink --- blix-plugins/blink/src/main.cjs | 26 +++++++++++++++++++ blix-plugins/blink/webview/types.ts | 16 ++++++++++-- .../lib/core-graph/CoreGraphInterpreter.ts | 1 + 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 03e02fb0..57d97d1a 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -179,6 +179,32 @@ const blinkNodes = { { id: "seed", min: 0, max: 1, step: 0.01 }, ] ], + "emboss": [ + "Emboss", + "An RGB Split Filter.", + [{ id: "strength", min: 0, max: 20, step: 0.1 }] + ], + "bulge": [ + "Bulge / Pinch", + "Bulges or pinches the image in a circle.", + [ + { id: "radius", min: 0, max: 1000, step: 1.0 }, + { id: "strenght", min: -1, max: 1, step: 0.01 }, + { id: "center.x", min: 0, max: 1, step: 0.01 }, + { id: "center.y", min: 0, max: 1, step: 0.01 }, + ] + ], + "zoomblur": [ + "ZoomBlur", + "The ZoomFilter applies a Zoom blur to an object.", + [ + { id: "strenght", min: 0, max: 0.5, step: 0.01 }, + { id: "innerRadius", min: 0, max: 1000, step: 1.0 }, + { id: "center.x", min: 0, max: 2000, step: 1.0 }, + { id: "center.y", min: 0, max: 2000, step: 1.0 }, + ] + ], + }; Object.keys(blinkNodes).forEach((key) => { diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index 6570fd9d..ffcfddee 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -80,9 +80,21 @@ export function getPixiFilter(filter: Filter) { } ); case "emboss": return new EmbossFilter(...filter.params); - case "bulge": return new BulgePinchFilter(...filter.params); + case "bulge": return new BulgePinchFilter( + { + radius: filter.params[0], + strength: filter.params[1], + center: [filter.params[2], filter.params[3]], + } + ); case "glitch": return new GlitchFilter(...filter.params); - case "zoomblur": return new ZoomBlurFilter(...filter.params); + case "zoomblur": return new ZoomBlurFilter( + { + strength: filter.params[0], + innerRadius: filter.params[1], + center: [filter.params[2], filter.params[3]], + } + ); case "twist": return new TwistFilter(...filter.params); } } catch { diff --git a/src/electron/lib/core-graph/CoreGraphInterpreter.ts b/src/electron/lib/core-graph/CoreGraphInterpreter.ts index 98e21c41..910b682e 100644 --- a/src/electron/lib/core-graph/CoreGraphInterpreter.ts +++ b/src/electron/lib/core-graph/CoreGraphInterpreter.ts @@ -147,6 +147,7 @@ export class CoreGraphInterpreter { } // Resolve all input values (functions) + const inputs: { [key: string]: T }[] = await Promise.all(inputPromises).catch((err) => { throw err; }); From ccb1627221efb1b2b1258fcb66da33ea74421be7 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Sun, 24 Sep 2023 16:12:29 +0200 Subject: [PATCH 138/209] TileBuilder tests improved --- .../lib/plugins/builders/TileBuilder.ts | 77 +++++++++ .../lib/plugins/builder/TileBuilder.spec.ts | 147 ++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/src/electron/lib/plugins/builders/TileBuilder.ts b/src/electron/lib/plugins/builders/TileBuilder.ts index 973a4775..0a995f29 100644 --- a/src/electron/lib/plugins/builders/TileBuilder.ts +++ b/src/electron/lib/plugins/builders/TileBuilder.ts @@ -170,31 +170,67 @@ export class TileUIBuilder { this.uiConfigs = {}; } + /** + * Returns the TileUI object that has been built. + * @returns any + * @todo implement this function + */ get build(): any { return null; } + /** + * Returns the main TileUIParent object + * @returns TileUIParent + */ + get main(): TileUIParent { return this._main; } + /** + * Returns the sidebar TileUIParent object + * @returns tileUIComponentBuilder | null + */ get sidebar(): tileUIComponentBuilder | null { return this._sidebar; } + /** + * Returns the statusbar TileUIParent object + * @returns tileUIComponentBuilder | null + */ + get statusbar(): tileUIComponentBuilder | null { return this._statusbar; } + /** + * Adds a layout to this builders main childUIs + * @param builder + * @returns void + */ + public addLayout(builder: TileUIBuilder): void { this.main.childUis = { ui: builder.getUI(), uiConfigs: builder.getUIConfigs() }; } + /** + * Adds a sidebar to this builders sidebar + * @param location string + * @returns + */ + public addSidebar(location: string): TileUIBuilder { this._sidebar = new tileUIComponentBuilder("", location); return this; } + /** + * Adds a statusbar to this builders statusbar + * @param location string + * @returns + */ public addStatusbar(location: string): TileUIBuilder { this._statusbar = new tileUIComponentBuilder("", location); return this; @@ -208,6 +244,11 @@ export class TileUIBuilder { // return; // } + /** + * Gets the UI object that has been built. + * @returns { [key: string]: TileUIParent | null } + */ + public getUI(): { [key: string]: TileUIParent | null } { return { main: this.main, @@ -216,11 +257,19 @@ export class TileUIBuilder { }; } + /** + * Gets the UI configs that have been built. + * @returns { [key: string]: UIComponentConfig } + */ + public getUIConfigs(): { [key: string]: UIComponentConfig } { return Object.assign({}, this.uiConfigs, this.sidebar?.uiConfigs, this.statusbar?.uiConfigs); } } +/** + * TileUIComponentBuilder is a builder class for creating TileUIParent objects. + */ class tileUIComponentBuilder { private _component: TileUIParent; private _uiConfigs: { [key: string]: UIComponentConfig }; @@ -238,6 +287,13 @@ class tileUIComponentBuilder { this._uiConfigs = {}; } + /** + * Adds a button to this builders component + * @param config UIComponentConfig + * @param props UIComponentProps + * @returns tileUIComponentBuilder + */ + public addButton(config: UIComponentConfig, props: UIComponentProps): tileUIComponentBuilder { const componentId = config.componentId ?? getRandomComponentId(TileUIComponent.Button); this._component?.params.push( @@ -280,6 +336,13 @@ class tileUIComponentBuilder { // return; // } + /** + * Adds a slider to this builders component + * @param config + * @param props + * @returns tileUIComponentBuilder + */ + public addSlider(config: UIComponentConfig, props: UIComponentProps): tileUIComponentBuilder { const componentId = config.componentId ?? getRandomComponentId(TileUIComponent.Slider); this.component.params.push( @@ -326,6 +389,13 @@ class tileUIComponentBuilder { // return this; // } + /** + * Adds a dropdown to this builders component + * @param config + * @param props + * @returns tileUIComponentBuilder + */ + public addDropdown(config: UIComponentConfig, props: UIComponentProps): tileUIComponentBuilder { const componentId = config.componentId ?? getRandomComponentId(TileUIComponent.Dropdown); this.component.params.push( @@ -354,6 +424,13 @@ class tileUIComponentBuilder { // return this; // } + /** + * Adds a text input to this builders component + * @param config + * @param props + * @returns tileUIComponentBuilder + */ + public addTextInput(config: UIComponentConfig, props: UIComponentProps): tileUIComponentBuilder { const componentId = config.componentId ?? getRandomComponentId(TileUIComponent.TextInput); this.component.params.push( diff --git a/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts index 763e53b0..4d427aff 100644 --- a/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts +++ b/tests/unit-tests/electron/lib/plugins/builder/TileBuilder.spec.ts @@ -1,6 +1,8 @@ +import { Slider } from "blix_svelvet"; import {TileBuilder, TileUIBuilder, forTesting} from "../../../../../../src/electron/lib/plugins/builders/TileBuilder" import { TileInstance } from "../../../../../../src/electron/lib/registries/TileRegistry"; +import { TileUI, TileUIParent, UIComponentConfig, UIComponentProps,TileUIComponent, TileUILeaf } from "../../../../../../src/shared/ui/TileUITypes"; describe("Test TileBuilder", () => { let tileBuilder : TileBuilder; @@ -48,6 +50,151 @@ describe("Test TileBuilder", () => { expect(tileBuilder.createUIBuilder()).toBeDefined(); expect(tileBuilder.addUIElement()).toReturn; }); +}); +describe("Test TileUIBuilder", () => { + let tileUIBuilder : TileUIBuilder; + beforeEach(() => { + jest.clearAllMocks(); + tileUIBuilder = new TileUIBuilder(); + }) + + test("Initial build", () => { + expect(tileUIBuilder.main).toBeDefined(); + expect(tileUIBuilder.sidebar).toBe(null); + expect(tileUIBuilder.statusbar).toBe(null); + expect(tileUIBuilder.getUIConfigs()).toEqual({}); + }); + + test("Getters and setters", () => { + expect(tileUIBuilder.build).toBe(null); + expect(tileUIBuilder.main).toBeInstanceOf(TileUIParent); + expect(tileUIBuilder.main).toBeDefined(); + expect(tileUIBuilder.sidebar).toBe(null); + expect(tileUIBuilder.statusbar).toBe(null); + }); + + test("Add UI element", () => { + const tileUIBuilder2 = new TileUIBuilder(); + + const UI = tileUIBuilder2.getUI(); + const UIConfigs = tileUIBuilder2.getUIConfigs(); + + + tileUIBuilder.addLayout(tileUIBuilder2); + + expect(tileUIBuilder.main).toBeDefined(); + + expect(tileUIBuilder.main.childUis?.ui).toEqual(UI); + expect(tileUIBuilder.main.childUis?.uiConfigs).toEqual(UIConfigs); + + + expect(tileUIBuilder.addSidebar("below")).toBe(tileUIBuilder) + expect(tileUIBuilder.sidebar).toBeDefined(); + expect(tileUIBuilder.sidebar?.component.location).toBe("below"); + + expect(tileUIBuilder.addStatusbar("below")).toBe(tileUIBuilder) + expect(tileUIBuilder.sidebar).toBeDefined(); + expect(tileUIBuilder.sidebar?.component.location).toBe("below"); + + expect(tileUIBuilder.sidebar?.uiConfigs).toEqual({}); + }) + + test("Add UI element with configs", () => { + tileUIBuilder.addSidebar("below"); + + const uiCompontentConfig : UIComponentConfig = { + label: "Hello", + componentId: "world", + defaultValue: "!", + updatesBackend: true, + } + + const uiComponentProps : UIComponentProps = { + "Hello": "world", + } + + const leaf = new TileUILeaf(tileUIBuilder.sidebar?.component!, TileUIComponent.Button, "world", [uiComponentProps]) + + tileUIBuilder.sidebar?.addButton(uiCompontentConfig, uiComponentProps); + expect(tileUIBuilder.sidebar?.component.params).toHaveLength(1); + expect(tileUIBuilder.sidebar?.component.params[0]).toEqual(leaf); + expect(tileUIBuilder.sidebar?.uiConfigs["world"]).toEqual(uiCompontentConfig); + }) + + test("Add slider UI element with configs", () => { + tileUIBuilder.addSidebar("below"); + + const uiCompontentConfig : UIComponentConfig = { + label: "Hello", + componentId: "world", + defaultValue: "!", + updatesBackend: true, + } + + const uiComponentProps : UIComponentProps = { + "Hello": "world", + } + + const leaf = new TileUILeaf(tileUIBuilder.sidebar?.component!, TileUIComponent.Slider, "world", [uiComponentProps]) + + tileUIBuilder.sidebar?.addSlider(uiCompontentConfig, uiComponentProps); + expect(tileUIBuilder.sidebar?.component.params).toHaveLength(1); + expect(tileUIBuilder.sidebar?.component.params[0]).toEqual(leaf); + expect(tileUIBuilder.sidebar?.uiConfigs["world"]).toEqual(uiCompontentConfig); + }) + + test("Add Dropdown UI element with configs", () => { + tileUIBuilder.addSidebar("below"); + + const uiCompontentConfig : UIComponentConfig = { + label: "Hello", + componentId: "world", + defaultValue: "!", + updatesBackend: true, + } + + const uiComponentProps : UIComponentProps = { + "Hello": "world", + } + + const leaf = new TileUILeaf(tileUIBuilder.sidebar?.component!, TileUIComponent.Dropdown, "world", [uiComponentProps]) + + tileUIBuilder.sidebar?.addDropdown(uiCompontentConfig, uiComponentProps); + expect(tileUIBuilder.sidebar?.component.params).toHaveLength(1); + expect(tileUIBuilder.sidebar?.component.params[0]).toEqual(leaf); + expect(tileUIBuilder.sidebar?.uiConfigs["world"]).toEqual(uiCompontentConfig); + }) + + test("Add TextInput UI element with configs", () => { + tileUIBuilder.addSidebar("below"); + + const uiCompontentConfig : UIComponentConfig = { + label: "Hello", + componentId: "world", + defaultValue: "!", + updatesBackend: true, + } + + const uiComponentProps : UIComponentProps = { + "Hello": "world", + } + + const leaf = new TileUILeaf(tileUIBuilder.sidebar?.component!, TileUIComponent.TextInput, "world", [uiComponentProps]) + + tileUIBuilder.sidebar?.addTextInput(uiCompontentConfig, uiComponentProps); + expect(tileUIBuilder.sidebar?.component.params).toHaveLength(1); + expect(tileUIBuilder.sidebar?.component.params[0]).toEqual(leaf); + expect(tileUIBuilder.sidebar?.uiConfigs["world"]).toEqual(uiCompontentConfig); + }) + + test("For testing", () => { + const tileUIBuilder2 = new TileUIBuilder(); + + const result = forTesting(); + + expect(result(TileUIComponent.Button).substring(0,7)).toBe(`${TileUIComponent.Button.toString()}-${Math.floor(Math.random() * 16 ** 6).toString(16)}`.substring(0,7)) + + }) }); \ No newline at end of file From 6860d88b80cf5688aee9d9888fbdc5fe178d2093 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Mon, 25 Sep 2023 00:53:44 +0200 Subject: [PATCH 139/209] Add frontend implementation of export system --- blix-plugins/blink/webview/App.svelte | 20 ++++++++++++++----- src/electron/lib/webviews/preload.ts | 11 +++++++---- src/frontend/ui/tiles/Media.svelte | 28 ++++++++++++++++++++++----- src/frontend/ui/tiles/WebView.svelte | 12 ++++++++++-- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index d333b138..27ee0bb9 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -24,6 +24,15 @@ onMount(async () => { // window.addEventListener("DOMContentLoaded", async () => { + + // Add export listener + + window.api.on("export", async () => { + exportImage(); + // send("exportResponse", "exported"); + }) + + //====== INITIALIZE PIXI ======// blink = new PIXI.Application({ view: pixiCanvas, @@ -173,12 +182,13 @@ const frame = new PIXI.Rectangle(-bounds.x-imgCanvasBlockW/2, -bounds.y-imgCanvasBlockH/2, imgCanvasBlockW, imgCanvasBlockH); const exportCanvas = blink.renderer.extract.canvas(currScene, frame); - exportCanvas.toBlob((blob) => { + exportCanvas.toBlob(async (blob) => { const metadata = { contentType: "image/png", name: `Blink Export ${Math.floor(100000 * Math.random())}` }; - window.cache.write(blob, metadata); + + send("exportResponse", {cacheUUID: await window.cache.write(blob, metadata)}); }, "image/png"); // REMOVED: Exporting straight to local file // const link = document.createElement("a"); @@ -191,7 +201,7 @@
- +
@@ -207,14 +217,14 @@ padding: 0px; } - button { + /* button { position: absolute; z-index: 10; top: 10px; right: 0px; width: 60px; height: 30px; - } + } */ .cursorPointer { cursor: pointer; diff --git a/src/electron/lib/webviews/preload.ts b/src/electron/lib/webviews/preload.ts index 9a5da9a2..638b079f 100644 --- a/src/electron/lib/webviews/preload.ts +++ b/src/electron/lib/webviews/preload.ts @@ -85,10 +85,13 @@ contextBridge.exposeInMainWorld("cache", { messageId, metadata, } as CacheRequest); - const metadataResp = (await sendCachePayload( - metadataMessageId, - metadataPayload - )) as CacheResponse; + // TODO fix this + // const metadataResp = ( await sendCachePayload( + // metadataMessageId, + // metadataPayload + // )) as CacheResponse; + + const metadataResp = sendCachePayload(metadataMessageId, metadataPayload); } return writeResp.id; diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index 95b00062..b9e16c76 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -55,6 +55,7 @@ let mediaId = writable(""); let oldMediaId: string | null = null; + let webview: WebView; const unsubMedia = mediaId.subscribe((newMediaId) => { // console.log("SUBSCRIBE MEDIA ID", oldMediaId, newMediaId); @@ -82,10 +83,18 @@ async function exportMedia(e: Event) { if ($media?.dataType && $media?.content) { - await mediaStore.exportMedia($media); + if ($media.display.displayType === "webview") { + webview.exportMedia(e); + } else { + await mediaStore.exportMedia($media); + } } } + async function exportCache(e: CustomEvent) { + console.log("export cache", e.detail); + } + function getDisplayProps(media: DisplayableMediaOutput) { let res = media.display.props; if (media.display.contentProp !== null) res[media.display.contentProp] ??= media.content; // If content nullish, use default value @@ -145,10 +154,19 @@
{#if $media} - + {#if $media.display.displayType === "webview"} + + {:else} + + {/if}
{#await asyncSrc then src} {#if src !== null} - +
diff --git a/src/index.ts b/src/index.ts index 92e0d01b..53b7a562 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ import { Blix } from "./electron/lib/Blix"; import { CoreGraphInterpreter } from "./electron/lib/core-graph/CoreGraphInterpreter"; import { exposeMainApis } from "./electron/lib/api/MainApi"; import { MainWindow, bindMainWindowApis } from "./electron/lib/api/apis/WindowApi"; -import { type } from "os"; +import { type, userInfo } from "os"; const isProd = process.env.NODE_ENV === "production" || app.isPackaged; // const isProd = true; @@ -76,9 +76,11 @@ app.on("ready", async () => { }); // TODO: Remove - // await session.defaultSession.loadExtension( - // "/home/rec1dite/.config/google-chrome/Default/Extensions/aamddddknhcagpehecnhphigffljadon/2.6.1_0" - // ); + if (userInfo().username === "rec1dite") { + await session.defaultSession.loadExtension( + "/home/rec1dite/.config/google-chrome/Default/Extensions/aamddddknhcagpehecnhphigffljadon/2.6.1_0" + ); + } // const coreGraphInterpreter = new CoreGraphInterpreter(new ToolboxRegistry); // coreGraphInterpreter.run(); From 054f6b169ddb81b011d1b02ac42b0f96e4fec63f Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 25 Sep 2023 16:34:01 +0200 Subject: [PATCH 143/209] Add curve atom rendering to blink --- blix-plugins/blink/src/main.cjs | 112 +++++++++++++++++- blix-plugins/blink/webview/atom.ts | 63 +++++++++- blix-plugins/blink/webview/diff.ts | 7 +- blix-plugins/blink/webview/types.ts | 43 +++++-- src/frontend/ui/utils/graph/PluginNode.svelte | 1 + 5 files changed, 210 insertions(+), 16 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index ca385a52..16b1fb77 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -62,7 +62,7 @@ function addState(ui) { function addTweakability(ui) { ui.addTweakDial("tweaks", {}); - ui.addDiffDial("diffs", {}); + // ui.addDiffDial("diffs", {}); } function createBlinkNode(type, title, desc, params) { @@ -342,6 +342,116 @@ const nodes = { nodeBuilder.addInput("Blink matrix", "transform", "Transform"); nodeBuilder.addOutput("Blink clump", "res", "Result"); }, + "curve": (context) => { + const nodeBuilder = context.instantiate("Blink/Input", "curve"); + nodeBuilder.setTitle("Blink Curve"); + nodeBuilder.setDescription("Draw a custom curve"); + + const ui = nodeBuilder.createUIBuilder(); + ui.addDropdown({ + componentId: "drawMode", + label: "Draw Mode", + defaultValue: "normal", + triggerUpdate: true, + }, { + options: { + "Normal": "normal", + } + }) + const getTransform = addTransformInput(ui, ["position", "rotation", "scale"]); + + ui.addColorPicker({ + componentId: "fill", + label: "Fill", + defaultValue: "#00000000", + triggerUpdate: true, + }, {}); + ui.addColorPicker({ + componentId: "stroke", + label: "Stroke", + defaultValue: "#000000", + triggerUpdate: true, + }, {}); + ui.addSlider({ + componentId: "strokeWidth", + label: "Stroke Width", + defaultValue: 1, + triggerUpdate: true, + }, { min: 0, max: 100, set: 0.1 }); + + ui.addBuffer({ + componentId: "curve", + label: "Curve Buffer", + defaultValue: { id: "", path: [] }, + triggerUpdate: true, + }, {} + ); + + addTweakability(ui); + + nodeBuilder.setUIInitializer((x) => { + return { + curve: { + id: getUUID(), + path: [ + { + control1: { x: 50, y: 10 }, + control2: { x: 10, y: 300 }, + point: { x: 0, y: 0 }, + }, + { + control1: { x: 50, y: 10 }, + control2: { x: 10, y: 300 }, + point: { x: 400, y: 400 }, + }, + { + control1: { x: 400, y: 500 }, + control2: { x: 10, y: 500 }, + point: { x: 50, y: 10 }, + }, + ] + } + }; + }); + + nodeBuilder.define(async (input, uiInput, from) => { + const canvas = { + assets: { + [uiInput["curve"]["id"]]: { + class: "asset", + type: "curve", + data: uiInput["curve"].path + } + }, + content: { + class: "clump", + nodeUUID: uiInput["tweaks"].nodeUUID, + changes: uiInput["diffs"]?.uiInputs ?? [], + transform: getTransform(uiInput), + elements: [ + { + class: "atom", + type: "curve", + assetId: uiInput["curve"].id, + + fill: colorHexToNumber(uiInput["fill"]), + fillAlpha: colorHexToAlpha(uiInput["fill"]), + stroke: colorHexToNumber(uiInput["stroke"]), + strokeAlpha: colorHexToAlpha(uiInput["stroke"]), + strokeWidth: uiInput["strokeWidth"], + } + ] + } + } + + return { res: canvas }; + }); + + nodeBuilder.setUI(ui); + + nodeBuilder.addInput("Blink matrix", "transform", "Transform"); + nodeBuilder.addOutput("Blink clump", "res", "Result"); + }, "inputText": (context) => { const nodeBuilder = context.instantiate("Blink/Input", "inputText"); nodeBuilder.setTitle("Blink Text"); diff --git a/blix-plugins/blink/webview/atom.ts b/blix-plugins/blink/webview/atom.ts index 4ab29ab8..dd4438d0 100644 --- a/blix-plugins/blink/webview/atom.ts +++ b/blix-plugins/blink/webview/atom.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import type { Asset, Atom, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; -import { diffAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; +import type { Asset, Atom, CurveAsset, CurvePoint, ImageAtom, PaintAtom, ShapeAtom, TextAtom } from "./types"; +import { diffAtom, diffCurveAtom, diffImageAtom, diffPaintAtom, diffShapeAtom, diffTextAtom } from './diff'; import { type HierarchyAtom, randomId } from './render'; export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: string]: Asset } | undefined, atom: Atom, prevAtom: HierarchyAtom | undefined, textures: { [key: string]: PIXI.Texture }): { @@ -20,7 +20,8 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: } if (assets[atom.assetId] && assets[atom.assetId].type === "image") { - const sprite = PIXI.Sprite.from(textures[assets[atom.assetId].data]); + const textureId = assets[atom.assetId].data as string; + const sprite = PIXI.Sprite.from(textures[textureId]); sprite.anchor.x = 0.5; sprite.anchor.y = 0.5; @@ -99,5 +100,61 @@ export function renderAtom(assets: { [key: string]: Asset }, prevAssets: { [key: } return { pixiAtom: null, changed: true }; + + case "curve": + const curveDiff = atomsDiffer || diffCurveAtom(atom, prevAtom as PaintAtom, assets, prevAssets); + if (!curveDiff) { + return { pixiAtom: prevAtom.container, changed: false }; + } + + const curveContainer = new PIXI.Container(); + const curve = new PIXI.Graphics(); + + curve.beginFill(atom.fill, atom.fillAlpha); + curve.lineStyle(atom.strokeWidth, atom.stroke, atom.strokeAlpha); + + if (assets[atom.assetId] && assets[atom.assetId].type === "curve") { + // const path = (assets[atom.assetId] as CurveAsset).data CurvePoint[]; + + const path = [ + { + control1: { x: 50, y: 10 }, + control2: { x: 10, y: 300 }, + point: { x: 0, y: 0 }, + }, + { + control1: { x: 50, y: 10 }, + control2: { x: 10, y: 300 }, + point: { x: 400, y: 400 }, + }, + { + control1: { x: 400, y: 500 }, + control2: { x: 10, y: 500 }, + point: { x: 50, y: 10 }, + }, + ]; + + if (path.length > 0) { + curve.moveTo(path[0].point.x, path[0].point.y); + + for (let i = 1; i < path.length; i++) { + curve.bezierCurveTo( + path[i].control1.x, + path[i].control1.y, + path[i].control2.x, + path[i].control2.y, + path[i].point.x, + path[i].point.y + ); + } + } + } + + curve.endFill(); + + curveContainer.addChild(curve); + curveContainer.name = `CurveContainer(${randomId()})`; + + return { pixiAtom: curveContainer, changed: true }; } } \ No newline at end of file diff --git a/blix-plugins/blink/webview/diff.ts b/blix-plugins/blink/webview/diff.ts index 4836cdb7..501491b7 100644 --- a/blix-plugins/blink/webview/diff.ts +++ b/blix-plugins/blink/webview/diff.ts @@ -105,8 +105,13 @@ export function diffImageAtom(a1: ImageAtom, a2: ImageAtom, assets1: { [key: str return false; } -export function diffBlobAtom(a1: BlobAtom, a2: BlobAtom) { +export function diffCurveAtom(a1: BlobAtom, a2: BlobAtom, assets1: { [key: string]: Asset }, assets2: { [key: string]: Asset }) { if (a1.assetId !== a2.assetId) return true; + if (a1.fill !== a2.fill) return true; + if (a1.fillAlpha !== a2.fillAlpha) return true; + if (a1.stroke !== a2.stroke) return true; + if (a1.strokeAlpha !== a2.strokeAlpha) return true; + if (a1.strokeWidth !== a2.strokeWidth) return true; return false; } diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index 8cede402..ea43c5c1 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -33,11 +33,28 @@ export type BlinkCanvas = { content: Clump | null; } -export type Asset = { - class: "asset"; - type: "image" | "blob"; - data: any; -}; +export type Asset = { class: "asset" } & (ImageAsset | CurveAsset); + +export type ImageAsset = { + type: "image"; + data: string; +} + +export type CurveAsset = { + type: "curve" + data: { + id: string; + path: CurvePoint[]; + } +} + +export type CurvePoint = { + control1: Vec2; + control2: Vec2; + point: Vec2; +} + +export type Vec2 = { x: number, y: number }; export type Clump = { class: "clump"; @@ -51,9 +68,9 @@ export type Clump = { }; export type Transform = { - position: { x: number, y: number }; + position: Vec2; rotation: number; - scale: { x: number, y: number }; + scale: Vec2; } export type Filter = { @@ -106,16 +123,20 @@ export function getPixiFilter(filter: Filter) { } // A single indivisible unit of a clump (E.g. image, shape, text etc.) -export type Atom = { class: "atom", nodeUUID: string } & (ImageAtom | ShapeAtom | TextAtom | PaintAtom | BlobAtom); +export type Atom = { class: "atom", nodeUUID: string } & (ImageAtom | ShapeAtom | TextAtom | PaintAtom | CurveAtom); export type ImageAtom = { type: "image"; assetId: string; }; -export type BlobAtom = { - type: "blob"; - blob: "image"; //TODO: Add more options +export type CurveAtom = { + type: "curve"; assetId: string; + fill: number; + fillAlpha: number; + stroke: number; + strokeAlpha: number; + strokeWidth: number; } export type ShapeAtom = { diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index e6f0f562..8f3ccb0e 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -248,6 +248,7 @@ color: white; border-radius: 2px; padding: 0.2em; + left: 110%; top: -3.5em; } .outlineText { From 2c245dcd0d118daba340a6905c3b3287e5153830 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Mon, 25 Sep 2023 18:08:48 +0200 Subject: [PATCH 144/209] Add export to cache and glfx plugin --- blix-plugins/glfx-plugin/webview/App.svelte | 22 +++++++++++++ src/frontend/ui/tiles/Assets.svelte | 34 +++++++++++++++++++-- src/frontend/ui/tiles/Media.svelte | 3 +- src/frontend/ui/tiles/WebView.svelte | 2 +- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/blix-plugins/glfx-plugin/webview/App.svelte b/blix-plugins/glfx-plugin/webview/App.svelte index f548ca8d..23905275 100644 --- a/blix-plugins/glfx-plugin/webview/App.svelte +++ b/blix-plugins/glfx-plugin/webview/App.svelte @@ -12,6 +12,11 @@ let image; let texture; + + const send = (message, data) => { + window.api.send(message, data); + } + async function updateImage(src) { return new Promise((resolve, reject) => { image = new Image(); @@ -83,8 +88,24 @@ // canvas.draw(texture).update(); } + function exportImage(){ + canvas.update(); + canvas.toBlob(async (blob) => { + const metadata = { + contentType: "image/png", + name: `Blink Export ${Math.floor(100000 * Math.random())}` + }; + send("exportResponse", {cacheUUID: await window.cache.write(blob, metadata)}); + }, "image/png"); + } + onMount(async () => { canvasContainer.appendChild(canvas); + + window.api.on("export", async () => { + exportImage(); + // send("exportResponse", "exported"); + }) }); onDestroy(() => { @@ -110,6 +131,7 @@ padding: 0px; margin: 4em; text-align: center; + overflow: none; } code { diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index c8796cb6..463cc134 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -53,6 +53,17 @@ const blobs: { [key: CacheUUID]: { blob: Blob; url: string } } = {}; + async function exportCache(UUID: CacheUUID) { + const blob = new Blob([await cacheStore.get(UUID)], { + type: $cacheStore[UUID].contentType, + }); + const link = document.createElement("a"); + link.download = $cacheStore[UUID].name ?? "export.png"; + link.href = URL.createObjectURL(blob); + link.click(); + link.remove(); + } + // let barrier = 0; async function getBlobURL(uuid: CacheUUID, type: string): Promise { if (blobs[uuid]) return blobs[uuid].url; @@ -82,12 +93,21 @@ {/await}
{$cacheStore[uuid].name ?? "-"}
{$cacheStore[uuid].contentType}
+
{:else}
{uuid.slice(0, 8)}
{$cacheStore[uuid].name ?? "-"}
{$cacheStore[uuid].contentType}
+ +
{/if} {/each} @@ -126,6 +146,14 @@ z-index: 100; } + .exportButton { + font-size: 0.6em; + height: 20px; + width: 50%; + margin-left: auto; + margin-right: auto; + } + .itemsBox { display: flex; flex-wrap: wrap; @@ -139,9 +167,9 @@ .item { display: grid; - grid-template-rows: 70% 15% 15%; - width: 100px; - height: 100px; + grid-template-rows: 55% 15% 15% 15%; + width: 120px; + height: 130px; min-width: 100px; min-height: 100px; border: 1px solid #2a2a3f; diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index dac6382a..a3f597a2 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -151,7 +151,7 @@ on:keydown="{null}" class="flex h-7 select-none items-center justify-center rounded-md border border-zinc-600 bg-zinc-800/80 p-2 text-zinc-400 hover:bg-zinc-700 active:bg-zinc-800/50" > - Export + Export To Cache - {#each Object.keys(items) as itemKey} { + const now = performance.now(); + if (!first) { + if (now - prev >= 1000) { + dispatch("inputInteraction", { id: config.componentId, value }); + } + } else { + first = false; + } + prev = now; + }); + }
diff --git a/src/frontend/ui/utils/graph/nodeUIComponents/Dropdown.svelte b/src/frontend/ui/utils/graph/nodeUIComponents/Dropdown.svelte index 97587a54..aa24fd6c 100644 --- a/src/frontend/ui/utils/graph/nodeUIComponents/Dropdown.svelte +++ b/src/frontend/ui/utils/graph/nodeUIComponents/Dropdown.svelte @@ -26,7 +26,6 @@ {#if Object.keys(items).length > 0} {#key inputStore.inputs[config.componentId]} - {#each Object.keys(items) as itemKey} - + {#if !$blixStore.production} + + {:else} + + {/if} {/each} {/key} {:else} {/if} From 25cf59228aafc7fe34d4ad178f11ba8ea3d5a4e7 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Mon, 25 Sep 2023 20:29:06 +0200 Subject: [PATCH 149/209] Settings.ts fully tested --- .../electron/utils/settings.spec.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/unit-tests/electron/utils/settings.spec.ts b/tests/unit-tests/electron/utils/settings.spec.ts index 0ed1563c..5a91af51 100644 --- a/tests/unit-tests/electron/utils/settings.spec.ts +++ b/tests/unit-tests/electron/utils/settings.spec.ts @@ -1,5 +1,6 @@ -import {setSecret,settings,getSecret,getSecrets} from "../../../../src/electron/utils/settings"; +import {setSecret,settings,getSecret,getSecrets, clearSecret, setRecentProjects, getRecentProjects, decryptWithSafeStorage} from "../../../../src/electron/utils/settings"; import { safeStorage } from "electron"; +import { recentProject } from "../../../../src/shared/types"; let flag = true; // Used to flip isEncryptionAvailable() return value @@ -63,5 +64,37 @@ describe("Testing settings.ts", () => { expect(settings["cookedKey"]).toBe("cookedValue"); }) + it("Test remaining functions", () => { + const invalidTypeInput = 42 as unknown as string; + expect(decryptWithSafeStorage(invalidTypeInput)).toBe(""); + + expect(getSecret(invalidTypeInput)).toBe(""); + + + setSecret("key", "value"); + clearSecret("key"); + expect(settings.get("secrets.key")).toBe(""); + + const projects : recentProject[] = [ + { + path: "path", + lastEdited: "lastEdited" + } + ]; + setRecentProjects(projects); + expect(settings.get("recentProjects")).toEqual(projects); + + expect(getRecentProjects()).toEqual(projects); + + settings.set("secrets","ay"); + expect(getSecrets()).toEqual({}); + + + const nextInvalid = undefined as unknown as string; + expect(decryptWithSafeStorage(nextInvalid)).toBe(""); + flag = false; + expect(decryptWithSafeStorage(nextInvalid)).toBe(""); + + }) }); \ No newline at end of file From 4430ed93bc5b4042ee93728f82a660e241a6d318 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Mon, 25 Sep 2023 21:03:05 +0200 Subject: [PATCH 150/209] Initial TypeClassRegistry tests --- .../lib/registries/TypeclassRegistry.ts | 32 ++++++++ .../lib/registries/TypeClassRegistry.spec.ts | 81 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts diff --git a/src/electron/lib/registries/TypeclassRegistry.ts b/src/electron/lib/registries/TypeclassRegistry.ts index b4902fb4..15d35b01 100644 --- a/src/electron/lib/registries/TypeclassRegistry.ts +++ b/src/electron/lib/registries/TypeclassRegistry.ts @@ -46,6 +46,16 @@ export class TypeclassRegistry implements Registry { }); } + /** + * Adds a typeclass instance to the registry. + * The instance will not be added if it already exists, unless `force` is true. + * The instance must be defined. + * This function will also notify the main window of the change. + * @param instance + * @param force default : false + * @returns + */ + addInstance(instance: Typeclass, force = false): void { if (!instance) { logger.warn("Invalid Typeclass"); @@ -59,10 +69,20 @@ export class TypeclassRegistry implements Registry { this.blix.mainWindow?.apis.commandClientApi.registryChanged(this.getTypeclasses()); } + /** + * @returns All typeclasses in the registry. + */ getRegistry() { return { ...this.typeclasses }; } + // ADD DOC + /** + * Inserts a new converter into the registry's converter array + * @param from input type + * @param to output type + * @param converter converter to be inserted + */ addConverter(from: TypeclassId, to: TypeclassId, converter: TypeConverter): void { if (!this.converters[from]) { this.converters[from] = {}; @@ -71,6 +91,13 @@ export class TypeclassRegistry implements Registry { } // Returns a composite converter from `from` to `to` + /** + * Searches for a composite converter from `from` to `to`. If none is found, returns null. + * @param from input type + * @param to output type + * @param depth Specifies how deep the search should go. Default is 2. + * @returns Returns a composite converter from `from` to `to` + */ resolveConversion(from: TypeclassId, to: TypeclassId, depth?: number): TypeConverter | null { depth = depth ?? MAX_SEARCH_DEPTH; if (depth <= 0) return null; @@ -97,6 +124,11 @@ export class TypeclassRegistry implements Registry { } // Returns true if the `from` type is compatible with the `to` type + /** + * Returns true if the `from` type is compatible with the `to` type + * @param from + * @param to + */ checkTypesCompatible(from: TypeclassId, to: TypeclassId): boolean { // Handle base cases if (from === to || from === "" || to === "") return true; diff --git a/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts b/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts new file mode 100644 index 00000000..babfbdbc --- /dev/null +++ b/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts @@ -0,0 +1,81 @@ + +import { Typeclass, TypeclassRegistry } from "../../../../../src/electron/lib/registries/TypeclassRegistry"; +import { Blix } from "../../../../../src/electron/lib/Blix"; +import { MediaDisplayType } from "../../../../../src/shared/types"; + +jest.mock("electron", () => ({ + app: { + getPath: jest.fn((path) => { + return "test/electron"; + }), + getName: jest.fn(() => { + return "TestElectron"; + }), + getVersion: jest.fn(() => { + return "v1.1.1"; + }), + getAppPath: jest.fn(() => { + return "test/electron"; + }) + }, + ipcMain: { + on: jest.fn() + } +})); + +jest.mock('ws', () => { + return { + WebSocketServer: jest.fn().mockImplementation(() => { + return { + on: jest.fn() + } + } + ) + } +}); + +jest.mock('../../../../../src/electron/lib/plugins/PluginManager') + +describe("Test TypeClassRegistry", () => { + + let typeclassRegistry : TypeclassRegistry; + let blix : Blix; + + beforeEach(() => { + blix = new Blix(); + typeclassRegistry = new TypeclassRegistry(blix); + }); + + + test("Test constructor", () => { + expect(typeclassRegistry).toBeDefined(); + }); + + test("Test addInstance and getRegistry", () => { + const invalidType = undefined as unknown as Typeclass; + expect(typeclassRegistry.addInstance(invalidType)).toReturn; + expect(typeclassRegistry.getTypeclasses().length).toBe(0); + + expect(typeclassRegistry.addInstance({id: "number", description: "test",subtypes: [], mediaDisplayConfig: (data: string) => ({ + displayType: MediaDisplayType.TextBox, + props: { + content: data, + }, + contentProp: "content", + }), + })).toReturn; + expect(typeclassRegistry.getTypeclasses().length).toBe(0); + + + expect(typeclassRegistry.getRegistry()).toEqual(typeclassRegistry["typeclasses"]) + }); + + test("Test converters functionality", () => { + typeclassRegistry.addConverter("number", "string", (data: number) => { + return data.toString(); + }); + + expect(typeclassRegistry.resolveConversion("number", "string")).toBeDefined(); // else returns null + }); + +}); \ No newline at end of file From a4299b9cbd2fae55be8e0c2dcfca6127648a317a Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 25 Sep 2023 22:06:56 +0200 Subject: [PATCH 151/209] Clean up exporting of assets --- blix-plugins/glfx-plugin/src/main.cjs | 4 +- blix-plugins/threlte-plugin/src/main.cjs | 54 +++++++++--------- src/electron/lib/cache/CacheManager.ts | 33 ++++++++++- src/frontend/lib/stores/CacheStore.ts | 11 ++++ src/frontend/ui/tiles/Assets.svelte | 72 +++++++++++++++++++----- src/frontend/ui/tiles/Media.svelte | 8 ++- src/shared/types/cache.ts | 7 ++- 7 files changed, 141 insertions(+), 48 deletions(-) diff --git a/blix-plugins/glfx-plugin/src/main.cjs b/blix-plugins/glfx-plugin/src/main.cjs index f9c1d030..0cd01021 100644 --- a/blix-plugins/glfx-plugin/src/main.cjs +++ b/blix-plugins/glfx-plugin/src/main.cjs @@ -165,8 +165,8 @@ const nodes = { "inputGLFXCache": (context) => { const nodeBuilder = context.instantiate("Input", "inputGLFXImage"); - nodeBuilder.setTitle("Input GLFX image"); - nodeBuilder.setDescription("Provides an cache input and returns a single image output"); + nodeBuilder.setTitle("GLFX Image"); + nodeBuilder.setDescription("Takes a cache object as input and outputs a GLFX image"); nodeBuilder.define(async (input, uiInput, from) => { return { "res": { src: uiInput["cacheid"] } }; diff --git a/blix-plugins/threlte-plugin/src/main.cjs b/blix-plugins/threlte-plugin/src/main.cjs index 3f9d1b6e..55f291b3 100644 --- a/blix-plugins/threlte-plugin/src/main.cjs +++ b/blix-plugins/threlte-plugin/src/main.cjs @@ -68,33 +68,33 @@ const glfxNodes = { const nodes = { // ...glfxNodes, - "inputGLFXImage": (context) => { - const nodeBuilder = context.instantiate("Input/Other", "inputGLFXImage"); - nodeBuilder.setTitle("Input GLFX image"); - nodeBuilder.setDescription("Provides an image input and returns a single image output"); - - nodeBuilder.define(async (input, uiInput, from) => { - return { "res": { src: uiInput["imagePicker"] } }; - }); - - const ui = nodeBuilder.createUIBuilder(); - ui.addFilePicker({ - componentId: "imagePicker", - label: "Pick an image", - defaultValue: "", - triggerUpdate: true, - }, {}); - // ui.addCachePicker({ - // componentId: "cacheid", - // label: "Pick an image", - // defaultValue: "", - // triggerUpdate: true, - // }, {}) - - nodeBuilder.setUI(ui); - - nodeBuilder.addOutput("GLFX image", "res", "Result"); - }, + // "inputGLFXImage": (context) => { + // const nodeBuilder = context.instantiate("Input/Other", "inputGLFXImage"); + // nodeBuilder.setTitle("Input GLFX image"); + // nodeBuilder.setDescription("Provides an image input and returns a single image output"); + + // nodeBuilder.define(async (input, uiInput, from) => { + // return { "res": { src: uiInput["imagePicker"] } }; + // }); + + // const ui = nodeBuilder.createUIBuilder(); + // ui.addFilePicker({ + // componentId: "imagePicker", + // label: "Pick an image", + // defaultValue: "", + // triggerUpdate: true, + // }, {}); + // // ui.addCachePicker({ + // // componentId: "cacheid", + // // label: "Pick an image", + // // defaultValue: "", + // // triggerUpdate: true, + // // }, {}) + + // nodeBuilder.setUI(ui); + + // nodeBuilder.addOutput("GLFX image", "res", "Result"); + // }, } const commands = {} diff --git a/src/electron/lib/cache/CacheManager.ts b/src/electron/lib/cache/CacheManager.ts index dc6cf097..92b962f3 100644 --- a/src/electron/lib/cache/CacheManager.ts +++ b/src/electron/lib/cache/CacheManager.ts @@ -15,8 +15,10 @@ import WebSocket, { WebSocketServer } from "ws"; // import { Server } from "socket.io"; import { randomBytes } from "crypto"; import logger from "../../utils/logger"; -import { ipcMain } from "electron"; -import { showOpenDialog } from "../../utils/dialog"; +import { app, ipcMain } from "electron"; +import { showSaveDialog } from "../../utils/dialog"; +import { join } from "path"; +import { writeFile } from "fs/promises"; // The main interface which this manager must expose is: // - get(cacheUUID: CacheUUID): CacheObject @@ -92,6 +94,11 @@ export class CacheManager { this.listeners.add(socket); socket.send(JSON.stringify(this.cacheUpdateNotification)); // Send initial cache update break; + case "export-cache": + if ("ids" in data && Array.isArray(data.ids)) { + this.export(data.ids as CacheUUID[]); + } + break; default: logger.info("Unknown cache message type", data.type); } @@ -170,4 +177,26 @@ export class CacheManager { delete(cacheUUID: CacheUUID) { delete this.cache[cacheUUID]; } + + async export(ids: CacheUUID[]) { + for (const id of ids) { + const cacheObject = this.cache[id]; + + if (!cacheObject) { + continue; + } + + const path = await showSaveDialog({ + title: "Save Asset", + defaultPath: join(app.getPath("downloads"), cacheObject.metadata.name || "export.png"), + properties: ["createDirectory"], + }); + + if (!path) { + continue; + } + + await writeFile(path, cacheObject.data); + } + } } diff --git a/src/frontend/lib/stores/CacheStore.ts b/src/frontend/lib/stores/CacheStore.ts index 9c2aa56d..551e0a1f 100644 --- a/src/frontend/lib/stores/CacheStore.ts +++ b/src/frontend/lib/stores/CacheStore.ts @@ -132,6 +132,17 @@ class CacheStore { }); } + public async exportCache(ids: CacheUUID[]): Promise { + const messageId = randomMessageId(); + + const payload = JSON.stringify({ type: "export-cache", ids, messageId }); + + return new Promise((resolve, reject) => { + this.lobby[messageId] = resolve; + this.ws.send(payload); + }); + } + public get subscribe() { return this.cacheStore.subscribe; } diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index 463cc134..d9e72769 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -16,6 +16,11 @@ faPizzaSlice, } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; + import { toastStore } from "lib/stores/ToastStore"; + + let selectedCacheItems: CacheUUID[] = []; + // Not used at the moment + let controlKeyDown = false; const noContentIcons = [ faBacon, @@ -36,6 +41,26 @@ return noContentIcons[Math.floor(Math.random() * noContentIcons.length)]; } + function handleKeyDown(event: KeyboardEvent) { + if (event.ctrlKey || event.metaKey) { + controlKeyDown = true; + } + } + + function handleItemOnClick(id: CacheUUID) { + if (selectedCacheItems.includes(id)) { + selectedCacheItems = selectedCacheItems.filter((item) => item !== id); + } else { + selectedCacheItems = [...selectedCacheItems, id]; + } + } + + function handleKeyUp(event: KeyboardEvent) { + if (!event.ctrlKey || !event.metaKey) { + controlKeyDown = false; + } + } + async function requestFileAccess() { try { const handle = await window.showOpenFilePicker(); @@ -53,15 +78,13 @@ const blobs: { [key: CacheUUID]: { blob: Blob; url: string } } = {}; - async function exportCache(UUID: CacheUUID) { - const blob = new Blob([await cacheStore.get(UUID)], { - type: $cacheStore[UUID].contentType, - }); - const link = document.createElement("a"); - link.download = $cacheStore[UUID].name ?? "export.png"; - link.href = URL.createObjectURL(blob); - link.click(); - link.remove(); + async function exportAsset() { + if (selectedCacheItems.length === 0) { + toastStore.trigger({ message: "No asset selected.", type: "warn" }); + return; + } + + await cacheStore.exportCache(selectedCacheItems); } // let barrier = 0; @@ -83,7 +106,11 @@
{#each Object.keys($cacheStore) as uuid} {#if ["image/png", "image/jpeg"].includes($cacheStore[uuid].contentType)} -
+
{#await getBlobURL(uuid, $cacheStore[uuid].contentType) then src}
Cached Image {uuid.slice(0, 8)} @@ -93,10 +120,6 @@ {/await}
{$cacheStore[uuid].name ?? "-"}
{$cacheStore[uuid].contentType}
-
{:else}
@@ -128,9 +151,28 @@ > Add Asset
+
+ {#if selectedCacheItems.length > 1} + Export Assets + {:else} + Export Asset + {/if} +
+ + diff --git a/src/frontend/ui/tiles/WebView.svelte b/src/frontend/ui/tiles/WebView.svelte index 9ce994ba..9d7cca51 100644 --- a/src/frontend/ui/tiles/WebView.svelte +++ b/src/frontend/ui/tiles/WebView.svelte @@ -4,6 +4,7 @@ import TextBox from "../../ui/utils/mediaDisplays/TextBox.svelte"; import { type RendererId } from "../../../shared/types/typeclass"; import type { TweakApi } from "../../lib/webview/TweakApi"; + import { blixStore } from "../../lib/stores/BlixStore"; let webview: Electron.WebviewTag | null = null; @@ -87,10 +88,12 @@
{#await asyncSrc then src} {#if src !== null} -
- - -
+ {#if !$blixStore.production} + + {/if} From 99f31ff2a70545781ce1556dcbe6a712ac0de51d Mon Sep 17 00:00:00 2001 From: Klairgo Date: Mon, 25 Sep 2023 23:07:09 +0200 Subject: [PATCH 155/209] Fix glfx load bug --- blix-plugins/glfx-plugin/webview/App.svelte | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/blix-plugins/glfx-plugin/webview/App.svelte b/blix-plugins/glfx-plugin/webview/App.svelte index 6359d371..d9105edb 100644 --- a/blix-plugins/glfx-plugin/webview/App.svelte +++ b/blix-plugins/glfx-plugin/webview/App.svelte @@ -49,16 +49,22 @@ $: canvasUpdate(canvasWidth); async function redraw(media) { + if(!media.src){ + // image = null; + // texture = null; + // canvas.draw(texture).update(); + var gl = canvas.getContext("webgl"); + + // Clear the canvas to a specified color + // gl.clearColor(0.0, 0.0, 0.0, 1.0); // This sets the clear color to black + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + return; + } if (media?.ops && canvas && texture) { if (media.src != lastMediaSrc) { - if (media.src === "") { - console.log("here"); - image = null; - texture = null; - canvas.draw(texture).update(); - return; - } await updateImage(media.src); + lastMediaSrc = media.src; reloadTexture(); } @@ -79,6 +85,7 @@ else{ if(media.src){ await updateImage(media.src); + lastMediaSrc = media.src; reloadTexture(); const dimRatio = image.height / image.width; let buffer = canvas.draw(texture, canvasWidth, canvasWidth*dimRatio); From ec04c8bd6295d285b11edbee693c80dfadc79eef Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 25 Sep 2023 23:19:11 +0200 Subject: [PATCH 156/209] Fix some UI button widths --- src/frontend/ui/tiles/Assets.svelte | 2 +- src/frontend/ui/tiles/Media.svelte | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index e6d5d21a..59d8469d 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -110,7 +110,7 @@ } -
+
{#if Object.keys($cacheStore).length > 0}
{#each Object.keys($cacheStore) as uuid} diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index a40e7fe6..9bb89ea9 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -98,19 +98,6 @@ } } - async function exportCache(e: CustomEvent) { - const blob = new Blob([await cacheStore.get(e.detail[0].cacheUUID)], { - type: $cacheStore[e.detail[0].cacheUUID].contentType, - }); - const link = document.createElement("a"); - link.download = $cacheStore[e.detail[0].cacheUUID].name ?? "export.png"; - link.href = URL.createObjectURL(blob); - link.click(); - link.remove(); - - // console.log(await cacheStore.get(e.detail.cacheUUID)); - } - function getDisplayProps(media: DisplayableMediaOutput) { let res = media.display.props; if (media.display.contentProp !== null) res[media.display.contentProp] ??= media.content; // If content nullish, use default value @@ -155,7 +142,7 @@
Save Asset
From a3cb5fffcc864ece3dec94f394973af7b3acb585 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Mon, 25 Sep 2023 23:35:55 +0200 Subject: [PATCH 157/209] Fix test --- blix-plugins/glfx-plugin/src/main.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blix-plugins/glfx-plugin/src/main.cjs b/blix-plugins/glfx-plugin/src/main.cjs index bc344cd9..9cbdbba9 100644 --- a/blix-plugins/glfx-plugin/src/main.cjs +++ b/blix-plugins/glfx-plugin/src/main.cjs @@ -162,8 +162,8 @@ Object.keys(glfxNodes).forEach((key) => { const nodes = { ...glfxNodes, - "inputGLFXCache": (context) => { - const nodeBuilder = context.instantiate("Input", "inputGLFXImage"); + "GLFXImage": (context) => { + const nodeBuilder = context.instantiate("Input", "GLFXImage"); nodeBuilder.setTitle("GLFX Image"); nodeBuilder.setDescription("Takes a cache object as input and outputs a GLFX image"); From 35f16684cde4c03dbee2a7fac79333a22cb32b02 Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Mon, 25 Sep 2023 23:44:50 +0200 Subject: [PATCH 158/209] Fix Blink deletion of child clump not updating scene --- blix-plugins/blink/webview/render.ts | 37 +++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 32109121..9c4f349b 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -37,6 +37,8 @@ type SelectionState = { prevMousePos: PIXI.Point }; +const MAX_CHILDREN_IN_CLUMP = 5; + let selection: SelectionState = null; let oldSceneStructure = ""; @@ -138,6 +140,7 @@ export async function renderScene( const { pixiClump, changed } = renderClump( blink, + 0, canvas.content, hierarchy?.content, canvas, @@ -166,6 +169,7 @@ export function renderCanvas(blink: PIXI.Application, root: Clump) {} function renderClump( blink: PIXI.Application, + index: number, clump: Clump, prevClump: HierarchyClump | undefined, canvas: BlinkCanvas, @@ -191,14 +195,23 @@ function renderClump( const hierarchyElements = []; if (clump.elements) { - for (let i = 0; i < clump.elements.length; i++) { + for (let i = 0; i < MAX_CHILDREN_IN_CLUMP; i++) { const child = clump.elements[i]; const prevChild = prevClump?.elements != null && prevClump.elements[i]; - if (child.class === "clump") { + if (child == null) { + //===== REMOVED CHILD =====// + if (prevChild) { + console.log("----> DELETED CHILD", i); + childChanged = true; + children.push({ changed: true, child: null }); + } + } + else if (child.class === "clump") { //===== CHILD CLUMP =====// const { pixiClump, changed } = renderClump( blink, + i, child as HierarchyClump, (prevChild?.class === "clump" ? prevChild : undefined) as HierarchyClump, canvas, @@ -253,20 +266,21 @@ function renderClump( // Create resClump if (newContainer) { resClump = new PIXI.Container(); - resClump.name = `Clump(${randomId()})`; + resClump.name = `Clump[${index}](${randomId()})`; // resClump.sortableChildren = true; addInteractivity(resClump, clump, viewport, send); } else { resClump = prevClump.container; } // Add content - if (resClump.children.length === 0) { + const prevContent = findChild(resClump, "content"); + if (prevContent == null) { content = new PIXI.Container(); content.name = `content(${randomId()})`; content.sortableChildren = true; resClump.addChild(content); - } else { content = resClump.getChildAt(0) as PIXI.Container; } + } else { content = prevContent as PIXI.Container; } content.visible = true; console.log(`==> ${newContainer ? "" : "NO "}NEW RESCLUMP (${resClump.name.split("(")[1].slice(0, 4)})`); @@ -278,6 +292,7 @@ function renderClump( if (true || newContainer) { content.removeChildren(); for (let i = 0; i < children.length; i++) { + if (children[i].child == null) continue; children[i].child.zIndex = children.length - i; console.log("=====> ADD CHILD", i, children[i].child.name); content.addChild(children[i].child); @@ -335,7 +350,7 @@ function renderClump( break; } } - if (appliedFilter) { + if (findChild(resClump, "FilterSprite") != null) { // If we've applied a filter (potentially in a previous step), hide the content content.visible = false; } @@ -371,6 +386,7 @@ function renderClump( if (diffs.has("mask")) { const mask = clump.mask != null ? renderClump( blink, + 0, clump.mask, undefined, canvas, @@ -474,4 +490,13 @@ function addInteractivity(container: PIXI.Container, clump: Clump, viewport: Vie // To get global mouse position at any point: // console.log("MOUSE", blink.renderer.plugins.interaction.pointer.global); +} + +function findChild(container: PIXI.Container, namePrefix: string): PIXI.DisplayObject | null { + for (let i = 0; i < container.children.length; i++) { + if (container.children[i].name.includes(namePrefix)) { + return container.children[i]; + } + } + return null; } \ No newline at end of file From 69498e20465a5c07d5a31741e41016e0ced7815e Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Tue, 26 Sep 2023 00:35:47 +0200 Subject: [PATCH 159/209] Add canvas configuration to Blink --- blix-plugins/blink/src/main.cjs | 83 ++++++++++++++++++++++---- blix-plugins/blink/webview/App.svelte | 86 ++++++++++++++++++--------- blix-plugins/blink/webview/diff.ts | 25 ++++++++ blix-plugins/blink/webview/render.ts | 1 + blix-plugins/blink/webview/types.ts | 9 +++ 5 files changed, 165 insertions(+), 39 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 16b1fb77..75ff8ad7 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -763,6 +763,64 @@ const nodes = { nodeBuilder.addInput("Blink clump", "clump", "Clump"); nodeBuilder.addInput("Blink clump", "mask", "Mask"); nodeBuilder.addOutput("Blink clump", "res", "Result"); + }, + "canvasConfig": (context) => { + const nodeBuilder = context.instantiate("Blink/Utils", "canvasConfig"); + nodeBuilder.setTitle("Canvas Config"); + nodeBuilder.setDescription("Configure the Blink canvas"); + + const ui = nodeBuilder.createUIBuilder(); + ui.addTextInput({ + componentId: "exportName", + label: "Canvas Export Name", + defaultValue: "Blink Export", + triggerUpdate: true, + }, {}); + + ui.addNumberInput( + { + componentId: "canvasW", + label: "Canvas Width", + defaultValue: 1920, + triggerUpdate: true, + }, + { step: 1 } + ); + ui.addNumberInput( + { + componentId: "canvasH", + label: "Canvas Height", + defaultValue: 1080, + triggerUpdate: true, + }, + { step: 1 } + ); + ui.addColorPicker({ + componentId: "canvasColor", + label: "Canvas Background", + defaultValue: "#ffffffff", + triggerUpdate: true, + }, {}) + + nodeBuilder.define(async (input, uiInput, from) => { + // Apply mask to outermost clump + const canvas = { + ...input["clump"], + config: { + canvasDims: { w: uiInput["canvasW"], h: uiInput["canvasH"] }, + canvasColor: colorHexToNumber(uiInput["canvasColor"]), + canvasAlpha: colorHexToAlpha(uiInput["canvasColor"]), + + exportName: uiInput["exportName"] + } + } + + return { "canvas": canvas }; + }); + + nodeBuilder.setUI(ui); + nodeBuilder.addInput("Blink clump", "clump", "Clump"); + nodeBuilder.addOutput("Blink canvas", "canvas", "Canvas"); } }; const commands = {}; @@ -770,15 +828,7 @@ const tiles = {}; function init(context) { - const glfxTypeBuilder = context.createTypeclassBuilder("Blink clump"); - // glfxTypeBuilder.setToConverters({ - // "image": (value) => ({}) - // }); - // glfxTypeBuilder.setFromConverters({ - // "image": (value) => ({}) - // }); - - glfxTypeBuilder.setDisplayConfigurator((data) => { + const configurator = (data) => { return { displayType: "webview", props: { @@ -787,7 +837,20 @@ function init(context) { }, contentProp: "media" }; - }); + } + + const clumpTypeBuilder = context.createTypeclassBuilder("Blink clump"); + clumpTypeBuilder.setDisplayConfigurator(configurator); + + // clumpTypeBuilder.setToConverters({ + // "image": (value) => ({}) + // }); + // clumpTypeBuilder.setFromConverters({ + // "image": (value) => ({}) + // }); + + const canvasTypeBuilder = context.createTypeclassBuilder("Blink canvas"); + canvasTypeBuilder.setDisplayConfigurator(configurator); } module.exports = { diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index 9d55072c..41161665 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -4,15 +4,21 @@ import { onDestroy, onMount, tick } from "svelte"; import { type Writable } from "svelte/store"; import { renderScene } from "./render"; - import { WindowWithApis, type BlinkCanvas } from "./types"; + import { WindowWithApis, type BlinkCanvas, BlinkCanvasConfig } from "./types"; import Debug from "./Debug.svelte"; + import { diffCanvasConfig } from "./diff"; export let media: Writable; export let send: (msg: string, data: any) => void; let imgCanvasInitialPadding = 100; - let imgCanvasBlockW = 1920; - let imgCanvasBlockH = 1080; + let canvasConfig: BlinkCanvasConfig = { + canvasDims: { w: 1920, h: 1080 }, + canvasColor: 0xffffff, + canvasAlpha: 1, + + exportName: "Blink Export" + } let blink: PIXI.Application; let pixiCanvas: HTMLCanvasElement; @@ -86,45 +92,65 @@ }); //===== CREATE BASE LAYOUT =====// - imgCanvasInitialPadding = 100; - imgCanvasBlockW = 1920; - imgCanvasBlockH = 1080; - let imgCanvas = new PIXI.Container(); imgCanvas.name = "imgCanvas"; - // canvas - let imgCanvasBlock = new PIXI.Graphics(); - imgCanvasBlock.beginFill(0xffffff, 0.9); - imgCanvasBlock.drawRect(0, 0, imgCanvasBlockW, imgCanvasBlockH); + function createImageCanvasBlock() { + const { w: imgCanvasBlockW, h: imgCanvasBlockH } = canvasConfig.canvasDims; - // x-axis - imgCanvasBlock.lineStyle(); - imgCanvasBlock.moveTo(0, imgCanvasBlockH/2); - imgCanvasBlock.lineStyle(1, 0xff0000); - imgCanvasBlock.lineTo(imgCanvasBlockW, imgCanvasBlockH/2); + // canvas + let imgCanvasBlock = new PIXI.Graphics(); + imgCanvasBlock.beginFill(canvasConfig.canvasColor, canvasConfig.canvasAlpha); + imgCanvasBlock.drawRect(0, 0, imgCanvasBlockW, imgCanvasBlockH); - // y-axis - imgCanvasBlock.lineStyle(); - imgCanvasBlock.moveTo(imgCanvasBlockW/2, 0); - imgCanvasBlock.lineStyle(1, 0x00ff00); - imgCanvasBlock.lineTo(imgCanvasBlockW/2, imgCanvasBlockH); + // x-axis + imgCanvasBlock.lineStyle(); + imgCanvasBlock.moveTo(0, imgCanvasBlockH/2); + imgCanvasBlock.lineStyle(1, 0xff0000); + imgCanvasBlock.lineTo(imgCanvasBlockW, imgCanvasBlockH/2); - imgCanvasBlock.position.set(-imgCanvasBlockW/2, -imgCanvasBlockH/2); - imgCanvas.addChild(imgCanvasBlock); + // y-axis + imgCanvasBlock.lineStyle(); + imgCanvasBlock.moveTo(imgCanvasBlockW/2, 0); + imgCanvasBlock.lineStyle(1, 0x00ff00); + imgCanvasBlock.lineTo(imgCanvasBlockW/2, imgCanvasBlockH); + + imgCanvasBlock.position.set(-imgCanvasBlockW/2, -imgCanvasBlockH/2); + return imgCanvasBlock; + } + + const imgCanvasBlock = createImageCanvasBlock(); + imgCanvas.addChild(imgCanvasBlock); canvasBlock = imgCanvasBlock; viewport.addChild(imgCanvas); - const viewportFitX = imgCanvasBlockW + 2 * imgCanvasInitialPadding; - const viewportFitY = imgCanvasBlockH + 2 * imgCanvasInitialPadding; + const viewportFitX = canvasConfig.canvasDims.w + 2 * imgCanvasInitialPadding; + const viewportFitY = canvasConfig.canvasDims.h + 2 * imgCanvasInitialPadding; viewport.fit(true, viewportFitX, viewportFitY); - viewport.moveCenter(imgCanvasBlockW/2, imgCanvasBlockH/2); + viewport.moveCenter(canvasConfig.canvasDims.w/2, canvasConfig.canvasDims.h/2); //===== RENDER Blink =====// let hasCentered = false; media.subscribe(async (media) => { + //===== UPDATE CANVAS CONFIG =====// + if (media?.config != null) { + const configDiffs = diffCanvasConfig(canvasConfig, media.config); + + if (configDiffs.size > 0) { + canvasConfig = media.config; + + if (configDiffs.has("canvasBlock")) { + imgCanvas.removeChildren(); + const newImgCanvasBlock = createImageCanvasBlock(); + imgCanvas.addChild(newImgCanvasBlock); + canvasBlock = newImgCanvasBlock; + } + } + } + + //===== RENDER SCENE =====// const { success, scene } = await renderScene(blink, media, viewport, send); currScene = scene; @@ -133,8 +159,8 @@ window.dispatchEvent(new Event("resize")); if (success && !hasCentered) { - const viewportFitX = imgCanvasBlockW + 2 * imgCanvasInitialPadding; - const viewportFitY = imgCanvasBlockH + 2 * imgCanvasInitialPadding; + const viewportFitX = canvasConfig.canvasDims.w + 2 * imgCanvasInitialPadding; + const viewportFitY = canvasConfig.canvasDims.h + 2 * imgCanvasInitialPadding; viewport.fit(true, viewportFitX, viewportFitY); viewport.moveCenter(0, 0); @@ -169,6 +195,8 @@ async function exportImage() { if (!currScene || !canvasBlock) return; + const { w: imgCanvasBlockW, h: imgCanvasBlockH } = canvasConfig.canvasDims; + const bounds = currScene.getLocalBounds(); const frame = new PIXI.Rectangle(-bounds.x-imgCanvasBlockW/2, -bounds.y-imgCanvasBlockH/2, imgCanvasBlockW, imgCanvasBlockH); @@ -176,7 +204,7 @@ exportCanvas.toBlob((blob) => { const metadata = { contentType: "image/png", - name: `Blink Export ${Math.floor(100000 * Math.random())}` + name: `${canvasConfig.exportName} ${Math.floor(100000 * Math.random())}` }; (window as WindowWithApis).cache.write(blob, metadata); }, "image/png"); diff --git a/blix-plugins/blink/webview/diff.ts b/blix-plugins/blink/webview/diff.ts index 9f55bcd8..75a504e5 100644 --- a/blix-plugins/blink/webview/diff.ts +++ b/blix-plugins/blink/webview/diff.ts @@ -2,6 +2,7 @@ import type { HierarchyAtom, HierarchyClump } from "./render"; import type { Asset, Atom, + BlinkCanvasConfig, Clump, CurveAtom, Filter, @@ -147,3 +148,27 @@ export function diffPaintAtom(a1: PaintAtom, a2: PaintAtom) { if (a1.uuid !== a2.uuid) return true; return false; } + +export type CanvasConfigDiff = "canvasBlock" | "exportName"; + +export function diffCanvasConfig(c1: BlinkCanvasConfig, c2: BlinkCanvasConfig) { + const diffs = new Set(); + if (c1 == null && c2 == null) return new Set([]); // Vacuous case + if (c1 == null || c2 == null) + return new Set(["canvasBlock", "exportName"]); + + if ( + c1.canvasDims.w !== c2.canvasDims.w || + c1.canvasDims.h !== c2.canvasDims.h || + c1.canvasColor !== c2.canvasColor || + c1.canvasAlpha !== c2.canvasAlpha + ) { + diffs.add("canvasBlock"); + } + + if (c1.exportName !== c2.exportName) { + diffs.add("exportName"); + } + + return diffs; +} \ No newline at end of file diff --git a/blix-plugins/blink/webview/render.ts b/blix-plugins/blink/webview/render.ts index 9c4f349b..547644ca 100644 --- a/blix-plugins/blink/webview/render.ts +++ b/blix-plugins/blink/webview/render.ts @@ -152,6 +152,7 @@ export async function renderScene( // Update hierarchy hierarchy = { assets: canvas.assets, + config: undefined, content: { ...canvas.content, container: pixiClump, diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index 3bda5658..065217e8 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -30,9 +30,18 @@ export type WindowWithApis = Window & typeof globalThis & { export type BlinkCanvas = { assets: { [key: string]: Asset }; + config?: BlinkCanvasConfig; content: Clump | null; } +export type BlinkCanvasConfig = { + canvasDims: { w: number, h: number }; + canvasColor: number; + canvasAlpha: number; + + exportName: string; +}; + export type Asset = { class: "asset" } & (ImageAsset | CurveAsset); export type ImageAsset = { From fb8687357701d37fb05851db2397bbaf6cb11dea Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Tue, 26 Sep 2023 10:27:23 +0200 Subject: [PATCH 160/209] TypeclassRegistry tests done --- .../lib/registries/TypeclassRegistry.ts | 4 + .../lib/registries/TypeClassRegistry.spec.ts | 102 +++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/electron/lib/registries/TypeclassRegistry.ts b/src/electron/lib/registries/TypeclassRegistry.ts index 15d35b01..3c799581 100644 --- a/src/electron/lib/registries/TypeclassRegistry.ts +++ b/src/electron/lib/registries/TypeclassRegistry.ts @@ -138,6 +138,10 @@ export class TypeclassRegistry implements Registry { return res; } + /** + * This function currently returns an empty array : TODO + * @returns All typeclasses in the registry. Currently empty + */ getTypeclasses(): ICommand[] { const commands: ICommand[] = []; // for (const key in this.registry) { diff --git a/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts b/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts index babfbdbc..dd9fb3b2 100644 --- a/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts +++ b/tests/unit-tests/electron/lib/registries/TypeClassRegistry.spec.ts @@ -1,7 +1,9 @@ import { Typeclass, TypeclassRegistry } from "../../../../../src/electron/lib/registries/TypeclassRegistry"; import { Blix } from "../../../../../src/electron/lib/Blix"; -import { MediaDisplayType } from "../../../../../src/shared/types"; +import { MediaDisplayType,MediaOutput,DisplayableMediaOutput } from "../../../../../src/shared/types"; +import { type } from "os"; +import exp from "constants"; jest.mock("electron", () => ({ app: { @@ -71,11 +73,107 @@ describe("Test TypeClassRegistry", () => { }); test("Test converters functionality", () => { + typeclassRegistry["converters"] = {}; + typeclassRegistry.addConverter("number", "string", (data: number) => { return data.toString(); }); expect(typeclassRegistry.resolveConversion("number", "string")).toBeDefined(); // else returns null + + typeclassRegistry.addConverter("boolean", "number", (data: boolean) => { + return data ? 1 : 0; + }); + + console.log(typeclassRegistry.resolveConversion("boolean", "string")!(true)) + expect(typeclassRegistry.resolveConversion("boolean", "string")).toBeDefined(); // else returns null + + + expect(typeclassRegistry.resolveConversion("number", "a",1)).toBe(null); + + expect(typeclassRegistry.checkTypesCompatible("number", "string")).toBe(true); + + expect(typeclassRegistry.checkTypesCompatible("number", "number")).toBe(true); + + expect(typeclassRegistry.checkTypesCompatible("number", "a")).toBe(false); + + expect(typeclassRegistry.getTypeclasses()).toEqual([]); + }); + + it("Test displayable Media Output", () => { + + console.log(typeclassRegistry["typeclasses"]["string"]); + const output : MediaOutput = { + outputId: "00000", + outputNodeUUID: "12345", + graphUUID: "67890", + content: "test", + dataType: "string" + } + + const value = typeclassRegistry["typeclasses"]["string"]; + + const expected = value.mediaDisplayConfig(output.content); + + const displayableMediaOutput : DisplayableMediaOutput = { + ...output, + display: expected, + } + + expect(typeclassRegistry.getDisplayableMedia(output)).toBeDefined(); + expect(typeof typeclassRegistry.getDisplayableMedia(output)).toBe('object'); + expect(typeclassRegistry.getDisplayableMedia(output)).toEqual(displayableMediaOutput); + + output.dataType = "unregisteredType"; + + const newDisplayableMediaOutput : DisplayableMediaOutput = { + ...output, + display: { + displayType: MediaDisplayType.TextBox, + props: { + content: `INVALID TYPE: ${output.dataType}\nCONTENT: ${JSON.stringify(output.content)}`, + status: "error", + }, + contentProp: null, + }, + } + + expect(typeclassRegistry.getDisplayableMedia(output)).toBeDefined(); + expect(typeof typeclassRegistry.getDisplayableMedia(output)).toBe('object'); + expect(typeclassRegistry.getDisplayableMedia(output)).toEqual(newDisplayableMediaOutput); }); -}); \ No newline at end of file + + it("Test renderer", () => { + // console.log(typeclassRegistry["renderers"]) + expect(typeclassRegistry["renderers"]).toEqual({}); + + typeclassRegistry.addRenderer(`${'Hello'}/${'Bruh'}`, "test"); + expect(typeclassRegistry["renderers"]).toEqual({[`${'Hello'}/${'Bruh'}`]: "test"}); + + + typeclassRegistry.addRenderer(`${'Ano'}/${'Second'}`, "test2"); + expect(typeclassRegistry.getRendererSrc(`${'Ano'}/${'Second'}`)).toEqual("test2"); + }); + + + it("Test typeclassses ",() => { // they all return the same object with different prop + expect(typeof typeclassRegistry["typeclasses"][""].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["number"].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["string"].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["boolean"].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["color"].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["image"].mediaDisplayConfig("")).toBe('object'); + expect(typeof typeclassRegistry["typeclasses"]["error"].mediaDisplayConfig("")).toBe('object'); + }) + + it("Test base converters", () => { + expect(typeclassRegistry["converters"].number.string(1)).toBe("1") + expect(typeclassRegistry["converters"].string.number(1)).toBe(1) + expect(typeclassRegistry["converters"].boolean.string(true)).toBe("true") + expect(typeclassRegistry["converters"].string.boolean("true")).toBe(true) + expect(typeclassRegistry["converters"].number.boolean(1)).toBe(true) + + }) + +}) \ No newline at end of file From 21fc4594abe7c95ab145b3cec95f84962dca7649 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Tue, 26 Sep 2023 10:59:19 +0200 Subject: [PATCH 161/209] Fix Camera Tile --- src/frontend/ui/tiles/WebCamera.svelte | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/frontend/ui/tiles/WebCamera.svelte b/src/frontend/ui/tiles/WebCamera.svelte index 186e76a6..08359e06 100644 --- a/src/frontend/ui/tiles/WebCamera.svelte +++ b/src/frontend/ui/tiles/WebCamera.svelte @@ -35,11 +35,29 @@ function takePicture() { capture - .takePhoto() - .then((blob: Blob) => { + .grabFrame() + .then((imageBitmap: ImageBitmap) => { // ws.send(blob); - pictureUrl = URL.createObjectURL(blob); - cacheStore.refreshStore(pictureUrl); + console.log(imageBitmap); + // cacheStore.addCacheObject(blob, {contentType: "image/png", name: `Webcam Capture ${Math.floor(100000 * Math.random())}`}) + // pictureUrl = URL.createObjectURL(blob); + // cacheStore.refreshStore(pictureUrl); + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + + canvas.width = imageBitmap.width; + canvas.height = imageBitmap.height; + + context?.drawImage(imageBitmap, 0, 0); + + canvas.toBlob((blob) => { + // const blobUrl = URL.createObjectURL(blob); + if (blob) + cacheStore.addCacheObject(blob, { + contentType: "image/png", + name: `Webcam Capture ${Math.floor(100000 * Math.random())}`, + }); + }, "image/png"); // You can specify the MIME type here }) .catch((err: string) => console.log("Error while taking photo ", err)); } From 62c1c7c85f37cc336ab08af4618a5e6a81194394 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Tue, 26 Sep 2023 11:03:27 +0200 Subject: [PATCH 162/209] Initial TypeClassBuilder tests --- .../lib/plugins/builders/TypeclassBuilder.ts | 29 ++++++++++++++++ .../plugins/builder/TypeClassBuilder.spec.ts | 34 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts diff --git a/src/electron/lib/plugins/builders/TypeclassBuilder.ts b/src/electron/lib/plugins/builders/TypeclassBuilder.ts index 3ca6d958..7022e432 100644 --- a/src/electron/lib/plugins/builders/TypeclassBuilder.ts +++ b/src/electron/lib/plugins/builders/TypeclassBuilder.ts @@ -18,6 +18,9 @@ type PartialTypeclass = { toConverters: [TypeclassId, TypeConverter][]; }; +/** + * Builder class for creating Typeclasses through Builder pattern + */ export class TypeclassBuilder implements PluginContextBuilder { private partial: PartialTypeclass; @@ -37,26 +40,45 @@ export class TypeclassBuilder implements PluginContextBuilder { }; } + /** + * Sets the description of the partial typeclass + * @param description + */ setDescription(description: string) { this.partial.description = description; } + /** + * Sets the From converters of the partial typeclass + * @param converters + */ setFromConverters(converters: { [key: string]: (fromType: string) => any }) { Object.keys(converters).forEach((fromTypeId) => { this.partial.fromConverters.push([fromTypeId, converters[fromTypeId]]); }); } + /** + * Set the To converters of the partial typeclass + * @param converters + */ setToConverters(converters: { [key: string]: (toType: string) => any }) { Object.keys(converters).forEach((toTypeId) => { this.partial.toConverters.push([toTypeId, converters[toTypeId]]); }); } + /** + * Set the mediaDisplayConfigurator of the partial typeclass + * @param configurator + */ setDisplayConfigurator(configurator: (data: string) => MediaDisplayConfig) { this.partial.mediaDisplayConfigurator = configurator; } + /** + * Returns the current build of the partial typeclass + */ private get buildTypeclass(): Typeclass { return { id: this.partial.id, @@ -66,6 +88,10 @@ export class TypeclassBuilder implements PluginContextBuilder { }; } + /** + * Returns the from and to converters of the current partial typeclass + * @returns List of converter Triples + */ private get buildConverters(): ConverterTriple[] { return [ ...this.partial.fromConverters.map( @@ -77,6 +103,9 @@ export class TypeclassBuilder implements PluginContextBuilder { ]; } + /** + * @returns the current build of the partial typeclass and its converters + */ get build(): [Typeclass, ConverterTriple[]] { return [this.buildTypeclass, this.buildConverters]; } diff --git a/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts new file mode 100644 index 00000000..de989b1c --- /dev/null +++ b/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts @@ -0,0 +1,34 @@ +import { type } from "os"; +import {TypeclassBuilder} from "../../../../../../src/electron/lib/plugins/builders/TypeclassBuilder" + + +describe("TypeClassBuilder", () => { + let typeclassBuilder : TypeclassBuilder; + + + beforeEach(() => { + typeclassBuilder = new TypeclassBuilder("test", "number"); + }); + + + it("Test constructor and getters and setters", () => { + expect(typeclassBuilder).not.toBeNull(); + expect(typeclassBuilder["partial"].id).toBe("number"); + expect(typeof typeclassBuilder["partial"].mediaDisplayConfigurator("a")).toBe("object"); + + typeclassBuilder.setDescription("test description"); + expect(typeclassBuilder["partial"].description).toBe("test description"); + + const fn = (fromType: string) => {return "test";} + typeclassBuilder.setFromConverters({"test1": fn}); + expect(typeclassBuilder["partial"].fromConverters).toEqual([["test1", fn]]); + + const fn1 = (toType: string) => {return "test";} + typeclassBuilder.setToConverters({"test2": fn1}); + expect(typeclassBuilder["partial"].toConverters).toEqual([["test2", fn1]]); + + }) + + + +}); \ No newline at end of file From fa659f3b00df759e895786ef5ecf61bcb0ea6551 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Tue, 26 Sep 2023 11:19:10 +0200 Subject: [PATCH 163/209] TypeClassBuilder finished --- .../plugins/builder/TypeClassBuilder.spec.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts b/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts index de989b1c..473ec699 100644 --- a/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts +++ b/tests/unit-tests/electron/lib/plugins/builder/TypeClassBuilder.spec.ts @@ -1,5 +1,8 @@ import { type } from "os"; import {TypeclassBuilder} from "../../../../../../src/electron/lib/plugins/builders/TypeclassBuilder" +import { MediaDisplayConfig, MediaDisplayType } from "../../../../../../src/shared/types"; +import { Typeclass } from "../../../../../../src/electron/lib/registries/TypeclassRegistry"; +import type { ConverterTriple } from "../../../../../../src/electron/lib/registries/TypeclassRegistry"; describe("TypeClassBuilder", () => { @@ -27,6 +30,31 @@ describe("TypeClassBuilder", () => { typeclassBuilder.setToConverters({"test2": fn1}); expect(typeclassBuilder["partial"].toConverters).toEqual([["test2", fn1]]); + + const configurator: (data: string) => MediaDisplayConfig = (data: string) => { + return { + displayType: MediaDisplayType.TextBox, + props: { content: "Invalid typeclass: Media display not specified" }, + contentProp: null, + } as MediaDisplayConfig; + } + + typeclassBuilder.setDisplayConfigurator(configurator); + expect(typeclassBuilder["partial"].mediaDisplayConfigurator("a")).toEqual(configurator("a")); + + const build : Typeclass = { + id: "number", + description: "test description", + subtypes: [], + mediaDisplayConfig: configurator, + } + + expect(typeclassBuilder["buildTypeclass"]).toEqual(build); + + const converters : ConverterTriple[] = [["test1",typeclassBuilder["partial"].id, fn], [typeclassBuilder["partial"].id,"test2", fn1]]; + expect(typeclassBuilder["buildConverters"]).toEqual(converters); + + expect(typeclassBuilder["build"]).toEqual([build,converters]); }) From 6f2afdbd6119e8917473c29217e53c365b66ee97 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 12:49:57 +0200 Subject: [PATCH 164/209] Clean up camera UI --- src/electron/lib/cache/CacheManager.ts | 9 +++- .../ui/base/layout/TileSelector.svelte | 10 +++- src/frontend/ui/tiles/WebCamera.svelte | 53 ++++++++++++------- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/electron/lib/cache/CacheManager.ts b/src/electron/lib/cache/CacheManager.ts index 7e98748a..6c025bb8 100644 --- a/src/electron/lib/cache/CacheManager.ts +++ b/src/electron/lib/cache/CacheManager.ts @@ -192,9 +192,16 @@ export class CacheManager { continue; } + const fileType = cacheObject.metadata.contentType.split("/")[1]; + let fileName = cacheObject.metadata.name || "export"; + + if (!fileName.endsWith(`.${fileType}`)) { + fileName = `${fileName}.${fileType}`; + } + const path = await showSaveDialog({ title: "Save Asset", - defaultPath: join(app.getPath("downloads"), cacheObject.metadata.name || "export.png"), + defaultPath: join(app.getPath("downloads"), fileName), properties: ["createDirectory"], }); diff --git a/src/frontend/ui/base/layout/TileSelector.svelte b/src/frontend/ui/base/layout/TileSelector.svelte index e57a4b15..da565f28 100644 --- a/src/frontend/ui/base/layout/TileSelector.svelte +++ b/src/frontend/ui/base/layout/TileSelector.svelte @@ -56,6 +56,12 @@ }; console.log("Icon", tiles[tile].icon); } + + function toTitleCase(str: string) { + return str.replace(/\w\S*/g, (txt) => { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + }
@@ -74,7 +80,9 @@ on:keydown="{null}" >
- {tileDict[to].displayName} + {toTitleCase( + tileDict[to].displayName + )}
{/each} diff --git a/src/frontend/ui/tiles/WebCamera.svelte b/src/frontend/ui/tiles/WebCamera.svelte index 08359e06..f9615609 100644 --- a/src/frontend/ui/tiles/WebCamera.svelte +++ b/src/frontend/ui/tiles/WebCamera.svelte @@ -1,11 +1,12 @@ -
+
+ + {#if loading} -

LOADING

+

Loading...

+ {:else} +
+ + + + +
{/if} - - -
- - From 1c52e133337e4317c9b36fe42c25a97134a6761c Mon Sep 17 00:00:00 2001 From: Klairgo Date: Tue, 26 Sep 2023 13:21:24 +0200 Subject: [PATCH 165/209] Add blink nodes --- blix-plugins/blink/src/main.cjs | 27 +++++++++++++++++++++- blix-plugins/blink/webview/types.ts | 35 +++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 57d97d1a..7bdf1147 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -204,7 +204,32 @@ const blinkNodes = { { id: "center.y", min: 0, max: 2000, step: 1.0 }, ] ], - + "brightnessContrast": [ + "Brightness / Contrast", + "Adjusts the brightness and contrast of the image.", + [ + { id: "brightness", min: 0, max: 10, step: 0.1 }, + { id: "contrast", min: 0, max: 10, step: 0.1 }, + ] + ], + "saturationGamma": [ + "Saturation / Gamma", + "Adjusts the saturation and gamma of the image.", + [ + { id: "saturation", min: 0, max: 10, step: 0.1 }, + { id: "gamma", min: 0, max: 10, step: 0.1 }, + ] + ], + "colorChannel": [ + "Color-channel", + "Shifts the color channel and alpha of the image.", + [ + { id: "red", min: 0, max: 10, step: 0.1 }, + { id: "green", min: 0, max: 10, step: 0.1 }, + { id: "blue", min: 0, max: 10, step: 0.1 }, + { id: "alpha", min: 0, max: 10, step: 0.1 }, + ] + ], }; Object.keys(blinkNodes).forEach((key) => { diff --git a/blix-plugins/blink/webview/types.ts b/blix-plugins/blink/webview/types.ts index ffcfddee..450bbcd7 100644 --- a/blix-plugins/blink/webview/types.ts +++ b/blix-plugins/blink/webview/types.ts @@ -11,7 +11,8 @@ import { BulgePinchFilter, GlitchFilter, ZoomBlurFilter, - TwistFilter + TwistFilter, + AdjustmentFilter } from "pixi-filters" export type BlinkCanvas = { @@ -43,7 +44,7 @@ export type Transform = { export type Filter = { class: "filter"; - type: "blur" | "noise" | "bloom" | "grayscale" | "bevel" | "outline" | "dot" | "crt" | "emboss" | "bulge" | "glitch" | "zoomblur" | "twist"; + type: "blur" | "noise" | "bloom" | "grayscale" | "bevel" | "outline" | "dot" | "crt" | "emboss" | "bulge" | "glitch" | "zoomblur" | "twist" | "brightnessContrast" | "saturationGamma" | "colorChannel"; params: any[] }; @@ -84,7 +85,7 @@ export function getPixiFilter(filter: Filter) { { radius: filter.params[0], strength: filter.params[1], - center: [filter.params[2], filter.params[3]], + center: new PIXI.Point(filter.params[2], filter.params[3]), } ); case "glitch": return new GlitchFilter(...filter.params); @@ -95,7 +96,33 @@ export function getPixiFilter(filter: Filter) { center: [filter.params[2], filter.params[3]], } ); - case "twist": return new TwistFilter(...filter.params); + case "twist": return new TwistFilter( + { + angle: filter.params[0], + radius: filter.params[1], + offset: new PIXI.Point(filter.params[2], filter.params[3]), + } + ); + case "brightnessContrast": return new AdjustmentFilter( + { + brightness: filter.params[0], + contrast: filter.params[1], + } + ); + case "saturationGamma": return new AdjustmentFilter( + { + saturation: filter.params[0], + gamma: filter.params[1], + } + ); + case "colorChannel": return new AdjustmentFilter( + { + red: filter.params[0], + green: filter.params[1], + blue: filter.params[2], + alpha: filter.params[3], + } + ); } } catch { return new PIXI.Filter(); From f831756cd6a871b221f23b0e06c99e356d4c10ae Mon Sep 17 00:00:00 2001 From: Rec1dite Date: Tue, 26 Sep 2023 14:03:58 +0200 Subject: [PATCH 166/209] Add label to ColorPicker, specialize filter padding --- blix-plugins/blink/src/main.cjs | 2 +- blix-plugins/blink/webview/App.svelte | 6 ---- blix-plugins/blink/webview/filter.ts | 4 ++- .../graph/nodeUIComponents/ColorPicker.svelte | 28 +++++++++++++------ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 75ff8ad7..42473366 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -772,7 +772,7 @@ const nodes = { const ui = nodeBuilder.createUIBuilder(); ui.addTextInput({ componentId: "exportName", - label: "Canvas Export Name", + label: "Export Name", defaultValue: "Blink Export", triggerUpdate: true, }, {}); diff --git a/blix-plugins/blink/webview/App.svelte b/blix-plugins/blink/webview/App.svelte index 41161665..2c9940e2 100644 --- a/blix-plugins/blink/webview/App.svelte +++ b/blix-plugins/blink/webview/App.svelte @@ -36,7 +36,6 @@ width: 500, height: 500, backgroundColor: "#181925", - // transparent: true, antialias: true, resolution: 1, autoDensity: true, @@ -126,11 +125,6 @@ viewport.addChild(imgCanvas); - const viewportFitX = canvasConfig.canvasDims.w + 2 * imgCanvasInitialPadding; - const viewportFitY = canvasConfig.canvasDims.h + 2 * imgCanvasInitialPadding; - viewport.fit(true, viewportFitX, viewportFitY); - viewport.moveCenter(canvasConfig.canvasDims.w/2, canvasConfig.canvasDims.h/2); - //===== RENDER Blink =====// let hasCentered = false; media.subscribe(async (media) => { diff --git a/blix-plugins/blink/webview/filter.ts b/blix-plugins/blink/webview/filter.ts index dd41b70f..e8fbe7e1 100644 --- a/blix-plugins/blink/webview/filter.ts +++ b/blix-plugins/blink/webview/filter.ts @@ -2,12 +2,14 @@ import * as PIXI from 'pixi.js'; import { type Filter, getPixiFilter } from './types'; import { randomId } from './render'; +const paddedFilters = new Set(["blur", "outline", "twist"]); + // Apply a series of filters and flatten the result to a sprite. // This is done so that the filters are applied evenly regardless of scaling. export function applyFilters(blink: PIXI.Application, content: PIXI.Container, filters: Filter[]) { // `renderPadding` is necessary for filters like // blur that spread beyond the bounds of the sprite - const renderPadding = 100; + const renderPadding = filters.some(v => paddedFilters.has(v.type)) ? 100 : 0; // Normalize offset to fit within renderTexture const { x: bx, y: by, width: bw, height: bh } = content.getLocalBounds(); diff --git a/src/frontend/ui/utils/graph/nodeUIComponents/ColorPicker.svelte b/src/frontend/ui/utils/graph/nodeUIComponents/ColorPicker.svelte index a23bb7f6..c252eca8 100644 --- a/src/frontend/ui/utils/graph/nodeUIComponents/ColorPicker.svelte +++ b/src/frontend/ui/utils/graph/nodeUIComponents/ColorPicker.svelte @@ -28,14 +28,21 @@ {#if typeof $valStore === "string"} - +
+
+ {config.label} +
+
+ +
+
{:else} ERR: Invaid colour: {JSON.stringify($valStore)} {/if} @@ -48,6 +55,11 @@ height: 100%; } + .row { + display: grid; + grid-template-columns: auto 6em; + } + /* .picker :global(label .color) { width: 45px !important; height: 24px !important; From b85355e7ec7a465304d515a1b2ce527136004208 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 20:10:15 +0200 Subject: [PATCH 167/209] Urgent AiManger toolbox bug fix --- src/electron/lib/ai/AiManagerv2.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/electron/lib/ai/AiManagerv2.ts b/src/electron/lib/ai/AiManagerv2.ts index 44ed928d..6aa418ee 100644 --- a/src/electron/lib/ai/AiManagerv2.ts +++ b/src/electron/lib/ai/AiManagerv2.ts @@ -42,7 +42,7 @@ type PromptOptions = { export const genericErrorResponse = "Oops, that wasn't supposed to happen😅"; export class AiManager { - private readonly graphExporter: CoreGraphExporter; + private graphExporter: CoreGraphExporter; private readonly chats: Chat[] = []; private blypescriptInterpreter: BlypescriptInterpreter; private blypescriptToolbox: BlypescriptToolbox; @@ -61,8 +61,12 @@ export class AiManager { } async executePrompt({ prompt, graphId, model, apiKey, chatId, verbose }: PromptOptions) { + this.blypescriptToolbox = BlypescriptToolbox.fromToolbox(this.toolbox); + this.graphExporter = new CoreGraphExporter( + new BlypescriptExportStrategyV2(this.blypescriptToolbox) + ); + // console.log(this.blypescriptToolbox.toString()); const blypescriptProgram = this.graphExporter.exportGraph(this.graphManager.getGraph(graphId)); - const blypescriptToolbox = BlypescriptToolbox.fromToolbox(this.toolbox); this.blypescriptInterpreter = new BlypescriptInterpreter(this.toolbox, this.graphManager); let chat = this.chats.find((chat) => chat.id === chatId); @@ -76,7 +80,7 @@ export class AiManager { { role: "system", content: generateGuidePrompt({ - interfaces: blypescriptToolbox.toString(), + interfaces: this.blypescriptToolbox.toString(), }), }, { @@ -119,7 +123,7 @@ export class AiManager { } satisfies Result; } - const result = BlypescriptProgram.fromString(response.data.content, blypescriptToolbox); + const result = BlypescriptProgram.fromString(response.data.content, this.blypescriptToolbox); if (!result.success) { logger.error(result.error, result.message); From 9c8e6113aacac18dc42bcac16555e1679787b591 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 20:21:12 +0200 Subject: [PATCH 168/209] Add initial plugin build script --- .github/workflows/build.yml | 2 +- scripts/plugin.js | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 scripts/plugin.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb5723f2..4bf72de3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: run: npm ci - name: Build Blix Plugins - run: cd ./blix-plugins/glfx-plugin && npm ci && npm run build + run: node scripts/plugin.js - name: Build/Release Blix run: | diff --git a/scripts/plugin.js b/scripts/plugin.js new file mode 100644 index 00000000..9720352d --- /dev/null +++ b/scripts/plugin.js @@ -0,0 +1,74 @@ +const { readFileSync, writeFileSync, existsSync } = require("fs"); +const { exec } = require("child_process"); +const { join, resolve } = require("path"); + +function buildGLFX() { + const directory = resolve(join(__dirname, "..", "blix-plugins/glfx-plugin")); + const options = { cwd: directory }; + const command = "npm ci && npm run build"; + + console.log("Building glfx..."); + + exec(command, options, (err, stdout) => { + if (err) { + console.log(err); + return; + } + + console.log(stdout); + console.log("Done building glfx."); + }); +} + +function buildBlink() { + const tsconfigPath = join( + __dirname, + "..", + "blix-plugins/blink/node_modules/@tsconfig/svelte/tsconfig.json" + ); + + const directory = resolve(join(__dirname, "..", "blix-plugins/blink")); + const options = { cwd: directory }; + let command = "npm ci"; + + console.log("Building blink ..."); + + exec(command, options, (err, stdout) => { + if (err) { + console.log(err); + return; + } + + console.log(stdout); + + if (existsSync(tsconfigPath)) { + const tsconfigRaw = readFileSync(tsconfigPath, "utf8"); + const withoutSingleLineComments = tsconfigRaw.replace( + /^(?!.*https?:\/\/)(.*)\/\/(.*)$/gm, + "$1" + ); + const withoutComments = withoutSingleLineComments.replace(/\/\*([\s\S]*?)\*\//g, ""); + const tsconfigJSON = JSON.parse(withoutComments); + tsconfigJSON.compilerOptions.moduleResolution = "node"; + delete tsconfigJSON.compilerOptions.verbatimModuleSyntax; + writeFileSync(tsconfigPath, JSON.stringify(tsconfigJSON, null, 2)); + } else { + console.log("TSConfig not found. Building..."); + } + + command = "npm run build"; + + exec(command, options, (err, stdout) => { + if (err) { + console.log(err); + return; + } + + console.log(stdout); + console.log("Done building blink."); + }); + }); +} + +// buildGLFX(); +buildBlink(); From d514a7a78ed9a73a20ff096a071bc7f37f5eea07 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 22:22:00 +0200 Subject: [PATCH 169/209] Add plugins build shell script --- .github/workflows/build.yml | 2 +- scripts/plugin.js | 79 ++++++++++++++++++++++--------------- scripts/plugins.sh | 43 ++++++++++++++++++++ 3 files changed, 91 insertions(+), 33 deletions(-) create mode 100755 scripts/plugins.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4bf72de3..56199c2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: run: npm ci - name: Build Blix Plugins - run: node scripts/plugin.js + run: ./scripts/plugins.sh - name: Build/Release Blix run: | diff --git a/scripts/plugin.js b/scripts/plugin.js index 9720352d..cddb5bef 100644 --- a/scripts/plugin.js +++ b/scripts/plugin.js @@ -1,5 +1,5 @@ const { readFileSync, writeFileSync, existsSync } = require("fs"); -const { exec } = require("child_process"); +const { execSync } = require("child_process"); const { join, resolve } = require("path"); function buildGLFX() { @@ -31,44 +31,59 @@ function buildBlink() { const options = { cwd: directory }; let command = "npm ci"; - console.log("Building blink ..."); + console.log("Installing blink node_modules..."); - exec(command, options, (err, stdout) => { - if (err) { - console.log(err); - return; - } + try { + const output = execSync(command, options); + console.log(output.toString()); + } catch (error) { + console.log(error.toString()); + } - console.log(stdout); + if (existsSync(tsconfigPath)) { + const tsconfigRaw = readFileSync(tsconfigPath, "utf8"); + const withoutSingleLineComments = tsconfigRaw.replace( + /^(?!.*https?:\/\/)(.*)\/\/(.*)$/gm, + "$1" + ); + const withoutComments = withoutSingleLineComments.replace(/\/\*([\s\S]*?)\*\//g, ""); + const tsconfigJSON = JSON.parse(withoutComments); + tsconfigJSON.compilerOptions.moduleResolution = "node"; + delete tsconfigJSON.compilerOptions.verbatimModuleSyntax; + writeFileSync(tsconfigPath, JSON.stringify(tsconfigJSON, null, 2)); + } else { + console.log("TSConfig not found. Building..."); + } - if (existsSync(tsconfigPath)) { - const tsconfigRaw = readFileSync(tsconfigPath, "utf8"); - const withoutSingleLineComments = tsconfigRaw.replace( - /^(?!.*https?:\/\/)(.*)\/\/(.*)$/gm, - "$1" - ); - const withoutComments = withoutSingleLineComments.replace(/\/\*([\s\S]*?)\*\//g, ""); - const tsconfigJSON = JSON.parse(withoutComments); - tsconfigJSON.compilerOptions.moduleResolution = "node"; - delete tsconfigJSON.compilerOptions.verbatimModuleSyntax; - writeFileSync(tsconfigPath, JSON.stringify(tsconfigJSON, null, 2)); - } else { - console.log("TSConfig not found. Building..."); - } + command = "npm run build"; - command = "npm run build"; + // console.log("Building blink..."); - exec(command, options, (err, stdout) => { - if (err) { - console.log(err); - return; - } + // try { + // const output = execSync(command, options); + // console.log(output.toString()); + // } catch (error) { + // console.log(error.toString()); + // } - console.log(stdout); - console.log("Done building blink."); - }); - }); + // console.log("Building blink completed"); +} + +function helper() { + const directory = resolve(join(__dirname, "..", "blix-plugins/blink")); + const options = { cwd: directory }; + const command = "npm run build"; + + console.log("Starting build..."); + try { + const output = execSync(command, options); + console.log(output.toString()); + } catch (error) { + console.log(error.toString()); + } + console.log("Done building blink"); } // buildGLFX(); buildBlink(); +helper(); diff --git a/scripts/plugins.sh b/scripts/plugins.sh new file mode 100755 index 00000000..eb570f43 --- /dev/null +++ b/scripts/plugins.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +blixPluginDirectory=$(realpath "$(dirname "$0")/../blix-plugins/") + +# =================================================================== +# GLFX +# =================================================================== + +echo "Installing glfx-plugin node_modules..." +cd "$blixPluginDirectory/glfx-plugin" +output=$(npm ci 2>&1) && echo "$output" || echo "$output" + +echo "Building glfx-plugin..." +output=$(npm run build 2>&1) && echo "$output" || echo "$output" + +echo "Building glfx-plugin completed" + +# =================================================================== +# BLINK +# =================================================================== + +tsconfigPath=$(realpath "$blixPluginDirectory/blink/node_modules/@tsconfig/svelte/tsconfig.json") +blinkDirectory=$(realpath "$blixPluginDirectory/blink") + +echo "Installing blink node_modules..." +cd "$blinkDirectory" +output=$(npm ci 2>&1) && echo "$output" || echo "$output" + +echo "Attempting to fix tsconfig..." +if test -f "$tsconfigPath"; then + tsconfigRaw=$(cat "$tsconfigPath") + withoutComments=$(echo "$tsconfigRaw" | sed '/\/\*/,/\*\//d') + tsconfigJSON="$(echo "$withoutComments" | sed -E 's/"moduleResolution"[^,}]*/"moduleResolution": "node"/' | sed -E '/"verbatimModuleSyntax"/d')" + printf "%s\n" "$tsconfigJSON" > "$tsconfigPath" + echo "TSConfig fixed" +else + echo "TSConfig not found. Building..." +fi + +echo "Building blink..." +output=$(npm run build 2>&1) && echo "$output" || echo "$output" + +echo "Building blink completed" From 8fee2db76ae830287b9e0f5d894caed5420d609d Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 22:25:57 +0200 Subject: [PATCH 170/209] Fix script variable instantiation --- scripts/plugins.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/plugins.sh b/scripts/plugins.sh index eb570f43..9735dc9c 100755 --- a/scripts/plugins.sh +++ b/scripts/plugins.sh @@ -19,13 +19,14 @@ echo "Building glfx-plugin completed" # BLINK # =================================================================== -tsconfigPath=$(realpath "$blixPluginDirectory/blink/node_modules/@tsconfig/svelte/tsconfig.json") blinkDirectory=$(realpath "$blixPluginDirectory/blink") echo "Installing blink node_modules..." cd "$blinkDirectory" output=$(npm ci 2>&1) && echo "$output" || echo "$output" +tsconfigPath=$(realpath "$blixPluginDirectory/blink/node_modules/@tsconfig/svelte/tsconfig.json") + echo "Attempting to fix tsconfig..." if test -f "$tsconfigPath"; then tsconfigRaw=$(cat "$tsconfigPath") From e704314b73906aaac66d48dcd7e7adb5b5febdca Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 22:36:38 +0200 Subject: [PATCH 171/209] Change back to JS build script --- .github/workflows/build.yml | 2 +- scripts/{plugin.js => plugins.js} | 26 ++++++-------------------- 2 files changed, 7 insertions(+), 21 deletions(-) rename scripts/{plugin.js => plugins.js} (78%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56199c2a..375f3b0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: run: npm ci - name: Build Blix Plugins - run: ./scripts/plugins.sh + run: node ./scripts/plugins.js - name: Build/Release Blix run: | diff --git a/scripts/plugin.js b/scripts/plugins.js similarity index 78% rename from scripts/plugin.js rename to scripts/plugins.js index cddb5bef..6cd97c8e 100644 --- a/scripts/plugin.js +++ b/scripts/plugins.js @@ -40,6 +40,7 @@ function buildBlink() { console.log(error.toString()); } + console.log("Attempting to fix tsconfig..."); if (existsSync(tsconfigPath)) { const tsconfigRaw = readFileSync(tsconfigPath, "utf8"); const withoutSingleLineComments = tsconfigRaw.replace( @@ -51,39 +52,24 @@ function buildBlink() { tsconfigJSON.compilerOptions.moduleResolution = "node"; delete tsconfigJSON.compilerOptions.verbatimModuleSyntax; writeFileSync(tsconfigPath, JSON.stringify(tsconfigJSON, null, 2)); + console.log("TSConfig fixed"); } else { console.log("TSConfig not found. Building..."); } command = "npm run build"; - // console.log("Building blink..."); + console.log("Building blink..."); - // try { - // const output = execSync(command, options); - // console.log(output.toString()); - // } catch (error) { - // console.log(error.toString()); - // } - - // console.log("Building blink completed"); -} - -function helper() { - const directory = resolve(join(__dirname, "..", "blix-plugins/blink")); - const options = { cwd: directory }; - const command = "npm run build"; - - console.log("Starting build..."); try { const output = execSync(command, options); console.log(output.toString()); } catch (error) { console.log(error.toString()); } - console.log("Done building blink"); + + console.log("Building blink completed"); } -// buildGLFX(); +buildGLFX(); buildBlink(); -helper(); From 9a4bac530d524283c6c7346dd1ba14bd184231b1 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 22:39:55 +0200 Subject: [PATCH 172/209] Fix plugin build script --- scripts/plugins.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/plugins.js b/scripts/plugins.js index 6cd97c8e..79ceac7c 100644 --- a/scripts/plugins.js +++ b/scripts/plugins.js @@ -9,15 +9,14 @@ function buildGLFX() { console.log("Building glfx..."); - exec(command, options, (err, stdout) => { - if (err) { - console.log(err); - return; - } + try { + const output = execSync(command, options); + console.log(output.toString()); + } catch (error) { + console.log(error.toString()); + } - console.log(stdout); - console.log("Done building glfx."); - }); + console.log("Building glfx completed"); } function buildBlink() { From c0391d608d59295a863336763b331219b1a8b85e Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Tue, 26 Sep 2023 22:53:35 +0200 Subject: [PATCH 173/209] Fix paths and add build timeout --- .github/workflows/build.yml | 4 +++- src/frontend/ui/tiles/Assets.svelte | 2 +- src/frontend/ui/tiles/Media.svelte | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 375f3b0b..519aee55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,4 +43,6 @@ jobs: fi env: GH_TOKEN: ${{ secrets.TEST_TOKEN }} - shell: bash \ No newline at end of file + shell: bash + + timeout-minutes: 30 \ No newline at end of file diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index f84f20dd..fca51b40 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -16,7 +16,7 @@ faPizzaSlice, } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; - import { toastStore } from "lib/stores/ToastStore"; + import { toastStore } from "../../lib/stores/ToastStore"; let selectedCacheItems: CacheUUID[] = []; // Not used at the moment diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index 9bb89ea9..4b2cd4ae 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -28,7 +28,7 @@ faPizzaSlice, } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; - import { toastStore } from "lib/stores/ToastStore"; + import { toastStore } from "../../lib/stores/ToastStore"; const noContentIcons = [ faBacon, From d153845143924ecd04e4621060183258a73e7caa Mon Sep 17 00:00:00 2001 From: Klairgo Date: Tue, 26 Sep 2023 22:55:20 +0200 Subject: [PATCH 174/209] Fix spelling mistakes --- blix-plugins/blink/src/main.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blix-plugins/blink/src/main.cjs b/blix-plugins/blink/src/main.cjs index 7bdf1147..22b4a1e3 100644 --- a/blix-plugins/blink/src/main.cjs +++ b/blix-plugins/blink/src/main.cjs @@ -189,7 +189,7 @@ const blinkNodes = { "Bulges or pinches the image in a circle.", [ { id: "radius", min: 0, max: 1000, step: 1.0 }, - { id: "strenght", min: -1, max: 1, step: 0.01 }, + { id: "strength", min: -1, max: 1, step: 0.01 }, { id: "center.x", min: 0, max: 1, step: 0.01 }, { id: "center.y", min: 0, max: 1, step: 0.01 }, ] @@ -198,7 +198,7 @@ const blinkNodes = { "ZoomBlur", "The ZoomFilter applies a Zoom blur to an object.", [ - { id: "strenght", min: 0, max: 0.5, step: 0.01 }, + { id: "strength", min: 0, max: 0.5, step: 0.01 }, { id: "innerRadius", min: 0, max: 1000, step: 1.0 }, { id: "center.x", min: 0, max: 2000, step: 1.0 }, { id: "center.y", min: 0, max: 2000, step: 1.0 }, From a37244fbdb58c1178696ce496255bf0ef41abdf0 Mon Sep 17 00:00:00 2001 From: Jake Mileham Date: Tue, 26 Sep 2023 23:45:38 +0200 Subject: [PATCH 175/209] Add CoreGraph manager tests --- .../core-graph/CoreGraphEventManager.spec.ts | 664 ++++++++++++++++++ .../lib/core-graph/CoreGraphManager.spec.ts | 7 + 2 files changed, 671 insertions(+) create mode 100644 tests/unit-tests/electron/lib/core-graph/CoreGraphEventManager.spec.ts diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraphEventManager.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraphEventManager.spec.ts new file mode 100644 index 00000000..f1f39c47 --- /dev/null +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraphEventManager.spec.ts @@ -0,0 +1,664 @@ +import expect from "expect"; +import { + CoreGraphEvent, + CoreGraphEventManager, +} from "../../../../../src/electron/lib/core-graph/CoreGraphEventManger"; +import { CoreGraphManager } from "../../../../../src/electron/lib/core-graph/CoreGraphManager"; +import { Blix } from "../../../../../src/electron/lib/Blix"; +import { MainWindow } from "../../../../../src/electron/lib/api/apis/WindowApi"; +import { + MinAnchor, + NodeInstance, +} from "../../../../../src/electron/lib/registries/ToolboxRegistry"; +import { CoreGraph } from "../../../../../src/electron/lib/core-graph/CoreGraph"; +import { NodeUIParent } from "../../../../../src/shared/ui/NodeUITypes"; +import exp from "constants"; + +const mainWindow: MainWindow = { + apis: { + commandRegistryApi: jest.fn(), + clientGraphApi: jest.fn(), + clientProjectApi: jest.fn(), + toolboxClientApi: { + registryChanged: jest.fn(), + }, + commandClientApi: { + registryChanged: jest.fn(), + }, + graphClientApi: { + graphRemoved: jest.fn(), + }, + }, +} as any; + +jest.mock("chokidar", () => ({ + default: { + watch: jest.fn(() => { + return { + on: jest.fn(), + }; + }), + }, +})); + +jest.mock("electron", () => ({ + app: { + getPath: jest.fn((path) => { + return "test/electron"; + }), + getName: jest.fn(() => { + return "TestElectron"; + }), + getVersion: jest.fn(() => { + return "v1.1.1"; + }), + getAppPath: jest.fn(() => { + return "test/electron"; + }), + }, + ipcMain: { + on: jest.fn(), + }, +})); + +jest.mock("fs", () => ({ + readFileSync: jest.fn().mockReturnValue("mocked_base64_string"), + readFile: jest.fn((filePath, callback) => callback(null, "mocked_file_data")), + readdirSync: jest.fn(() => ["hello-plugin"]), + existsSync: jest.fn(), + mkdirSync: jest.fn(), + writeFileSync: jest.fn(), +})); + +jest.mock("electron-store", () => ({ + default: jest.fn().mockImplementation(() => { + return {}; + }), +})); + +jest.mock("ws", () => { + return { + WebSocketServer: jest.fn().mockImplementation(() => { + return { + on: jest.fn(), + }; + }), + }; +}); + +jest.mock("../../../../../src/electron/lib/plugins/PluginManager"); +describe("Test Core Graph Event Manager", () => { + let blix: Blix; + let graph_manager: CoreGraphManager; + let graph: CoreGraph; + let nodeA: NodeInstance; + let nodeB: NodeInstance; + // const errorOccurred = (test: string, error?: string) => { + // console.log(`\u001b[31mAn error occured!!!\n\u001b[0mTest: ${test}\nError: ${error}\n`); + // }; + beforeEach(() => { + // INIT + blix = new Blix(); + blix.init(mainWindow); + graph_manager = new CoreGraphManager(blix.toolbox, mainWindow); + // SETUP GRAPH + graph = new CoreGraph(); + graph_manager.addGraph(graph); + const inputA: MinAnchor = { + type: "string", + displayName: `Test-plugin.Node-A.0`, + identifier: `inA`, + }; + const outputA: MinAnchor = { + type: "string", + displayName: `Test-plugin.Node-A.1`, + identifier: `outA`, + }; + nodeA = new NodeInstance( + `Node-A`, + `Test-plugin`, + "folder", + `Node-A`, + `This is node A`, + `fa-duotone fa-bell`, + [inputA], + [outputA], + undefined, + null, + { "numberA": { + label: "numberA", + defaultValue: "0", + componentId: "numberA", + triggerUpdate: true + }}, + undefined + ); + + const inputB: MinAnchor = { + type: "string", + displayName: `Test-plugin.Node-B.0`, + identifier: `inB`, + }; + const outputB: MinAnchor = { + type: "string", + displayName: `Test-plugin.Node-B.1`, + identifier: `outB`, + }; + nodeB = new NodeInstance( + `Node-B`, + `Test-plugin`, + "folder", + `Node-B`, + `This is node B`, + `fa-duotone fa-bell`, + [inputB], + [outputB], + undefined, + new NodeUIParent("label",null), + { "numberB": { + label: "numberB", + defaultValue: "0", + componentId: "numberB", + triggerUpdate: true + }}, + undefined + ); + + // SETUP TOOLBOX + blix.toolbox.addInstance( + new NodeInstance( + `Node-A`, + `Test-plugin`, + "folder", + `Node-A`, + `This is node A`, + `fa-duotone fa-bell`, + [inputA], + [outputA], + undefined, + null, + { "numberA": { + label: "numberA", + defaultValue: "0", + componentId: "numberA", + triggerUpdate: true + }}, + undefined + ) + ); + blix.toolbox.addInstance( + new NodeInstance( + `Node-B`, + `Test-plugin`, + "folder", + `Node-B`, + `This is node B`, + `fa-duotone fa-bell`, + [inputB], + [outputB], + undefined, + new NodeUIParent("label",null), + { "numberB": { + label: "numberB", + defaultValue: "0", + componentId: "numberB", + triggerUpdate: true + }}, + undefined + ) + ); + }); + + test("Roll forwards limit", () => { + const res = graph_manager.redoEvent(graph.uuid); + expect(res.status).toBe("error"); + }); + + test("Roll backwards limit", () => { + const res = graph_manager.undoEvent(graph.uuid); + expect(res.status).toBe("error"); + }) + + test("Resting event manager", () => { + expect(() => { + const event_manager = new CoreGraphEventManager(); + event_manager.reset(); + }).not.toThrow("some error"); + }) + + test("Adding a Node", () => { + const addRes = graph_manager.addNode(graph.uuid, nodeA, { x: 0, y: 0 }, 1); + expect(addRes.status).toBe("success"); + if (addRes.status === "success") { + // Undo - Remove the node + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Check node is removed + if (addRes.data) expect(graph.getNodes[addRes.data.nodeId]).toBeUndefined(); + // Redo - Add the node + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + // Node was added back + expect(Object.values(graph.getNodes)[0]).toBeDefined(); + } + }); + + test("Removing a Node", () => { + const addRes = graph_manager.addNode(graph.uuid, nodeA, { x: 0, y: 0 }, 1); + expect(addRes.status).toBe("success"); + expect(addRes.data).toBeDefined(); + if (addRes.status === "success" && addRes.data) { + // Node exists + expect(graph.getNodes[addRes.data.nodeId]).toBeDefined(); + // Remove Node + const removeRes = graph_manager.removeNode(graph.uuid, addRes.data.nodeId, 1); + expect(removeRes.status).toBe("success"); + if (removeRes.status === "success") { + // Node Removed + expect(graph.getNodes[addRes.data.nodeId]).toBeUndefined(); + // Undo - Add the node + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Check node is added back + expect(Object.values(graph.getNodes)).toBeDefined(); + // Redo - Remove the node + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + // Check node is removed again + expect(Object.values(graph.getNodes)[0]).toBeUndefined(); + } + } + }); + + test("Adding an edge", () => { + const pos = { x: 0, y: 0 }; + const addNodeARes = graph_manager.addNode(graph.uuid, nodeA, pos, 1); + const addNodeBRes = graph_manager.addNode(graph.uuid, nodeB, pos, 1); + + expect(addNodeARes.status).toBe("success"); + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.status).toBe("success"); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.status === "success" && addNodeBRes.status === "success") { + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.data && addNodeBRes.data) { + const nodes = graph.getNodes; + const anchorARes = nodes[addNodeARes.data.nodeId].findAnchorUUID("outA"); + const anchorBRes = nodes[addNodeBRes.data.nodeId].findAnchorUUID("inB"); + expect(anchorARes.status).toBe("success"); + expect(anchorBRes.status).toBe("success"); + if (anchorARes.status === "success" && anchorBRes.status === "success") { + expect(anchorARes.data).toBeDefined(); + expect(anchorBRes.data).toBeDefined(); + if (anchorARes.data && anchorBRes.data) { + // Add edge + const addEdgeRes = graph_manager.addEdge( + graph.uuid, + anchorARes.data.anchorUUID, + anchorBRes.data.anchorUUID, + 1 + ); + expect(addEdgeRes.status).toBe("success"); + if (addEdgeRes.status === "success" && addEdgeRes.data) { + // Edge should be added + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID].uuid).toBe(edge.uuid); + expect(edge.uuid).toBe(addEdgeRes.data.edgeId); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + // Undo - Remove the edge + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Edge should be removed + expect(graph.getEdgeDest[addEdgeRes.data.edgeId]).toBeUndefined(); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toBeUndefined(); + // Redo - Add the edge + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + expect(redoRes.data).toBeDefined(); + // Edge should be added again + if (redoRes.status === "success" && redoRes.data) { + const edge = Object.values(graph.getEdgeDest)[0]; + expect(edge).toBeDefined(); + expect(anchorARes.data.anchorUUID).toBe(edge.getAnchorFrom); + expect(anchorBRes.data.anchorUUID).toBe(edge.getAnchorTo); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toBeDefined(); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + expect(graph.getEdgeDest[edge.getAnchorTo]).toBeDefined(); + expect(graph.getEdgeDest[edge.getAnchorTo].uuid).toBe(edge.uuid); + } + } + } + } + } + } + }); + + test("Changing UiInput of node", () => { + const addRes = graph_manager.addNode(graph.uuid, nodeA, { x: 0, y: 0 }, 1); + expect(addRes.status).toBe("success"); + if (addRes.status === "success") { + expect(addRes.data).toBeDefined(); + if (addRes.data) { + // Update uiInput of node and create event + const oldinputs = graph.getAllUIInputs[addRes.data.nodeId].getInputs; + expect(oldinputs["numberA"]).toBeDefined(); + oldinputs["numberA"] = 1; + const updateRes = graph_manager.updateUIInputs(graph.uuid, addRes.data.nodeId, { inputs: oldinputs, changes: ["numberA"] }, 1); + expect(updateRes.status).toBe("success"); + const interRes = graph_manager.handleNodeInputInteraction(graph.uuid, addRes.data.nodeId, { id: "numberA", value: 1 }); + expect(interRes.status).toBe("success"); + + // Undo + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Redo + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + // Remove and add node so trigger update of ui change event + const removeRes = graph_manager.removeNode(graph.uuid, addRes.data.nodeId, 1); + expect(removeRes.status).toBe("success"); + if (removeRes.status === "success") { + // Node Removed + expect(graph.getNodes[addRes.data.nodeId]).toBeUndefined(); + // Undo - Add the node + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Check node is added back + expect(Object.values(graph.getNodes)).toBeDefined(); + + const newNode = Object.values(graph.getNodes)[0]; + // Update uiInput of node and create event + const oldinputs = graph.getAllUIInputs[newNode.uuid].getInputs; + expect(oldinputs["numberA"]).toBeDefined(); + oldinputs["numberA"] = 2; + const updateRes = graph_manager.updateUIInputs(graph.uuid, newNode.uuid, { inputs: oldinputs, changes: ["numberA"] }, 1); + expect(updateRes.status).toBe("success"); + const interRes = graph_manager.handleNodeInputInteraction(graph.uuid, newNode.uuid, { id: "numberA", value: 2 }); + expect(interRes.status).toBe("success"); + } + } + } + }); + + test("Removing an edge", () => { + const pos = { x: 0, y: 0 }; + const addNodeARes = graph_manager.addNode(graph.uuid, nodeA, pos, 1); + const addNodeBRes = graph_manager.addNode(graph.uuid, nodeB, pos, 1); + + expect(addNodeARes.status).toBe("success"); + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.status).toBe("success"); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.status === "success" && addNodeBRes.status === "success") { + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.data && addNodeBRes.data) { + const nodes = graph.getNodes; + const anchorARes = nodes[addNodeARes.data.nodeId].findAnchorUUID("outA"); + const anchorBRes = nodes[addNodeBRes.data.nodeId].findAnchorUUID("inB"); + expect(anchorARes.status).toBe("success"); + expect(anchorBRes.status).toBe("success"); + if (anchorARes.status === "success" && anchorBRes.status === "success") { + expect(anchorARes.data).toBeDefined(); + expect(anchorBRes.data).toBeDefined(); + if (anchorARes.data && anchorBRes.data) { + // Add edge + const addEdgeRes = graph_manager.addEdge( + graph.uuid, + anchorARes.data.anchorUUID, + anchorBRes.data.anchorUUID, + 1 + ); + expect(addEdgeRes.status).toBe("success"); + if (addEdgeRes.status === "success" && addEdgeRes.data) { + // Edge should be added + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID].uuid).toBe(edge.uuid); + expect(edge.uuid).toBe(addEdgeRes.data.edgeId); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + // Remove edge + const removeEdgeRes = graph_manager.removeEdge( + graph.uuid, + anchorBRes.data.anchorUUID, + 1 + ); + expect(removeEdgeRes.status).toBe("success"); + if (removeEdgeRes.status === "success") { + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeSrc[anchorBRes.data.anchorUUID]).toBeUndefined(); + // Undo - Add Edge Back + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + // Edge should be added + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID].uuid).toBe(edge.uuid); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + // Redo - Remove Edge + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + if (redoRes.status === "success") { + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID]).toBeUndefined(); + } + } + } + } + } + } + } + }); + + test("Remove nodeA with edges", () => { + const pos = { x: 0, y: 0 }; + const addNodeARes = graph_manager.addNode(graph.uuid, nodeA, pos, 1); + const addNodeBRes = graph_manager.addNode(graph.uuid, nodeB, pos, 1); + + expect(addNodeARes.status).toBe("success"); + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.status).toBe("success"); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.status === "success" && addNodeBRes.status === "success") { + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.data && addNodeBRes.data) { + const nodes = graph.getNodes; + const anchorARes = nodes[addNodeARes.data.nodeId].findAnchorUUID("outA"); + const anchorBRes = nodes[addNodeBRes.data.nodeId].findAnchorUUID("inB"); + expect(anchorARes.status).toBe("success"); + expect(anchorBRes.status).toBe("success"); + if (anchorARes.status === "success" && anchorBRes.status === "success") { + expect(anchorARes.data).toBeDefined(); + expect(anchorBRes.data).toBeDefined(); + if (anchorARes.data && anchorBRes.data) { + // Add edge + const addEdgeRes = graph_manager.addEdge( + graph.uuid, + anchorARes.data.anchorUUID, + anchorBRes.data.anchorUUID, + 1 + ); + expect(addEdgeRes.status).toBe("success"); + if (addEdgeRes.status === "success" && addEdgeRes.data) { + // Edge should be added + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID].uuid).toBe(edge.uuid); + expect(edge.uuid).toBe(addEdgeRes.data.edgeId); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + // Remove Node + const removeNodeRes = graph_manager.removeNode( + graph.uuid, + addNodeARes.data.nodeId, + 1 + ); + expect(removeNodeRes.status).toBe("success"); + if (removeNodeRes.status === "success") { + // Node Should be removed + expect(graph.getNodes[addNodeARes.data.nodeId]).toBeUndefined(); + expect(Object.keys(graph.getNodes).length).toBe(1); + // Edge should be removed + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID]).toBeUndefined(); + // Undo - Add node and its edges + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + if (undoRes.status === "success") { + // Check for node + const newNode = + Object.keys(graph.getNodes)[0] === addNodeBRes.data.nodeId + ? Object.values(graph.getNodes)[1] + : Object.values(graph.getNodes)[0]; + expect(newNode).toBeDefined(); + expect(newNode.getName).toBe("Node-A"); + const newAnchorA = newNode.findAnchorUUID("outA"); + expect(newAnchorA.status).toBe("success"); + if (newAnchorA.status === "success") { + expect(newAnchorA.data).toBeDefined(); + if (newAnchorA.data) { + // Check for edge + expect(graph.getEdgeSrc[newAnchorA.data.anchorUUID]).toBeDefined(); + expect(graph.getEdgeSrc[newAnchorA.data.anchorUUID]).toContain( + anchorBRes.data.anchorUUID + ); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID]).toBeDefined(); + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(edge.getAnchorFrom).toBe(newAnchorA.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + // Redo - Remove Node and its edges + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + if (redoRes.status === "success") { + // Node should be removed + expect(graph.getNodes[newNode.uuid]).toBeUndefined(); + expect(Object.keys(graph.getNodes).length).toBe(1); + // Edge should be removed + expect(graph.getEdgeSrc[newAnchorA.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID]).toBeUndefined(); + } + } + } + } + } + } + } + } + } + } + }); + + test("Remove nodeB with edges", () => { + const pos = { x: 0, y: 0 }; + const addNodeARes = graph_manager.addNode(graph.uuid, nodeA, pos, 1); + const addNodeBRes = graph_manager.addNode(graph.uuid, nodeB, pos, 1); + + expect(addNodeARes.status).toBe("success"); + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.status).toBe("success"); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.status === "success" && addNodeBRes.status === "success") { + expect(addNodeARes.data).toBeDefined(); + expect(addNodeBRes.data).toBeDefined(); + if (addNodeARes.data && addNodeBRes.data) { + const nodes = graph.getNodes; + const anchorARes = nodes[addNodeARes.data.nodeId].findAnchorUUID("outA"); + const anchorBRes = nodes[addNodeBRes.data.nodeId].findAnchorUUID("inB"); + expect(anchorARes.status).toBe("success"); + expect(anchorBRes.status).toBe("success"); + if (anchorARes.status === "success" && anchorBRes.status === "success") { + expect(anchorARes.data).toBeDefined(); + expect(anchorBRes.data).toBeDefined(); + if (anchorARes.data && anchorBRes.data) { + // Add edge + const addEdgeRes = graph_manager.addEdge( + graph.uuid, + anchorARes.data.anchorUUID, + anchorBRes.data.anchorUUID, + 1 + ); + expect(addEdgeRes.status).toBe("success"); + if (addEdgeRes.status === "success" && addEdgeRes.data) { + // Edge should be added + const edge = graph.getEdgeDest[anchorBRes.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID].uuid).toBe(edge.uuid); + expect(edge.uuid).toBe(addEdgeRes.data.edgeId); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(anchorBRes.data.anchorUUID); + expect(graph.getEdgeSrc[edge.getAnchorFrom]).toContain(edge.getAnchorTo); + // Remove Node + const removeNodeRes = graph_manager.removeNode( + graph.uuid, + addNodeBRes.data.nodeId, + 1 + ); + expect(removeNodeRes.status).toBe("success"); + if (removeNodeRes.status === "success") { + // Node Should be removed + expect(graph.getNodes[addNodeBRes.data.nodeId]).toBeUndefined(); + expect(Object.keys(graph.getNodes).length).toBe(1); + // Edge should be removed + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeDest[anchorBRes.data.anchorUUID]).toBeUndefined(); + // Undo - Add node and its edges + const undoRes = graph_manager.undoEvent(graph.uuid); + expect(undoRes.status).toBe("success"); + if (undoRes.status === "success") { + // Check for node + const newNode = + Object.keys(graph.getNodes)[0] === addNodeARes.data.nodeId + ? Object.values(graph.getNodes)[1] + : Object.values(graph.getNodes)[0]; + expect(newNode).toBeDefined(); + expect(newNode.getName).toBe("Node-B"); + const newAnchorB = newNode.findAnchorUUID("inB"); + expect(newAnchorB.status).toBe("success"); + if (newAnchorB.status === "success") { + expect(newAnchorB.data).toBeDefined(); + if (newAnchorB.data) { + // Check for edge + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeDefined(); + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toContain( + newAnchorB.data.anchorUUID + ); + expect(graph.getEdgeDest[newAnchorB.data.anchorUUID]).toBeDefined(); + const edge = graph.getEdgeDest[newAnchorB.data.anchorUUID]; + expect(edge).toBeDefined(); + expect(edge.getAnchorFrom).toBe(anchorARes.data.anchorUUID); + expect(edge.getAnchorTo).toBe(newAnchorB.data.anchorUUID); + // Redo - Remove Node and its edges + const redoRes = graph_manager.redoEvent(graph.uuid); + expect(redoRes.status).toBe("success"); + if (redoRes.status === "success") { + // Node should be removed + expect(graph.getNodes[newNode.uuid]).toBeUndefined(); + expect(Object.keys(graph.getNodes).length).toBe(1); + // Edge should be removed + expect(graph.getEdgeSrc[anchorARes.data.anchorUUID]).toBeUndefined(); + expect(graph.getEdgeDest[newAnchorB.data.anchorUUID]).toBeUndefined(); + } + } + } + } + } + } + } + } + } + } + }); + + +}); diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts index 7acfd71e..704ea6fd 100644 --- a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts @@ -273,4 +273,11 @@ jest.mock('../../../../../src/electron/lib/plugins/PluginManager') graphManager.onGraphUpdated(graph.uuid,GRAPH_UPDATED_EVENT,CoreGraphUpdateParticipant.system); expect(subscriber.onGraphChanged).toBeCalled() }) + + test("Update Graph metadata", () => { + expect(() => { const res = graphManager.addGraph(graph) }).not.toThrow("some error"); + + const res = graphManager.updateGraphMetadata(graph.uuid, {}, 1); + expect(res.status).toBe("success"); + }) }); \ No newline at end of file From 3dff9cfad7d04c829963b51d1cc3063776c28f37 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 27 Sep 2023 06:46:49 +0200 Subject: [PATCH 176/209] Bump Blix to v1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb0b0b67..5f2255df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blix", "description": "Blix - An AI-assisted graph photo editor", - "version": "1.1.0", + "version": "1.2.0", "repository": { "type": "git", "url": "https://github.com/COS301-SE-2023/AI-Photo-Editor.git" From 2311f915cb8a5ea36b83168c1cc3ced2b00eb887 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Wed, 27 Sep 2023 08:24:05 +0200 Subject: [PATCH 177/209] pluginCommand tests done --- .../electron/lib/plugins/plugin.spec.ts | 5 + .../lib/plugins/pluginCommands.spec.ts | 110 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 tests/unit-tests/electron/lib/plugins/pluginCommands.spec.ts diff --git a/tests/unit-tests/electron/lib/plugins/plugin.spec.ts b/tests/unit-tests/electron/lib/plugins/plugin.spec.ts index 2cd01f30..0d6dafa0 100644 --- a/tests/unit-tests/electron/lib/plugins/plugin.spec.ts +++ b/tests/unit-tests/electron/lib/plugins/plugin.spec.ts @@ -38,6 +38,11 @@ jest.mock("electron", () => ({ }, ipcMain: { on: jest.fn() + }, + session: { + defaultSession: { + clearCache: jest.fn() + } } })); diff --git a/tests/unit-tests/electron/lib/plugins/pluginCommands.spec.ts b/tests/unit-tests/electron/lib/plugins/pluginCommands.spec.ts new file mode 100644 index 00000000..beda3a4d --- /dev/null +++ b/tests/unit-tests/electron/lib/plugins/pluginCommands.spec.ts @@ -0,0 +1,110 @@ +import {refreshPluginsCommand} from "../../../../../src/electron/lib/plugins/pluginCommands"; +import { Blix } from "../../../../../src/electron/lib/Blix"; +import { MainWindow } from "../../../../../src/electron/lib/api/apis/WindowApi"; +import { boolean } from "zod"; +import { CommandContext } from "../../../../../src/electron/lib/registries/CommandRegistry"; + +jest.mock('../../../../../src/electron/lib/registries/CommandRegistry', () => { + const originalModule = jest.requireActual('../../../../../src/electron/lib/registries/CommandRegistry'); // Replace with the correct path + return { + ...originalModule, + CommandContext: { + ...originalModule.CommandContext, + pluginManager: { + loadBasePlugins: jest.fn(), + }, + }, + }; +}); + + +// ==================================================== +const mainWindow: MainWindow = { + apis: { + commandRegistryApi: jest.fn(), + clientGraphApi: jest.fn(), + clientProjectApi: jest.fn(), + toolboxClientApi: { + registryChanged: jest.fn(), + }, + commandClientApi: { + registryChanged: jest.fn(), + }, + projectClientApi: { + onProjectCreated: jest.fn(), + onProjectChanged: jest.fn() + }, + graphClientApi: { + graphChanged: jest.fn(), + graphRemoved: jest.fn(), + uiInputsChanged: jest.fn() + }, + utilClientApi: { + showToast: jest.fn((message) => { + // console.log(message); + }), + } + + } + } as any; + +// jest.mock("../../../src/electron/lib/projects/ProjectManager"); + + +jest.mock("chokidar", () => ({ + default: { + watch: jest.fn(() => { + return { + on: jest.fn() + } + }), + } +})); + +jest.mock("electron", () => ({ + app: { + getPath: jest.fn((path) => { + return "test/electron"; + }), + getName: jest.fn(() => { + return "TestElectron"; + }), + getVersion: jest.fn(() => { + return "v1.1.1"; + }), + getAppPath: jest.fn(() => { + return "test/electron"; + }) + }, + dialog: { + showSaveDialog: jest.fn(() => { + return { filePath: "path.blix" }; + }), + showOpenDialog: jest.fn(() => { + return { filePaths: ["path1.blix", "path2.blix"] }; + }) + }, + ipcMain: { + on: jest.fn() + }, + session: { + defaultSession: { + clearCache: jest.fn() + } + } +})); + + +it("Test refreshPluginsCommand", async () => { + + const blix = new Blix(); + const ctx = { + pluginManager: { + loadBasePlugins: jest.fn(), + } + } as unknown as CommandContext; + + + const a = await refreshPluginsCommand.handler(ctx); + expect(a.status).toBe("success"); + }) \ No newline at end of file From 57972dfdaf35ddbdc7d2715a48000010675f6e49 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Wed, 27 Sep 2023 08:28:47 +0200 Subject: [PATCH 178/209] small fix --- jest.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.json b/jest.config.json index 24c01e15..181f591f 100644 --- a/jest.config.json +++ b/jest.config.json @@ -15,7 +15,7 @@ ], "^.+\\.js$": "babel-jest" }, - "coveragePathIgnorePatterns": ["blix-plugins"], + "coveragePathIgnorePatterns": ["blix-plugins","node_modules","src/electron/lib/plugins/PluginManager"], "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node", "svelte"], "coverageDirectory": "coverage", From 49eb2bc25cc028ab2e1d0a0f14692624c2bbe3d2 Mon Sep 17 00:00:00 2001 From: CenturionLC Date: Wed, 27 Sep 2023 09:02:17 +0200 Subject: [PATCH 179/209] update package.json --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4a766b6..a2b6f1df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blix", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blix", - "version": "1.1.0", + "version": "1.2.0", "hasInstallScript": true, "license": "MIT", "dependencies": { From c84c027f5c8a33764ac2635f4b04a9c5445ee087 Mon Sep 17 00:00:00 2001 From: Klairgo Date: Wed, 27 Sep 2023 09:49:58 +0200 Subject: [PATCH 180/209] Fix e2e test --- tests/e2e/blix.spec.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/e2e/blix.spec.ts b/tests/e2e/blix.spec.ts index 9b637ca5..bf5dcab4 100644 --- a/tests/e2e/blix.spec.ts +++ b/tests/e2e/blix.spec.ts @@ -4,9 +4,9 @@ import { join } from "path"; import { waitForDebugger } from "inspector"; import exp from "constants"; -test('E2E testing blix', async () => { - const electronApp = await electron.launch({ args: ['.']}) +test('E2E testing blix', async () => { +const electronApp = await electron.launch({ args: ['.']}) const isPackaged = await electronApp.evaluate(async ({ app }) => { // This runs in Electron's main process, parameter here is always // the result of the require('electron') in the main app script. @@ -32,45 +32,47 @@ test('E2E testing blix', async () => { // and return its Page object const window = await electronApp.firstWindow(); - await window.locator('svg').first().click(); + await window.keyboard.press('Escape', {delay: 4000}); + // await window.locator('svg').first().click(); - expect((await window.getByTitle('Untitled').allInnerTexts()).at(0)).toBe("Untitled"); + expect((await window.getByTitle('Untitled').allInnerTexts()).at(0)).toBe("Untitled-1"); const graph = window.locator('section').first(); await graph.click({button: 'right'}); // Check plugin menu const plugin = window.getByText('blix'); - expect((await plugin.allInnerTexts()).at(0)).toBe("blix"); - expect((await window.getByText('hello-plugin').allInnerTexts()).at(0)).toBe("hello-plugin"); - expect((await window.getByText('math-plugin').allInnerTexts()).at(0)).toBe("math-plugin"); - expect((await window.getByText('input-plugin').allInnerTexts()).at(0)).toBe("input-plugin"); - expect((await window.getByText('sharp-plugin').allInnerTexts()).at(0)).toBe("sharp-plugin"); + expect(((await plugin.allInnerTexts()).at(2))).toBe("Blix"); + expect((await window.getByText('Blink').allInnerTexts()).at(0)).toBe("Blink"); + expect((await window.getByText('GLFX').allInnerTexts()).at(0)).toBe("GLFX"); + expect((await window.getByText('Input').allInnerTexts()).at(0)).toBe("Input"); + expect((await window.getByText('Logic').allInnerTexts()).at(0)).toBe("Logic"); + expect((await window.getByText('Math').allInnerTexts()).at(0)).toBe("Math"); // Check add node to graph - await plugin.click(); - await window.getByText('Output').click(); + await (await plugin.all()).at(2)?.click(); + await (await window.getByText('Output').all()).at(2)?.click(); await window.getByText('Output').first().click({button: 'right'}); expect((await window.locator('Output').allInnerTexts()).at(0)).toBe(undefined); // Check add graph await window.getByText('Graph').click(); - expect((await window.getByText('Graph', { exact: true }).allInnerTexts()).length).toBe(4); + expect((await window.getByText('Graph', { exact: true }).allInnerTexts()).length).toBe(2); await window.getByTitle('Add Graph').getByRole('img').click(); - expect((await window.getByText('Graph', { exact: true }).allInnerTexts()).length).toBe(4); + expect((await window.getByText('Graph', { exact: true }).allInnerTexts()).length).toBe(2); // Connect edges - await graph.click({button: 'right'}); - await plugin.click(); + await graph.click({button: 'right', position: {x: 100, y: 100}}); + await (await plugin.all()).at(2)?.click(); await window.getByText('Output').click(); - await graph.click({button: 'right'}); - await window.getByText('input-plugin').first().click(); + await graph.click({button: 'right', position: {x: 300, y: 600}}); + await (await window.getByText('Input').all()).at(1)?.click({delay: 100}); await window.getByText('Input number').click(); await window.locator('css=div.svelvet-anchor').nth(1).dragTo(window.locator('css=div.svelvet-anchor').first()); - expect((await window.locator('css=div.output.normal').allInnerTexts()).at(0)).toBe("3"); + expect((await window.locator('css=div.output.normal').allInnerTexts()).at(0)).toBe("0"); // close app await electronApp.close() From cfc4b605466da75ec230f9d095e6dcdad8dd94b1 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 27 Sep 2023 10:01:16 +0200 Subject: [PATCH 181/209] Rewrite media output id logic --- src/electron/lib/api/apis/GraphApi.ts | 4 ++ .../lib/core-graph/CoreGraphManager.ts | 37 +++++++++++------ src/frontend/lib/api/apis/MediaClientApi.ts | 8 +++- src/frontend/lib/stores/GraphStore.ts | 31 +++----------- src/frontend/lib/stores/MediaStore.ts | 40 +++++++------------ src/frontend/ui/tiles/Assets.svelte | 4 +- src/frontend/ui/tiles/Media.svelte | 38 +++++++++--------- src/frontend/ui/utils/graph/PluginNode.svelte | 6 +-- .../nodeUIComponents/dials/DiffDial.svelte | 5 ++- .../nodeUIComponents/dials/TweakDial.svelte | 5 ++- 10 files changed, 87 insertions(+), 91 deletions(-) diff --git a/src/electron/lib/api/apis/GraphApi.ts b/src/electron/lib/api/apis/GraphApi.ts index ff2d72e9..1047a20c 100644 --- a/src/electron/lib/api/apis/GraphApi.ts +++ b/src/electron/lib/api/apis/GraphApi.ts @@ -95,4 +95,8 @@ export class GraphApi implements ElectronMainApi { async updateUIPositions(graphUUID: UUID, positions: { [key: UUID]: SvelvetCanvasPos }) { this._blix.graphManager.updateUIPositions(graphUUID, positions); } + + async getMediaOutputs(graphIds: UUID[]) { + return this._blix.graphManager.getMediaOutputs(graphIds); + } } diff --git a/src/electron/lib/core-graph/CoreGraphManager.ts b/src/electron/lib/core-graph/CoreGraphManager.ts index d6b2a7d2..3e8341d9 100644 --- a/src/electron/lib/core-graph/CoreGraphManager.ts +++ b/src/electron/lib/core-graph/CoreGraphManager.ts @@ -72,13 +72,6 @@ export class CoreGraphManager { execute: { graphUUID, node, pos }, revert: { graphUUID, nodeUUId: res.data.nodeId }, }); - - if (node.signature === "blix.output") { - this._outputIds[res.data.nodeId] = "default"; - this._mainWindow?.apis.mediaClientApi.onMediaOutputIdsChanged( - new Set(Object.values(this._outputIds)) - ); - } } else if (eventArgs) { const { event } = eventArgs; if (event?.element === "Node" && event.operation === "Add") { @@ -123,6 +116,7 @@ export class CoreGraphManager { } delete this._outputIds[nodeUUID]; + this._mainWindow?.apis.mediaClientApi.outputNodesChanged(); } return res; @@ -240,7 +234,9 @@ export class CoreGraphManager { participant: CoreGraphUpdateParticipant, eventArgs?: EventArgs ): QueryResponse { - if (!this._graphs[graphUUID]) { + const graph = this._graphs[graphUUID]; + + if (!graph) { return { status: "error", message: "Graph does not exist" }; } @@ -257,11 +253,9 @@ export class CoreGraphManager { const uiConfigs = this._toolbox.getNodeInstance(signature).uiConfigs; const changes = nodeUIInputs.changes; - if (signature === "blix.output") { + if (signature === "blix.output" && changes.includes("outputId")) { this._outputIds[nodeUUID] = nodeUIInputs.inputs.outputId as string; - this._mainWindow?.apis.mediaClientApi.onMediaOutputIdsChanged( - new Set(Object.values(this._outputIds)) - ); + this._mainWindow?.apis.mediaClientApi.outputNodesChanged(); } let shouldUpdate = false; @@ -422,6 +416,25 @@ export class CoreGraphManager { return; } + getMediaOutputs(graphIds: UUID[]) { + const outputs: { nodeId: string; mediaId: string }[] = []; + + graphIds.forEach((graphId) => { + const graph = this._graphs[graphId]; + if (graph) { + const outputNodes = graph.getOutputNodes; + Object.keys(outputNodes).forEach((outputNodeId) => { + const mediaOutputId = this._outputIds[outputNodeId]; + if (mediaOutputId) { + outputs.push({ nodeId: outputNodeId, mediaId: mediaOutputId }); + } + }); + } + }); + + return outputs; + } + // =============================================== // Graph Events // =============================================== diff --git a/src/frontend/lib/api/apis/MediaClientApi.ts b/src/frontend/lib/api/apis/MediaClientApi.ts index 6a165678..c08f5e68 100644 --- a/src/frontend/lib/api/apis/MediaClientApi.ts +++ b/src/frontend/lib/api/apis/MediaClientApi.ts @@ -7,7 +7,11 @@ export class MediaClientApi implements ElectronWindowApi { mediaStore.refreshStore(mediaOutput); } - public onMediaOutputIdsChanged(outputIds: Set) { - mediaStore.updateOutputIds(outputIds); + // public onMediaOutputIdsChanged(outputIds: Set) { + // mediaStore.updateOutputIds(outputIds); + // } + + public async outputNodesChanged() { + await mediaStore.outputNodesChanged(); } } diff --git a/src/frontend/lib/stores/GraphStore.ts b/src/frontend/lib/stores/GraphStore.ts index 05db7e09..bf587d2f 100644 --- a/src/frontend/lib/stores/GraphStore.ts +++ b/src/frontend/lib/stores/GraphStore.ts @@ -17,18 +17,6 @@ import type { MediaOutputId } from "@shared/types/media"; import { tick } from "svelte"; import type { UUID } from "@shared/utils/UniqueEntity"; -// When the the CoreGraphApi type has to be imported into the backend -// (WindowApi.ts) so that the API can be bound then it tries to import the type -// below because the GraphStore gets used in the CoreGraphApi (its like one long -// type dependency chain), this seems to cause some sort of duplicate export -// issue originating from the svelvet node files when it tries to check the -// types at compile time: node_modules/svelvet/dist/types/index.d.ts:4:1 - error -// TS2308: Module './general' has already exported a member named -// 'ActiveIntervals'. Consider explicitly re-exporting to resolve the ambiguity. - -// Not sure how to solve this at the moment, so had to add a temp fix below -// unfortunately because of time constraints. - // import type { Connections } from "blix_svelvet"; // type Connections = (string | number | [string | number, string | number] | null)[]; @@ -116,25 +104,16 @@ export class GraphStore { for (const input in inputs) { if (!inputs.hasOwnProperty(input)) continue; - let first = true; - let oldState: unknown; + this.uiInputUnsubscribers[node].push( inputs[input].subscribe((state) => { - // Ensure the subscribe does not run when first created - if (first) { - first = false; - oldState = state; - } else if (state !== oldState) { - // Prevent unnecessary updates when the value is the exact same as the prior - // Only would then update if maybe the set function is used but old state === new state - this.globalizeUIInputs(node, input).catch((err) => { - return; - }); - oldState = state; - } + this.globalizeUIInputs(node, input).catch((err) => { + return; + }); }) ); } + // Update frontend if (position) this.uiPositionUnsubscribers[node].push( diff --git a/src/frontend/lib/stores/MediaStore.ts b/src/frontend/lib/stores/MediaStore.ts index 2adb719b..e1341cc5 100644 --- a/src/frontend/lib/stores/MediaStore.ts +++ b/src/frontend/lib/stores/MediaStore.ts @@ -1,6 +1,7 @@ import type { DisplayableMediaOutput, MediaOutputId } from "@shared/types/media"; import { derived, get, writable } from "svelte/store"; import { commandStore } from "./CommandStore"; +import { projectsStore } from "./ProjectStore"; type MediaOutputs = { [key: MediaOutputId]: DisplayableMediaOutput; @@ -8,27 +9,19 @@ type MediaOutputs = { class MediaStore { private store = writable({}); - private outputIds = writable>(); + private outputIds = writable<{ nodeId: string; mediaId: string }[]>([]); public refreshStore(media: DisplayableMediaOutput) { - // Refresh media store this.store.update((mediaOutputs) => { mediaOutputs[media.outputId] = media; return mediaOutputs; }); } - public updateOutputIds(ids: Set) { - const oldIds = get(this.outputIds); - if (!oldIds) this.outputIds.set(ids); - - // Wont set store and notfiy subscribers if the value allOutputIds have remained the same. - // This is the case where the undo/redo system willchange something and then the stores see a change and do their - // own update but their update wont matter. - - if (oldIds && ids && !equivSets(oldIds, ids)) { - this.outputIds.set(ids); - } + public async outputNodesChanged() { + const projectGraphIds = get(projectsStore.activeProjectGraphIds); + const mediaOutputs = await window.apis.graphApi.getMediaOutputs(projectGraphIds); + this.outputIds.set(mediaOutputs); } // Stop listening for graph changes @@ -78,7 +71,8 @@ class MediaStore { public getMediaOutputIdsReactive() { return derived(this.outputIds, (store) => { - return store; + const set = new Set(store.map((id) => id.mediaId)); + return Array.from(set); }); } @@ -89,21 +83,15 @@ class MediaStore { }); } - public get subscribe() { - return this.store.subscribe; + public getMediaOutputId(nodeId: string) { + return get(this.outputIds).find((id) => id.nodeId === nodeId)?.mediaId || null; } -} -function equivSets(s1: Set, s2: Set): boolean { - if (s1.size !== s2.size) return false; - for (const element of s1) { - if (!s2.has(element)) { - return false; - } else { - s2.delete(element); - } + public get subscribe() { + return this.store.subscribe; } - return true; } export const mediaStore = new MediaStore(); + +export const nodeIdLastClicked = writable(""); diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index fca51b40..9193f0b6 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -110,7 +110,7 @@ } -
+
{#if Object.keys($cacheStore).length > 0}
{#each Object.keys($cacheStore) as uuid} @@ -178,7 +178,7 @@ ; $: if ($mediaOutputIds) { selectedItems = Array.from($mediaOutputIds) @@ -55,31 +56,32 @@ .map((id) => ({ id, title: id })); } - let mediaId = writable(""); - let oldMediaId: string | null = null; - let webview: WebView; + // Changes output on output node click + // $: if ($nodeIdLastClicked) { + // const mediaOutputId = mediaStore.getMediaOutputId($nodeIdLastClicked); + // if (mediaOutputId) { + // selectedMediaId.set(mediaOutputId); + // } + // } - const unsubMedia = mediaId.subscribe((newMediaId) => { - // console.log("SUBSCRIBE MEDIA ID", oldMediaId, newMediaId); + const unsubMedia = selectedMediaId.subscribe((newMediaId) => { connectNewMedia(oldMediaId, newMediaId); oldMediaId = newMediaId; }); - // TODO: Add toggle that auto-switches media to the last selected output node - let selectedNode: { graphUUID: GraphUUID; outNode: GraphNodeUUID } | null; - let media: Readable; - async function connectNewMedia(oldMediaId: string | null, mediaId: string) { if (oldMediaId !== null) { - // console.log("STOPPING OLD", oldMediaId); await mediaStore.stopMediaReactive(oldMediaId); } - media = await mediaStore.getMediaReactive(mediaId); - // console.log("CONNECT NEW MEDIA", mediaId, media); + if (mediaId) { + media = await mediaStore.getMediaReactive(mediaId); + } else { + media = writable(null); + } } onDestroy(async () => { - await mediaStore.stopMediaReactive($mediaId); + await mediaStore.stopMediaReactive($selectedMediaId); unsubMedia(); }); @@ -135,7 +137,7 @@
@@ -175,7 +177,7 @@ /> --> {:else} -
+

No content!

Add an Output node to the graph to create a media output

diff --git a/src/frontend/ui/utils/graph/PluginNode.svelte b/src/frontend/ui/utils/graph/PluginNode.svelte index e6f0f562..bff99fc4 100644 --- a/src/frontend/ui/utils/graph/PluginNode.svelte +++ b/src/frontend/ui/utils/graph/PluginNode.svelte @@ -7,6 +7,7 @@ import { graphMall } from "../../../lib/stores/GraphStore"; import { colord, extend } from "colord"; import a11yPlugin from "colord/plugins/a11y"; + import { nodeIdLastClicked } from "../../../lib/stores/MediaStore"; extend([a11yPlugin]); @@ -59,13 +60,12 @@ } async function nodeClicked(e: CustomEvent) { - console.log("NODE CLICKED"); if (e.detail.e.button === 2) { - console.log("DELETE NODE EVENT"); - const [_2, ...nodeUUIDParts] = e.detail.node.id.split("_"); const nodeUUID = nodeUUIDParts.join("_"); await window.apis.graphApi.removeNode(graphId, nodeUUID); + } else { + nodeIdLastClicked.set(node.uuid); } } diff --git a/src/frontend/ui/utils/graph/nodeUIComponents/dials/DiffDial.svelte b/src/frontend/ui/utils/graph/nodeUIComponents/dials/DiffDial.svelte index 37b03cdd..a50c548e 100644 --- a/src/frontend/ui/utils/graph/nodeUIComponents/dials/DiffDial.svelte +++ b/src/frontend/ui/utils/graph/nodeUIComponents/dials/DiffDial.svelte @@ -3,10 +3,13 @@ import type { UIComponentConfig, UIComponentProps } from "@shared/ui/NodeUITypes"; import Dial from "./Dial.svelte"; import { faCodeCompare } from "@fortawesome/free-solid-svg-icons"; + import { blixStore } from "../../../../../lib/stores/BlixStore"; export let props: UIComponentProps; export let inputStore: UIValueStore; export let config: UIComponentConfig; - +
+ +
diff --git a/src/frontend/ui/utils/graph/nodeUIComponents/dials/TweakDial.svelte b/src/frontend/ui/utils/graph/nodeUIComponents/dials/TweakDial.svelte index 7ea70e4c..ff4d7154 100644 --- a/src/frontend/ui/utils/graph/nodeUIComponents/dials/TweakDial.svelte +++ b/src/frontend/ui/utils/graph/nodeUIComponents/dials/TweakDial.svelte @@ -3,10 +3,13 @@ import type { UIComponentConfig, UIComponentProps } from "@shared/ui/NodeUITypes"; import { faCogs } from "@fortawesome/free-solid-svg-icons"; import Dial from "./Dial.svelte"; + import { blixStore } from "../../../../../lib/stores/BlixStore"; export let props: UIComponentProps; export let inputStore: UIValueStore; export let config: UIComponentConfig; - +
+ +
From d9fdb29125962a70bed67232764715d5e00260b3 Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 27 Sep 2023 10:10:56 +0200 Subject: [PATCH 182/209] Fix test --- .../electron/lib/core-graph/CoreGraphManager.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts index 7acfd71e..f5521b4b 100644 --- a/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts +++ b/tests/unit-tests/electron/lib/core-graph/CoreGraphManager.spec.ts @@ -24,6 +24,9 @@ const mainWindow: MainWindow = { }, graphClientApi: { graphRemoved: jest.fn(), + }, + mediaClientApi: { + outputNodesChanged: jest.fn(), } } From cc204200accd43f17d36ad9894168d8a3004239e Mon Sep 17 00:00:00 2001 From: Armand Krynauw Date: Wed, 27 Sep 2023 11:21:14 +0200 Subject: [PATCH 183/209] General UI improvements --- src/frontend/ui/base/layout/Layout.svelte | 38 ++++++++++++++++++- src/frontend/ui/base/layout/Panel.svelte | 4 +- src/frontend/ui/base/layout/PanelBlip.svelte | 4 +- .../ui/base/layout/TileSelector.svelte | 34 ++++++++++------- src/frontend/ui/tiles/Assets.svelte | 9 +++-- src/frontend/ui/tiles/Graph.svelte | 38 ++++++++++++++++++- src/frontend/ui/tiles/Media.svelte | 11 +++--- .../ui/utils/graph/SelectionBoxItem.svelte | 2 +- 8 files changed, 109 insertions(+), 31 deletions(-) diff --git a/src/frontend/ui/base/layout/Layout.svelte b/src/frontend/ui/base/layout/Layout.svelte index 5f2c46d1..91e9392a 100644 --- a/src/frontend/ui/base/layout/Layout.svelte +++ b/src/frontend/ui/base/layout/Layout.svelte @@ -1,6 +1,8 @@
@@ -8,14 +10,48 @@ {#key $projectsStore.activeProject.id} {/key} + {:else} +
+
+

No projects!

+

Create a project to begin creating

+
{/if}
- diff --git a/src/frontend/ui/base/layout/Panel.svelte b/src/frontend/ui/base/layout/Panel.svelte index f6a2e4bc..2ef431f7 100644 --- a/src/frontend/ui/base/layout/Panel.svelte +++ b/src/frontend/ui/base/layout/Panel.svelte @@ -388,12 +388,12 @@ } .splitpanes.main-theme .splitpanes__splitter:active { - background-color: rgb(244 63 94 / 0.7); + background-color: rgb(244, 63, 94); border: none; } .splitpanes.main-theme .splitpanes__splitter:hover { - background-color: rgb(244 63 94 / 0.7); + background-color: rgb(244, 63, 94); border: none; } diff --git a/src/frontend/ui/base/layout/PanelBlip.svelte b/src/frontend/ui/base/layout/PanelBlip.svelte index a0e560d2..823d9157 100644 --- a/src/frontend/ui/base/layout/PanelBlip.svelte +++ b/src/frontend/ui/base/layout/PanelBlip.svelte @@ -124,7 +124,7 @@ height: var(--width); background-color: transparent; - color: #f38ba8; + color: rgb(244, 63, 94); text-align: center; font-size: 0.8em; @@ -134,7 +134,7 @@ z-index: 10000; } .blip:hover { - background-color: #f38ba8; + background-color: rgb(244, 63, 94); cursor: crosshair; } diff --git a/src/frontend/ui/base/layout/TileSelector.svelte b/src/frontend/ui/base/layout/TileSelector.svelte index da565f28..afd988f4 100644 --- a/src/frontend/ui/base/layout/TileSelector.svelte +++ b/src/frontend/ui/base/layout/TileSelector.svelte @@ -19,6 +19,7 @@ faPuzzlePiece, faGlobe, faCamera, + faBrush, } from "@fortawesome/free-solid-svg-icons"; import { tileStore } from "../../../lib/stores/TileStore"; @@ -36,17 +37,17 @@ tileDict["graph"] = { displayName: "graph", description: null, icon: faDiagramProject }; tileDict["media"] = { displayName: "media", description: null, icon: faImage }; tileDict["webcamera"] = { displayName: "camera", description: null, icon: faCamera }; - tileDict["inspector"] = { displayName: "inspector", description: null, icon: faMagnifyingGlass }; + // tileDict["inspector"] = { displayName: "inspector", description: null, icon: faMagnifyingGlass }; tileDict["webview"] = { displayName: "webview", description: null, icon: faPuzzlePiece }; tileDict["browser"] = { displayName: "browser", description: null, icon: faGlobe }; - tileDict["promptBox"] = { displayName: "promptBox", description: null, icon: faTerminal }; - tileDict["shortcutSettings"] = { - displayName: "shortcutSettings", - description: null, - icon: faKeyboard, - }; + // tileDict["promptBox"] = { displayName: "promptBox", description: null, icon: faTerminal }; + // tileDict["shortcutSettings"] = { + // displayName: "shortcutSettings", + // description: null, + // icon: faKeyboard, + // }; tileDict["debug"] = { displayName: "debug", description: null, icon: faCode }; - tileDict["assets"] = { displayName: "assets", description: null, icon: faImage }; + tileDict["assets"] = { displayName: "assets", description: null, icon: faBrush }; for (const tile in tiles) { tileDict[tiles[tile].signature] = { @@ -64,8 +65,13 @@ } -
+
+
{#if open}
@@ -94,17 +100,16 @@ position: absolute; top: 0px; left: 0.8rem; - width: 1.6rem; - height: 1rem; + /* height: 1.3rem; */ z-index: 40; - padding: 0px; - padding-top: 0.1rem; + /* padding: 0px; + padding-top: 0.1rem; */ border-radius: 0px 0px 0.4rem 0.4rem; background-color: #11111b; overflow: hidden; - font-size: 0.6em; + font-size: 0.7em; text-align: center; cursor: pointer; @@ -158,6 +163,7 @@ .tileOption { cursor: pointer; + border-radius: 7px; } .innerTileOption { diff --git a/src/frontend/ui/tiles/Assets.svelte b/src/frontend/ui/tiles/Assets.svelte index 9193f0b6..e00a0552 100644 --- a/src/frontend/ui/tiles/Assets.svelte +++ b/src/frontend/ui/tiles/Assets.svelte @@ -260,12 +260,13 @@ } .placeholder { - padding-top: 140px; - text-align: center; - display: inline-block; width: 100%; height: 100%; - margin-top: 1em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; h1 { font-size: 1.5em; diff --git a/src/frontend/ui/tiles/Graph.svelte b/src/frontend/ui/tiles/Graph.svelte index 26dec67d..f7e1a021 100644 --- a/src/frontend/ui/tiles/Graph.svelte +++ b/src/frontend/ui/tiles/Graph.svelte @@ -16,6 +16,8 @@ import { projectsStore } from "../../lib/stores/ProjectStore"; import { get } from "svelte/store"; import type { SelectionBoxItem } from "../../types/selection-box"; + import { faDiagramProject } from "@fortawesome/free-solid-svg-icons"; + import Fa from "svelte-fa"; // import { type Anchor } from "blix_svelvet/dist/types"; // TODO: Use to createEdge // TODO: Abstract panelId to use a generic UUID @@ -392,10 +394,14 @@ {/key} --> {:else} -
No graphs
+
+
+

No graphs!

+

Create a graph to start a workflow

+
{/if} - diff --git a/src/frontend/ui/tiles/Media.svelte b/src/frontend/ui/tiles/Media.svelte index c6e6188c..6ddcf0bd 100644 --- a/src/frontend/ui/tiles/Media.svelte +++ b/src/frontend/ui/tiles/Media.svelte @@ -138,7 +138,7 @@