36 Commits

Author SHA1 Message Date
Eric Tuvesson
307a13f7be chore: bump version 2025-06-07 17:39:32 +02:00
Eric Tuvesson
4e96d23585 feat: Deploy popup forward in editor version (#96) 2025-06-03 00:17:55 +02:00
Eric Tuvesson
2fd4b67a08 feat: Command, add and use Cloud Service (#95) 2025-06-03 00:08:10 +02:00
Eric Tuvesson
50e266e3e4 feat: Save "nodelibrary.json" with the project (#94)
* feat: Save "nodelibrary.json" with the project

To manage the project without the editor/preview, we need to know more about the Node Library. To make this possible, let's save the Node Library next to the project. Hopefully it will work smoothly for a lot of projects.

I have tested it with a few big projects, and it works well for them.

* chore: clean up

* fix: Make compact and check if there is some data
2025-06-02 19:38:18 +02:00
Eric Tuvesson
7ca69b809a fix: Cloud Function failure response (#93)
Error: undefined is not an object (evaluating 'e.error')
2025-03-28 16:11:18 +01:00
Eric Tuvesson
80c7c01805 chore: Rename "Noodl AI" (#92) 2025-03-25 21:38:39 +13:00
Eric Tuvesson
fc89bd9ce1 feat(editor): Search only show ID searches when 1:1 match (#90)
This removes a lot of the clutter from the search results.
2025-02-25 15:39:15 +01:00
Eric Tuvesson
2cfd36147a chore(editor): Convert Universal Search to TypeScript (#89) 2025-02-25 13:55:53 +01:00
Eric Tuvesson
0e13a8b033 fix: Keep object id when fetching a Object column from the Cloud Service (#87) 2025-01-16 14:40:44 +01:00
Eric Tuvesson
680bd58442 chore: Update "Used in x places" text (#84)
We cannot guarantee that this is always correct since there is repeaters and dynamic repeaters etc, but this can give a hint to how many times a component is used.
2025-01-08 22:02:10 +01:00
Eric Tuvesson
95db9f6528 chore: Add some TS types to WarningsModel (#86) 2025-01-08 21:58:37 +01:00
Eric Tuvesson
6205d08451 fix: showPopup replace error (#82)
Error: "TypeError: Cannot read properties of undefined (reading 'replace')"
2024-11-18 17:06:10 +01:00
Eric Tuvesson
14786b2144 chore(runtime): Add some JSDocs (#81) 2024-11-18 17:01:49 +01:00
Eric Tuvesson
e25155556f feat(runtime): 'Set Variable' node, add editor getInspectInfo (#80)
It is very nice to be able to inspect the value inside the variable even via the "Set Variable" node. Most of the time I see an additional variable node placed above the "Set Variable" node to just inspect the current value.
2024-11-13 13:35:03 +01:00
Eric Tuvesson
016837f466 feat(runtime): Add "keys" and "excludeKeys" to fetch record api (#79)
Adding support for some more options when fetching a record.
91f9aca25b/src/Routers/ClassesRouter.js (L60-L68)
2024-11-12 15:50:49 +01:00
Eric Tuvesson
fff03c05bf fix(runtime): Close Popup node with no actions causing error (#78)
TypeError: Cannot read properties of undefined (reading 'replace')
    at Object.onClosePopup (navigation.js:10:1)
    at EventEmitter.<anonymous> (nodecontext.js:491:1)
    at Object.onceWrapper (events.js:242:1)
    at EventEmitter.emit (events.js:153:1)
    at NoodlRuntime._doUpdate (noodl-runtime.js:338:1)
2024-10-03 11:24:57 +02:00
Eric Tuvesson
5dbb11bac8 chore: code clean up (#76) 2024-10-01 16:09:03 +02:00
Eric Tuvesson
d80870e835 fix(runtime): Passing in invalid date to "Date To String" node causes node scope to fail (#75) 2024-09-23 21:11:32 +02:00
Eric Tuvesson
a98e381f8c fix: deploy in devmode (#74)
This occurred because building in dev mode does not create the source map files which it expects to copy over.
2024-09-23 08:44:54 +02:00
Eric Tuvesson
72aec29e27 feat(runtime): Add "className" option support to "relatedTo" (#73)
* feat(runtime): Add "className" option support to "relatedTo"

Also includes error handling when "className" is not found.
2024-09-16 22:12:07 +02:00
Eric Tuvesson
2eb18acfef fix(viewer-react): Update CurrentUserObject TS typings (#72) 2024-09-11 16:15:02 +02:00
Eric Tuvesson
e1a1b31213 feat(viewer-react): Array, add "First Item Id" output (#71)
Same behaviour as Query Record's "First Record Id"
2024-09-11 16:14:49 +02:00
Eric Tuvesson
5febc490b4 feat(runtime): Query Records, add "Is Empty" output (#70) 2024-09-10 11:31:51 +02:00
Eric Tuvesson
48541347f0 fix(editor): Remove all the Cloud Triggers from the Cloud Function node Functions dropdown (#69) 2024-09-09 17:35:08 +02:00
Eric Tuvesson
cc79ea5f7e feat(viewer-react): Add groups to Component Stack outputs (#67) 2024-09-07 14:31:06 +02:00
Eric Tuvesson
46f6cb2da9 feat(viewer-react): Add Target Page input to "Push Component To Stack" (#66) 2024-09-07 14:14:39 +02:00
Eric Tuvesson
34c3d07112 feat(editor): Add "Used in x places" in Component List menu (#65) 2024-09-05 13:35:15 +02:00
Eric Tuvesson
d85dce8d02 refactor(editor): useNodeReferences to React context (#64) 2024-09-05 13:19:17 +02:00
Eric Tuvesson
89ed2d602f feat: Add source maps (#63) 2024-09-05 09:59:11 +02:00
Eric Tuvesson
94dd3dbf0e chore: bump version (#62) 2024-08-08 21:27:12 +02:00
Eric Tuvesson
c593a134b3 chore: rename project name (#60) 2024-08-08 21:23:23 +02:00
Eric Tuvesson
dc638ea8fc feat: Upgrade Electron (#61) 2024-08-08 21:12:39 +02:00
Eric Tuvesson
759c8a0030 fix(runtime): Column node, hide children on calculation (#59)
This removes the initial visible layout shift
2024-07-24 15:59:10 +02:00
Eric Tuvesson
aea80c6586 feat(runtime): Add capture attribute on Open File Picker node (#57)
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#capture
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture
2024-07-19 15:03:03 +02:00
Eric Tuvesson
46e2efa576 feat(runtime): Add "Switched" signal output on the Switch node (#58) 2024-07-19 15:02:49 +02:00
Eric Tuvesson
c508e15546 chore: clean up user nodes (#53) 2024-07-15 15:21:01 +02:00
69 changed files with 1298 additions and 884 deletions

View File

@@ -1,4 +1,4 @@
name: Build noodl-editor name: Build fluxscape-editor
on: on:
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
@@ -72,6 +72,6 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: noodl-editor-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }} name: fluxscape-editor-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }}
path: publish path: publish
retention-days: "12" retention-days: '12'

View File

@@ -1,4 +1,4 @@
name: Test noodl-editor name: Test fluxscape-editor
on: on:
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab

301
package-lock.json generated
View File

@@ -25220,9 +25220,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "31.1.0", "version": "31.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-31.1.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-31.3.1.tgz",
"integrity": "sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==", "integrity": "sha512-9fiuWlRhBfygtcT+auRd/WdBK/f8LZZcrpx0RjpXhH2DPTP/PfnkC4JB1PW55qCbGbh4wAgkYbf4ExIag8oGCA==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
@@ -27211,6 +27211,10 @@
"react": "^15.0.2 || ^16.0.0 || ^17.0.0" "react": "^15.0.2 || ^16.0.0 || ^17.0.0"
} }
}, },
"node_modules/fluxscape-editor": {
"resolved": "packages/noodl-editor",
"link": true
},
"node_modules/focus-lock": { "node_modules/focus-lock": {
"version": "0.8.1", "version": "0.8.1",
"dev": true, "dev": true,
@@ -35451,10 +35455,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/noodl-editor": {
"resolved": "packages/noodl-editor",
"link": true
},
"node_modules/nopt": { "node_modules/nopt": {
"version": "1.0.10", "version": "1.0.10",
"license": "MIT", "license": "MIT",
@@ -48980,7 +48980,8 @@
"dev": true "dev": true
}, },
"packages/noodl-editor": { "packages/noodl-editor": {
"version": "1.0.0", "name": "fluxscape-editor",
"version": "1.2.0",
"dependencies": { "dependencies": {
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"@jaames/iro": "^5.5.2", "@jaames/iro": "^5.5.2",
@@ -49038,7 +49039,7 @@
"babel-loader": "^8.2.4", "babel-loader": "^8.2.4",
"concurrently": "^7.4.0", "concurrently": "^7.4.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"electron": "^31.1.0", "electron": "31.3.1",
"electron-builder": "^24.9.1", "electron-builder": "^24.9.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-loader": "^3.1.0", "html-loader": "^3.1.0",
@@ -74070,9 +74071,9 @@
} }
}, },
"electron": { "electron": {
"version": "31.1.0", "version": "31.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-31.1.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-31.3.1.tgz",
"integrity": "sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==", "integrity": "sha512-9fiuWlRhBfygtcT+auRd/WdBK/f8LZZcrpx0RjpXhH2DPTP/PfnkC4JB1PW55qCbGbh4wAgkYbf4ExIag8oGCA==",
"requires": { "requires": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
@@ -75431,6 +75432,144 @@
"fbjs": "^3.0.1" "fbjs": "^3.0.1"
} }
}, },
"fluxscape-editor": {
"version": "file:packages/noodl-editor",
"requires": {
"@babel/core": "^7.19.1",
"@babel/preset-react": "^7.18.6",
"@electron/remote": "^2.1.2",
"@jaames/iro": "^5.5.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@noodl/git": "file:../noodl-git",
"@noodl/noodl-parse-dashboard": "file:../noodl-parse-dashboard",
"@noodl/platform": "file:../noodl-platform",
"@noodl/platform-electron": "file:../noodl-platform-electron",
"@svgr/webpack": "^6.4.0",
"@types/checksum": "^0.1.33",
"@types/jasmine": "^4.3.0",
"@types/jquery": "^3.5.14",
"@types/react": "^17.0.50",
"@types/react-dom": "^18.0.0",
"@types/remarkable": "^2.0.3",
"@types/rimraf": "^3.0.2",
"@types/split2": "^3.2.1",
"@types/string.prototype.matchall": "^4.0.1",
"@types/underscore": "^1.11.4",
"@types/webpack-env": "^1.18.0",
"about-window": "^1.15.2",
"algoliasearch": "^4.14.2",
"archiver": "^5.3.0",
"async": "^3.2.4",
"babel-loader": "^8.2.4",
"classnames": "^2.3.2",
"concurrently": "^7.4.0",
"css-loader": "^6.7.1",
"diff3": "0.0.4",
"dmg-license": "^1.0.11",
"electron": "31.3.1",
"electron-builder": "^24.9.1",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.7",
"express": "^4.17.3",
"file-loader": "^6.2.0",
"highlight.js": "^11.5.1",
"html-loader": "^3.1.0",
"isbinaryfile": "^5.0.0",
"md5": "^2.3.0",
"md5-file": "^5.0.0",
"mixpanel-browser": "^2.45.0",
"mkdirp": "0.5.1",
"mkdirp-sync": "0.0.2",
"monaco-editor": "^0.34.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"ncp": "^2.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.0",
"react-hot-toast": "^2.4.0",
"react-instantsearch-hooks-web": "^6.38.0",
"react-json-view": "^1.21.3",
"react-rnd": "^10.3.7",
"remarkable": "^2.0.1",
"rimraf": "^3.0.2",
"s3": "git+https://github.com/noodlapp/node-s3-client.git",
"sass": "^1.55.0",
"sass-loader": "^12.6.0",
"string.prototype.matchall": "^4.0.7",
"stringify": "^5.2.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"ts-node": "^10.7.0",
"typescript": "^4.8.3",
"underscore": "^1.13.6",
"url-loader": "^4.1.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^5.8.0",
"websocket-stream": "^5.5.2",
"ws": "^8.9.0"
},
"dependencies": {
"@webpack-cli/configtest": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
"integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
"dev": true,
"requires": {}
},
"@webpack-cli/info": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
"integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
"dev": true,
"requires": {
"envinfo": "^7.7.3"
}
},
"@webpack-cli/serve": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
"integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
"dev": true,
"requires": {}
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
},
"rechoir": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
"integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
"dev": true,
"requires": {
"resolve": "^1.9.0"
}
},
"webpack-cli": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
"@webpack-cli/info": "^1.5.0",
"@webpack-cli/serve": "^1.7.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
"cross-spawn": "^7.0.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
"rechoir": "^0.7.0",
"webpack-merge": "^5.7.3"
}
}
}
},
"focus-lock": { "focus-lock": {
"version": "0.8.1", "version": "0.8.1",
"dev": true, "dev": true,
@@ -81006,144 +81145,6 @@
"version": "2.0.10", "version": "2.0.10",
"dev": true "dev": true
}, },
"noodl-editor": {
"version": "file:packages/noodl-editor",
"requires": {
"@babel/core": "^7.19.1",
"@babel/preset-react": "^7.18.6",
"@electron/remote": "^2.1.2",
"@jaames/iro": "^5.5.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@noodl/git": "file:../noodl-git",
"@noodl/noodl-parse-dashboard": "file:../noodl-parse-dashboard",
"@noodl/platform": "file:../noodl-platform",
"@noodl/platform-electron": "file:../noodl-platform-electron",
"@svgr/webpack": "^6.4.0",
"@types/checksum": "^0.1.33",
"@types/jasmine": "^4.3.0",
"@types/jquery": "^3.5.14",
"@types/react": "^17.0.50",
"@types/react-dom": "^18.0.0",
"@types/remarkable": "^2.0.3",
"@types/rimraf": "^3.0.2",
"@types/split2": "^3.2.1",
"@types/string.prototype.matchall": "^4.0.1",
"@types/underscore": "^1.11.4",
"@types/webpack-env": "^1.18.0",
"about-window": "^1.15.2",
"algoliasearch": "^4.14.2",
"archiver": "^5.3.0",
"async": "^3.2.4",
"babel-loader": "^8.2.4",
"classnames": "^2.3.2",
"concurrently": "^7.4.0",
"css-loader": "^6.7.1",
"diff3": "0.0.4",
"dmg-license": "^1.0.11",
"electron": "^31.1.0",
"electron-builder": "^24.9.1",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.7",
"express": "^4.17.3",
"file-loader": "^6.2.0",
"highlight.js": "^11.5.1",
"html-loader": "^3.1.0",
"isbinaryfile": "^5.0.0",
"md5": "^2.3.0",
"md5-file": "^5.0.0",
"mixpanel-browser": "^2.45.0",
"mkdirp": "0.5.1",
"mkdirp-sync": "0.0.2",
"monaco-editor": "^0.34.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"ncp": "^2.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.0",
"react-hot-toast": "^2.4.0",
"react-instantsearch-hooks-web": "^6.38.0",
"react-json-view": "^1.21.3",
"react-rnd": "^10.3.7",
"remarkable": "^2.0.1",
"rimraf": "^3.0.2",
"s3": "git+https://github.com/noodlapp/node-s3-client.git",
"sass": "^1.55.0",
"sass-loader": "^12.6.0",
"string.prototype.matchall": "^4.0.7",
"stringify": "^5.2.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"ts-node": "^10.7.0",
"typescript": "^4.8.3",
"underscore": "^1.13.6",
"url-loader": "^4.1.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^5.8.0",
"websocket-stream": "^5.5.2",
"ws": "^8.9.0"
},
"dependencies": {
"@webpack-cli/configtest": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
"integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
"dev": true,
"requires": {}
},
"@webpack-cli/info": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
"integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
"dev": true,
"requires": {
"envinfo": "^7.7.3"
}
},
"@webpack-cli/serve": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
"integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
"dev": true,
"requires": {}
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
},
"rechoir": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
"integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
"dev": true,
"requires": {
"resolve": "^1.9.0"
}
},
"webpack-cli": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
"@webpack-cli/info": "^1.5.0",
"@webpack-cli/serve": "^1.7.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
"cross-spawn": "^7.0.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
"rechoir": "^0.7.0",
"webpack-merge": "^5.7.3"
}
}
}
},
"nopt": { "nopt": {
"version": "1.0.10", "version": "1.0.10",
"requires": { "requires": {

View File

@@ -12,7 +12,7 @@
"graph": "npx nx graph", "graph": "npx nx graph",
"ci:prepare:editor": "ts-node ./scripts/ci-editor-prepare.ts", "ci:prepare:editor": "ts-node ./scripts/ci-editor-prepare.ts",
"ci:build:viewer": "lerna exec --scope @noodl/noodl-viewer-react -- npm run build", "ci:build:viewer": "lerna exec --scope @noodl/noodl-viewer-react -- npm run build",
"ci:build:editor": "lerna exec --scope noodl-editor -- npm run ci:build", "ci:build:editor": "lerna exec --scope fluxscape-editor -- npm run ci:build",
"build:editor": "ts-node ./scripts/build-editor.ts", "build:editor": "ts-node ./scripts/build-editor.ts",
"build:editor:_viewer": "ts-node ./scripts/noodl-editor/build-viewer.ts", "build:editor:_viewer": "ts-node ./scripts/noodl-editor/build-viewer.ts",
"build:editor:_editor": "ts-node ./scripts/noodl-editor/build-editor.ts", "build:editor:_editor": "ts-node ./scripts/noodl-editor/build-editor.ts",
@@ -20,7 +20,7 @@
"build:cloud-runtime": "lerna run build --scope @noodl/cloud-runtime --stream && lerna run build:pack --scope @noodl/cloud-runtime --stream", "build:cloud-runtime": "lerna run build --scope @noodl/cloud-runtime --stream && lerna run build:pack --scope @noodl/cloud-runtime --stream",
"start:storybook": "lerna exec --scope @noodl/noodl-core-ui -- npm run start", "start:storybook": "lerna exec --scope @noodl/noodl-core-ui -- npm run start",
"start:viewer": "lerna run start --scope @noodl/noodl-viewer-react --stream", "start:viewer": "lerna run start --scope @noodl/noodl-viewer-react --stream",
"start:editor": "lerna run start --scope noodl-editor --stream", "start:editor": "lerna run start --scope fluxscape-editor --stream",
"dev": "ts-node ./scripts/start.ts", "dev": "ts-node ./scripts/start.ts",
"start": "ts-node ./scripts/start.ts -- --build-viewer", "start": "ts-node ./scripts/start.ts -- --build-viewer",
"test:editor": "ts-node ./scripts/test-editor.ts", "test:editor": "ts-node ./scripts/test-editor.ts",

View File

@@ -1,10 +1,10 @@
{ {
"name": "noodl-editor", "name": "fluxscape-editor",
"productName": "Fluxscape", "productName": "Fluxscape",
"description": "Node-Based App Builder for Scalability & Rapid Development, a fork of Noodl", "description": "Node-Based App Builder for Scalability & Rapid Development, a fork of Noodl",
"author": "Fluxscape <contact@fluxscape.io>", "author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io", "homepage": "https://fluxscape.io",
"version": "1.0.0", "version": "1.2.0",
"main": "src/main/main.js", "main": "src/main/main.js",
"scripts": { "scripts": {
"build": "npx ts-node -P ./tsconfig.build.json ./scripts/build.ts", "build": "npx ts-node -P ./tsconfig.build.json ./scripts/build.ts",
@@ -114,7 +114,7 @@
"babel-loader": "^8.2.4", "babel-loader": "^8.2.4",
"concurrently": "^7.4.0", "concurrently": "^7.4.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"electron": "^31.1.0", "electron": "31.3.1",
"electron-builder": "^24.9.1", "electron-builder": "^24.9.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-loader": "^3.1.0", "html-loader": "^3.1.0",

View File

@@ -6,7 +6,7 @@ import { EventDispatcher } from '../../shared/utils/EventDispatcher';
import ProjectModules from '../../shared/utils/projectmodules'; import ProjectModules from '../../shared/utils/projectmodules';
import { NodeLibrary } from './models/nodelibrary'; import { NodeLibrary } from './models/nodelibrary';
import { ProjectModel } from './models/projectmodel'; import { ProjectModel } from './models/projectmodel';
import { WarningsModel } from './models/warningsmodel'; import { WarningRef, WarningsModel } from './models/warningsmodel';
import DebugInspector from './utils/debuginspector'; import DebugInspector from './utils/debuginspector';
import * as Exporter from './utils/exporter'; import * as Exporter from './utils/exporter';
@@ -112,7 +112,7 @@ export class ViewerConnection extends Model {
} else if (request.cmd === 'showwarning' && request.type === 'viewer') { } else if (request.cmd === 'showwarning' && request.type === 'viewer') {
const content = JSON.parse(request.content); const content = JSON.parse(request.content);
if (ProjectModel.instance !== undefined) { if (ProjectModel.instance !== undefined) {
const ref = { const ref: WarningRef = {
component: ProjectModel.instance.getComponentWithName(content.componentName), component: ProjectModel.instance.getComponentWithName(content.componentName),
node: ProjectModel.instance.findNodeWithId(content.nodeId), node: ProjectModel.instance.findNodeWithId(content.nodeId),
key: content.key, key: content.key,
@@ -124,7 +124,7 @@ export class ViewerConnection extends Model {
} }
} else if (request.cmd === 'clearwarnings' && request.type === 'viewer') { } else if (request.cmd === 'clearwarnings' && request.type === 'viewer') {
const content = JSON.parse(request.content); const content = JSON.parse(request.content);
const ref = { const ref: WarningRef = {
component: ProjectModel.instance.getComponentWithName(content.componentName), component: ProjectModel.instance.getComponentWithName(content.componentName),
node: ProjectModel.instance.findNodeWithId(content.nodeId) node: ProjectModel.instance.findNodeWithId(content.nodeId)
}; };

View File

@@ -0,0 +1,142 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { type ComponentModel } from '@noodl-models/componentmodel';
import { type NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { type NodeLibraryNodeType } from '@noodl-models/nodelibrary';
import { type Slot } from '@noodl-core-ui/types/global';
import { ProjectModel } from '@noodl-models/projectmodel';
import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
export type NodeReference = {
type: NodeLibraryNodeType | undefined;
displayName: string;
referenaces: {
displayName: string;
node?: NodeGraphNode;
component: ComponentModel;
}[];
};
export interface NodeReferencesContext {
nodeReferences: NodeReference[];
}
const NodeReferencesContext = createContext<NodeReferencesContext>({
nodeReferences: [],
});
// Since all the editor code is not written in React we need a way to be able to
// access this information outside of React too.
let HACK_nodeReferences: NodeReference[] = [];
export function HACK_findNodeReference(componentName: string): NodeReference | undefined {
return HACK_nodeReferences.find(x => x.type?.fullName === componentName);
}
export interface NodeReferencesContextProps {
children: Slot;
}
export function NodeReferencesContextProvider({ children }: NodeReferencesContextProps) {
const [group] = useState({});
const [nodeReferences, setNodeReferences] = useState<NodeReference[]>([]);
useEffect(() => {
function updateIndex() {
const types: { [key: string]: NodeReference['type'] } = {};
const references = new Map<string, NodeReference['referenaces']>();
function handleComponent(component: ComponentModel) {
component.forEachNode((node: NodeGraphNode) => {
const name = node.type.name;
// Add the reference
references.set(name, [
...(references.get(name) || []),
{
displayName: component.displayName || component.name,
node,
component
}
]);
// Repeater
if (name === 'For Each' && node.parameters.template) {
const templateComponent = ProjectModel.instance.getComponentWithName(node.parameters.template);
if (templateComponent) {
references.set(templateComponent.fullName, [
...(references.get(templateComponent.fullName) || []),
{
displayName: component.displayName || component.name,
node,
component
}
]);
handleComponent(templateComponent);
}
}
// Add some metadata for this node if we dont have it yet.
if (!types[name]) {
types[name] = node.type;
}
});
}
// Loop all the nodes in the project
ProjectModel.instance.forEachComponent(handleComponent);
// Combine the result to look a little better.
const results: NodeReference[] = Array.from(references.keys())
.map((key) => ({
type: types[key],
displayName: types[key]?.displayName || key,
referenaces: references.get(key)
}))
.sort((a, b) => b.referenaces.length - a.referenaces.length);
HACK_nodeReferences = results;
setNodeReferences(results);
}
updateIndex();
EventDispatcher.instance.on(
[
'Model.nodeAdded',
'Model.nodeRemoved',
'Model.componentAdded',
'Model.componentRemoved',
'Model.componentRenamed'
],
updateIndex,
group
);
return function () {
EventDispatcher.instance.off(group);
};
}, []);
return (
<NodeReferencesContext.Provider
value={{
nodeReferences,
}}
>
{children}
</NodeReferencesContext.Provider>
);
}
export function useNodeReferencesContext() {
const context = useContext(NodeReferencesContext);
if (context === undefined) {
throw new Error('useNodeReferencesContext must be a child of NodeReferencesContextProvider');
}
return context;
}

View File

@@ -0,0 +1 @@
export * from './NodeReferencesContext';

View File

@@ -0,0 +1,72 @@
import { CloudService } from '@noodl-models/CloudServices';
import { ProjectModel } from '@noodl-models/projectmodel';
import { setCloudServices } from '@noodl-models/projectmodel.editor';
import { ToastLayer } from '../../../views/ToastLayer';
export type Command = {
kind: 'cloud-service';
use?: boolean;
name: string;
description?: string;
endpoint: string;
appId: string;
masterKey: string;
};
export async function execute(command: Command, _event: MessageEvent) {
const environment = await getOrCreate(command);
if (command.use) {
setCloudServices(ProjectModel.instance, environment);
}
}
async function getOrCreate(command: Command) {
const cloudServices = await CloudService.instance.backend.fetch();
const environment = cloudServices.find((c) => c.url === command.endpoint);
if (environment) {
if (
environment.name !== command.name ||
environment.description !== command.description ||
environment.appId !== command.appId ||
environment.masterKey !== command.masterKey
) {
await CloudService.instance.backend.update({
id: environment.id,
url: environment.url,
// Update the existing environment
name: command.name,
description: command.description,
appId: command.appId,
masterKey: command.masterKey
});
ToastLayer.showSuccess(`Cloud service "${command.name}" updated.`);
}
return {
id: environment.id,
endpoint: environment.url,
appId: command.appId
};
} else {
const create = await CloudService.instance.backend.create({
name: command.name,
description: command.description,
appId: command.appId,
url: command.endpoint,
masterKey: command.masterKey
});
ToastLayer.showSuccess(`Cloud service "${command.name}" added successfully.`);
return {
id: create.id,
endpoint: create.url,
appId: create.appId
};
}
}

View File

@@ -1,11 +1,13 @@
import * as CloudService from './commands/cloud-service';
import * as Notify from './commands/notify'; import * as Notify from './commands/notify';
import * as UploadAwsS3 from './commands/upload-aws-s3'; import * as UploadAwsS3 from './commands/upload-aws-s3';
type IFrameCommand = Notify.Command | UploadAwsS3.Command; type IFrameCommand = Notify.Command | UploadAwsS3.Command | CloudService.Command;
const commands: Record<IFrameCommand['kind'], (command: IFrameCommand, event: MessageEvent) => Promise<void>> = { const commands: Record<IFrameCommand['kind'], (command: IFrameCommand, event: MessageEvent) => Promise<void>> = {
notify: Notify.execute, notify: Notify.execute,
'upload-aws-s3': UploadAwsS3.execute 'upload-aws-s3': UploadAwsS3.execute,
'cloud-service': CloudService.execute
}; };
export function commandEventHandler(event: MessageEvent) { export function commandEventHandler(event: MessageEvent) {

View File

@@ -57,10 +57,13 @@ export class CloudFunctionAdapter extends NodeTypeAdapter {
// Collect all cloud function components // Collect all cloud function components
const functionRequestNodes = ProjectModel.instance.getNodesWithType('noodl.cloud.request'); const functionRequestNodes = ProjectModel.instance.getNodesWithType('noodl.cloud.request');
const functions = functionRequestNodes.map((r) => { const functions = functionRequestNodes
const component = r.owner.owner; .map((r) => {
return component.fullName; const component = r.owner.owner;
}); return component.fullName;
})
// Remove all the Cloud Trigger functions
.filter((x) => !x.startsWith('/#__cloud__/__noodl_cloud_triggers__'));
ports.push({ ports.push({
plug: 'input', plug: 'input',

View File

@@ -1,5 +1,5 @@
import { NodeGraphNode } from '@noodl-models/nodegraphmodel'; import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { WarningsModel } from '@noodl-models/warningsmodel'; import { type Warning, WarningsModel } from '@noodl-models/warningsmodel';
import { ProjectModel } from '../projectmodel'; import { ProjectModel } from '../projectmodel';
import NodeTypeAdapter from './NodeTypeAdapter'; import NodeTypeAdapter from './NodeTypeAdapter';
@@ -103,7 +103,7 @@ export class RouterNavigateAdapter extends NodeTypeAdapter {
const hasValidTarget = target && pageComponents.includes(target); const hasValidTarget = target && pageComponents.includes(target);
const warning = const warning: Warning =
hasValidTarget === false hasValidTarget === false
? { ? {
message: "The target page doesn't belong to the target router", message: "The target page doesn't belong to the target router",

View File

@@ -458,11 +458,11 @@ export class VariantModel extends Model {
} }
); );
} else if (c.type === 'defaultStateTransition') { } else if (c.type === 'defaultStateTransition') {
var state = const state =
this.getType().visualStates !== undefined this.getType().visualStates !== undefined
? this.getType().visualStates.find((s) => s.name === c.state) ? this.getType().visualStates.find((s) => s.name === c.state)
: undefined; : undefined;
var stateName = state !== undefined ? state.label : c.state; const stateName = state !== undefined ? state.label : c.state;
WarningsModel.instance.setWarning( WarningsModel.instance.setWarning(
{ key: 'variant-dst-conflict-' + this.name + '-' + this.getType().fullName + '-' + c.state }, { key: 'variant-dst-conflict-' + this.name + '-' + this.getType().fullName + '-' + c.state },

View File

@@ -72,7 +72,7 @@ export class NodeGraphNode extends Model {
metadata?: Record<string, any>; metadata?: Record<string, any>;
private _variant: TSFixme; private _variant: TSFixme;
private _label: TSFixme; _label: TSFixme;
private _type: TSFixme; private _type: TSFixme;
private _ports: TSFixme; private _ports: TSFixme;

View File

@@ -1167,6 +1167,28 @@ EventDispatcher.instance.on(
null null
); );
NodeLibrary.instance.on('libraryUpdated', () => {
const library = NodeLibrary.instance.library;
// Check if we have any data from the browser
if (Object.keys(library?.nodeIndex || {}).length > 0) {
const filepath = filesystem.join(ProjectModel.instance._retainedProjectDirectory, 'nodelibrary.json');
const compactLibrary = {
typecasts: library.typecasts,
dynamicports: library.dynamicports,
nodetypes: library.nodetypes,
// NOTE: Let's save the node index for now, most likely this is something we will just ignore.
nodeIndex: library.nodeIndex,
projectsettings: library.projectsettings
};
filesystem.writeJson(filepath, compactLibrary).then(() => {
console.log('saved nodelibrary.json');
});
}
});
function saveProject() { function saveProject() {
if (!ProjectModel.instance) return; if (!ProjectModel.instance) return;

View File

@@ -1,9 +1,42 @@
import { ComponentModel } from '@noodl-models/componentmodel';
import { ProjectModel } from '@noodl-models/projectmodel';
import { toArray } from 'underscore'; import { toArray } from 'underscore';
import type { ComponentModel } from '@noodl-models/componentmodel';
import Model from '../../../shared/model'; import Model from '../../../shared/model';
import type { NodeGraphNode } from './nodegraphmodel';
import { NodeLibrary } from './nodelibrary'; import { NodeLibrary } from './nodelibrary';
export type WarningLabel = 'warning' | 'error';
export type Warning =
| {
type?: string;
level?: WarningLabel;
message: string;
showGlobally?: boolean;
}
| {
type: 'conflict' | 'conflict-source-code';
level?: WarningLabel;
message: string;
showGlobally?: boolean;
conflictMetadata: {
parameter: string;
ours: string;
theirs: string;
};
onDismiss: () => void;
onUseTheirs: () => void;
};
export type WarningRef = {
key?: string;
component?: ComponentModel;
connection?: TSFixme;
node?: NodeGraphNode;
isFromViewer?: boolean;
};
/** /**
* The first level of the warnings object is component name * The first level of the warnings object is component name
* Second is the connection / node identifier * Second is the connection / node identifier
@@ -14,7 +47,7 @@ interface Warnings {
[node_connection_id: string]: { [node_connection_id: string]: {
[warningKey: string]: { [warningKey: string]: {
ref: TSFixme; ref: TSFixme;
warning: TSFixme; warning: Warning;
}; };
}; };
}; };
@@ -24,7 +57,7 @@ export class WarningsModel extends Model {
public static instance = new WarningsModel(); public static instance = new WarningsModel();
private warnings: Warnings = {}; private warnings: Warnings = {};
private notifyChangedScheduled: boolean = false; private notifyChangedScheduled = false;
constructor() { constructor() {
super(); super();
@@ -35,10 +68,12 @@ export class WarningsModel extends Model {
}); });
} }
public setWarning(ref, warning) { public setWarning(ref: WarningRef, warning: Warning) {
var w = this.getWarningsForRef(ref, warning !== undefined); const w = this.getWarningsForRef(ref, warning !== undefined);
if (!warning) { if (!warning) {
if (w) delete w[ref.key]; if (w) {
delete w[ref.key];
}
} else { } else {
warning.level = warning.level || 'warning'; warning.level = warning.level || 'warning';
w[ref.key] = { ref: ref, warning: warning }; w[ref.key] = { ref: ref, warning: warning };
@@ -47,31 +82,34 @@ export class WarningsModel extends Model {
this.scheduleNotifyChanged(); this.scheduleNotifyChanged();
} }
public clearWarningsForRef(ref) { public clearWarningsForRef(ref: WarningRef) {
var w = this.getWarningsForRef(ref); const w = this.getWarningsForRef(ref);
if (!w) return; if (!w) return;
for (var i in w) delete w[i]; for (const i in w) {
delete w[i];
}
this.scheduleNotifyChanged(); this.scheduleNotifyChanged();
} }
public clearAllWarningsForComponent(component) { public clearAllWarningsForComponent(component: ComponentModel) {
const warnings = this.warnings[component.name]; const warnings = this.warnings[component.name];
if (!warnings) return; if (!warnings) return;
for (let i in warnings) delete warnings[i]; for (const i in warnings) {
delete warnings[i];
}
this.scheduleNotifyChanged(); this.scheduleNotifyChanged();
} }
public clearWarningsForRefMatching(matchCb) { public clearWarningsForRefMatching(matchFn: (ref: TSFixme) => boolean) {
for (const cw of Object.values(this.warnings)) { for (const cw of Object.values(this.warnings)) {
for (const ws of Object.values(cw)) { for (const ws of Object.values(cw)) {
for (const key in ws) { for (const key in ws) {
const w = ws[key]; const w = ws[key];
if (matchCb(w.ref)) { if (matchFn(w.ref)) {
delete ws[key]; delete ws[key];
} }
} }
@@ -87,15 +125,14 @@ export class WarningsModel extends Model {
this.scheduleNotifyChanged(); this.scheduleNotifyChanged();
} }
public getWarnings(ref) { public getWarnings(ref: WarningRef) {
var w = this.getWarningsForRef(ref); const w = this.getWarningsForRef(ref);
if (!w) return; if (!w) return;
if (Object.keys(w).length === 0) return; if (Object.keys(w).length === 0) return;
// Create short message for hover // Create short message for hover
var messages = []; const messages = [];
for (var k in w) { for (const k in w) {
if (w[k].warning) messages.push(w[k].warning.message); if (w[k].warning) messages.push(w[k].warning.message);
} }
@@ -106,13 +143,13 @@ export class WarningsModel extends Model {
} }
public forEachWarningInComponent(c, callback, args) { public forEachWarningInComponent(c, callback, args) {
var cw = this.warnings[c ? c.name : '/']; const cw = this.warnings[c ? c.name : '/'];
if (!cw) return; if (!cw) return;
for (var ref in cw) { for (const ref in cw) {
var ws = cw[ref]; const ws = cw[ref];
for (var w in ws) { for (const w in ws) {
if (!args || !args.levels || args.levels.indexOf(ws[w].warning.level) !== -1) { if (!args || !args.levels || args.levels.indexOf(ws[w].warning.level) !== -1) {
callback(ws[w]); callback(ws[w]);
} }
@@ -121,7 +158,7 @@ export class WarningsModel extends Model {
} }
public getAllWarningsForComponent(c, args) { public getAllWarningsForComponent(c, args) {
var warnings = []; const warnings = [];
this.forEachWarningInComponent( this.forEachWarningInComponent(
c, c,
function (warning) { function (warning) {
@@ -152,15 +189,15 @@ export class WarningsModel extends Model {
} }
public getTotalNumberOfWarnings(args) { public getTotalNumberOfWarnings(args) {
var total = 0; let total = 0;
for (var key in this.warnings) { for (const key in this.warnings) {
total += this.getNumberOfWarningsForComponent({ name: key }, args); total += this.getNumberOfWarningsForComponent({ name: key }, args);
} }
return total; return total;
} }
public getTotalNumberOfWarningsMatching(matchCb) { public getTotalNumberOfWarningsMatching(matchCb) {
var total = 0; let total = 0;
this.forEachWarning((c, ref, key, warning) => { this.forEachWarning((c, ref, key, warning) => {
if (matchCb(key, ref, warning)) total++; if (matchCb(key, ref, warning)) total++;
}); });
@@ -168,16 +205,16 @@ export class WarningsModel extends Model {
} }
public forEachWarning(callback: (c: string, ref, key, warning) => void) { public forEachWarning(callback: (c: string, ref, key, warning) => void) {
var w = this.warnings; const w = this.warnings;
for (const c in w) { for (const c in w) {
// Loop over all components // Loop over all components
var _w = w[c]; const _w = w[c];
for (const ref in _w) { for (const ref in _w) {
// Loop over all refs // Loop over all refs
var __w = _w[ref]; const __w = _w[ref];
for (const key in __w) { for (const key in __w) {
// Loop over all keys // Loop over all keys
var warning = __w[key]; const warning = __w[key];
callback(c, ref, key, warning); callback(c, ref, key, warning);
} }
@@ -195,12 +232,12 @@ export class WarningsModel extends Model {
// node: nodeRef, // node: nodeRef,
// connection: connectionRef, // connection: connectionRef,
// key: key of warning as string} // key: key of warning as string}
private getWarningsForRef(ref, create?) { private getWarningsForRef(ref: WarningRef, create?) {
var componentName = ref.component ? ref.component.name : '/'; const componentName = ref.component ? ref.component.name : '/';
if (!this.warnings[componentName]) this.warnings[componentName] = {}; if (!this.warnings[componentName]) this.warnings[componentName] = {};
var cw = this.warnings[componentName]; const cw = this.warnings[componentName];
var key; let key;
if (ref.node) key = 'node/' + ref.node.id; if (ref.node) key = 'node/' + ref.node.id;
else if (ref.connection) else if (ref.connection)
key = key =
@@ -220,7 +257,8 @@ export class WarningsModel extends Model {
/** Batch changed notifications so listeners don't get peppered */ /** Batch changed notifications so listeners don't get peppered */
private scheduleNotifyChanged() { private scheduleNotifyChanged() {
var _this = this; // eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this;
if (this.notifyChangedScheduled) return; if (this.notifyChangedScheduled) return;
this.notifyChangedScheduled = true; this.notifyChangedScheduled = true;

View File

@@ -1,4 +1,6 @@
import { NodeGraphContextProvider } from '@noodl-contexts/NodeGraphContext/NodeGraphContext'; import { NodeGraphContextProvider } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
import { NodeReferencesContextProvider } from '@noodl-contexts/NodeReferencesContext';
import { PluginContextProvider } from '@noodl-contexts/PluginContext';
import { ProjectDesignTokenContextProvider } from '@noodl-contexts/ProjectDesignTokenContext'; import { ProjectDesignTokenContextProvider } from '@noodl-contexts/ProjectDesignTokenContext';
import { useKeyboardCommands } from '@noodl-hooks/useKeyboardCommands'; import { useKeyboardCommands } from '@noodl-hooks/useKeyboardCommands';
import { useModel } from '@noodl-hooks/useModel'; import { useModel } from '@noodl-hooks/useModel';
@@ -43,7 +45,6 @@ import { BaseWindow } from '../../views/windows/BaseWindow';
import { whatsnewRender } from '../../whats-new'; import { whatsnewRender } from '../../whats-new';
import { IRouteProps } from '../AppRoute'; import { IRouteProps } from '../AppRoute';
import { useSetupSettings } from './useSetupSettings'; import { useSetupSettings } from './useSetupSettings';
import { PluginContextProvider } from '@noodl-contexts/PluginContext';
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const ImportOverwritePopupTemplate = require('../../templates/importoverwritepopup.html'); const ImportOverwritePopupTemplate = require('../../templates/importoverwritepopup.html');
@@ -223,28 +224,30 @@ export function EditorPage({ route }: EditorPageProps) {
return ( return (
<NodeGraphContextProvider> <NodeGraphContextProvider>
<ProjectDesignTokenContextProvider> <NodeReferencesContextProvider>
<PluginContextProvider> <ProjectDesignTokenContextProvider>
<BaseWindow> <PluginContextProvider>
{isLoading ? ( <BaseWindow>
<ActivityIndicator /> {isLoading ? (
) : ( <ActivityIndicator />
<> ) : (
<FrameDivider <>
first={<SidePanel />} <FrameDivider
second={<ErrorBoundary>{Boolean(Document) && <Document />}</ErrorBoundary>} first={<SidePanel />}
sizeMin={200} second={<ErrorBoundary>{Boolean(Document) && <Document />}</ErrorBoundary>}
size={frameDividerSize} sizeMin={200}
horizontal size={frameDividerSize}
onSizeChanged={setFrameDividerSize} horizontal
/> onSizeChanged={setFrameDividerSize}
/>
{Boolean(lesson) && <Frame instance={lesson} isContentSize isFitWidth />} {Boolean(lesson) && <Frame instance={lesson} isContentSize isFitWidth />}
</> </>
)} )}
</BaseWindow> </BaseWindow>
</PluginContextProvider> </PluginContextProvider>
</ProjectDesignTokenContextProvider> </ProjectDesignTokenContextProvider>
</NodeReferencesContextProvider>
</NodeGraphContextProvider> </NodeGraphContextProvider>
); );
} }

View File

@@ -4,7 +4,7 @@ import { clearFolders } from './cleanup';
export async function copyProjectFilesToFolder(projectPath: string, direntry: string): Promise<void> { export async function copyProjectFilesToFolder(projectPath: string, direntry: string): Promise<void> {
// TODO: Load something like .noodlignore file list // TODO: Load something like .noodlignore file list
const ignoreFiles = ['.DS_Store', '.gitignore', '.gitattributes', 'project.json', 'Dockerfile']; const ignoreFiles = ['.DS_Store', '.gitignore', '.gitattributes', 'project.json', 'Dockerfile', 'nodelibrary.json'];
// Copy everything from the project folder // Copy everything from the project folder
if (!projectPath) { if (!projectPath) {

View File

@@ -67,6 +67,14 @@ async function _writeFileToFolder({
runtimeType runtimeType
}: WriteFileToFolderArgs) { }: WriteFileToFolderArgs) {
const fullPath = filesystem.join(getExternalFolderPath(), runtimeType, url); const fullPath = filesystem.join(getExternalFolderPath(), runtimeType, url);
if (!filesystem.exists(fullPath)) {
// TODO: Save this warning somewhere, usually, this is not an issue though.
// This occurred because building in dev mode does not create the source map
// files which it expects to copy over.
return;
}
let content = await filesystem.readFile(fullPath); let content = await filesystem.readFile(fullPath);
let filename = url; let filename = url;

View File

@@ -1,22 +1,32 @@
const { ProjectModel } = require('../models/projectmodel'); import type { ComponentModel } from '@noodl-models/componentmodel';
import type { NodeGraphNode } from '@noodl-models/nodegraphmodel';
function matchStrings(string1, string2) { import { ProjectModel } from '../models/projectmodel';
export type SearchResult = {
componentTarget: ComponentModel;
nodeTarget?: NodeGraphNode;
type?: string;
userLabel?: string;
label: string;
};
function matchStrings(string1: string, string2: string) {
return string1.toLowerCase().indexOf(string2.toLowerCase()) !== -1; return string1.toLowerCase().indexOf(string2.toLowerCase()) !== -1;
} }
function searchInNodeRecursive(node, searchTerms, component) { function searchInNodeRecursive(node: NodeGraphNode, searchTerms: string, component: ComponentModel) {
var results = []; let results: SearchResult[] = [];
var matchLabel = null; let matchLabel: string | null = null;
var i = 0;
if (node._label !== undefined && matchStrings(node._label, searchTerms)) { if (node._label !== undefined && matchStrings(node._label, searchTerms)) {
matchLabel = node.label; matchLabel = node.label;
} else if (matchStrings(node.id, searchTerms)) { } else if (node.id === searchTerms) {
matchLabel = node.id; matchLabel = node.id;
} else if (matchStrings(node.type.displayName || node.type.name, searchTerms)) { } else if (matchStrings(node.type.displayName || node.type.name, searchTerms)) {
matchLabel = node.label; matchLabel = node.label;
} else { } else {
let parameterNames = Object.keys(node.parameters); const parameterNames = Object.keys(node.parameters);
for (const parameterNameIndex in parameterNames) { for (const parameterNameIndex in parameterNames) {
const parameterName = parameterNames[parameterNameIndex]; const parameterName = parameterNames[parameterNameIndex];
@@ -25,7 +35,7 @@ function searchInNodeRecursive(node, searchTerms, component) {
matchStrings(node.parameters[parameterName], searchTerms) matchStrings(node.parameters[parameterName], searchTerms)
) { ) {
let displayLabel = parameterName; let displayLabel = parameterName;
let connectionPort = node.type.ports?.find((port) => port.name === parameterName); const connectionPort = node.type.ports?.find((port) => port.name === parameterName);
if (connectionPort) { if (connectionPort) {
displayLabel = connectionPort.displayName; displayLabel = connectionPort.displayName;
} }
@@ -51,9 +61,9 @@ function searchInNodeRecursive(node, searchTerms, component) {
} }
if (matchLabel === null) { if (matchLabel === null) {
var ports = node.dynamicports; const ports = node.dynamicports;
for (i = 0; i < ports.length; ++i) { for (let i = 0; i < ports.length; ++i) {
var port = ports[i]; const port = ports[i];
if (matchStrings(port.name, searchTerms)) { if (matchStrings(port.name, searchTerms)) {
matchLabel = node.label + ' : ' + port.name; matchLabel = node.label + ' : ' + port.name;
break; break;
@@ -62,9 +72,9 @@ function searchInNodeRecursive(node, searchTerms, component) {
} }
if (matchLabel === null) { if (matchLabel === null) {
var ports = node.ports; const ports = node.ports;
for (i = 0; i < ports.length; ++i) { for (let i = 0; i < ports.length; ++i) {
var port = ports[i]; const port = ports[i];
if (matchStrings(port.name, searchTerms)) { if (matchStrings(port.name, searchTerms)) {
matchLabel = node.label + ' : ' + port.name; matchLabel = node.label + ' : ' + port.name;
break; break;
@@ -83,17 +93,17 @@ function searchInNodeRecursive(node, searchTerms, component) {
}); });
} }
for (i = 0; i < node.children.length; ++i) { for (let i = 0; i < node.children.length; ++i) {
var child = node.children[i]; const child = node.children[i];
var childResults = searchInNodeRecursive(child, searchTerms, component); const childResults = searchInNodeRecursive(child, searchTerms, component);
results = results.concat(childResults); results = results.concat(childResults);
} }
return results; return results;
} }
function searchInComponent(component, searchTerms) { function searchInComponent(component: ComponentModel, searchTerms: string) {
var results = []; let results: SearchResult[] = [];
if (matchStrings(component.displayName, searchTerms)) { if (matchStrings(component.displayName, searchTerms)) {
results.push({ results.push({
componentTarget: component, componentTarget: component,
@@ -102,14 +112,14 @@ function searchInComponent(component, searchTerms) {
}); });
} }
for (var i = 0; i < component.graph.roots.length; ++i) { for (let i = 0; i < component.graph.roots.length; ++i) {
var node = component.graph.roots[i]; const node = component.graph.roots[i];
var nodeResults = searchInNodeRecursive(node, searchTerms, component); const nodeResults = searchInNodeRecursive(node, searchTerms, component);
results = results.concat(nodeResults); results = results.concat(nodeResults);
} }
if (component.graph.commentsModel.comments) { if (component.graph.commentsModel.comments) {
for (var i = 0; i < component.graph.commentsModel.comments.length; ++i) { for (let i = 0; i < component.graph.commentsModel.comments.length; ++i) {
const comment = component.graph.commentsModel.comments[i]; const comment = component.graph.commentsModel.comments[i];
if (matchStrings(comment.text, searchTerms)) { if (matchStrings(comment.text, searchTerms)) {
results.push({ results.push({
@@ -132,17 +142,17 @@ function searchInComponent(component, searchTerms) {
} }
} }
export function performSearch(searchTerms) { export function performSearch(searchTerms: string) {
var results = []; const results: ReturnType<typeof searchInComponent>[] = [];
var root = ProjectModel.instance.getRootNode(); const root = ProjectModel.instance.getRootNode();
if (root === undefined) return; if (root === undefined) return;
var components = ProjectModel.instance.components; const components = ProjectModel.instance.components;
for (var i = 0; i < components.length; ++i) { for (let i = 0; i < components.length; ++i) {
var component = components[i]; const component = components[i];
var componentResults = searchInComponent(component, searchTerms); const componentResults = searchInComponent(component, searchTerms);
if (componentResults !== null) { if (componentResults !== null) {
//limit the label length (it can search in markdown, css, etc) //limit the label length (it can search in markdown, css, etc)
for (const result of componentResults.results) { for (const result of componentResults.results) {

View File

@@ -211,7 +211,7 @@ export default function Clippy() {
aiAssistantModel.removeActivity(id); aiAssistantModel.removeActivity(id);
} }
const initialPlaceholder = isInputOpen ? 'Select (or type) a command below' : 'Ask Noodl AI'; const initialPlaceholder = isInputOpen ? 'Select (or type) a command below' : 'Ask FluxScape AI';
const isPromptInWrongOrder = Boolean(!selectedOption) && Boolean(secondInputValue); const isPromptInWrongOrder = Boolean(!selectedOption) && Boolean(secondInputValue);
const isFullBeta = ['full-beta', 'enterprise'].includes(version); const isFullBeta = ['full-beta', 'enterprise'].includes(version);
const isLimitedBeta = false; // TODO: version === 'limited-beta'; const isLimitedBeta = false; // TODO: version === 'limited-beta';
@@ -412,8 +412,8 @@ export default function Clippy() {
<Text hasBottomSpacing>4. Click the "Verify API Key" button</Text> <Text hasBottomSpacing>4. Click the "Verify API Key" button</Text>
<Text hasBottomSpacing> <Text hasBottomSpacing>
If you dont have an API key with GPT-4 access, you can set the Noodl AI to use the Limited Beta in the If you dont have an API key with GPT-4 access, you can set the FluxScape AI to use the Limited Beta in
editor settings. the editor settings.
</Text> </Text>
<PrimaryButton <PrimaryButton
size={PrimaryButtonSize.Small} size={PrimaryButtonSize.Small}
@@ -597,8 +597,8 @@ export default function Clippy() {
</Label> </Label>
<Text hasBottomSpacing size={TextSize.Medium}> <Text hasBottomSpacing size={TextSize.Medium}>
You are running the limited beta of Noodl AI. If features fewer commands and a less capable AI. Get full You are running the limited beta of FluxScape AI. If features fewer commands and a less capable AI. Get
beta access by bringing your own GPT-4 API key. full beta access by bringing your own GPT-4 API key.
</Text> </Text>
<PrimaryButton <PrimaryButton

View File

@@ -1,4 +1,5 @@
import React, { RefObject } from 'react'; import React, { RefObject } from 'react';
import { platform } from '@noodl/platform';
import { ProjectModel } from '@noodl-models/projectmodel'; import { ProjectModel } from '@noodl-models/projectmodel';
@@ -64,7 +65,9 @@ export function DeployPopup(props: DeployPopupProps) {
} }
function FluxscapeDeployTab() { function FluxscapeDeployTab() {
const params = {}; const params = {
version: platform.getVersion()
};
const projectId = ProjectModel.instance.id; const projectId = ProjectModel.instance.id;
if (projectId) { if (projectId) {

View File

@@ -6,8 +6,6 @@ import { CloudService } from '@noodl-models/CloudServices';
import { ActivityIndicator } from '@noodl-core-ui/components/common/ActivityIndicator'; import { ActivityIndicator } from '@noodl-core-ui/components/common/ActivityIndicator';
import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon'; import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
import { IconButton } from '@noodl-core-ui/components/inputs/IconButton'; import { IconButton } from '@noodl-core-ui/components/inputs/IconButton';
import { PrimaryButton } from '@noodl-core-ui/components/inputs/PrimaryButton';
import { Box } from '@noodl-core-ui/components/layout/Box';
import { ConditionalContainer } from '@noodl-core-ui/components/layout/ConditionalContainer'; import { ConditionalContainer } from '@noodl-core-ui/components/layout/ConditionalContainer';
import { Container } from '@noodl-core-ui/components/layout/Container'; import { Container } from '@noodl-core-ui/components/layout/Container';
import { VStack } from '@noodl-core-ui/components/layout/Stack'; import { VStack } from '@noodl-core-ui/components/layout/Stack';

View File

@@ -44,7 +44,7 @@ export function OpenAiSection() {
} }
return ( return (
<CollapsableSection title="Noodl AI (Beta)"> <CollapsableSection title="FluxScape AI (Beta)">
<Box hasXSpacing> <Box hasXSpacing>
<VStack> <VStack>
<PropertyPanelRow label="Version" isChanged={false}> <PropertyPanelRow label="Version" isChanged={false}>
@@ -66,7 +66,7 @@ export function OpenAiSection() {
{enabledState === 'disabled' && ( {enabledState === 'disabled' && (
<Box hasYSpacing> <Box hasYSpacing>
<Text>Noodl AI is currently disabled.</Text> <Text>FluxScape AI is currently disabled.</Text>
</Box> </Box>
)} )}
@@ -157,16 +157,16 @@ export function OpenAiSection() {
UNSAFE_style={{ borderRadius: '2px', background: 'var(--theme-color-bg-3)' }} UNSAFE_style={{ borderRadius: '2px', background: 'var(--theme-color-bg-3)' }}
> >
<Title size={TitleSize.Medium} hasBottomSpacing> <Title size={TitleSize.Medium} hasBottomSpacing>
Noodl AI docs FluxScape AI docs
</Title> </Title>
<Text hasBottomSpacing>See setup instructions and guides for how to use Noodl AI on our docs.</Text> <Text hasBottomSpacing>See setup instructions and guides for how to use FluxScape AI on our docs.</Text>
<PrimaryButton <PrimaryButton
variant={PrimaryButtonVariant.Muted} variant={PrimaryButtonVariant.Muted}
size={PrimaryButtonSize.Small} size={PrimaryButtonSize.Small}
isGrowing isGrowing
label="Open docs" label="Open docs"
onClick={() => { onClick={() => {
platform.openExternal('https://docs.noodl.net/#/docs/getting-started/noodl-ai/'); platform.openExternal('https://docs.fluxscape.io/docs/getting-started/noodl-ai');
}} }}
/> />
</Box> </Box>

View File

@@ -1,14 +1,12 @@
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext'; import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
import { type NodeReference, useNodeReferencesContext } from '@noodl-contexts/NodeReferencesContext';
import { useFocusRefOnPanelActive } from '@noodl-hooks/useFocusRefOnPanelActive'; import { useFocusRefOnPanelActive } from '@noodl-hooks/useFocusRefOnPanelActive';
import { useNodeLibraryLoaded } from '@noodl-hooks/useNodeLibraryLoaded'; import { useNodeLibraryLoaded } from '@noodl-hooks/useNodeLibraryLoaded';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { INodeColorScheme } from '@noodl-types/nodeTypes'; import { INodeColorScheme } from '@noodl-types/nodeTypes';
import { ComponentModel } from '@noodl-models/componentmodel'; import { NodeLibrary } from '@noodl-models/nodelibrary';
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { NodeLibrary, NodeLibraryNodeType } from '@noodl-models/nodelibrary';
import { BasicNodeType } from '@noodl-models/nodelibrary/BasicNodeType'; import { BasicNodeType } from '@noodl-models/nodelibrary/BasicNodeType';
import { ProjectModel } from '@noodl-models/projectmodel';
import { EditorNode } from '@noodl-core-ui/components/common/EditorNode'; import { EditorNode } from '@noodl-core-ui/components/common/EditorNode';
import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon'; import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
@@ -26,113 +24,17 @@ import { Section, SectionVariant } from '@noodl-core-ui/components/sidebar/Secti
import { Label } from '@noodl-core-ui/components/typography/Label'; import { Label } from '@noodl-core-ui/components/typography/Label';
import { NodeReferencesPanel_ID } from '.'; import { NodeReferencesPanel_ID } from '.';
import { EventDispatcher } from '../../../../../shared/utils/EventDispatcher';
type ResultItem = {
type: NodeLibraryNodeType;
displayName: string;
referenaces: {
displayName: string;
node?: NodeGraphNode;
component: ComponentModel;
}[];
};
function useNodeReferences() {
const [group] = useState({});
const [result, setResult] = useState<ResultItem[]>([]);
useEffect(() => {
function updateIndex() {
const types: { [key: string]: ResultItem['type'] } = {};
const references = new Map<string, ResultItem['referenaces']>();
function handleComponent(component: ComponentModel) {
component.forEachNode((node: NodeGraphNode) => {
const name = node.type.name;
// Add the reference
references.set(name, [
...(references.get(name) || []),
{
displayName: component.displayName || component.name,
node,
component
}
]);
// Repeater
if (name === 'For Each' && node.parameters.template) {
const templateComponent = ProjectModel.instance.getComponentWithName(node.parameters.template);
if (templateComponent) {
references.set(templateComponent.fullName, [
...(references.get(templateComponent.fullName) || []),
{
displayName: component.displayName || component.name,
node,
component
}
]);
handleComponent(templateComponent);
}
}
// Add some metadata for this node if we dont have it yet.
if (!types[name]) {
types[name] = node.type;
}
});
}
// Loop all the nodes in the project
ProjectModel.instance.forEachComponent(handleComponent);
// Combine the result to look a little better.
const results: ResultItem[] = Array.from(references.keys())
.map((key) => ({
type: types[key],
displayName: types[key]?.displayName || key,
referenaces: references.get(key)
}))
.sort((a, b) => b.referenaces.length - a.referenaces.length);
setResult(results);
}
updateIndex();
EventDispatcher.instance.on(
[
'Model.nodeAdded',
'Model.nodeRemoved',
'Model.componentAdded',
'Model.componentRemoved',
'Model.componentRenamed'
],
updateIndex,
group
);
return function () {
EventDispatcher.instance.off(group);
};
}, []);
return [result];
}
export function NodeReferencesPanel() { export function NodeReferencesPanel() {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [includeCoreNodes, setIncludeCoreNodes] = useState(false); const [includeCoreNodes, setIncludeCoreNodes] = useState(false);
const inputRef = useRef(null); const inputRef = useRef(null);
const [result] = useNodeReferences(); const { nodeReferences } = useNodeReferencesContext();
const nodeLibraryLoaded = useNodeLibraryLoaded(); const nodeLibraryLoaded = useNodeLibraryLoaded();
useFocusRefOnPanelActive(inputRef, NodeReferencesPanel_ID); useFocusRefOnPanelActive(inputRef, NodeReferencesPanel_ID);
function searchFilter(x: ResultItem) { function searchFilter(x: NodeReference) {
if (x.displayName.toLowerCase().includes(searchTerm)) { if (x.displayName.toLowerCase().includes(searchTerm)) {
return true; return true;
} }
@@ -144,7 +46,7 @@ export function NodeReferencesPanel() {
return false; return false;
} }
let filteredResult = result.filter(searchFilter); let filteredResult = nodeReferences.filter(searchFilter);
if (!includeCoreNodes) { if (!includeCoreNodes) {
filteredResult = filteredResult.filter((x) => x.displayName.startsWith('/')); filteredResult = filteredResult.filter((x) => x.displayName.startsWith('/'));
} }
@@ -185,7 +87,7 @@ export function NodeReferencesPanel() {
} }
interface ItemProps { interface ItemProps {
entry: ResultItem; entry: NodeReference;
} }
function Item({ entry }: ItemProps) { function Item({ entry }: ItemProps) {
@@ -245,8 +147,8 @@ function Item({ entry }: ItemProps) {
} }
interface ItemReferenceProps { interface ItemReferenceProps {
entry: ResultItem; entry: NodeReference;
referenace: ResultItem['referenaces'][0]; referenace: NodeReference['referenaces'][0];
colors: INodeColorScheme; colors: INodeColorScheme;
} }

View File

@@ -17,9 +17,11 @@ import { EventDispatcher } from '../../../../../shared/utils/EventDispatcher';
import View from '../../../../../shared/view'; import View from '../../../../../shared/view';
import { NodeGraphEditor } from '../../nodegrapheditor'; import { NodeGraphEditor } from '../../nodegrapheditor';
import * as NewPopupLayer from '../../PopupLayer/index'; import * as NewPopupLayer from '../../PopupLayer/index';
import { type PopupMenuItem } from '../../PopupLayer/index';
import { ToastLayer } from '../../ToastLayer/ToastLayer'; import { ToastLayer } from '../../ToastLayer/ToastLayer';
import { ComponentsPanelFolder } from './ComponentsPanelFolder'; import { ComponentsPanelFolder } from './ComponentsPanelFolder';
import { ComponentTemplates } from './ComponentTemplates'; import { ComponentTemplates } from './ComponentTemplates';
import { HACK_findNodeReference } from '@noodl-contexts/NodeReferencesContext';
const PopupLayer = require('@noodl-views/popuplayer'); const PopupLayer = require('@noodl-views/popuplayer');
const ComponentsPanelTemplate = require('../../../templates/componentspanel.html'); const ComponentsPanelTemplate = require('../../../templates/componentspanel.html');
@@ -961,7 +963,7 @@ export class ComponentsPanelView extends View {
forRuntimeType: this.getRuntimeType() forRuntimeType: this.getRuntimeType()
}); });
let items: TSFixme[] = templates.map((t) => ({ let items: PopupMenuItem[] = templates.map((t) => ({
icon: IconName.Plus, icon: IconName.Plus,
label: t.label, label: t.label,
onClick: () => { onClick: () => {
@@ -987,6 +989,10 @@ export class ComponentsPanelView extends View {
}); });
} }
// Find references
const nodeReference = HACK_findNodeReference(scope.comp.name);
const nodeReferencesText = `Used in ~${nodeReference?.referenaces?.length || 0} places`;
items = items.concat([ items = items.concat([
{ {
icon: IconName.Pencil, icon: IconName.Pencil,
@@ -1011,6 +1017,9 @@ export class ComponentsPanelView extends View {
_this.onDeleteClicked(scope, el); _this.onDeleteClicked(scope, el);
evt.stopPropagation(); evt.stopPropagation();
} }
},
{
label: nodeReferencesText
} }
]); ]);
@@ -1110,6 +1119,16 @@ export class ComponentsPanelView extends View {
} }
]); ]);
if (scope.canBecomeRoot) {
// Find references
const nodeReference = HACK_findNodeReference(scope.folder.component.name);
const nodeReferencesText = `Used in ~${nodeReference?.referenaces?.length || 0} places`;
items = items.concat([{
label: nodeReferencesText
}]);
}
const menu = new NewPopupLayer.PopupMenu({ const menu = new NewPopupLayer.PopupMenu({
items: items items: items
}); });

View File

@@ -182,7 +182,7 @@ function AiNodeChat({ context, onUpdated }: AiNodeChatProps) {
footer={ footer={
version === 'disabled' ? ( version === 'disabled' ? (
<Center> <Center>
<Text textType={TextType.Shy}>Noodl AI is currently disabled.</Text> <Text textType={TextType.Shy}>FluxScape AI is currently disabled.</Text>
</Center> </Center>
) : ( ) : (
<> <>

View File

@@ -5,10 +5,8 @@ import { useSidePanelKeyboardCommands } from '@noodl-hooks/useKeyboardCommands';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { ComponentModel } from '@noodl-models/componentmodel';
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode'; import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
import { performSearch } from '@noodl-utils/universal-search'; import { performSearch, SearchResult } from '@noodl-utils/universal-search';
import { SearchInput } from '@noodl-core-ui/components/inputs/SearchInput'; import { SearchInput } from '@noodl-core-ui/components/inputs/SearchInput';
import { Container } from '@noodl-core-ui/components/layout/Container'; import { Container } from '@noodl-core-ui/components/layout/Container';
@@ -21,7 +19,7 @@ import css from './search-panel.module.scss';
export function SearchPanel() { export function SearchPanel() {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]); const [searchResults, setSearchResults] = useState<ReturnType<typeof performSearch>>([]);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
// TODO: Not same context // TODO: Not same context
@@ -56,7 +54,7 @@ export function SearchPanel() {
} }
}, [debouncedSearchTerm]); }, [debouncedSearchTerm]);
function onSearchItemClicked(searchResult: SearchResultItem) { function onSearchItemClicked(searchResult: SearchResult) {
if (searchResult.type === 'Component') { if (searchResult.type === 'Component') {
NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, { NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, {
breadcrumbs: false, breadcrumbs: false,
@@ -99,21 +97,9 @@ export function SearchPanel() {
); );
} }
type SearchResultItem = {
componentTarget: ComponentModel;
label: string;
nodeTarget: NodeGraphNode;
type: string;
userLabel: string;
};
type SearchItemProps = { type SearchItemProps = {
component: { component: ReturnType<typeof performSearch>[0];
componentName: string; onSearchItemClicked: (item: SearchResult) => void;
componentId: string;
results: SearchResultItem[];
};
onSearchItemClicked: (item: SearchResultItem) => void;
}; };
function SearchItem({ component, onSearchItemClicked }: SearchItemProps) { function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
@@ -130,11 +116,11 @@ function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
<Section title={titleText} variant={SectionVariant.Panel}> <Section title={titleText} variant={SectionVariant.Panel}>
{component.results.map((result, index) => ( {component.results.map((result, index) => (
<div <div
key={index}
className={classNames( className={classNames(
css.SearchResultItem css.SearchResultItem
// lastActiveComponentId === result.componentTarget.id && css['is-active'] // lastActiveComponentId === result.componentTarget.id && css['is-active']
)} )}
key={index}
onClick={() => onSearchItemClicked(result)} onClick={() => onSearchItemClicked(result)}
> >
<Label variant={TextType.Proud}> <Label variant={TextType.Proud}>

View File

@@ -73,8 +73,9 @@ class CloudStore {
xhr.open(options.method || 'GET', this.endpoint + path, true); xhr.open(options.method || 'GET', this.endpoint + path, true);
xhr.setRequestHeader('X-Parse-Application-Id', this.appId); xhr.setRequestHeader('X-Parse-Application-Id', this.appId);
if (typeof _noodl_cloudservices !== 'undefined') if (typeof _noodl_cloudservices !== 'undefined') {
xhr.setRequestHeader('X-Parse-Master-Key', _noodl_cloudservices.masterKey); xhr.setRequestHeader('X-Parse-Master-Key', _noodl_cloudservices.masterKey);
}
// Check for current users // Check for current users
var _cu = localStorage['Parse/' + this.appId + '/currentUser']; var _cu = localStorage['Parse/' + this.appId + '/currentUser'];
@@ -257,11 +258,34 @@ class CloudStore {
}); });
} }
/**
*
* @param {{
* objectId: string;
* collection: string;
* keys?: string[] | string;
* include?: string[] | string;
* excludeKeys?: string[] | string;
* success: (data: unknown) => void;
* error: (error: unknown) => void;
* }} options
*/
fetch(options) { fetch(options) {
const args = []; const args = [];
if (options.include) if (options.include) {
args.push('include=' + (Array.isArray(options.include) ? options.include.join(',') : options.include)); args.push('include=' + (Array.isArray(options.include) ? options.include.join(',') : options.include));
}
if (options.keys) {
args.push('keys=' + (Array.isArray(options.keys) ? options.keys.join(',') : options.keys));
}
if (options.excludeKeys) {
args.push(
'excludeKeys=' + (Array.isArray(options.excludeKeys) ? options.excludeKeys.join(',') : options.excludeKeys)
);
}
this._makeRequest( this._makeRequest(
'/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''), '/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''),
@@ -433,6 +457,8 @@ class CloudStore {
* file: { * file: {
* name: string; * name: string;
* } * }
* success: (data: unknown) => void;
* error: (error: unknown) => void;
* }} options * }} options
*/ */
deleteFile(options) { deleteFile(options) {
@@ -445,8 +471,15 @@ class CloudStore {
} }
function _isArrayOfObjects(a) { function _isArrayOfObjects(a) {
if (!Array.isArray(a)) return false; if (!Array.isArray(a)) {
for (var i = 0; i < a.length; i++) if (typeof a[i] !== 'object' || a[i] === null) return false; return false;
}
for (let i = 0; i < a.length; i++) {
if (typeof a[i] !== 'object' || a[i] === null) {
return false;
}
}
return true; return true;
} }
@@ -518,66 +551,104 @@ function _serializeObject(data, collectionName, modelScope) {
return data; return data;
} }
/**
*
* @param {unknown} data
* @param {string} type
* @param {*} modelScope
* @returns
*/
function _deserializeJSON(data, type, modelScope) { function _deserializeJSON(data, type, modelScope) {
if (data === undefined) return; if (data === undefined) return undefined;
if (data === null) return null; if (data === null) return null;
if (type === 'Relation' && data.__type === 'Relation') { if (type === 'Relation' && data.__type === 'Relation') {
return undefined; // Ignore relation fields return undefined; // Ignore relation fields
} else if (type === 'Pointer' && data.__type === 'Pointer') { }
// This is a pointer type, resolve into id
// This is a pointer type, resolve into id
if (type === 'Pointer' && data.__type === 'Pointer') {
return data.objectId; return data.objectId;
} else if (type === 'Date' && data.__type === 'Date') { }
if (type === 'Date' && data.__type === 'Date') {
return new Date(data.iso); return new Date(data.iso);
} else if (type === 'Date' && typeof data === 'string') { }
if (type === 'Date' && typeof data === 'string') {
return new Date(data); return new Date(data);
} else if (type === 'File' && data.__type === 'File') { }
if (type === 'File' && data.__type === 'File') {
return new CloudFile(data); return new CloudFile(data);
} else if (type === 'GeoPoint' && data.__type === 'GeoPoint') { }
if (type === 'GeoPoint' && data.__type === 'GeoPoint') {
return { return {
latitude: data.latitude, latitude: data.latitude,
longitude: data.longitude longitude: data.longitude
}; };
} else if (_isArrayOfObjects(data)) { }
var a = [];
for (var i = 0; i < data.length; i++) { if (_isArrayOfObjects(data)) {
const a = [];
for (let i = 0; i < data.length; i++) {
a.push(_deserializeJSON(data[i], undefined, modelScope)); a.push(_deserializeJSON(data[i], undefined, modelScope));
} }
var c = Collection.get(); const c = Collection.get();
c.set(a); c.set(a);
return c; return c;
} else if (Array.isArray(data)) return data; }
// An array with mixed types
if (Array.isArray(data)) {
return data;
}
// This is an array with mixed data, just return it // This is an array with mixed data, just return it
else if (data && data.__type === 'Object' && data.className !== undefined && data.objectId !== undefined) { if (data && data.__type === 'Object' && data.className !== undefined && data.objectId !== undefined) {
const _data = Object.assign({}, data); const _data = Object.assign({}, data);
delete _data.className; delete _data.className;
delete _data.__type; delete _data.__type;
return _fromJSON(_data, data.className, modelScope); return _fromJSON(_data, data.className, modelScope);
} else if (typeof data === 'object' && data !== null) { }
var m = (modelScope || Model).get();
for (var key in data) { if (typeof data === 'object' && data !== null) {
m.set(key, _deserializeJSON(data[key], undefined, modelScope)); // Try to get the model by id, if it is defined, otherwise we create a new unique id.
const model = (modelScope || Model).get(data.id);
for (const key in data) {
const nestedValue = _deserializeJSON(data[key], undefined, modelScope);
model.set(key, nestedValue);
} }
return m; return model;
} else return data; }
return data;
} }
function _fromJSON(item, collectionName, modelScope) { function _fromJSON(item, collectionName, modelScope) {
const m = (modelScope || Model).get(item.objectId); const modelStore = modelScope || Model;
m._class = collectionName;
if (collectionName !== undefined && CloudStore._collections[collectionName] !== undefined) // Try to get the model by the object id (record) or id, otherwise we create a new unique id.
var schema = CloudStore._collections[collectionName].schema; const model = modelStore.get(item.objectId || item.id);
model._class = collectionName;
for (var key in item) { let schema = undefined;
if (key === 'objectId' || key === 'ACL') continue; if (collectionName !== undefined && CloudStore._collections[collectionName] !== undefined) {
schema = CloudStore._collections[collectionName].schema;
var _type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
m.set(key, _deserializeJSON(item[key], _type, modelScope));
} }
return m; for (const key in item) {
if (key === 'objectId' || key === 'ACL') {
continue;
}
const _type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
const nestedValue = _deserializeJSON(item[key], _type, modelScope);
model.set(key, nestedValue);
}
return model;
} }
CloudStore._fromJSON = _fromJSON; CloudStore._fromJSON = _fromJSON;

View File

@@ -38,12 +38,11 @@ function convertVisualFilter(query, options) {
var value = query.input !== undefined ? inputs[query.input] : query.value; var value = query.input !== undefined ? inputs[query.input] : query.value;
if (query.operator === 'exist') { if (query.operator === 'exist') {
_res[query.property] = { $exists: true }; _res[query.property] = { $exists: true };
return _res; return _res;
} } else if (query.operator === 'not exist') {
else if (query.operator === 'not exist') { _res[query.property] = { $exists: false };
_res[query.property] = { $exists: false }; return _res;
return _res;
} }
if (value === undefined) return; if (value === undefined) return;
@@ -80,7 +79,6 @@ function convertVisualFilter(query, options) {
cond = { $regex: value, $options: 'i' }; cond = { $regex: value, $options: 'i' };
} }
_res[query.property] = cond; _res[query.property] = cond;
return _res; return _res;
@@ -163,10 +161,22 @@ function _value(v) {
return v; return v;
} }
/**
*
* @param {Record<string, unknown>} filter
* @param {{
* collectionName?: string;
* modelScope?: unknown;
* error: (error: string) => void;
* }} options
* @returns
*/
function convertFilterOp(filter, options) { function convertFilterOp(filter, options) {
const keys = Object.keys(filter); const keys = Object.keys(filter);
if (keys.length === 0) return {}; if (keys.length === 0) return {};
if (keys.length !== 1) return options.error('Filter must only have one key found ' + keys.join(',')); if (keys.length !== 1) {
return options.error('Filter must only have one key found ' + keys.join(','));
}
const res = {}; const res = {};
const key = keys[0]; const key = keys[0];
@@ -179,18 +189,27 @@ function convertFilterOp(filter, options) {
} else if (filter['idContainedIn'] !== undefined) { } else if (filter['idContainedIn'] !== undefined) {
res['objectId'] = { $in: filter['idContainedIn'] }; res['objectId'] = { $in: filter['idContainedIn'] };
} else if (filter['relatedTo'] !== undefined) { } else if (filter['relatedTo'] !== undefined) {
var modelId = filter['relatedTo']['id']; const modelId = filter['relatedTo']['id'];
if (modelId === undefined) return options.error('Must provide id in relatedTo filter'); if (modelId === undefined) {
return options.error('Must provide id in relatedTo filter');
}
var relationKey = filter['relatedTo']['key']; const relationKey = filter['relatedTo']['key'];
if (relationKey === undefined) return options.error('Must provide key in relatedTo filter'); if (relationKey === undefined) {
return options.error('Must provide key in relatedTo filter');
}
const className = filter['relatedTo']['className'] || (options.modelScope || Model).get(modelId)?._class;
if (typeof className === 'undefined') {
// Either the pointer is loaded as an object or we allow passing in the className.
return options.error('Must preload the Pointer or include className');
}
var m = (options.modelScope || Model).get(modelId);
res['$relatedTo'] = { res['$relatedTo'] = {
object: { object: {
__type: 'Pointer', __type: 'Pointer',
objectId: modelId, objectId: modelId,
className: m._class className
}, },
key: relationKey key: relationKey
}; };
@@ -208,13 +227,14 @@ function convertFilterOp(filter, options) {
else if (opAndValue['containedIn'] !== undefined) res[key] = { $in: opAndValue['containedIn'] }; else if (opAndValue['containedIn'] !== undefined) res[key] = { $in: opAndValue['containedIn'] };
else if (opAndValue['notContainedIn'] !== undefined) res[key] = { $nin: opAndValue['notContainedIn'] }; else if (opAndValue['notContainedIn'] !== undefined) res[key] = { $nin: opAndValue['notContainedIn'] };
else if (opAndValue['pointsTo'] !== undefined) { else if (opAndValue['pointsTo'] !== undefined) {
var m = (options.modelScope || Model).get(opAndValue['pointsTo']); let schema = null;
if (CloudStore._collections[options.collectionName]) if (CloudStore._collections[options.collectionName]) {
var schema = CloudStore._collections[options.collectionName].schema; schema = CloudStore._collections[options.collectionName].schema;
}
var targetClass = const targetClass =
schema && schema.properties && schema.properties[key] ? schema.properties[key].targetClass : undefined; schema && schema.properties && schema.properties[key] ? schema.properties[key].targetClass : undefined;
var type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined; const type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
if (type === 'Relation') { if (type === 'Relation') {
res[key] = { res[key] = {
@@ -223,13 +243,13 @@ function convertFilterOp(filter, options) {
className: targetClass className: targetClass
}; };
} else { } else {
if (Array.isArray(opAndValue['pointsTo'])) if (Array.isArray(opAndValue['pointsTo'])) {
res[key] = { res[key] = {
$in: opAndValue['pointsTo'].map((v) => { $in: opAndValue['pointsTo'].map((v) => {
return { __type: 'Pointer', objectId: v, className: targetClass }; return { __type: 'Pointer', objectId: v, className: targetClass };
}) })
}; };
else } else {
res[key] = { res[key] = {
$eq: { $eq: {
__type: 'Pointer', __type: 'Pointer',
@@ -237,6 +257,7 @@ function convertFilterOp(filter, options) {
className: targetClass className: targetClass
} }
}; };
}
} }
} else if (opAndValue['matchesRegex'] !== undefined) { } else if (opAndValue['matchesRegex'] !== undefined) {
res[key] = { res[key] = {
@@ -257,43 +278,42 @@ function convertFilterOp(filter, options) {
} }
} }
}; };
// Geo points // Geo points
} else if (opAndValue['nearSphere'] !== undefined) { } else if (opAndValue['nearSphere'] !== undefined) {
var _v = opAndValue['nearSphere']; var _v = opAndValue['nearSphere'];
res[key] = { res[key] = {
$nearSphere: { $nearSphere: {
__type: "GeoPoint", __type: 'GeoPoint',
latitude: _v.latitude, latitude: _v.latitude,
longitude: _v.longitude, longitude: _v.longitude
}, },
$maxDistanceInMiles:_v.$maxDistanceInMiles, $maxDistanceInMiles: _v.$maxDistanceInMiles,
$maxDistanceInKilometers:_v.maxDistanceInKilometers, $maxDistanceInKilometers: _v.maxDistanceInKilometers,
$maxDistanceInRadians:_v.maxDistanceInRadians $maxDistanceInRadians: _v.maxDistanceInRadians
}; };
} else if (opAndValue['withinBox'] !== undefined) { } else if (opAndValue['withinBox'] !== undefined) {
var _v = opAndValue['withinBox']; var _v = opAndValue['withinBox'];
res[key] = { res[key] = {
$within:{ $within: {
$box: _v.map(gp => ({ $box: _v.map((gp) => ({
__type:"GeoPoint", __type: 'GeoPoint',
latitude:gp.latitude, latitude: gp.latitude,
longitude:gp.longitude longitude: gp.longitude
})) }))
} }
}; };
} else if (opAndValue['withinPolygon'] !== undefined) { } else if (opAndValue['withinPolygon'] !== undefined) {
var _v = opAndValue['withinPolygon']; var _v = opAndValue['withinPolygon'];
res[key] = { res[key] = {
$geoWithin:{ $geoWithin: {
$polygon: _v.map(gp => ({ $polygon: _v.map((gp) => ({
__type:"GeoPoint", __type: 'GeoPoint',
latitude:gp.latitude, latitude: gp.latitude,
longitude:gp.longitude longitude: gp.longitude
})) }))
} }
}; };
} }
} else { } else {
options.error('Unrecognized filter keys ' + keys.join(',')); options.error('Unrecognized filter keys ' + keys.join(','));
} }

View File

@@ -12,7 +12,7 @@ function createRecordsAPI(modelScope) {
return { return {
async query(className, query, options) { async query(className, query, options) {
if (typeof className === "undefined") throw new Error("'className' is undefined"); if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
cloudstore().query({ cloudstore().query({
collection: className, collection: className,
@@ -27,9 +27,9 @@ function createRecordsAPI(modelScope) {
include: options ? options.include : undefined, include: options ? options.include : undefined,
select: options ? options.select : undefined, select: options ? options.select : undefined,
count: options ? options.count : undefined, count: options ? options.count : undefined,
success: (results,count) => { success: (results, count) => {
const _results = results.map((r) => cloudstore()._fromJSON(r, className)); const _results = results.map((r) => cloudstore()._fromJSON(r, className));
if(count !== undefined) resolve({results:_results,count}); if (count !== undefined) resolve({ results: _results, count });
else resolve(_results); else resolve(_results);
}, },
error: (err) => { error: (err) => {
@@ -40,7 +40,7 @@ function createRecordsAPI(modelScope) {
}, },
async count(className, query) { async count(className, query) {
if (typeof className === "undefined") throw new Error("'className' is undefined"); if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
cloudstore().count({ cloudstore().count({
collection: className, collection: className,
@@ -62,7 +62,7 @@ function createRecordsAPI(modelScope) {
}, },
async distinct(className, property, query) { async distinct(className, property, query) {
if (typeof className === "undefined") throw new Error("'className' is undefined"); if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
cloudstore().distinct({ cloudstore().distinct({
collection: className, collection: className,
@@ -85,7 +85,7 @@ function createRecordsAPI(modelScope) {
}, },
async aggregate(className, group, query) { async aggregate(className, group, query) {
if (typeof className === "undefined") throw new Error("'className' is undefined"); if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
cloudstore().aggregate({ cloudstore().aggregate({
collection: className, collection: className,
@@ -107,20 +107,35 @@ function createRecordsAPI(modelScope) {
}); });
}, },
/**
*
* @param {string | { getId(): string; }} objectOrId
* @param {{
* className: string;
* keys?: string[] | string;
* include?: string[] | string;
* excludeKeys?: string[] | string;
* }} options
* @returns {Promise<unknown>}
*/
async fetch(objectOrId, options) { async fetch(objectOrId, options) {
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined.")); if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId(); if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class; const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!className) return reject('No class name specified'); if (!className) {
return reject('No class name specified');
}
cloudstore().fetch({ cloudstore().fetch({
collection: className, collection: className,
objectId: objectOrId, objectId: objectOrId,
include: options ? options.include : undefined, keys: options?.keys,
include: options?.include,
excludeKeys: options?.excludeKeys,
success: function (response) { success: function (response) {
var record = cloudstore()._fromJSON(response, className); const record = cloudstore()._fromJSON(response, className);
resolve(record); resolve(record);
}, },
error: function (err) { error: function (err) {
@@ -186,7 +201,7 @@ function createRecordsAPI(modelScope) {
}, },
async create(className, properties, options) { async create(className, properties, options) {
if (typeof className === "undefined") throw new Error("'className' is undefined"); if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
cloudstore().create({ cloudstore().create({
collection: className, collection: className,

View File

@@ -1,6 +1,6 @@
"use strict"; 'use strict';
var Model = require("./model"); var Model = require('./model');
// Get and set proxy // Get and set proxy
/*const proxies = {} /*const proxies = {}
@@ -221,48 +221,48 @@ Collection.prototype.toJSON = function() {
}*/ }*/
// ---- // ----
Object.defineProperty(Array.prototype, "items", { Object.defineProperty(Array.prototype, 'items', {
enumerable: false, enumerable: false,
get() { get() {
return this; return this;
}, },
set(data) { set(data) {
this.set(data); this.set(data);
}, }
}); });
Object.defineProperty(Array.prototype, "each", { Object.defineProperty(Array.prototype, 'each', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: Array.prototype.forEach, value: Array.prototype.forEach
}); });
Object.defineProperty(Array.prototype, "size", { Object.defineProperty(Array.prototype, 'size', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function () { value: function () {
return this.length; return this.length;
}, }
}); });
Object.defineProperty(Array.prototype, "get", { Object.defineProperty(Array.prototype, 'get', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (index) { value: function (index) {
return this[index]; return this[index];
}, }
}); });
Object.defineProperty(Array.prototype, "getId", { Object.defineProperty(Array.prototype, 'getId', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function () { value: function () {
return this._id; return this._id;
}, }
}); });
Object.defineProperty(Array.prototype, "id", { Object.defineProperty(Array.prototype, 'id', {
enumerable: false, enumerable: false,
get() { get() {
return this.getId(); return this.getId();
}, }
}); });
Object.defineProperty(Array.prototype, "set", { Object.defineProperty(Array.prototype, 'set', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (src) { value: function (src) {
@@ -323,10 +323,10 @@ Object.defineProperty(Array.prototype, "set", {
for (i = aItems.length; i < bItems.length; i++) { for (i = aItems.length; i < bItems.length; i++) {
this.add(bItems[i]); this.add(bItems[i]);
} }
}, }
}); });
Object.defineProperty(Array.prototype, "notify", { Object.defineProperty(Array.prototype, 'notify', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: async function (event, args) { value: async function (event, args) {
@@ -337,80 +337,80 @@ Object.defineProperty(Array.prototype, "notify", {
for (var i = 0; i < l.length; i++) { for (var i = 0; i < l.length; i++) {
await l[i](args); await l[i](args);
} }
}, }
}); });
Object.defineProperty(Array.prototype, "contains", { Object.defineProperty(Array.prototype, 'contains', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (item) { value: function (item) {
return this.indexOf(item) !== -1; return this.indexOf(item) !== -1;
}, }
}); });
Object.defineProperty(Array.prototype, "add", { Object.defineProperty(Array.prototype, 'add', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: async function (item) { value: async function (item) {
if (this.contains(item)) return; // Already contains item if (this.contains(item)) return; // Already contains item
this.items.push(item); this.items.push(item);
await this.notify("add", { item: item, index: this.items.length - 1 }); await this.notify('add', { item: item, index: this.items.length - 1 });
await this.notify("change"); await this.notify('change');
await item.notify("add", { collection: this }); await item.notify('add', { collection: this });
}, }
}); });
Object.defineProperty(Array.prototype, "remove", { Object.defineProperty(Array.prototype, 'remove', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (item) { value: function (item) {
var idx = this.items.indexOf(item); var idx = this.items.indexOf(item);
if (idx !== -1) this.removeAtIndex(idx); if (idx !== -1) this.removeAtIndex(idx);
}, }
}); });
Object.defineProperty(Array.prototype, "addAtIndex", { Object.defineProperty(Array.prototype, 'addAtIndex', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: async function (item, index) { value: async function (item, index) {
if (this.contains(item)) return; // Already contains item if (this.contains(item)) return; // Already contains item
this.items.splice(index, 0, item); this.items.splice(index, 0, item);
await this.notify("add", { item: item, index: index }); await this.notify('add', { item: item, index: index });
await this.notify("change"); await this.notify('change');
await item.notify("add", { collection: this, index: index }); await item.notify('add', { collection: this, index: index });
}, }
}); });
Object.defineProperty(Array.prototype, "removeAtIndex", { Object.defineProperty(Array.prototype, 'removeAtIndex', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: async function (idx) { value: async function (idx) {
var item = this.items[idx]; var item = this.items[idx];
this.items.splice(idx, 1); this.items.splice(idx, 1);
await this.notify("remove", { item: item, index: idx }); await this.notify('remove', { item: item, index: idx });
await this.notify("change"); await this.notify('change');
await item.notify("remove", { collection: this }); await item.notify('remove', { collection: this });
}, }
}); });
Object.defineProperty(Array.prototype, "on", { Object.defineProperty(Array.prototype, 'on', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (event, listener) { value: function (event, listener) {
if (!this._listeners) if (!this._listeners)
Object.defineProperty(this, "_listeners", { Object.defineProperty(this, '_listeners', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: {}, value: {}
}); });
if (!this._listeners[event]) this._listeners[event] = []; if (!this._listeners[event]) this._listeners[event] = [];
this._listeners[event].push(listener); this._listeners[event].push(listener);
}, }
}); });
Object.defineProperty(Array.prototype, "off", { Object.defineProperty(Array.prototype, 'off', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (event, listener) { value: function (event, listener) {
@@ -418,20 +418,20 @@ Object.defineProperty(Array.prototype, "off", {
if (!this._listeners[event]) return; if (!this._listeners[event]) return;
var idx = this._listeners[event].indexOf(listener); var idx = this._listeners[event].indexOf(listener);
if (idx !== -1) this._listeners[event].splice(idx, 1); if (idx !== -1) this._listeners[event].splice(idx, 1);
}, }
}); });
class Collection extends Array {} class Collection extends Array {}
var collections = (Collection._collections = {}); const collections = (Collection._collections = {});
Collection.create = function (items) { Collection.create = function (items) {
const name = Model.guid(); const name = Model.guid();
collections[name] = new Collection(); collections[name] = new Collection();
Object.defineProperty(collections[name], "_id", { Object.defineProperty(collections[name], '_id', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: name, value: name
}); });
if (items) { if (items) {
collections[name].set(items); collections[name].set(items);
@@ -439,14 +439,18 @@ Collection.create = function (items) {
return collections[name]; return collections[name];
}; };
/**
* @param {string} name
* @returns {Collection}
*/
Collection.get = function (name) { Collection.get = function (name) {
if (name === undefined) name = Model.guid(); if (name === undefined) name = Model.guid();
if (!collections[name]) { if (!collections[name]) {
collections[name] = new Collection(); collections[name] = new Collection();
Object.defineProperty(collections[name], "_id", { Object.defineProperty(collections[name], '_id', {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: name, value: name
}); });
} }

View File

@@ -35,6 +35,11 @@ const _modelProxyHandler = {
} }
}; };
/**
*
* @param {*} id
* @returns {Model}
*/
Model.get = function (id) { Model.get = function (id) {
if (id === undefined) id = Model.guid(); if (id === undefined) id = Model.guid();
if (!models[id]) { if (!models[id]) {
@@ -122,13 +127,22 @@ Model.prototype.fill = function (value = null) {
} }
}; };
/**
* @param {string} name
* @param {unknown} value
* @param {{
* resolve?: boolean;
* forceChange?: boolean;
* silent?: boolean;
* }} args
*/
Model.prototype.set = function (name, value, args) { Model.prototype.set = function (name, value, args) {
if (args && args.resolve && name.indexOf('.') !== -1) { if (args && args.resolve && name.indexOf('.') !== -1) {
// We should resolve path references // We should resolve path references
var path = name.split('.'); const path = name.split('.');
var model = this; let model = this;
for (var i = 0; i < path.length - 1; i++) { for (let i = 0; i < path.length - 1; i++) {
var v = model.get(path[i]); const v = model.get(path[i]);
if (Model.instanceOf(v)) model = v; if (Model.instanceOf(v)) model = v;
else return; // Path resolve failed else return; // Path resolve failed
} }
@@ -138,24 +152,35 @@ Model.prototype.set = function (name, value, args) {
const forceChange = args && args.forceChange; const forceChange = args && args.forceChange;
var oldValue = this.data[name]; const oldValue = this.data[name];
this.data[name] = value; this.data[name] = value;
(forceChange || oldValue !== value) &&
(!args || !args.silent) && if ((forceChange || oldValue !== value) && (!args || !args.silent)) {
this.notify('change', { name: name, value: value, old: oldValue }); this.notify('change', { name: name, value: value, old: oldValue });
}
}; };
/**
* @returns {string}
*/
Model.prototype.getId = function () { Model.prototype.getId = function () {
return this.id; return this.id;
}; };
/**
* @param {string} name
* @param {{
* resolve?: boolean;
* }} args
* @returns {unknown}
*/
Model.prototype.get = function (name, args) { Model.prototype.get = function (name, args) {
if (args && args.resolve && name.indexOf('.') !== -1) { if (args && args.resolve && name.indexOf('.') !== -1) {
// We should resolve path references // We should resolve path references
var path = name.split('.'); const path = name.split('.');
var model = this; let model = this;
for (var i = 0; i < path.length - 1; i++) { for (let i = 0; i < path.length - 1; i++) {
var v = model.get(path[i]); const v = model.get(path[i]);
if (Model.instanceOf(v)) model = v; if (Model.instanceOf(v)) model = v;
else return; // Path resolve failed else return; // Path resolve failed
} }

View File

@@ -230,7 +230,7 @@ NodeContext.prototype.deregisterComponentModel = function (componentModel) {
NodeContext.prototype.fetchComponentBundle = async function (name) { NodeContext.prototype.fetchComponentBundle = async function (name) {
const fetchBundle = async (name) => { const fetchBundle = async (name) => {
let baseUrl = Noodl.Env["BaseUrl"] || '/'; let baseUrl = Noodl.Env['BaseUrl'] || '/';
let bundleUrl = `${baseUrl}noodl_bundles/${name}.json`; let bundleUrl = `${baseUrl}noodl_bundles/${name}.json`;
const response = await fetch(bundleUrl); const response = await fetch(bundleUrl);
@@ -455,6 +455,15 @@ NodeContext.prototype.setPopupCallbacks = function ({ onShow, onClose }) {
this.onClosePopup = onClose; this.onClosePopup = onClose;
}; };
/**
* @param {string} popupComponent
* @param {Record<string, unknown>} params
* @param {{
* senderNode?: unknown;
* onClosePopup?: (action?: string, results: object) => void;
* }} args
* @returns
*/
NodeContext.prototype.showPopup = async function (popupComponent, params, args) { NodeContext.prototype.showPopup = async function (popupComponent, params, args) {
if (!this.onShowPopup) return; if (!this.onShowPopup) return;

View File

@@ -28,6 +28,7 @@ var DbCollectionNode = {
_this.scheduleAfterInputsHaveUpdated(function () { _this.scheduleAfterInputsHaveUpdated(function () {
_this.flagOutputDirty('count'); _this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId'); _this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
collectionChangedScheduled = false; collectionChangedScheduled = false;
}); });
}; };
@@ -66,6 +67,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count'); _this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId'); _this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
} }
if (args.type === 'create') { if (args.type === 'create') {
@@ -91,6 +93,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count'); _this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId'); _this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
} else if (matchesQuery && !_this._internal.collection.contains(m)) { } else if (matchesQuery && !_this._internal.collection.contains(m)) {
// It's not part of the result collection but now matches they query, add it and resort // It's not part of the result collection but now matches they query, add it and resort
_addModelAtCorrectIndex(m); _addModelAtCorrectIndex(m);
@@ -106,6 +109,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count'); _this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId'); _this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
} }
} }
}; };
@@ -153,6 +157,17 @@ var DbCollectionNode = {
} }
} }
}, },
isEmpty: {
type: 'boolean',
displayName: 'Is Empty',
group: 'General',
getter: function () {
if (this._internal.collection) {
return this._internal.collection.size() === 0;
}
return true;
}
},
count: { count: {
type: 'number', type: 'number',
displayName: 'Count', displayName: 'Count',
@@ -189,6 +204,7 @@ var DbCollectionNode = {
setCollection: function (collection) { setCollection: function (collection) {
this.bindCollection(collection); this.bindCollection(collection);
this.flagOutputDirty('firstItemId'); this.flagOutputDirty('firstItemId');
this.flagOutputDirty('isEmpty');
this.flagOutputDirty('items'); this.flagOutputDirty('items');
this.flagOutputDirty('count'); this.flagOutputDirty('count');
}, },
@@ -257,7 +273,7 @@ var DbCollectionNode = {
limit: limit, limit: limit,
skip: skip, skip: skip,
count: count, count: count,
success: (results,count) => { success: (results, count) => {
if (results !== undefined) { if (results !== undefined) {
_c.set( _c.set(
results.map((i) => { results.map((i) => {
@@ -267,10 +283,9 @@ var DbCollectionNode = {
}) })
); );
} }
if(count !== undefined) { if (count !== undefined) {
this._internal.storageSettings.storageTotalCount = count; this._internal.storageSettings.storageTotalCount = count;
if(this.hasOutput('storageTotalCount')) if (this.hasOutput('storageTotalCount')) this.flagOutputDirty('storageTotalCount');
this.flagOutputDirty('storageTotalCount');
} }
this.setCollection(_c); this.setCollection(_c);
this.sendSignalOnOutput('fetched'); this.sendSignalOnOutput('fetched');
@@ -383,7 +398,7 @@ var DbCollectionNode = {
if (!storageSettings['storageEnableLimit']) return; if (!storageSettings['storageEnableLimit']) return;
else return storageSettings['storageSkip'] || 0; else return storageSettings['storageSkip'] || 0;
}, },
getStorageFetchTotalCount: function() { getStorageFetchTotalCount: function () {
const storageSettings = this._internal.storageSettings; const storageSettings = this._internal.storageSettings;
return !!storageSettings['storageEnableCount']; return !!storageSettings['storageEnableCount'];

View File

@@ -2,10 +2,10 @@
const { Node, EdgeTriggeredInput } = require('../../../../noodl-runtime'); const { Node, EdgeTriggeredInput } = require('../../../../noodl-runtime');
var Model = require('../../../model'); const Model = require('../../../model');
const CloudStore = require('../../../api/cloudstore'); const CloudStore = require('../../../api/cloudstore');
var ModelNodeDefinition = { const ModelNodeDefinition = {
name: 'DbModel2', name: 'DbModel2',
docs: 'https://docs.noodl.net/nodes/data/cloud-data/record', docs: 'https://docs.noodl.net/nodes/data/cloud-data/record',
displayNodeName: 'Record', displayNodeName: 'Record',
@@ -21,11 +21,11 @@ var ModelNodeDefinition = {
} }
], ],
initialize: function () { initialize: function () {
var internal = this._internal; const internal = this._internal;
internal.inputValues = {}; internal.inputValues = {};
internal.relationModelIds = {}; internal.relationModelIds = {};
var _this = this; const _this = this;
this._internal.onModelChangedCallback = function (args) { this._internal.onModelChangedCallback = function (args) {
if (_this.isInputConnected('fetch')) return; if (_this.isInputConnected('fetch')) return;
@@ -109,13 +109,18 @@ var ModelNodeDefinition = {
displayName: 'Id', displayName: 'Id',
group: 'General', group: 'General',
set: function (value) { set: function (value) {
if (value instanceof Model) value = value.getId(); if (value instanceof Model) {
// Can be passed as model as well // Can be passed as model as well
else if (typeof value === 'object') value = Model.create(value).getId(); // If this is an js object, dereference it value = value.getId();
} else if (typeof value === 'object') {
// If this is an js object, dereference it
value = Model.create(value).getId();
}
this._internal.modelId = value; // Wait to fetch data this._internal.modelId = value; // Wait to fetch data
if (this.isInputConnected('fetch') === false) this.setModelID(value); if (this.isInputConnected('fetch') === false) {
else { this.setModelID(value);
} else {
this.flagOutputDirty('id'); this.flagOutputDirty('id');
} }
} }
@@ -138,9 +143,10 @@ var ModelNodeDefinition = {
this.setModel(model); this.setModel(model);
}, },
setModel: function (model) { setModel: function (model) {
if (this._internal.model) if (this._internal.model) {
// Remove old listener if existing // Remove old listener if existing
this._internal.model.off('change', this._internal.onModelChangedCallback); this._internal.model.off('change', this._internal.onModelChangedCallback);
}
this._internal.model = model; this._internal.model = model;
this.flagOutputDirty('id'); this.flagOutputDirty('id');
@@ -148,7 +154,9 @@ var ModelNodeDefinition = {
// We have a new model, mark all outputs as dirty // We have a new model, mark all outputs as dirty
for (var key in model.data) { for (var key in model.data) {
if (this.hasOutput('prop-' + key)) this.flagOutputDirty('prop-' + key); if (this.hasOutput('prop-' + key)) {
this.flagOutputDirty('prop-' + key);
}
} }
this.sendSignalOnOutput('fetched'); this.sendSignalOnOutput('fetched');
}, },
@@ -184,7 +192,7 @@ var ModelNodeDefinition = {
} }
}, },
scheduleFetch: function () { scheduleFetch: function () {
var _this = this; const _this = this;
const internal = this._internal; const internal = this._internal;
this.scheduleOnce('Fetch', function () { this.scheduleOnce('Fetch', function () {
@@ -199,12 +207,13 @@ var ModelNodeDefinition = {
collection: internal.collectionId, collection: internal.collectionId,
objectId: internal.modelId, // Get the objectId part of the model id objectId: internal.modelId, // Get the objectId part of the model id
success: function (response) { success: function (response) {
var model = cloudstore._fromJSON(response, internal.collectionId); const model = cloudstore._fromJSON(response, internal.collectionId);
if (internal.model !== model) { if (internal.model !== model) {
// Check if we need to change model // Check if we need to change model
if (internal.model) if (internal.model) {
// Remove old listener if existing // Remove old listener if existing
internal.model.off('change', internal.onModelChangedCallback); internal.model.off('change', internal.onModelChangedCallback);
}
internal.model = model; internal.model = model;
model.on('change', internal.onModelChangedCallback); model.on('change', internal.onModelChangedCallback);
@@ -213,8 +222,10 @@ var ModelNodeDefinition = {
delete response.objectId; delete response.objectId;
for (var key in response) { for (const key in response) {
if (_this.hasOutput('prop-' + key)) _this.flagOutputDirty('prop-' + key); if (_this.hasOutput('prop-' + key)) {
_this.flagOutputDirty('prop-' + key);
}
} }
_this.sendSignalOnOutput('fetched'); _this.sendSignalOnOutput('fetched');
@@ -226,7 +237,6 @@ var ModelNodeDefinition = {
}); });
}, },
scheduleStore: function () { scheduleStore: function () {
var _this = this;
var internal = this._internal; var internal = this._internal;
if (!internal.model) return; if (!internal.model) return;
@@ -247,8 +257,6 @@ var ModelNodeDefinition = {
}); });
}, },
registerInputIfNeeded: function (name) { registerInputIfNeeded: function (name) {
var _this = this;
if (this.hasInput(name)) { if (this.hasInput(name)) {
return; return;
} }
@@ -328,8 +336,7 @@ function updatePorts(nodeId, parameters, editorConnection, graphModel) {
var p = props[key]; var p = props[key];
if (ports.find((_p) => _p.name === key)) continue; if (ports.find((_p) => _p.name === key)) continue;
if (p.type === 'Relation') { if (p.type !== 'Relation') {
} else {
// Other schema type ports // Other schema type ports
const _typeMap = { const _typeMap = {
String: 'string', String: 'string',
@@ -373,16 +380,16 @@ module.exports = {
function _managePortsForNode(node) { function _managePortsForNode(node) {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel); updatePorts(node.id, node.parameters, context.editorConnection, graphModel);
node.on('parameterUpdated', function (event) { node.on('parameterUpdated', function () {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel); updatePorts(node.id, node.parameters, context.editorConnection, graphModel);
}); });
graphModel.on('metadataChanged.dbCollections', function (data) { graphModel.on('metadataChanged.dbCollections', function () {
CloudStore.invalidateCollections(); CloudStore.invalidateCollections();
updatePorts(node.id, node.parameters, context.editorConnection, graphModel); updatePorts(node.id, node.parameters, context.editorConnection, graphModel);
}); });
graphModel.on('metadataChanged.systemCollections', function (data) { graphModel.on('metadataChanged.systemCollections', function () {
CloudStore.invalidateCollections(); CloudStore.invalidateCollections();
updatePorts(node.id, node.parameters, context.editorConnection, graphModel); updatePorts(node.id, node.parameters, context.editorConnection, graphModel);
}); });

View File

@@ -31,8 +31,6 @@ const DateToStringNode = {
this._internal.currentInput = _value; this._internal.currentInput = _value;
this._format(); this._format();
this.flagOutputDirty('currentValue');
this.sendSignalOnOutput('inputChanged');
} }
} }
}, },
@@ -49,30 +47,45 @@ const DateToStringNode = {
type: 'signal', type: 'signal',
displayName: 'Date Changed', displayName: 'Date Changed',
group: 'Signals' group: 'Signals'
},
onError: {
type: 'signal',
displayName: 'Invalid Date',
group: 'Signals'
} }
}, },
methods: { methods: {
_format() { _format() {
const t = this._internal.currentInput; try {
const format = this._internal.formatString; const t = this._internal.currentInput;
const date = ('0' + t.getDate()).slice(-2); const format = this._internal.formatString;
const month = ('0' + (t.getMonth() + 1)).slice(-2); const date = ('0' + t.getDate()).slice(-2);
const monthShort = new Intl.DateTimeFormat('en-US', { month: 'short' }).format(t); const month = ('0' + (t.getMonth() + 1)).slice(-2);
const year = t.getFullYear(); const monthShort = new Intl.DateTimeFormat('en-US', { month: 'short' }).format(t);
const yearShort = year.toString().substring(2); const year = t.getFullYear();
const hours = ('0' + t.getHours()).slice(-2); const yearShort = year.toString().substring(2);
const minutes = ('0' + t.getMinutes()).slice(-2); const hours = ('0' + t.getHours()).slice(-2);
const seconds = ('0' + t.getSeconds()).slice(-2); const minutes = ('0' + t.getMinutes()).slice(-2);
const seconds = ('0' + t.getSeconds()).slice(-2);
this._internal.dateString = format this._internal.dateString = format
.replace(/\{date\}/g, date) .replace(/\{date\}/g, date)
.replace(/\{month\}/g, month) .replace(/\{month\}/g, month)
.replace(/\{monthShort\}/g, monthShort) .replace(/\{monthShort\}/g, monthShort)
.replace(/\{year\}/g, year) .replace(/\{year\}/g, year)
.replace(/\{yearShort\}/g, yearShort) .replace(/\{yearShort\}/g, yearShort)
.replace(/\{hours\}/g, hours) .replace(/\{hours\}/g, hours)
.replace(/\{minutes\}/g, minutes) .replace(/\{minutes\}/g, minutes)
.replace(/\{seconds\}/g, seconds); .replace(/\{seconds\}/g, seconds);
} catch (error) {
// Set the output to be blank, makes it easier to handle.
this._internal.dateString = '';
this.flagOutputDirty('onError');
}
// Flag that the value have changed
this.flagOutputDirty('currentValue');
this.sendSignalOnOutput('inputChanged');
} }
} }
}; };

View File

@@ -227,6 +227,9 @@ declare namespace Noodl {
objectOrId: string | { getId(): string; }, objectOrId: string | { getId(): string; },
options?: { options?: {
className?: RecordClassName; className?: RecordClassName;
keys?: string[] | string;
include?: string[] | string;
excludeKeys?: string[] | string;
} }
): Promise<any>; ): Promise<any>;

View File

@@ -1,8 +1,8 @@
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.viewer.common.js"); const common = require('./webpack.viewer.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: "development", mode: 'development',
devtool: "inline-source-map", devtool: 'inline-source-map',
watch: true, watch: true
}); });

View File

@@ -2,5 +2,6 @@ const { merge } = require('webpack-merge');
const common = require('./webpack.viewer.common.js'); const common = require('./webpack.viewer.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'production' mode: 'production',
devtool: 'source-map'
}); });

View File

@@ -2,12 +2,18 @@ const { RouterHandler } = require('../nodes/navigation/router-handler');
const NoodlRuntime = require('@noodl/runtime'); const NoodlRuntime = require('@noodl/runtime');
const navigation = { const navigation = {
/**
* This is set by "packages/noodl-viewer-react/src/noodl-js-api.js"
* @type {NoodlRuntime}
*/
_noodlRuntime: undefined,
async showPopup(componentPath, params) { async showPopup(componentPath, params) {
return new Promise((resolve) => { return new Promise((resolve) => {
navigation._noodlRuntime.context.showPopup(componentPath, params, { navigation._noodlRuntime.context.showPopup(componentPath, params, {
onClosePopup: (action, results) => { onClosePopup: (action, results) => {
resolve({ resolve({
action: action.replace('closeAction-', ''), action: action?.replace('closeAction-', ''),
parameters: results parameters: results
}); });
} }

View File

@@ -132,6 +132,7 @@ export function Columns(props: ColumnsProps) {
className={['columns-container', props.className].join(' ')} className={['columns-container', props.className].join(' ')}
ref={containerRef} ref={containerRef}
style={{ style={{
visibility: containerWidth === null ? "hidden" : "visible",
marginTop: parseFloat(props.marginY) * -1, marginTop: parseFloat(props.marginY) * -1,
marginLeft: parseFloat(props.marginX) * -1, marginLeft: parseFloat(props.marginX) * -1,
display: 'flex', display: 'flex',

View File

@@ -1,10 +1,9 @@
'use strict'; 'use strict';
const { Node } = require('@noodl/runtime'); const { Node } = require('@noodl/runtime');
const Model = require('@noodl/runtime/src/model');
var Model = require('@noodl/runtime/src/model'); const VariableNodeDefinition = {
var VariableNodeDefinition = {
name: 'Variable', name: 'Variable',
docs: 'https://docs.noodl.net/nodes/data/variable', docs: 'https://docs.noodl.net/nodes/data/variable',
category: 'Data', category: 'Data',

View File

@@ -40,8 +40,8 @@ const ClosePopupNode = {
this._internal.closeCallback = cb; this._internal.closeCallback = cb;
}, },
scheduleClose: function () { scheduleClose: function () {
var _this = this; const _this = this;
var internal = this._internal; const internal = this._internal;
if (!internal.hasScheduledClose) { if (!internal.hasScheduledClose) {
internal.hasScheduledClose = true; internal.hasScheduledClose = true;
this.scheduleAfterInputsHaveUpdated(function () { this.scheduleAfterInputsHaveUpdated(function () {
@@ -51,8 +51,9 @@ const ClosePopupNode = {
} }
}, },
close: function () { close: function () {
if (this._internal.closeCallback) if (this._internal.closeCallback) {
this._internal.closeCallback(this._internal.closeAction, this._internal.resultValues); this._internal.closeCallback(this._internal.closeAction, this._internal.resultValues);
}
}, },
closeActionTriggered: function (name) { closeActionTriggered: function (name) {
this._internal.closeAction = name; this._internal.closeAction = name;
@@ -112,9 +113,8 @@ module.exports = {
var closeActions = node.parameters['closeActions']; var closeActions = node.parameters['closeActions'];
if (closeActions) { if (closeActions) {
closeActions = closeActions ? closeActions.split(',') : undefined; closeActions = closeActions ? closeActions.split(',') : undefined;
for (var i in closeActions) { for (const i in closeActions) {
var p = closeActions[i]; const p = closeActions[i];
ports.push({ ports.push({
type: 'signal', type: 'signal',
plug: 'input', plug: 'input',

View File

@@ -192,8 +192,7 @@ function setup(context, graphModel) {
enums: pages.map((p) => ({ enums: pages.map((p) => ({
label: p.label, label: p.label,
value: p.id value: p.id
})), }))
allowEditOnly: true
}, },
group: 'General', group: 'General',
displayName: 'Target Page', displayName: 'Target Page',

View File

@@ -1,3 +1,5 @@
import React from 'react';
import ASyncQueue from '../../async-queue'; import ASyncQueue from '../../async-queue';
import { createNodeFromReactComponent } from '../../react-component-node'; import { createNodeFromReactComponent } from '../../react-component-node';
@@ -75,10 +77,13 @@ const PageStack = {
const info = [{ type: 'text', value: 'Active Components:' }]; const info = [{ type: 'text', value: 'Active Components:' }];
return info.concat( return info.concat(
this._internal.stack.map((p, i) => ({ this._internal.stack.map((p) => {
type: 'text', const pageInfo = this._findPage(p.pageId);
value: '- ' + this._internal.pages.find((pi) => pi.id === p.pageId).label return {
})) type: 'text',
value: '- ' + pageInfo.label
};
})
); );
}, },
defaultCss: { defaultCss: {
@@ -170,6 +175,7 @@ const PageStack = {
topPageName: { topPageName: {
type: 'string', type: 'string',
displayName: 'Top Component Name', displayName: 'Top Component Name',
group: 'General',
get() { get() {
return this._internal.topPageName; return this._internal.topPageName;
} }
@@ -177,6 +183,7 @@ const PageStack = {
stackDepth: { stackDepth: {
type: 'number', type: 'number',
displayName: 'Stack Depth', displayName: 'Stack Depth',
group: 'General',
get() { get() {
return this._internal.stackDepth; return this._internal.stackDepth;
} }
@@ -189,12 +196,31 @@ const PageStack = {
_deregisterPageStack() { _deregisterPageStack() {
NavigationHandler.instance.deregisterPageStack(this._internal.name, this); NavigationHandler.instance.deregisterPageStack(this._internal.name, this);
}, },
_pageNameForId(id) { /**
if (this._internal.pages === undefined) return; * @param {String} pageIdOrLabel
const page = this._internal.pages.find((p) => p.id === id); */
if (page === undefined) return; _findPage(pageIdOrLabel) {
if (this._internal.pageInfo[pageIdOrLabel]) {
const pageInfo = this._internal.pageInfo[pageIdOrLabel];
const pageRef = this._internal.pages.find((x) => x.id === pageIdOrLabel);
return {
component: String(pageInfo.component),
label: String(pageRef.label),
id: String(pageIdOrLabel)
};
}
return page.label; const pageRef = this._internal.pages.find((x) => x.label === pageIdOrLabel);
if (pageRef) {
const pageInfo = this._internal.pageInfo[pageRef.id];
return {
component: String(pageInfo.component),
label: String(pageRef.label),
id: String(pageRef.id)
};
}
return undefined;
}, },
setPageOutputs(outputs) { setPageOutputs(outputs) {
for (const prop in outputs) { for (const prop in outputs) {
@@ -230,8 +256,9 @@ const PageStack = {
if (this._internal.pages === undefined || this._internal.pages.length === 0) return; if (this._internal.pages === undefined || this._internal.pages.length === 0) return;
var startPageId, let startPageId;
params = {}; let params = {};
var pageFromUrl = this.matchPageFromUrl(); var pageFromUrl = this.matchPageFromUrl();
if (pageFromUrl !== undefined) { if (pageFromUrl !== undefined) {
// We have an url matching a page, use that page as start page // We have an url matching a page, use that page as start page
@@ -239,13 +266,16 @@ const PageStack = {
params = Object.assign({}, pageFromUrl.query, pageFromUrl.params); params = Object.assign({}, pageFromUrl.query, pageFromUrl.params);
} else { } else {
var startPageId = this._internal.startPageId; startPageId = this._internal.startPageId;
if (startPageId === undefined) startPageId = this._internal.pages[0].id; if (startPageId === undefined) startPageId = this._internal.pages[0].id;
} }
var pageInfo = this._internal.pageInfo[startPageId]; // Find the page by either ID or by Label
const pageInfo = this._findPage(startPageId);
if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page if (pageInfo === undefined || pageInfo.component === undefined) {
// No page was found
return;
}
var content = await this.nodeScope.createNode(pageInfo.component, guid()); var content = await this.nodeScope.createNode(pageInfo.component, guid());
@@ -269,7 +299,7 @@ const PageStack = {
]; ];
this.setPageOutputs({ this.setPageOutputs({
topPageName: this._pageNameForId(startPageId), topPageName: pageInfo.label,
stackDepth: this._internal.stack.length stackDepth: this._internal.stack.length
}); });
}, },
@@ -458,13 +488,22 @@ const PageStack = {
this._internal.asyncQueue.enqueue(this.replaceAsync.bind(this, args)); this._internal.asyncQueue.enqueue(this.replaceAsync.bind(this, args));
}, },
async replaceAsync(args) { async replaceAsync(args) {
if (this._internal.pages === undefined || this._internal.pages.length === 0) return; if (this._internal.pages === undefined || this._internal.pages.length === 0) {
return;
}
if (this._internal.isTransitioning) return; if (this._internal.isTransitioning) {
return;
}
var pageId = args.target || this._internal.pages[0].id; const pageId = args.target || this._internal.pages[0].id;
var pageInfo = this._internal.pageInfo[pageId];
if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page // Find the page by either ID or by Label
const pageInfo = this._findPage(pageId);
if (pageInfo === undefined || pageInfo.component === undefined) {
// No page was found
return;
}
// Remove all current pages in the stack // Remove all current pages in the stack
var children = this.getChildren(); var children = this.getChildren();
@@ -498,7 +537,7 @@ const PageStack = {
]; ];
this.setPageOutputs({ this.setPageOutputs({
topPageName: this._pageNameForId(pageId), topPageName: pageInfo.label,
stackDepth: this._internal.stack.length stackDepth: this._internal.stack.length
}); });
@@ -510,13 +549,22 @@ const PageStack = {
this._internal.asyncQueue.enqueue(this.navigateAsync.bind(this, args)); this._internal.asyncQueue.enqueue(this.navigateAsync.bind(this, args));
}, },
async navigateAsync(args) { async navigateAsync(args) {
if (this._internal.pages === undefined || this._internal.pages.length === 0) return; if (this._internal.pages === undefined || this._internal.pages.length === 0) {
return;
}
if (this._internal.isTransitioning) return; if (this._internal.isTransitioning) {
return;
}
var pageId = args.target || this._internal.pages[0].id; const pageId = args.target || this._internal.pages[0].id;
var pageInfo = this._internal.pageInfo[pageId];
if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page // Find the page by either ID or by Label
const pageInfo = this._findPage(pageId);
if (pageInfo === undefined || pageInfo.component === undefined) {
// No page was found
return;
}
// Create the container group // Create the container group
const group = this.createPageContainer(); const group = this.createPageContainer();
@@ -530,7 +578,7 @@ const PageStack = {
group.addChild(content); group.addChild(content);
// Connect navigate back nodes // Connect navigate back nodes
var navigateBackNodes = content.nodeScope.getNodesWithType('PageStackNavigateBack'); const navigateBackNodes = content.nodeScope.getNodesWithType('PageStackNavigateBack');
if (navigateBackNodes && navigateBackNodes.length > 0) { if (navigateBackNodes && navigateBackNodes.length > 0) {
for (var j = 0; j < navigateBackNodes.length; j++) { for (var j = 0; j < navigateBackNodes.length; j++) {
navigateBackNodes[j]._setBackCallback(this.back.bind(this)); navigateBackNodes[j]._setBackCallback(this.back.bind(this));
@@ -538,8 +586,8 @@ const PageStack = {
} }
// Push the new top // Push the new top
var top = this._internal.stack[this._internal.stack.length - 1]; const top = this._internal.stack[this._internal.stack.length - 1];
var newTop = { const newTop = {
from: top.page, from: top.page,
page: group, page: group,
pageInfo: pageInfo, pageInfo: pageInfo,
@@ -551,7 +599,7 @@ const PageStack = {
}; };
this._internal.stack.push(newTop); this._internal.stack.push(newTop);
this.setPageOutputs({ this.setPageOutputs({
topPageName: this._pageNameForId(args.target), topPageName: pageInfo.label,
stackDepth: this._internal.stack.length stackDepth: this._internal.stack.length
}); });
this._updateUrlWithTopPage(); this._updateUrlWithTopPage();
@@ -584,8 +632,11 @@ const PageStack = {
this.addChild(top.from, 0); this.addChild(top.from, 0);
top.backCallback && top.backCallback(args.backAction, args.results); top.backCallback && top.backCallback(args.backAction, args.results);
// Find the page by either ID or by Label
const pageInfo = this._findPage(this._internal.stack[this._internal.stack.length - 2].pageId);
this.setPageOutputs({ this.setPageOutputs({
topPageName: this._pageNameForId(this._internal.stack[this._internal.stack.length - 2].pageId), topPageName: pageInfo.label,
stackDepth: this._internal.stack.length - 1 stackDepth: this._internal.stack.length - 1
}); });

View File

@@ -53,15 +53,24 @@ const ShowPopupNode = {
this.context.showPopup(this._internal.target, this._internal.popupParams, { this.context.showPopup(this._internal.target, this._internal.popupParams, {
senderNode: this.nodeScope.componentOwner, senderNode: this.nodeScope.componentOwner,
/**
* @param {string | undefined} action
* @param {*} results
*/
onClosePopup: (action, results) => { onClosePopup: (action, results) => {
this._internal.closeResults = results; this._internal.closeResults = results;
for (var key in results) { for (const key in results) {
if (this.hasOutput('closeResult-' + key)) this.flagOutputDirty('closeResult-' + key); if (this.hasOutput('closeResult-' + key)) {
this.flagOutputDirty('closeResult-' + key);
}
} }
if (!action) this.sendSignalOnOutput('Closed'); if (!action) {
else this.sendSignalOnOutput(action); this.sendSignalOnOutput('Closed');
} else {
this.sendSignalOnOutput(action);
}
} }
}); });
}, },

View File

@@ -3,6 +3,17 @@ const CloudStore = require('@noodl/runtime/src/api/cloudstore');
('use strict'); ('use strict');
/**
* @param {string} path
* @param {{
* endpoint: string;
* method: string;
* appId: string;
* content: any;
* success: (json: object) => void;
* error: (json: object | undefined) => void;
* }} options
*/
function _makeRequest(path, options) { function _makeRequest(path, options) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@@ -15,7 +26,9 @@ function _makeRequest(path, options) {
if (xhr.status === 200 || xhr.status === 201) { if (xhr.status === 200 || xhr.status === 201) {
options.success(json); options.success(json);
} else options.error(json); } else {
options.error(json);
}
} }
}; };
@@ -191,7 +204,7 @@ var CloudFunctionNode = {
this.sendSignalOnOutput('success'); this.sendSignalOnOutput('success');
}, },
error: (e) => { error: (e) => {
const error = typeof e === 'string' ? e : e.error || 'Failed running cloud function.'; const error = typeof e === 'string' ? e : e?.error || 'Failed running cloud function.';
this._internal.lastCallResult = { this._internal.lastCallResult = {
status: 'failure', status: 'failure',
error error

View File

@@ -2,8 +2,7 @@
const { Node } = require('@noodl/runtime'); const { Node } = require('@noodl/runtime');
var Model = require('@noodl/runtime/src/model'), const Collection = require('@noodl/runtime/src/collection');
Collection = require('@noodl/runtime/src/collection');
var CollectionNode = { var CollectionNode = {
name: 'Collection2', name: 'Collection2',
@@ -27,6 +26,7 @@ var CollectionNode = {
_this.scheduleAfterInputsHaveUpdated(function () { _this.scheduleAfterInputsHaveUpdated(function () {
_this.sendSignalOnOutput('changed'); _this.sendSignalOnOutput('changed');
_this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('count'); _this.flagOutputDirty('count');
collectionChangedScheduled = false; collectionChangedScheduled = false;
}); });
@@ -117,6 +117,17 @@ var CollectionNode = {
return this._internal.collection; return this._internal.collection;
} }
}, },
firstItemId: {
type: 'string',
displayName: 'First Item Id',
group: 'General',
getter: function () {
if (this._internal.collection) {
var firstItem = this._internal.collection.get(0);
if (firstItem !== undefined) return firstItem.getId();
}
}
},
count: { count: {
type: 'number', type: 'number',
displayName: 'Count', displayName: 'Count',
@@ -150,6 +161,7 @@ var CollectionNode = {
collection.on('change', this._internal.collectionChangedCallback); collection.on('change', this._internal.collectionChangedCallback);
this.flagOutputDirty('items'); this.flagOutputDirty('items');
this.flagOutputDirty('firstItemId');
this.flagOutputDirty('count'); this.flagOutputDirty('count');
}, },
setSourceCollection: function (collection) { setSourceCollection: function (collection) {

View File

@@ -1,21 +1,25 @@
'use strict'; 'use strict';
const { Node } = require('@noodl/runtime');
const Model = require('@noodl/runtime/src/model'); const Model = require('@noodl/runtime/src/model');
const Collection = require('@noodl/runtime/src/collection'); const Collection = require('@noodl/runtime/src/collection');
var SetVariableNodeDefinition = { const SetVariableNodeDefinition = {
name: 'Set Variable', name: 'Set Variable',
docs: 'https://docs.noodl.net/nodes/data/variable/set-variable', docs: 'https://docs.noodl.net/nodes/data/variable/set-variable',
category: 'Data', category: 'Data',
usePortAsLabel: 'name', usePortAsLabel: 'name',
color: 'data', color: 'data',
initialize: function () { initialize: function () {
var internal = this._internal; const internal = this._internal;
internal.variablesModel = Model.get('--ndl--global-variables'); internal.variablesModel = Model.get('--ndl--global-variables');
}, },
getInspectInfo() {
if (this._internal.name) {
return this._internal.variablesModel.get(this._internal.name);
}
return '[No value set]';
},
outputs: { outputs: {
done: { done: {
type: 'signal', type: 'signal',
@@ -74,17 +78,22 @@ var SetVariableNodeDefinition = {
if (this.hasScheduledStore) return; if (this.hasScheduledStore) return;
this.hasScheduledStore = true; this.hasScheduledStore = true;
var internal = this._internal; const internal = this._internal;
this.scheduleAfterInputsHaveUpdated(function () { this.scheduleAfterInputsHaveUpdated(function () {
this.hasScheduledStore = false; this.hasScheduledStore = false;
var value = internal.setWith === 'emptyString' ? '' : internal.value; let value = internal.setWith === 'emptyString' ? '' : internal.value;
// Can set arrays with "id" or array
if (internal.setWith === 'object' && typeof value === 'string') value = Model.get(value);
// Can set arrays with "id" or array
if (internal.setWith === 'array' && typeof value === 'string') value = Collection.get(value);
if (internal.setWith === 'object' && typeof value === 'string') value = Model.get(value); // Can set arrays with "id" or array
if (internal.setWith === 'array' && typeof value === 'string') value = Collection.get(value); // Can set arrays with "id" or array
if (internal.setWith === 'boolean') value = !!value; if (internal.setWith === 'boolean') value = !!value;
//use forceChange to always trigger Variable nodes to send the value on their output, even if it's the same value twice // use forceChange to always trigger Variable nodes to send the value on
// their output, even if it's the same value twice
internal.variablesModel.set(internal.name, value, { internal.variablesModel.set(internal.name, value, {
forceChange: true forceChange: true
}); });
@@ -96,10 +105,11 @@ var SetVariableNodeDefinition = {
return; return;
} }
if (name === 'value') if (name === 'value') {
this.registerInput(name, { this.registerInput(name, {
set: this.setValue.bind(this) set: this.setValue.bind(this)
}); });
}
} }
} }
}; };

View File

@@ -43,6 +43,10 @@ const OpenFilePicker = {
input.accept = this._internal.acceptedFileTypes; input.accept = this._internal.acceptedFileTypes;
if (this._internal.capture) {
input.capture = this._internal.capture;
}
input.onchange = onChange; input.onchange = onChange;
input.click(); input.click();
} }
@@ -54,6 +58,14 @@ const OpenFilePicker = {
set(value) { set(value) {
this._internal.acceptedFileTypes = value; this._internal.acceptedFileTypes = value;
} }
},
capture: {
group: 'General',
type: 'string',
displayName: 'Capture',
set(value) {
this._internal.capture = value;
}
} }
}, },
outputs: { outputs: {

View File

@@ -4,7 +4,7 @@ const Switch = {
name: 'Switch', name: 'Switch',
docs: 'https://docs.noodl.net/nodes/logic/switch', docs: 'https://docs.noodl.net/nodes/logic/switch',
category: 'Logic', category: 'Logic',
initialize: function () { initialize() {
this._internal.state = false; this._internal.state = false;
this._internal.initialized = false; this._internal.initialized = false;
}, },
@@ -15,7 +15,7 @@ const Switch = {
on: { on: {
displayName: 'On', displayName: 'On',
group: 'Change State', group: 'Change State',
valueChangedToTrue: function () { valueChangedToTrue() {
if (this._internal.state === true) { if (this._internal.state === true) {
return; return;
} }
@@ -27,7 +27,7 @@ const Switch = {
off: { off: {
displayName: 'Off', displayName: 'Off',
group: 'Change State', group: 'Change State',
valueChangedToTrue: function () { valueChangedToTrue() {
if (this._internal.state === false) { if (this._internal.state === false) {
return; return;
} }
@@ -39,7 +39,7 @@ const Switch = {
flip: { flip: {
displayName: 'Flip', displayName: 'Flip',
group: 'Change State', group: 'Change State',
valueChangedToTrue: function () { valueChangedToTrue() {
this._internal.state = !this._internal.state; this._internal.state = !this._internal.state;
this.flagOutputDirty('state'); this.flagOutputDirty('state');
this.emitSignals(); this.emitSignals();
@@ -50,7 +50,7 @@ const Switch = {
displayName: 'State', displayName: 'State',
group: 'General', group: 'General',
default: false, default: false,
set: function (value) { set(value) {
this._internal.state = !!value; this._internal.state = !!value;
this.flagOutputDirty('state'); this.flagOutputDirty('state');
this.emitSignals(); this.emitSignals();
@@ -61,10 +61,15 @@ const Switch = {
state: { state: {
type: 'boolean', type: 'boolean',
displayName: 'Current State', displayName: 'Current State',
getter: function () { getter() {
return this._internal.state; return this._internal.state;
} }
}, },
switched: {
displayName: 'Switched',
type: 'signal',
group: 'Signals'
},
switchedToOn: { switchedToOn: {
displayName: 'Switched To On', displayName: 'Switched To On',
type: 'signal', type: 'signal',
@@ -77,12 +82,13 @@ const Switch = {
} }
}, },
prototypeExtensions: { prototypeExtensions: {
emitSignals: function () { emitSignals() {
if (this._internal.state === true) { if (this._internal.state === true) {
this.sendSignalOnOutput('switchedToOn'); this.sendSignalOnOutput('switchedToOn');
} else { } else {
this.sendSignalOnOutput('switchedToOff'); this.sendSignalOnOutput('switchedToOff');
} }
this.sendSignalOnOutput('switched');
} }
} }
}; };

View File

@@ -1,18 +1,13 @@
'use strict'; 'use strict';
const { Node, EdgeTriggeredInput } = require('@noodl/runtime');
const UserService = require('./userservice'); const UserService = require('./userservice');
var LoginNodeDefinition = { const LoginNodeDefinition = {
name: 'net.noodl.user.LogIn', name: 'net.noodl.user.LogIn',
docs: 'https://docs.noodl.net/nodes/data/user/log-in', docs: 'https://docs.noodl.net/nodes/data/user/log-in',
displayNodeName: 'Log In', displayNodeName: 'Log In',
category: 'Cloud Services', category: 'Cloud Services',
color: 'data', color: 'data',
initialize: function () {
var internal = this._internal;
},
getInspectInfo() {},
outputs: { outputs: {
success: { success: {
type: 'signal', type: 'signal',
@@ -28,7 +23,7 @@ var LoginNodeDefinition = {
type: 'string', type: 'string',
displayName: 'Error', displayName: 'Error',
group: 'Error', group: 'Error',
getter: function () { getter() {
return this._internal.error; return this._internal.error;
} }
} }
@@ -37,7 +32,7 @@ var LoginNodeDefinition = {
login: { login: {
displayName: 'Do', displayName: 'Do',
group: 'Actions', group: 'Actions',
valueChangedToTrue: function () { valueChangedToTrue() {
this.scheduleLogIn(); this.scheduleLogIn();
} }
}, },
@@ -45,7 +40,7 @@ var LoginNodeDefinition = {
displayName: 'Username', displayName: 'Username',
type: 'string', type: 'string',
group: 'General', group: 'General',
set: function (value) { set(value) {
this._internal.username = value; this._internal.username = value;
} }
}, },
@@ -53,13 +48,13 @@ var LoginNodeDefinition = {
displayName: 'Password', displayName: 'Password',
type: 'string', type: 'string',
group: 'General', group: 'General',
set: function (value) { set(value) {
this._internal.password = value; this._internal.password = value;
} }
} }
}, },
methods: { methods: {
setError: function (err) { setError(err) {
this._internal.error = err; this._internal.error = err;
this.flagOutputDirty('error'); this.flagOutputDirty('error');
this.sendSignalOnOutput('failure'); this.sendSignalOnOutput('failure');
@@ -76,9 +71,7 @@ var LoginNodeDefinition = {
this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning'); this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning');
} }
}, },
scheduleLogIn: function () { scheduleLogIn() {
const internal = this._internal;
if (this.logInScheduled === true) return; if (this.logInScheduled === true) return;
this.logInScheduled = true; this.logInScheduled = true;
@@ -100,89 +93,7 @@ var LoginNodeDefinition = {
} }
}; };
/*function updatePorts(nodeId, parameters, editorConnection, dbCollections) {
var ports = [];
ports.push({
name: 'collectionName',
displayName: "Class",
group: "General",
type: { name: 'enum', enums: (dbCollections !== undefined) ? dbCollections.map((c) => { return { value: c.name, label: c.name } }) : [], allowEditOnly: true },
plug: 'input'
})
if (parameters.collectionName && dbCollections) {
// Fetch ports from collection keys
var c = dbCollections.find((c) => c.name === parameters.collectionName);
if (c && c.schema && c.schema.properties) {
var props = c.schema.properties;
for (var key in props) {
var p = props[key];
if (ports.find((_p) => _p.name === key)) continue;
if(p.type === 'Relation') {
}
else { // Other schema type ports
const _typeMap = {
"String":"string",
"Boolean":"boolean",
"Number":"number",
"Date":"date"
}
ports.push({
type: {
name: _typeMap[p.type]?_typeMap[p.type]:'*',
},
plug: 'output',
group: 'Properties',
name: 'prop-' + key,
displayName: key,
})
ports.push({
type: 'signal',
plug: 'output',
group: 'Changed Events',
displayName: key+ ' Changed',
name: 'changed-' + key,
})
}
}
}
}
editorConnection.sendDynamicPorts(nodeId, ports);
}*/
module.exports = { module.exports = {
node: LoginNodeDefinition, node: LoginNodeDefinition,
setup: function (context, graphModel) { setup(_context, _graphModel) {}
/* if (!context.editorConnection || !context.editorConnection.isRunningLocally()) {
return;
}
function _managePortsForNode(node) {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('dbCollections'));
node.on("parameterUpdated", function (event) {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('dbCollections'));
});
graphModel.on('metadataChanged.dbCollections', function (data) {
updatePorts(node.id, node.parameters, context.editorConnection, data);
})
}
graphModel.on("editorImportComplete", ()=> {
graphModel.on("nodeAdded.DbModel2", function (node) {
_managePortsForNode(node)
})
for(const node of graphModel.getNodesWithType('DbModel2')) {
_managePortsForNode(node)
}
})*/
}
}; };

View File

@@ -1,18 +1,13 @@
'use strict'; 'use strict';
const { Node, EdgeTriggeredInput } = require('@noodl/runtime');
const UserService = require('./userservice'); const UserService = require('./userservice');
var LogOutNodeDefinition = { const LogOutNodeDefinition = {
name: 'net.noodl.user.LogOut', name: 'net.noodl.user.LogOut',
docs: 'https://docs.noodl.net/nodes/data/user/log-out', docs: 'https://docs.noodl.net/nodes/data/user/log-out',
displayNodeName: 'Log Out', displayNodeName: 'Log Out',
category: 'Cloud Services', category: 'Cloud Services',
color: 'data', color: 'data',
initialize: function () {
var internal = this._internal;
},
getInspectInfo() {},
outputs: { outputs: {
success: { success: {
type: 'signal', type: 'signal',
@@ -28,7 +23,7 @@ var LogOutNodeDefinition = {
type: 'string', type: 'string',
displayName: 'Error', displayName: 'Error',
group: 'Error', group: 'Error',
getter: function () { getter() {
return this._internal.error; return this._internal.error;
} }
} }
@@ -37,13 +32,13 @@ var LogOutNodeDefinition = {
login: { login: {
displayName: 'Do', displayName: 'Do',
group: 'Actions', group: 'Actions',
valueChangedToTrue: function () { valueChangedToTrue() {
this.scheduleLogOut(); this.scheduleLogOut();
} }
} }
}, },
methods: { methods: {
setError: function (err) { setError(err) {
this._internal.error = err; this._internal.error = err;
this.flagOutputDirty('error'); this.flagOutputDirty('error');
this.sendSignalOnOutput('failure'); this.sendSignalOnOutput('failure');
@@ -60,9 +55,7 @@ var LogOutNodeDefinition = {
this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning'); this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning');
} }
}, },
scheduleLogOut: function () { scheduleLogOut() {
const internal = this._internal;
if (this.logOutScheduled === true) return; if (this.logOutScheduled === true) return;
this.logOutScheduled = true; this.logOutScheduled = true;
@@ -84,5 +77,5 @@ var LogOutNodeDefinition = {
module.exports = { module.exports = {
node: LogOutNodeDefinition, node: LogOutNodeDefinition,
setup: function (context, graphModel) {} setup(_context, _graphModel) {}
}; };

View File

@@ -1,20 +1,17 @@
'use strict'; 'use strict';
const { Node, EdgeTriggeredInput } = require('@noodl/runtime');
const UserService = require('./userservice'); const UserService = require('./userservice');
var SignUpNodeDefinition = { const SignUpNodeDefinition = {
name: 'net.noodl.user.SignUp', name: 'net.noodl.user.SignUp',
docs: 'https://docs.noodl.net/nodes/data/user/sign-up', docs: 'https://docs.noodl.net/nodes/data/user/sign-up',
displayNodeName: 'Sign Up', displayNodeName: 'Sign Up',
category: 'Cloud Services', category: 'Cloud Services',
color: 'data', color: 'data',
initialize: function () { initialize() {
var internal = this._internal; const internal = this._internal;
internal.userProperties = {}; internal.userProperties = {};
}, },
getInspectInfo() {},
outputs: { outputs: {
success: { success: {
type: 'signal', type: 'signal',
@@ -30,7 +27,7 @@ var SignUpNodeDefinition = {
type: 'string', type: 'string',
displayName: 'Error', displayName: 'Error',
group: 'Error', group: 'Error',
getter: function () { getter() {
return this._internal.error; return this._internal.error;
} }
} }
@@ -39,7 +36,7 @@ var SignUpNodeDefinition = {
signup: { signup: {
displayName: 'Do', displayName: 'Do',
group: 'Actions', group: 'Actions',
valueChangedToTrue: function () { valueChangedToTrue() {
this.scheduleSignUp(); this.scheduleSignUp();
} }
}, },
@@ -47,7 +44,7 @@ var SignUpNodeDefinition = {
displayName: 'Username', displayName: 'Username',
type: 'string', type: 'string',
group: 'General', group: 'General',
set: function (value) { set(value) {
this._internal.username = value; this._internal.username = value;
} }
}, },
@@ -55,7 +52,7 @@ var SignUpNodeDefinition = {
displayName: 'Password', displayName: 'Password',
type: 'string', type: 'string',
group: 'General', group: 'General',
set: function (value) { set(value) {
this._internal.password = value; this._internal.password = value;
} }
}, },
@@ -63,13 +60,13 @@ var SignUpNodeDefinition = {
displayName: 'Email', displayName: 'Email',
type: 'string', type: 'string',
group: 'General', group: 'General',
set: function (value) { set(value) {
this._internal.email = value; this._internal.email = value;
} }
} }
}, },
methods: { methods: {
setError: function (err) { setError(err) {
this._internal.error = err; this._internal.error = err;
this.flagOutputDirty('error'); this.flagOutputDirty('error');
this.sendSignalOnOutput('failure'); this.sendSignalOnOutput('failure');
@@ -86,7 +83,7 @@ var SignUpNodeDefinition = {
this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning'); this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'user-login-warning');
} }
}, },
scheduleSignUp: function () { scheduleSignUp() {
const internal = this._internal; const internal = this._internal;
if (this.signUpScheduled === true) return; if (this.signUpScheduled === true) return;
@@ -109,23 +106,24 @@ var SignUpNodeDefinition = {
}); });
}); });
}, },
setUserProperty: function (name, value) { setUserProperty(name, value) {
this._internal.userProperties[name] = value; this._internal.userProperties[name] = value;
}, },
registerInputIfNeeded: function (name) { registerInputIfNeeded(name) {
if (this.hasInput(name)) { if (this.hasInput(name)) {
return; return;
} }
if (name.startsWith('prop-')) if (name.startsWith('prop-')) {
return this.registerInput(name, { return this.registerInput(name, {
set: this.setUserProperty.bind(this, name.substring('prop-'.length)) set: this.setUserProperty.bind(this, name.substring('prop-'.length))
}); });
}
} }
} }
}; };
function updatePorts(nodeId, parameters, editorConnection, systemCollections) { function updatePorts(nodeId, _parameters, editorConnection, systemCollections) {
var ports = []; var ports = [];
if (systemCollections) { if (systemCollections) {
@@ -169,7 +167,7 @@ function updatePorts(nodeId, parameters, editorConnection, systemCollections) {
module.exports = { module.exports = {
node: SignUpNodeDefinition, node: SignUpNodeDefinition,
setup: function (context, graphModel) { setup(context, graphModel) {
if (!context.editorConnection || !context.editorConnection.isRunningLocally()) { if (!context.editorConnection || !context.editorConnection.isRunningLocally()) {
return; return;
} }
@@ -177,7 +175,7 @@ module.exports = {
function _managePortsForNode(node) { function _managePortsForNode(node) {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('systemCollections')); updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('systemCollections'));
node.on('parameterUpdated', function (event) { node.on('parameterUpdated', function (_event) {
updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('systemCollections')); updatePorts(node.id, node.parameters, context.editorConnection, graphModel.getMetaData('systemCollections'));
}); });

View File

@@ -4,6 +4,7 @@
{"url":"noodl-app.png"}, {"url":"noodl-app.png"},
{"url":"load_terminator.js"}, {"url":"load_terminator.js"},
{"url":"noodl.deploy.js"}, {"url":"noodl.deploy.js"},
{"url":"noodl.deploy.js.map"},
{"url":"react.production.min.js"}, {"url":"react.production.min.js"},
{"url":"react-dom.production.min.js"} {"url":"react-dom.production.min.js"}
] ]

View File

@@ -274,6 +274,9 @@ declare namespace Noodl {
objectOrId: string | { getId(): string; }, objectOrId: string | { getId(): string; },
options?: { options?: {
className?: RecordClassName; className?: RecordClassName;
keys?: string[] | string;
include?: string[] | string;
excludeKeys?: string[] | string;
} }
): Promise<any>; ): Promise<any>;
@@ -482,7 +485,12 @@ declare namespace Noodl {
const Records: RecordsApi; const Records: RecordsApi;
interface CurrentUserObject { interface CurrentUserObject {
UserId: string; id: string;
email: string;
emailVerified: boolean;
username: string;
Properties: unknown;
/** /**
* Log out the current user and terminate the session. * Log out the current user and terminate the session.

View File

@@ -1,10 +1,8 @@
const path = require("path"); const path = require('path');
module.exports = { module.exports = {
// Allows to define the output path of the files built by the viewer. // Allows to define the output path of the files built by the viewer.
// //
// For example in the CLI, we will also build this, just with a different output path. // For example in the CLI, we will also build this, just with a different output path.
outPath: outPath: process.env.OUT_PATH || path.resolve(__dirname, '../../noodl-editor/src/external')
process.env.OUT_PATH ||
path.resolve(__dirname, "../../noodl-editor/src/external"),
}; };

View File

@@ -8,7 +8,7 @@ module.exports = {
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'], extensions: ['.tsx', '.ts', '.jsx', '.js'],
fallback: { fallback: {
events: require.resolve('events/'), events: require.resolve('events/')
} }
}, },
module: { module: {

View File

@@ -1,8 +1,8 @@
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.deploy.common.js"); const common = require('./webpack.deploy.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: "development", mode: 'development',
devtool: "inline-source-map", devtool: 'inline-source-map',
watch: true, watch: true
}); });

View File

@@ -1,6 +1,7 @@
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.deploy.common.js"); const common = require('./webpack.deploy.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: "production", mode: 'production',
devtool: 'source-map'
}); });

View File

@@ -42,7 +42,7 @@ module.exports = {
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'], extensions: ['.tsx', '.ts', '.jsx', '.js'],
fallback: { fallback: {
events: require.resolve('events/'), events: require.resolve('events/')
} }
}, },
module: { module: {

View File

@@ -2,5 +2,6 @@ const { merge } = require('webpack-merge');
const common = require('./webpack.ssr.common.js'); const common = require('./webpack.ssr.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'production' mode: 'production',
devtool: 'source-map'
}); });

View File

@@ -1,8 +1,8 @@
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.viewer.common.js"); const common = require('./webpack.viewer.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: "development", mode: 'development',
devtool: "inline-source-map", devtool: 'inline-source-map',
watch: true, watch: true
}); });

View File

@@ -1,6 +1,7 @@
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.viewer.common.js"); const common = require('./webpack.viewer.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: "production", mode: 'production',
devtool: 'source-map'
}); });

View File

@@ -73,17 +73,17 @@ import { getCurrentPlatform } from '../helper';
// It basically means that some package is not relative to this path. // It basically means that some package is not relative to this path.
console.log("--- Run 'npm install' ..."); console.log("--- Run 'npm install' ...");
if (platform === "darwin") { if (platform === 'darwin') {
execSync(`npm install electron-notarize`, { execSync(`npm install electron-notarize`, {
stdio: 'inherit', stdio: 'inherit',
env: process.env env: process.env
}) });
} }
execSync(`npm install --arch=${arch} --scope noodl-editor`, { execSync(`npm install --arch=${arch} --scope fluxscape-editor`, {
stdio: 'inherit', stdio: 'inherit',
env: process.env env: process.env
}) });
console.log("--- 'npm install' done!"); console.log("--- 'npm install' done!");
// NOTE: npm install --arch= does this too // NOTE: npm install --arch= does this too
@@ -99,13 +99,13 @@ import { getCurrentPlatform } from '../helper';
// Build: Replace "dugite" // Build: Replace "dugite"
// Build: Replace "desktop-trampoline" // Build: Replace "desktop-trampoline"
console.log("--- Run 'npm run build' ..."); console.log("--- Run 'npm run build' ...");
execSync('npx lerna exec --scope noodl-editor -- npm run build', { execSync('npx lerna exec --scope fluxscape-editor -- npm run build', {
stdio: 'inherit', stdio: 'inherit',
env: { env: {
...process.env, ...process.env,
TARGET_PLATFORM, TARGET_PLATFORM,
DISABLE_SIGNING, DISABLE_SIGNING,
CSC_NAME, CSC_NAME
} }
}); });
console.log("--- 'npm run build' done!"); console.log("--- 'npm run build' done!");

View File

@@ -74,7 +74,7 @@ const cloudRuntimeProcess = attachStdio(
} }
); );
const editorProcess = attachStdio(exec('npx lerna exec --scope noodl-editor -- npm run start', processOptions), { const editorProcess = attachStdio(exec('npx lerna exec --scope fluxscape-editor -- npm run start', processOptions), {
prefix: 'Editor', prefix: 'Editor',
color: ConsoleColor.FgCyan color: ConsoleColor.FgCyan
}); });

View File

@@ -1,36 +1,27 @@
import path from "path"; import { execSync } from 'child_process';
import { execSync } from "child_process"; import path from 'path';
const CWD = path.join(__dirname, ".."); const CWD = path.join(__dirname, '..');
const LOCAL_GIT_DIRECTORY = path.join( const LOCAL_GIT_DIRECTORY = path.join(__dirname, '..', 'node_modules', 'dugite', 'git');
__dirname,
"..",
"node_modules",
"dugite",
"git"
);
const LOCAL_GIT_TRAMPOLINE_DIRECTORY = path.join( const LOCAL_GIT_TRAMPOLINE_DIRECTORY = path.join(
__dirname, __dirname,
"..", '..',
"node_modules", 'node_modules',
"desktop-trampoline/build/Release/desktop-trampoline" 'desktop-trampoline/build/Release/desktop-trampoline'
); );
console.log("---"); console.log('---');
console.log(`> CWD: `, CWD); console.log(`> CWD: `, CWD);
console.log(`> LOCAL_GIT_DIRECTORY: `, LOCAL_GIT_DIRECTORY); console.log(`> LOCAL_GIT_DIRECTORY: `, LOCAL_GIT_DIRECTORY);
console.log( console.log(`> LOCAL_GIT_TRAMPOLINE_DIRECTORY: `, LOCAL_GIT_TRAMPOLINE_DIRECTORY);
`> LOCAL_GIT_TRAMPOLINE_DIRECTORY: `, console.log('---');
LOCAL_GIT_TRAMPOLINE_DIRECTORY
);
console.log("---");
execSync("npx lerna exec --scope noodl-editor -- npm run test", { execSync('npx lerna exec --scope fluxscape-editor -- npm run test', {
cwd: CWD, cwd: CWD,
stdio: "inherit", stdio: 'inherit',
env: { env: {
...process.env, ...process.env,
LOCAL_GIT_DIRECTORY, LOCAL_GIT_DIRECTORY,
LOCAL_GIT_TRAMPOLINE_DIRECTORY, LOCAL_GIT_TRAMPOLINE_DIRECTORY
}, }
}); });