59 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
Eric Tuvesson
d61effc615 feat(editor): Deploy popup always mounted (#51)
This removes the delayed loading when opening the deploy popup
2024-07-08 12:47:28 +02:00
Eric Tuvesson
12be6dc69f feat(editor): Upgrade electron (#50) 2024-07-08 09:35:18 +02:00
Eric Tuvesson
17e3d16436 chore: clean up editor assets (#12) 2024-07-08 08:09:39 +02:00
Eric Tuvesson
233479a1bc feat: Add Support for Parse Server v7 (#20)
* feat: Upgrade Aggregate queries to latest Parse API

* feat: Save parse server major version in metadata

This can be used to determine which version of the Parse API that will be used.

* fix: Add support for both versions of aggregate queries
2024-07-08 08:06:47 +02:00
Eric Tuvesson
57e5246022 feat(runtime): Add default label to String Format node (#49)
Update the String Format node default label to show the format, like how the other nodes work.
2024-07-04 15:54:20 +02:00
Eric Tuvesson
dda22e0de6 feat(runtime): Add Image node output "On Error" signal (#48)
"On Error" is triggered when the image is not loading correctly, making it possible to hide the Image node or show a placeholder instead.
2024-07-04 14:02:34 +02:00
Eric Tuvesson
618955e1ee feat: Portal include Project ID (#47) 2024-06-28 00:43:34 +02:00
Eric Tuvesson
8c7d4faeca fix(runtime): Column node added a div with empty Repeater (#45)
When the Column node only had an empty Repeater child, there was an empty HTML element.
2024-06-27 21:16:50 +02:00
Eric Tuvesson
c5754c9160 feat(runtime): Add "data-testId" attribute to Columns node (#44) 2024-06-27 15:42:25 +02:00
Eric Tuvesson
3fb3668fc3 feat: Allow relative git repository (#41) 2024-06-26 21:11:50 +02:00
Eric Tuvesson
fa282d6169 feat(runtime): Add "data-testid" attributes to UI nodes (#42) 2024-06-26 21:08:01 +02:00
Eric Tuvesson
0ee55c26eb feat(editor): Search panel show "(Cloud Function)" if result is in Cloud Functions sheet (#40) 2024-06-17 10:35:02 +02:00
Eric Tuvesson
3a31b86d48 chore(runtime): Clean up "Push Component To Stack" (#30) 2024-06-13 22:25:16 +02:00
Eric Tuvesson
2f06952e4a fix(runtime): JavaScript Records API error handling (#32) 2024-06-13 22:24:34 +02:00
Eric Tuvesson
44a40aef96 fix: Add more error handling to JavaScript Records API (#33) 2024-06-13 22:24:05 +02:00
Eric Tuvesson
0a69765460 chore: Update template provider name (#38) 2024-06-13 22:23:30 +02:00
alan-x-n
2ebd57b29a chore: Update README.md 2024-06-02 15:29:25 -07:00
alan-x-n
5225d26870 chore: Updated README.md 2024-06-02 15:27:35 -07:00
Eric Tuvesson
eb71536cb0 chore: Update branding info (#31)
* fix: branding info for package.json

* fix: updated package jsons with updated contact & author info

---------

Co-authored-by: alan-x-n <x.alanan.x@gmail.com>
2024-06-02 17:43:03 +02:00
Eric Tuvesson
d67afd3c60 feat(runtime): Date To String node, add "yearShort" format (#29) 2024-06-02 07:55:39 -07:00
Eric Tuvesson
1a048587d9 feat(editor): Tooltip support multiple fineTypes (#28) 2024-05-31 08:58:17 +02:00
Eric Tuvesson
8e16d5f9d4 feat: Add tooltip to cloud service "Open Dashboard" button (#27) 2024-05-29 10:05:38 +02:00
Eric Tuvesson
cdeb4b1c15 feat: Improve the Noodl.Records.query typings API (#25) 2024-05-24 15:49:05 +02:00
132 changed files with 2046 additions and 1151 deletions

View File

@@ -1,15 +1,15 @@
name: Build noodl-editor
name: Build fluxscape-editor
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to run this workflow from another workflow
workflow_call:
# release:
# types: [created]
jobs:
build_noodl_editor:
runs-on: ${{ matrix.os }}
@@ -32,11 +32,11 @@ jobs:
platform: linux-x64
steps:
- if: ${{ matrix.platform == 'darwin-arm64' }}
- if: ${{ matrix.platform == 'darwin-arm64' }}
name: Setup
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.11'
- name: Checkout
uses: actions/checkout@v4
@@ -72,6 +72,6 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: noodl-editor-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }}
name: fluxscape-editor-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }}
path: publish
retention-days: "12"
retention-days: '12'

View File

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

View File

@@ -1,21 +1,21 @@
# Noodl
# Fluxscape
[Noodl](https://noodl.net) is a low-code platform where designers and developers build custom applications and experiences. Designed as a visual programming environment, it aims to expedite your development process. It promotes the swift and efficient creation of applications, requiring minimal coding knowledge.
Fluxscape is a low-code platform where designers and developers build custom applications and experiences. Designed as a visual programming environment, it aims to expedite your development process. It promotes the swift and efficient creation of applications, requiring minimal coding knowledge.
## Documentation
Documentation for how to use Noodl can be found here:
[https://noodlapp.github.io/noodl-docs/](https://noodlapp.github.io/noodl-docs/)
Documentation for how to use Fluxscape can be found here:
[Fluxscape Documentation](https://docs.fluxscape.io)
## Community
Main support channel is Discord: [https://www.noodl.net/community](https://www.noodl.net/community)
Main support channel is Discord: [Fluxscape Discord](https://discord.gg/fXNW9EXa6A)
## Download releases
Pre-built binaries can be [downloaded from Github](https://github.com/noodlapp/noodl/releases)
Pre-built binaries can be [downloaded from Github](https://github.com/fluxscape/fluxscape/releases)
## Note for users who are migrating from the deprecated closed source version
- [Migrating the project files and workspaces to a Git provider](https://noodlapp.github.io/noodl-docs/docs/guides/collaboration/migrating-from-noodl-hosted-git)
- [Migrate backend and database](https://noodlapp.github.io/noodl-docs/docs/guides/deploy/using-an-external-backend#migrating-from-a-noodl-cloud-service)
- [Self-host frontend](https://noodlapp.github.io/noodl-docs/docs/guides/deploy/hosting-frontend)
- [Migrating the project files and workspaces to a Git provider](https://docs.fluxscape.io/docs/guides/collaboration/migrating-from-noodl-hosted-git/)
- [Migrate backend and database](https://docs.fluxscape.io/docs/guides/deploy/using-an-external-backend/#migrating-from-a-noodl-cloud-service)
- [Self-host frontend](https://docs.fluxscape.io/docs/guides/deploy/hosting-frontend/)
## Building from source
@@ -23,24 +23,24 @@ Pre-built binaries can be [downloaded from Github](https://github.com/noodlapp/n
# Install all dependencies
$ npm install
# Start the Noodl Editor and build a production version of the cloud and react runtime (useful when running Noodl from source but want to deploy to production)
# Start the Fluxscape Editor and build a production version of the cloud and react runtime (useful when running Fluxscape from source but want to deploy to production)
$ npm start
# Start the Noodl Editor and watch the filesystem for changes to the runtimes. Development versions of the runtimes, not meant for production (mostly due to source maps and file size)
# Start the Fluxscape Editor and watch the filesystem for changes to the runtimes. Development versions of the runtimes, not meant for production (mostly due to source maps and file size)
# This is ideal for a quick workflow when doing changes on the runtimes.
$ npm run dev
# Start Noodl Editor test runner
# Start Fluxscape Editor test runner
$ npm run test:editor
```
## Licenses
This repository contains two different licenses for different parts of the Noodl platform.
This repository contains two different licenses for different parts of the Fluxscape platform.
- Components related to the editor, used to edit Noodl projects, are under GPLv3
- Components related to the end applications, used by the applications Noodl deploys, are under MIT
- Components related to the editor, used to edit Fluxscape projects, are under GPLv3
- Components related to the end applications, used by the applications Fluxscape deploys, are under MIT
All of the source code of applications created with Noodl are under MIT. This means you can do project specific changes to the runtime without having to redistribute your changes.
All of the source code of applications created with Fluxscape are under MIT. This means you can do project specific changes to the runtime without having to redistribute your changes.
Packaged licensed under MIT:
- `noodl-runtime`

347
package-lock.json generated
View File

@@ -2849,9 +2849,9 @@
}
},
"node_modules/@electron/remote": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.1.1.tgz",
"integrity": "sha512-Lfxul2yBxL+FBVaKszNAkuUqSIDbUQ1I7BC394iRXyqA2XGz7im2bAxroNIM51jhySSPKUaOLHaFLxfV6pC9VQ==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.1.2.tgz",
"integrity": "sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==",
"peerDependencies": {
"electron": ">= 13.0.0"
}
@@ -25220,13 +25220,13 @@
}
},
"node_modules/electron": {
"version": "28.1.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.1.4.tgz",
"integrity": "sha512-WE6go611KOhtH6efRPMnVC7FE7DCKnQ3ZyHFeI1DbaCy8OU4UjZ8/CZGcuZmZgRdxSBEHoHdgaJkWRHZzF0FOg==",
"version": "31.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-31.3.1.tgz",
"integrity": "sha512-9fiuWlRhBfygtcT+auRd/WdBK/f8LZZcrpx0RjpXhH2DPTP/PfnkC4JB1PW55qCbGbh4wAgkYbf4ExIag8oGCA==",
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^18.11.18",
"@types/node": "^20.9.0",
"extract-zip": "^2.0.1"
},
"bin": {
@@ -25452,6 +25452,14 @@
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/elliptic": {
"version": "6.5.4",
"dev": true,
@@ -27203,6 +27211,10 @@
"react": "^15.0.2 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/fluxscape-editor": {
"resolved": "packages/noodl-editor",
"link": true
},
"node_modules/focus-lock": {
"version": "0.8.1",
"dev": true,
@@ -35443,10 +35455,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/noodl-editor": {
"resolved": "packages/noodl-editor",
"link": true
},
"node_modules/nopt": {
"version": "1.0.10",
"license": "MIT",
@@ -43191,6 +43199,11 @@
"version": "1.13.6",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/unfetch": {
"version": "4.2.0",
"dev": true,
@@ -48967,9 +48980,10 @@
"dev": true
},
"packages/noodl-editor": {
"version": "1.0.0",
"name": "fluxscape-editor",
"version": "1.2.0",
"dependencies": {
"@electron/remote": "^2.1.1",
"@electron/remote": "^2.1.2",
"@jaames/iro": "^5.5.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@noodl/git": "file:../noodl-git",
@@ -49025,7 +49039,7 @@
"babel-loader": "^8.2.4",
"concurrently": "^7.4.0",
"css-loader": "^6.7.1",
"electron": "28.1.4",
"electron": "31.3.1",
"electron-builder": "^24.9.1",
"file-loader": "^6.2.0",
"html-loader": "^3.1.0",
@@ -53375,9 +53389,9 @@
}
},
"@electron/remote": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.1.1.tgz",
"integrity": "sha512-Lfxul2yBxL+FBVaKszNAkuUqSIDbUQ1I7BC394iRXyqA2XGz7im2bAxroNIM51jhySSPKUaOLHaFLxfV6pC9VQ==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.1.2.tgz",
"integrity": "sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==",
"requires": {}
},
"@electron/universal": {
@@ -74057,13 +74071,23 @@
}
},
"electron": {
"version": "28.1.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.1.4.tgz",
"integrity": "sha512-WE6go611KOhtH6efRPMnVC7FE7DCKnQ3ZyHFeI1DbaCy8OU4UjZ8/CZGcuZmZgRdxSBEHoHdgaJkWRHZzF0FOg==",
"version": "31.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-31.3.1.tgz",
"integrity": "sha512-9fiuWlRhBfygtcT+auRd/WdBK/f8LZZcrpx0RjpXhH2DPTP/PfnkC4JB1PW55qCbGbh4wAgkYbf4ExIag8oGCA==",
"requires": {
"@electron/get": "^2.0.0",
"@types/node": "^18.11.18",
"@types/node": "^20.9.0",
"extract-zip": "^2.0.1"
},
"dependencies": {
"@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
"requires": {
"undici-types": "~5.26.4"
}
}
}
},
"electron-builder": {
@@ -75408,6 +75432,144 @@
"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": {
"version": "0.8.1",
"dev": true,
@@ -80983,144 +81145,6 @@
"version": "2.0.10",
"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.1",
"@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": "28.1.4",
"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": {
"version": "1.0.10",
"requires": {
@@ -86403,6 +86427,11 @@
"underscore": {
"version": "1.13.6"
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"unfetch": {
"version": "4.2.0",
"dev": true

View File

@@ -2,8 +2,8 @@
"private": true,
"name": "@noodl/repo",
"description": "Low-code for when experience matter",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"author": "Fluxscape <contact@fluxcsape.io>",
"homepage": "https://fluxscape.io",
"version": "1.0.0",
"workspaces": [
"packages/*"
@@ -12,7 +12,7 @@
"graph": "npx nx graph",
"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: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:_viewer": "ts-node ./scripts/noodl-editor/build-viewer.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",
"start:storybook": "lerna exec --scope @noodl/noodl-core-ui -- npm run start",
"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",
"start": "ts-node ./scripts/start.ts -- --build-viewer",
"test:editor": "ts-node ./scripts/test-editor.ts",
@@ -47,4 +47,4 @@
"npm": ">=6.0.0",
"node": ">=16.0.0 <=18"
}
}
}

View File

@@ -1,123 +1,128 @@
.Root {
border: none;
padding: 0;
background: transparent;
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.has-backdrop {
background-color: var(--theme-color-bg-1-transparent);
}
&.is-locking-scroll {
background-color: transparent;
}
&:not(.has-backdrop):not(.is-locking-scroll) {
pointer-events: none;
}
}
.VisibleDialog {
filter: drop-shadow(0 4px 15px var(--theme-color-bg-1-transparent-2));
box-shadow: 0 0 10px -5px var(--theme-color-bg-1-transparent-2);
position: absolute;
width: var(--width);
pointer-events: all;
.Root.is-centered & {
top: 50%;
left: 50%;
animation: enter-centered var(--speed-quick) var(--easing-base) both;
}
.Root:not(.is-centered) &.is-visible {
&.is-variant-default {
animation: enter var(--speed-quick) var(--easing-base) both;
}
&.is-variant-select {
transform: translate(var(--offsetX), var(--offsetY));
}
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--background);
border-radius: 2px;
overflow: hidden;
}
}
.Arrow {
position: absolute;
width: 0;
height: 0;
top: var(--arrow-top);
left: var(--arrow-left);
pointer-events: none;
&::after {
content: '';
display: block;
width: 11px;
height: 11px;
transform: translate(-50%, -50%) rotate(45deg);
background: var(--background);
}
&.is-contrast::after {
background: var(--backgroundContrast);
}
}
.Title {
background-color: var(--backgroundContrast);
padding: 12px;
}
.MeasuringContainer {
pointer-events: none;
height: 0;
overflow: visible;
opacity: 0;
}
.ChildContainer {
position: relative;
z-index: 1;
}
@keyframes enter {
from {
opacity: 0;
transform: translate(
calc(var(--animationStartOffsetX) + var(--offsetX)),
calc(var(--animationStartOffsetY) + var(--offsetY))
);
}
to {
opacity: 1;
transform: translate(var(--offsetX), var(--offsetY));
}
}
@keyframes enter-centered {
from {
opacity: 0;
transform: translate(-50%, calc(-50% + 16px));
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.Root {
border: none;
padding: 0;
background: transparent;
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.is-hidden {
visibility: hidden;
pointer-events: none;
}
&.has-backdrop {
background-color: var(--theme-color-bg-1-transparent);
}
&.is-locking-scroll {
background-color: transparent;
}
&:not(.has-backdrop):not(.is-locking-scroll) {
pointer-events: none;
}
}
.VisibleDialog {
filter: drop-shadow(0 4px 15px var(--theme-color-bg-1-transparent-2));
box-shadow: 0 0 10px -5px var(--theme-color-bg-1-transparent-2);
position: absolute;
width: var(--width);
pointer-events: all;
.Root.is-centered & {
top: 50%;
left: 50%;
animation: enter-centered var(--speed-quick) var(--easing-base) both;
}
.Root:not(.is-centered) &.is-visible {
&.is-variant-default {
animation: enter var(--speed-quick) var(--easing-base) both;
}
&.is-variant-select {
transform: translate(var(--offsetX), var(--offsetY));
}
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--background);
border-radius: 2px;
overflow: hidden;
}
}
.Arrow {
position: absolute;
width: 0;
height: 0;
top: var(--arrow-top);
left: var(--arrow-left);
pointer-events: none;
&::after {
content: '';
display: block;
width: 11px;
height: 11px;
transform: translate(-50%, -50%) rotate(45deg);
background: var(--background);
}
&.is-contrast::after {
background: var(--backgroundContrast);
}
}
.Title {
background-color: var(--backgroundContrast);
padding: 12px;
}
.MeasuringContainer {
pointer-events: none;
height: 0;
overflow: visible;
opacity: 0;
}
.ChildContainer {
position: relative;
z-index: 1;
}
@keyframes enter {
from {
opacity: 0;
transform: translate(
calc(var(--animationStartOffsetX) + var(--offsetX)),
calc(var(--animationStartOffsetY) + var(--offsetY))
);
}
to {
opacity: 1;
transform: translate(var(--offsetX), var(--offsetY));
}
}
@keyframes enter-centered {
from {
opacity: 0;
transform: translate(-50%, calc(-50% + 16px));
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}

View File

@@ -42,6 +42,7 @@ export interface BaseDialogProps extends UnsafeStyleProps {
isVisible?: boolean;
hasBackdrop?: boolean;
hasArrow?: boolean;
alwaysMounted?: boolean;
children?: Slot;
@@ -69,6 +70,7 @@ export function CoreBaseDialog({
isVisible,
hasBackdrop,
hasArrow,
alwaysMounted,
children,
@@ -261,7 +263,7 @@ export function CoreBaseDialog({
}
}, [background]);
if (!isVisible) return null;
if (!isVisible && !alwaysMounted) return null;
return (
<div
@@ -270,6 +272,7 @@ export function CoreBaseDialog({
hasBackdrop && css['has-backdrop'],
isLockingScroll && css['is-locking-scroll'],
typeof triggerRef === 'undefined' && css['is-centered'],
!isVisible && css['is-hidden'],
css[variant]
)}
onClick={onClose}

View File

@@ -11,7 +11,7 @@ import css from './Tooltip.module.scss';
export interface TooltipProps extends UnsafeStyleProps {
content: SingleSlot;
fineType?: string;
fineType?: string | string[];
children: Slot;
showAfterMs?: number;
@@ -79,9 +79,17 @@ export function Tooltip({
{fineType && (
<div className={css['FineType']}>
<Label size={LabelSize.Small} variant={TextType.Secondary}>
{fineType}
</Label>
{Array.isArray(fineType) ? (
fineType.map((x) => (
<Label size={LabelSize.Small} variant={TextType.Secondary}>
{x}
</Label>
))
) : (
<Label size={LabelSize.Small} variant={TextType.Secondary}>
{fineType}
</Label>
)}
</div>
)}
</BaseDialog>

View File

@@ -1,10 +1,10 @@
{
"name": "noodl-editor",
"name": "fluxscape-editor",
"productName": "Fluxscape",
"description": "Node-Based App Builder for Scalability & Rapid Development, a fork of Noodl",
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io",
"version": "1.0.0",
"version": "1.2.0",
"main": "src/main/main.js",
"scripts": {
"build": "npx ts-node -P ./tsconfig.build.json ./scripts/build.ts",
@@ -58,7 +58,7 @@
]
},
"dependencies": {
"@electron/remote": "^2.1.1",
"@electron/remote": "^2.1.2",
"@jaames/iro": "^5.5.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@noodl/git": "file:../noodl-git",
@@ -114,7 +114,7 @@
"babel-loader": "^8.2.4",
"concurrently": "^7.4.0",
"css-loader": "^6.7.1",
"electron": "28.1.4",
"electron": "31.3.1",
"electron-builder": "^24.9.1",
"file-loader": "^6.2.0",
"html-loader": "^3.1.0",
@@ -141,4 +141,4 @@
"optionalDependencies": {
"dmg-license": "^1.0.11"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3zm-9-3.82l-2.09-2.09L6.5 13.5 10 17l6.01-6.01-1.41-1.41z"/></svg>

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>

Before

Width:  |  Height:  |  Size: 175 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path d="M0 0h24v24H0V0z"/><path opacity=".87" d="M0 0h24v24H0V0z"/></g><path d="M21 3.01H3c-1.1 0-2 .9-2 2V9h2V4.99h18v14.03H3V15H1v4.01c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98v-14c0-1.11-.9-2-2-2zM11 16l4-4-4-4v3H1v2h10v3zM23 3.01H1V9h2V4.99h18v14.03H3V15H1v5.99h22V3.01zM11 16l4-4-4-4v3H1v2h10v3z"/></svg>

Before

Width:  |  Height:  |  Size: 408 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M4 6H2v16h16v-2H4V6zm18-4H6v16h16V2zm-3 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z"/></svg>

Before

Width:  |  Height:  |  Size: 220 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path d="M0 0h24v24H0V0z"/><path opacity=".87" d="M0 0h24v24H0V0z"/></g><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7zm-4 6h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>

Before

Width:  |  Height:  |  Size: 360 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19.44 12.99l-.01.02c.04-.33.08-.67.08-1.01 0-.34-.03-.66-.07-.99l.01.02 2.44-1.92-2.43-4.22-2.87 1.16.01.01c-.52-.4-1.09-.74-1.71-1h.01L14.44 2H9.57l-.44 3.07h.01c-.62.26-1.19.6-1.71 1l.01-.01-2.88-1.17-2.44 4.22 2.44 1.92.01-.02c-.04.33-.07.65-.07.99 0 .34.03.68.08 1.01l-.01-.02-2.1 1.65-.33.26 2.43 4.2 2.88-1.15-.02-.04c.53.41 1.1.75 1.73 1.01h-.03L9.58 22h4.85s.03-.18.06-.42l.38-2.65h-.01c.62-.26 1.2-.6 1.73-1.01l-.02.04 2.88 1.15 2.43-4.2s-.14-.12-.33-.26l-2.11-1.66zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/></svg>

Before

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Noodl</title>
<title>Fluxscape</title>
<link href="../assets/lib/fontawesome/css/font-awesome.min.css" rel="stylesheet" />
<link href="../assets/css/style.css" rel="stylesheet" />

View File

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

View File

@@ -2,6 +2,11 @@ import { Keybinding } from '@noodl-utils/keyboard/Keybinding';
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
export namespace Keybindings {
export const SEARCH = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_F);
export const CLOUD_SERVICE_OPEN_DASHBOARD = new Keybinding(KeyMod.CtrlCmd, KeyMod.Shift, KeyCode.KEY_P);
export const CLOUD_SERVICE_OPEN_DASHBOARD_BROWSER = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_P);
export const REFRESH_PREVIEW = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_R);
export const OPEN_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_D);
export const OPEN_CLOUD_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyMod.Shift, KeyCode.KEY_R);

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 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>> = {
notify: Notify.execute,
'upload-aws-s3': UploadAwsS3.execute
'upload-aws-s3': UploadAwsS3.execute,
'cloud-service': CloudService.execute
};
export function commandEventHandler(event: MessageEvent) {

View File

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

View File

@@ -1,5 +1,5 @@
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 NodeTypeAdapter from './NodeTypeAdapter';
@@ -103,7 +103,7 @@ export class RouterNavigateAdapter extends NodeTypeAdapter {
const hasValidTarget = target && pageComponents.includes(target);
const warning =
const warning: Warning =
hasValidTarget === false
? {
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') {
var state =
const state =
this.getType().visualStates !== undefined
? this.getType().visualStates.find((s) => s.name === c.state)
: undefined;
var stateName = state !== undefined ? state.label : c.state;
const stateName = state !== undefined ? state.label : c.state;
WarningsModel.instance.setWarning(
{ 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>;
private _variant: TSFixme;
private _label: TSFixme;
_label: TSFixme;
private _type: TSFixme;
private _ports: TSFixme;

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import { AppRegistry } from '@noodl-models/app_registry';
import { SidebarModel } from '@noodl-models/sidebar';
import { Keybinding } from '@noodl-utils/keyboard/Keybinding';
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
import { Keybindings } from '@noodl-constants/Keybindings';
import { IconName } from '@noodl-core-ui/components/common/Icon';
@@ -69,7 +68,7 @@ export function installSidePanel({ isLesson }: SetupEditorOptions) {
SidebarModel.instance.register({
id: 'search',
name: 'Search',
fineType: new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_F).label,
fineType: Keybindings.SEARCH.label,
order: 2,
icon: IconName.Search,
panel: SearchPanel

View File

@@ -16,6 +16,7 @@ import { projectFromDirectory, unzipIntoDirectory } from '../models/projectmodel
import FileSystem from './filesystem';
import { tracker } from './tracker';
import { guid } from './utils';
import { getTopLevelWorkingDirectory } from '@noodl/git/src/core/open';
export interface ProjectItem {
id: string;
@@ -267,12 +268,15 @@ export class LocalProjectsModel extends Model {
});
}
isGitProject(project: ProjectModel): boolean {
// TODO: check if there's is git in any parent folder too
// Check if the git folder exists.
const gitPath = filesystem.join(project._retainedProjectDirectory, '.git');
return filesystem.exists(gitPath);
/**
* Check if this project is in a git repository.
*
* @param project
* @returns
*/
async isGitProject(project: ProjectModel): Promise<boolean> {
const gitPath = await getTopLevelWorkingDirectory(project._retainedProjectDirectory);
return gitPath !== null;
}
setCurrentGlobalGitAuth(projectId: string) {

View File

@@ -4,7 +4,7 @@ import { clearFolders } from './cleanup';
export async function copyProjectFilesToFolder(projectPath: string, direntry: string): Promise<void> {
// 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
if (!projectPath) {

View File

@@ -67,6 +67,14 @@ async function _writeFileToFolder({
runtimeType
}: WriteFileToFolderArgs) {
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 filename = url;

View File

@@ -41,7 +41,7 @@ export class HtmlProcessor {
baseUrl = baseUrl + '/';
}
const title = parameters.title || settings.htmlTitle || 'Noodl Viewer';
const title = parameters.title || settings.htmlTitle || 'Fluxscape Viewer';
let headCode = settings.headCode || '';
if (parameters.headCode) {

View File

@@ -6,7 +6,7 @@ import { ITemplateProvider, ProgressCallback, TemplateItem, TemplateListFilter }
*/
export class NoodlDocsTemplateProvider implements ITemplateProvider {
get name(): string {
return 'https://docs.noodl.net';
return this.getDocsEndpoint() || 'https://docs.fluxscape.io';
}
constructor(private readonly getDocsEndpoint: () => string) {}

View File

@@ -11,6 +11,14 @@ export interface ArrayDiff<T> {
changed: T[];
unchanged: T[];
}
export function createEmptyArrayDiff<T>(): ArrayDiff<T> {
return {
deleted: [],
created: [],
changed: [],
unchanged: [],
}
}
export interface ProjectDiffItem {
graph: TSFixme;

View File

@@ -13,6 +13,7 @@ export default class SchemaHandler {
public dbCollections: TSFixme[];
public systemCollections: TSFixme[];
public configSchema: TSFixme;
public parseServerVersion: string;
constructor() {
EventDispatcher.instance.on(
@@ -119,6 +120,20 @@ export default class SchemaHandler {
console.log(e);
}
});
// Get Parse Server Version & Supported features
fetch(environment.url + '/serverInfo', {
method: 'POST',
body: JSON.stringify({
"_method": "GET",
"_ApplicationId": environment.appId,
"_MasterKey": environment.masterKey,
})
})
.then((response) => response.json())
.then((json) => {
this.parseServerVersion = json.parseServerVersion;
});
});
});
}
@@ -129,10 +144,20 @@ export default class SchemaHandler {
ProjectModel.instance.setMetaData('dbCollections', this.dbCollections);
ProjectModel.instance.setMetaData('systemCollections', this.systemCollections);
ProjectModel.instance.setMetaData('dbConfigSchema', this.configSchema);
const versionNumbers = this.parseServerVersion?.split(".")
if (versionNumbers && versionNumbers.length > 0) {
// Let's only save the major version number,
// since this will be used to determine which verison of the API to use.
ProjectModel.instance.setMetaData('dbVersionMajor', versionNumbers[0]);
} else {
ProjectModel.instance.setMetaData('dbVersionMajor', undefined);
}
} else {
ProjectModel.instance.setMetaData('dbCollections', undefined);
ProjectModel.instance.setMetaData('systemCollections', undefined);
ProjectModel.instance.setMetaData('dbConfigSchema', undefined);
ProjectModel.instance.setMetaData('dbVersionMajor', undefined);
}
}
}

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

View File

@@ -211,7 +211,7 @@ export default function Clippy() {
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 isFullBeta = ['full-beta', 'enterprise'].includes(version);
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>
If you dont have an API key with GPT-4 access, you can set the Noodl AI to use the Limited Beta in the
editor settings.
If you dont have an API key with GPT-4 access, you can set the FluxScape AI to use the Limited Beta in
the editor settings.
</Text>
<PrimaryButton
size={PrimaryButtonSize.Small}
@@ -597,8 +597,8 @@ export default function Clippy() {
</Label>
<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
beta access by bringing your own GPT-4 API key.
You are running the limited beta of FluxScape AI. If features fewer commands and a less capable AI. Get
full beta access by bringing your own GPT-4 API key.
</Text>
<PrimaryButton

View File

@@ -1,5 +1,7 @@
import { usePluginContext } from '@noodl-contexts/PluginContext';
import React, { RefObject, useEffect, useRef } from 'react';
import React, { RefObject } from 'react';
import { platform } from '@noodl/platform';
import { ProjectModel } from '@noodl-models/projectmodel';
import { ActivityIndicator } from '@noodl-core-ui/components/common/ActivityIndicator';
import { BaseDialog } from '@noodl-core-ui/components/layout/BaseDialog';
@@ -54,6 +56,7 @@ export function DeployPopup(props: DeployPopupProps) {
onClose={props.onClose}
hasArrow
isLockingScroll
alwaysMounted
>
<DeployPopupChild />
</BaseDialog>
@@ -62,6 +65,21 @@ export function DeployPopup(props: DeployPopupProps) {
}
function FluxscapeDeployTab() {
// Preview URL: 'http://192.168.0.33:8574/'
return <iframe src="https://portal.fluxscape.io" style={{ width: "100%", height: "50vh", borderStyle: "none" }} />;
const params = {
version: platform.getVersion()
};
const projectId = ProjectModel.instance.id;
if (projectId) {
params['projectId'] = projectId;
}
const urlParams = new URLSearchParams(params);
return (
<iframe
src={`https://portal.fluxscape.io/?${urlParams.toString()}`}
style={{ width: '100%', height: '50vh', borderStyle: 'none' }}
/>
);
}

View File

@@ -1,13 +1,16 @@
import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import { Keybindings } from '@noodl-constants/Keybindings';
import { Environment } from '@noodl-models/CloudServices';
import ParseDashboardServer from '@noodl-utils/parsedashboardserver';
import { Icon, IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
import { IconButton, IconButtonState, IconButtonVariant } from '@noodl-core-ui/components/inputs/IconButton';
import { PrimaryButton, PrimaryButtonSize, PrimaryButtonVariant } from '@noodl-core-ui/components/inputs/PrimaryButton';
import { DialogRenderDirection } from '@noodl-core-ui/components/layout/BaseDialog';
import { Collapsible } from '@noodl-core-ui/components/layout/Collapsible';
import { Tooltip } from '@noodl-core-ui/components/popups/Tooltip';
import { Label, LabelSpacingSize } from '@noodl-core-ui/components/typography/Label';
import { Text, TextType } from '@noodl-core-ui/components/typography/Text';
@@ -118,12 +121,21 @@ export function CloudServiceCard({
</div>
{isEditorEnvironment && (
<PrimaryButton
label="Open dashboard"
size={PrimaryButtonSize.Small}
onClick={onDashboardClicked}
isGrowing
/>
<Tooltip
content="Open the Parse Dashboard"
fineType={[
`In Window: ${Keybindings.CLOUD_SERVICE_OPEN_DASHBOARD.label}`,
`In Browser: ${Keybindings.CLOUD_SERVICE_OPEN_DASHBOARD_BROWSER.label}`
]}
renderDirection={DialogRenderDirection.Below}
>
<PrimaryButton
label="Open dashboard"
size={PrimaryButtonSize.Small}
onClick={onDashboardClicked}
isGrowing
/>
</Tooltip>
)}
</div>
</div>

View File

@@ -6,8 +6,6 @@ import { CloudService } from '@noodl-models/CloudServices';
import { ActivityIndicator } from '@noodl-core-ui/components/common/ActivityIndicator';
import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
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 { Container } from '@noodl-core-ui/components/layout/Container';
import { VStack } from '@noodl-core-ui/components/layout/Stack';

View File

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

View File

@@ -1,14 +1,12 @@
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
import { type NodeReference, useNodeReferencesContext } from '@noodl-contexts/NodeReferencesContext';
import { useFocusRefOnPanelActive } from '@noodl-hooks/useFocusRefOnPanelActive';
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 { ComponentModel } from '@noodl-models/componentmodel';
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { NodeLibrary, NodeLibraryNodeType } from '@noodl-models/nodelibrary';
import { NodeLibrary } from '@noodl-models/nodelibrary';
import { BasicNodeType } from '@noodl-models/nodelibrary/BasicNodeType';
import { ProjectModel } from '@noodl-models/projectmodel';
import { EditorNode } from '@noodl-core-ui/components/common/EditorNode';
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 { 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() {
const [searchTerm, setSearchTerm] = useState('');
const [includeCoreNodes, setIncludeCoreNodes] = useState(false);
const inputRef = useRef(null);
const [result] = useNodeReferences();
const { nodeReferences } = useNodeReferencesContext();
const nodeLibraryLoaded = useNodeLibraryLoaded();
useFocusRefOnPanelActive(inputRef, NodeReferencesPanel_ID);
function searchFilter(x: ResultItem) {
function searchFilter(x: NodeReference) {
if (x.displayName.toLowerCase().includes(searchTerm)) {
return true;
}
@@ -144,7 +46,7 @@ export function NodeReferencesPanel() {
return false;
}
let filteredResult = result.filter(searchFilter);
let filteredResult = nodeReferences.filter(searchFilter);
if (!includeCoreNodes) {
filteredResult = filteredResult.filter((x) => x.displayName.startsWith('/'));
}
@@ -185,7 +87,7 @@ export function NodeReferencesPanel() {
}
interface ItemProps {
entry: ResultItem;
entry: NodeReference;
}
function Item({ entry }: ItemProps) {
@@ -245,8 +147,8 @@ function Item({ entry }: ItemProps) {
}
interface ItemReferenceProps {
entry: ResultItem;
referenace: ResultItem['referenaces'][0];
entry: NodeReference;
referenace: NodeReference['referenaces'][0];
colors: INodeColorScheme;
}

View File

@@ -199,6 +199,7 @@ function BaseVersionControlPanel() {
export function VersionControlPanel() {
const [git, setGit] = useState<Git>(null);
const [state, setState] = useState<'loading' | 'loaded' | 'not-git'>('loading');
async function createGit() {
const gitClient = new Git(mergeProject);
@@ -206,12 +207,18 @@ export function VersionControlPanel() {
setGit(gitClient);
}
const isGitProject = git === null ? LocalProjectsModel.instance.isGitProject(ProjectModel.instance) : true;
useEffect(() => {
if (isGitProject) {
createGit();
}
}, [isGitProject]);
LocalProjectsModel.instance
.isGitProject(ProjectModel.instance)
.then(async (isGitProject) => {
if (isGitProject) {
await createGit();
setState('loaded');
} else {
setState('not-git');
}
});
}, []);
async function setupGit() {
const gitClient = new Git(mergeProject);
@@ -220,7 +227,7 @@ export function VersionControlPanel() {
setGit(gitClient);
}
if (git === null && !isGitProject) {
if (git === null && state === 'not-git') {
return (
<BasePanel isFill title="Version Control">
<Box hasXSpacing hasYSpacing>

View File

@@ -6,11 +6,13 @@ import { Commit } from '@noodl/git/src/core/models/snapshot';
import { FileChange } from '@noodl/git/src/core/models/status';
import { revRange } from '@noodl/git/src/core/rev-list';
import { ProjectModel } from '@noodl-models/projectmodel';
import { applyPatches } from '@noodl-models/ProjectPatches/applypatches';
import { mergeProject } from '@noodl-utils/projectmerger';
import { ProjectDiff, diffProject } from '@noodl-utils/projectmerger.diff';
import { useVersionControlContext } from '../context';
import { getProjectFilePath } from '../context/DiffUtils';
import { DiffList } from './DiffList';
//Kind:
@@ -124,7 +126,10 @@ async function getMergeDiff(repositoryPath: string, commit: Commit, refToDiffTo:
}
async function getProjectFile(commit: Commit) {
const projectContent = JSON.parse(await commit.getFileAsString('project.json'));
const projectFilePath = getProjectFilePath(commit.repositoryDir, ProjectModel.instance._retainedProjectDirectory);
const projectContentRaw = await commit.getFileAsString(projectFilePath);
const projectContent = JSON.parse(projectContentRaw);
applyPatches(projectContent);
return projectContent;
}

View File

@@ -61,7 +61,7 @@ export function DiffList({ diff, fileChanges, componentDiffTitle, actions, commi
const [imageDiff, setImageDiff] = useState<IImageDiff>(null);
const components = diff?.components ? getChangedComponents(diff.components) : [];
const files = (fileChanges || [])?.filter((f) => f.path !== 'project.json') || [];
const files = (fileChanges || [])?.filter((f) => !f.path.endsWith('project.json')) || [];
const settings = diff?.settings ? getChangedObjectProperties(diff.settings) : [];
const colorStyles = diff?.styles.colors ? getChangedObjectProperties(diff.styles.colors) : [];
const textStyles = diff?.styles.text ? getChangedObjectProperties(diff.styles.text) : [];

View File

@@ -1,21 +1,24 @@
import path from 'path';
import { getCommit } from '@noodl/git/src/core/logs';
import { FileStatusKind } from '@noodl/git/src/core/models/status';
import { FeedbackType } from '@noodl-constants/FeedbackType';
import { applyPatches } from '@noodl-models/ProjectPatches/applypatches';
import {
ProjectDiff,
ProjectDiffItem,
ProjectBasicDiffItem,
ArrayDiff,
diffProject
diffProject,
createEmptyArrayDiff
} from '@noodl-utils/projectmerger.diff';
import { IconName } from '@noodl-core-ui/components/common/Icon';
import { ListItemProps } from '@noodl-core-ui/components/layout/ListItem';
import { ProjectModel } from '../../../../models/projectmodel';
import { applyPatches } from '@noodl-models/ProjectPatches/applypatches';
import { FileStatusKind } from '@noodl/git/src/core/models/status';
import { IconName } from '@noodl-core-ui/components/common/Icon';
import { ListItemProps } from '@noodl-core-ui/components/layout/ListItem';
import { FeedbackType } from '@noodl-constants/FeedbackType';
import { getCommit } from '@noodl/git/src/core/logs';
export interface ProjectLocalDiff extends ProjectDiff{
export interface ProjectLocalDiff extends ProjectDiff {
baseProject: TSFixme; //Project model as an object from raw json
commitShaDiffedTo: string;
}
@@ -84,17 +87,49 @@ export function getFileStatusIconProps(status: FileStatusKind): Partial<ListItem
}
}
export async function doLocalDiff(repositoryPath: string, headCommitId: string): Promise<ProjectLocalDiff> {
const baseCommit = await getCommit(repositoryPath, headCommitId);
const baseProjectJson = await baseCommit.getFileAsString('project.json');
const baseProject = JSON.parse(baseProjectJson);
applyPatches(baseProject);
const diff = diffProject(baseProject, ProjectModel.instance.toJSON());
return {
...diff,
baseProject,
commitShaDiffedTo: headCommitId
};
export function getProjectFilePath(repositoryPath: string, projectPath: string) {
const relativePath = path.relative(repositoryPath, projectPath);
const projectFilePath = path.join(relativePath, 'project.json').replaceAll('\\', '/');
return projectFilePath;
}
export async function doLocalDiff(
repositoryPath: string,
projectPath: string,
headCommitId: string
): Promise<ProjectLocalDiff> {
const projectFilePath = getProjectFilePath(repositoryPath, projectPath);
try {
const baseCommit = await getCommit(projectPath, headCommitId);
const baseProjectJson = await baseCommit.getFileAsString(projectFilePath);
const baseProject = JSON.parse(baseProjectJson);
applyPatches(baseProject);
const diff = diffProject(baseProject, ProjectModel.instance.toJSON());
return {
...diff,
baseProject,
commitShaDiffedTo: headCommitId
};
} catch (error) {
if (error.toString().includes('exists on disk, but not in')) {
console.warn('project.json does not exist in this commit.');
}
// Return empty state
return {
baseProject: {},
commitShaDiffedTo: headCommitId,
components: createEmptyArrayDiff(),
variants: createEmptyArrayDiff(),
settings: createEmptyArrayDiff(),
styles: {
colors: createEmptyArrayDiff(),
text: createEmptyArrayDiff()
},
cloudservices: createEmptyArrayDiff()
};
}
}

View File

@@ -8,6 +8,7 @@ import { Slot } from '@noodl-core-ui/types/global';
import { doLocalDiff, ProjectLocalDiff } from './DiffUtils';
import { useVersionControlFetch } from './fetch.context';
import { BranchStatus, IVersionControlContext } from './types';
import { ProjectModel } from '@noodl-models/projectmodel';
const VersionControlContext = createContext<IVersionControlContext>({
git: null,
@@ -57,7 +58,8 @@ export function VersionControlProvider({ git, children }: { git: Git; children:
(async () => {
const currentCommitSha = await git.getHeadCommitId();
if (currentCommitSha) {
const diff = await doLocalDiff(git.repositoryPath, currentCommitSha);
const projectPath = ProjectModel.instance._retainedProjectDirectory;
const diff = await doLocalDiff(git.repositoryPath, projectPath, currentCommitSha);
setLocalDiff(diff);
}
})();

View File

@@ -17,9 +17,11 @@ import { EventDispatcher } from '../../../../../shared/utils/EventDispatcher';
import View from '../../../../../shared/view';
import { NodeGraphEditor } from '../../nodegrapheditor';
import * as NewPopupLayer from '../../PopupLayer/index';
import { type PopupMenuItem } from '../../PopupLayer/index';
import { ToastLayer } from '../../ToastLayer/ToastLayer';
import { ComponentsPanelFolder } from './ComponentsPanelFolder';
import { ComponentTemplates } from './ComponentTemplates';
import { HACK_findNodeReference } from '@noodl-contexts/NodeReferencesContext';
const PopupLayer = require('@noodl-views/popuplayer');
const ComponentsPanelTemplate = require('../../../templates/componentspanel.html');
@@ -961,7 +963,7 @@ export class ComponentsPanelView extends View {
forRuntimeType: this.getRuntimeType()
});
let items: TSFixme[] = templates.map((t) => ({
let items: PopupMenuItem[] = templates.map((t) => ({
icon: IconName.Plus,
label: t.label,
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([
{
icon: IconName.Pencil,
@@ -1011,6 +1017,9 @@ export class ComponentsPanelView extends View {
_this.onDeleteClicked(scope, el);
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({
items: items
});

View File

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

View File

@@ -6,7 +6,7 @@ import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
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 { Container } from '@noodl-core-ui/components/layout/Container';
@@ -19,7 +19,7 @@ import css from './search-panel.module.scss';
export function SearchPanel() {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [searchResults, setSearchResults] = useState<ReturnType<typeof performSearch>>([]);
const inputRef = useRef<HTMLInputElement>(null);
// TODO: Not same context
@@ -54,7 +54,7 @@ export function SearchPanel() {
}
}, [debouncedSearchTerm]);
function onSearchItemClicked(searchResult) {
function onSearchItemClicked(searchResult: SearchResult) {
if (searchResult.type === 'Component') {
NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, {
breadcrumbs: false,
@@ -85,29 +85,7 @@ export function SearchPanel() {
<div className={css.SearchResults}>
{searchResults.map((component) => (
<Section
title={`${component.componentName} (${component.results.length} result${
component.results.length > 1 ? 's' : ''
})`}
key={component.componentId}
variant={SectionVariant.Panel}
>
{component.results.map((result, index) => (
<div
className={classNames(
css.SearchResultItem
// lastActiveComponentId === result.componentTarget.id && css['is-active']
)}
key={index}
onClick={() => onSearchItemClicked(result)}
>
<Label variant={TextType.Proud}>
{result.userLabel ? result.type + ' - ' + result.userLabel : result.type}
</Label>
<Text>{result.label}</Text>
</div>
))}
</Section>
<SearchItem key={component.componentId} component={component} onSearchItemClicked={onSearchItemClicked} />
))}
{searchResults.length === 0 && debouncedSearchTerm.length > 0 && (
<Container hasXSpacing hasYSpacing>
@@ -118,3 +96,39 @@ export function SearchPanel() {
</BasePanel>
);
}
type SearchItemProps = {
component: ReturnType<typeof performSearch>[0];
onSearchItemClicked: (item: SearchResult) => void;
};
function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
const resultCountText = `${component.results.length} result${component.results.length > 1 ? 's' : ''}`;
let titleText = `${component.componentName} (${resultCountText})`;
// We expect there to always be at least one result, to get the full component name.
const isInCloudFunctions = component.results[0].componentTarget.name.startsWith('/#__cloud__/');
if (isInCloudFunctions) {
titleText += ' (Cloud Function)';
}
return (
<Section title={titleText} variant={SectionVariant.Panel}>
{component.results.map((result, index) => (
<div
key={index}
className={classNames(
css.SearchResultItem
// lastActiveComponentId === result.componentTarget.id && css['is-active']
)}
onClick={() => onSearchItemClicked(result)}
>
<Label variant={TextType.Proud}>
{result.userLabel ? result.type + ' - ' + result.userLabel : result.type}
</Label>
<Text>{result.label}</Text>
</div>
))}
</Section>
);
}

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Noodl Viewer</title>
<title>Fluxscape Viewer</title>
<link href="../../assets/lib/fontawesome/css/font-awesome.min.css" rel="stylesheet">
<link href="assets/style.css" rel="stylesheet">
<script type="text/javascript" src="../../assets/lib/jquery-min.js"></script>

View File

@@ -67,7 +67,7 @@ function startServer(app, projectGetSettings, projectGetInfo, projectGetComponen
ProjectModules.instance.injectIntoHtml(info.projectDirectory, data, '/', function (injected) {
projectGetSettings((settings) => {
settings = settings || {};
injected = injected.replace('{{#title#}}', settings.htmlTitle || 'Noodl Viewer');
injected = injected.replace('{{#title#}}', settings.htmlTitle || 'Fluxscape Viewer');
injected = injected.replace('{{#customHeadCode#}}', settings.headCode || '');
response.writeHead(200, {

View File

@@ -3,8 +3,8 @@
"version": "2.7.0",
"main": "src/index.ts",
"description": "",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io",
"dependencies": {
"desktop-trampoline": "https://github.com/desktop/desktop-trampoline/archive/refs/tags/v0.9.8.tar.gz",
"dugite": "^1.106.0",

View File

@@ -22,7 +22,11 @@ export async function open(basePath: string): Promise<string> {
// console.log("VCS error when opening project: " + e);
// }
return basePath;
// Find the relative git repository path
const repositoryPath = await getTopLevelWorkingDirectory(basePath);
return repositoryPath;
}
/**

View File

@@ -3,8 +3,8 @@
"version": "2.7.0",
"main": "src/index.ts",
"description": "Cross platform implementation of platform specific features.",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io",
"dependencies": {
"@noodl/platform": "file:../noodl-platform",
"@noodl/platform-node": "file:../noodl-platform-node"

View File

@@ -3,8 +3,8 @@
"version": "2.7.0",
"main": "src/index.ts",
"description": "Cross platform implementation of platform specific features.",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io",
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage"

View File

@@ -3,6 +3,6 @@
"version": "2.7.0",
"main": "src/index.ts",
"description": "Cross platform implementation of platform specific features.",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net"
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io"
}

View File

@@ -32,12 +32,15 @@ class CloudStore {
_initCloudServices() {
_collections = undefined; // clear collection cache, so it's refetched
const cloudServices = NoodlRuntime.instance.getMetaData('cloudservices');
const cloudServices = NoodlRuntime.instance.getMetaData('cloudservices');
if (cloudServices) {
this.appId = cloudServices.appId;
this.endpoint = cloudServices.endpoint;
}
const dbVersionMajor = NoodlRuntime.instance.getMetaData('dbVersionMajor');
this.dbVersionMajor = dbVersionMajor;
}
on() {
@@ -70,8 +73,9 @@ class CloudStore {
xhr.open(options.method || 'GET', this.endpoint + path, true);
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);
}
// Check for current users
var _cu = localStorage['Parse/' + this.appId + '/currentUser'];
@@ -168,13 +172,10 @@ class CloudStore {
return;
}
if (options.where) args.push('match=' + encodeURIComponent(JSON.stringify(options.where)));
if (options.limit) args.push('limit=' + options.limit);
if (options.skip) args.push('skip=' + options.skip);
const grouping = {
objectId: null
};
const grouping = {};
Object.keys(options.group).forEach((k) => {
const _g = {};
@@ -188,7 +189,20 @@ class CloudStore {
grouping[k] = _g;
});
args.push('group=' + JSON.stringify(grouping));
// I don't know which version the API was changed, lets just say above 4 for now.
if (this.dbVersionMajor && this.dbVersionMajor > 4) {
grouping._id = null;
if (options.where) args.push('$match=' + encodeURIComponent(JSON.stringify(options.where)));
args.push('$group=' + JSON.stringify(grouping));
} else {
grouping.objectId = null;
if (options.where) args.push('match=' + encodeURIComponent(JSON.stringify(options.where)));
args.push('group=' + JSON.stringify(grouping));
}
this._makeRequest('/aggregate/' + options.collection + (args.length > 0 ? '?' + args.join('&') : ''), {
success: function (response) {
@@ -244,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) {
const args = [];
if (options.include)
if (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(
'/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''),
@@ -420,6 +457,8 @@ class CloudStore {
* file: {
* name: string;
* }
* success: (data: unknown) => void;
* error: (error: unknown) => void;
* }} options
*/
deleteFile(options) {
@@ -432,8 +471,15 @@ class CloudStore {
}
function _isArrayOfObjects(a) {
if (!Array.isArray(a)) return false;
for (var i = 0; i < a.length; i++) if (typeof a[i] !== 'object' || a[i] === null) return false;
if (!Array.isArray(a)) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (typeof a[i] !== 'object' || a[i] === null) {
return false;
}
}
return true;
}
@@ -505,66 +551,104 @@ function _serializeObject(data, collectionName, modelScope) {
return data;
}
/**
*
* @param {unknown} data
* @param {string} type
* @param {*} modelScope
* @returns
*/
function _deserializeJSON(data, type, modelScope) {
if (data === undefined) return;
if (data === undefined) return undefined;
if (data === null) return null;
if (type === 'Relation' && data.__type === 'Relation') {
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;
} else if (type === 'Date' && data.__type === 'Date') {
}
if (type === 'Date' && data.__type === 'Date') {
return new Date(data.iso);
} else if (type === 'Date' && typeof data === 'string') {
}
if (type === 'Date' && typeof data === 'string') {
return new Date(data);
} else if (type === 'File' && data.__type === 'File') {
}
if (type === 'File' && data.__type === 'File') {
return new CloudFile(data);
} else if (type === 'GeoPoint' && data.__type === 'GeoPoint') {
}
if (type === 'GeoPoint' && data.__type === 'GeoPoint') {
return {
latitude: data.latitude,
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));
}
var c = Collection.get();
const c = Collection.get();
c.set(a);
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
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);
delete _data.className;
delete _data.__type;
return _fromJSON(_data, data.className, modelScope);
} else if (typeof data === 'object' && data !== null) {
var m = (modelScope || Model).get();
for (var key in data) {
m.set(key, _deserializeJSON(data[key], undefined, modelScope));
}
if (typeof data === 'object' && data !== null) {
// 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;
} else return data;
return model;
}
return data;
}
function _fromJSON(item, collectionName, modelScope) {
const m = (modelScope || Model).get(item.objectId);
m._class = collectionName;
const modelStore = modelScope || Model;
if (collectionName !== undefined && CloudStore._collections[collectionName] !== undefined)
var schema = CloudStore._collections[collectionName].schema;
// Try to get the model by the object id (record) or id, otherwise we create a new unique id.
const model = modelStore.get(item.objectId || item.id);
model._class = collectionName;
for (var key in item) {
if (key === 'objectId' || key === 'ACL') continue;
var _type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
m.set(key, _deserializeJSON(item[key], _type, modelScope));
let schema = undefined;
if (collectionName !== undefined && CloudStore._collections[collectionName] !== undefined) {
schema = CloudStore._collections[collectionName].schema;
}
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;

View File

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

View File

@@ -12,6 +12,7 @@ function createRecordsAPI(modelScope) {
return {
async query(className, query, options) {
if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => {
cloudstore().query({
collection: className,
@@ -26,9 +27,9 @@ function createRecordsAPI(modelScope) {
include: options ? options.include : undefined,
select: options ? options.select : undefined,
count: options ? options.count : undefined,
success: (results,count) => {
success: (results, count) => {
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);
},
error: (err) => {
@@ -39,6 +40,7 @@ function createRecordsAPI(modelScope) {
},
async count(className, query) {
if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => {
cloudstore().count({
collection: className,
@@ -60,6 +62,7 @@ function createRecordsAPI(modelScope) {
},
async distinct(className, property, query) {
if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => {
cloudstore().distinct({
collection: className,
@@ -82,6 +85,7 @@ function createRecordsAPI(modelScope) {
},
async aggregate(className, group, query) {
if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => {
cloudstore().aggregate({
collection: className,
@@ -103,19 +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) {
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
return new Promise((resolve, reject) => {
if (!className) return reject('No class name specified');
if (!className) {
return reject('No class name specified');
}
cloudstore().fetch({
collection: className,
objectId: objectOrId,
include: options ? options.include : undefined,
keys: options?.keys,
include: options?.include,
excludeKeys: options?.excludeKeys,
success: function (response) {
var record = cloudstore()._fromJSON(response, className);
const record = cloudstore()._fromJSON(response, className);
resolve(record);
},
error: function (err) {
@@ -126,6 +146,7 @@ function createRecordsAPI(modelScope) {
},
async increment(objectOrId, properties, options) {
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
@@ -149,6 +170,7 @@ function createRecordsAPI(modelScope) {
},
async save(objectOrId, properties, options) {
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
@@ -179,6 +201,7 @@ function createRecordsAPI(modelScope) {
},
async create(className, properties, options) {
if (typeof className === 'undefined') throw new Error("'className' is undefined");
return new Promise((resolve, reject) => {
cloudstore().create({
collection: className,
@@ -197,6 +220,7 @@ function createRecordsAPI(modelScope) {
},
async delete(objectOrId, options) {
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
@@ -265,7 +289,7 @@ function createRecordsAPI(modelScope) {
resolve();
},
error: (err) => {
reject(Error(rr || 'Failed to add relation.'));
reject(Error(err || 'Failed to add relation.'));
}
});
});

View File

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

View File

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

View File

@@ -230,7 +230,7 @@ NodeContext.prototype.deregisterComponentModel = function (componentModel) {
NodeContext.prototype.fetchComponentBundle = async function (name) {
const fetchBundle = async (name) => {
let baseUrl = Noodl.Env["BaseUrl"] || '/';
let baseUrl = Noodl.Env['BaseUrl'] || '/';
let bundleUrl = `${baseUrl}noodl_bundles/${name}.json`;
const response = await fetch(bundleUrl);
@@ -455,6 +455,15 @@ NodeContext.prototype.setPopupCallbacks = function ({ onShow, 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) {
if (!this.onShowPopup) return;

View File

@@ -28,6 +28,7 @@ var DbCollectionNode = {
_this.scheduleAfterInputsHaveUpdated(function () {
_this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
collectionChangedScheduled = false;
});
};
@@ -66,6 +67,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
}
if (args.type === 'create') {
@@ -91,6 +93,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count');
_this.flagOutputDirty('firstItemId');
_this.flagOutputDirty('isEmpty');
} 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
_addModelAtCorrectIndex(m);
@@ -106,6 +109,7 @@ var DbCollectionNode = {
_this.flagOutputDirty('count');
_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: {
type: 'number',
displayName: 'Count',
@@ -189,6 +204,7 @@ var DbCollectionNode = {
setCollection: function (collection) {
this.bindCollection(collection);
this.flagOutputDirty('firstItemId');
this.flagOutputDirty('isEmpty');
this.flagOutputDirty('items');
this.flagOutputDirty('count');
},
@@ -257,7 +273,7 @@ var DbCollectionNode = {
limit: limit,
skip: skip,
count: count,
success: (results,count) => {
success: (results, count) => {
if (results !== undefined) {
_c.set(
results.map((i) => {
@@ -267,10 +283,9 @@ var DbCollectionNode = {
})
);
}
if(count !== undefined) {
if (count !== undefined) {
this._internal.storageSettings.storageTotalCount = count;
if(this.hasOutput('storageTotalCount'))
this.flagOutputDirty('storageTotalCount');
if (this.hasOutput('storageTotalCount')) this.flagOutputDirty('storageTotalCount');
}
this.setCollection(_c);
this.sendSignalOnOutput('fetched');
@@ -383,7 +398,7 @@ var DbCollectionNode = {
if (!storageSettings['storageEnableLimit']) return;
else return storageSettings['storageSkip'] || 0;
},
getStorageFetchTotalCount: function() {
getStorageFetchTotalCount: function () {
const storageSettings = this._internal.storageSettings;
return !!storageSettings['storageEnableCount'];

View File

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

View File

@@ -75,12 +75,12 @@ var SetDbModelPropertiedNodeDefinition = {
_this.setError('Missing Record Id');
return;
}
var model = internal.model;
for (var i in internal.inputValues) {
model.set(i, internal.inputValues[i], { resolve: true });
const model = internal.model;
for (const key in internal.inputValues) {
model.set(key, internal.inputValues[key], { resolve: true });
}
CloudStore.forScope(_this.nodeScope.modelScope).save({
collection: internal.collectionId,
objectId: model.getId(), // Get the objectId part of the model id

View File

@@ -31,8 +31,6 @@ const DateToStringNode = {
this._internal.currentInput = _value;
this._format();
this.flagOutputDirty('currentValue');
this.sendSignalOnOutput('inputChanged');
}
}
},
@@ -49,28 +47,45 @@ const DateToStringNode = {
type: 'signal',
displayName: 'Date Changed',
group: 'Signals'
},
onError: {
type: 'signal',
displayName: 'Invalid Date',
group: 'Signals'
}
},
methods: {
_format() {
const t = this._internal.currentInput;
const format = this._internal.formatString;
const date = ('0' + t.getDate()).slice(-2);
const month = ('0' + (t.getMonth() + 1)).slice(-2);
const monthShort = new Intl.DateTimeFormat('en-US', { month: 'short' }).format(t);
const year = t.getFullYear();
const hours = ('0' + t.getHours()).slice(-2);
const minutes = ('0' + t.getMinutes()).slice(-2);
const seconds = ('0' + t.getSeconds()).slice(-2);
try {
const t = this._internal.currentInput;
const format = this._internal.formatString;
const date = ('0' + t.getDate()).slice(-2);
const month = ('0' + (t.getMonth() + 1)).slice(-2);
const monthShort = new Intl.DateTimeFormat('en-US', { month: 'short' }).format(t);
const year = t.getFullYear();
const yearShort = year.toString().substring(2);
const hours = ('0' + t.getHours()).slice(-2);
const minutes = ('0' + t.getMinutes()).slice(-2);
const seconds = ('0' + t.getSeconds()).slice(-2);
this._internal.dateString = format
.replace(/\{date\}/g, date)
.replace(/\{month\}/g, month)
.replace(/\{monthShort\}/g, monthShort)
.replace(/\{year\}/g, year)
.replace(/\{hours\}/g, hours)
.replace(/\{minutes\}/g, minutes)
.replace(/\{seconds\}/g, seconds);
this._internal.dateString = format
.replace(/\{date\}/g, date)
.replace(/\{month\}/g, month)
.replace(/\{monthShort\}/g, monthShort)
.replace(/\{year\}/g, year)
.replace(/\{yearShort\}/g, yearShort)
.replace(/\{hours\}/g, hours)
.replace(/\{minutes\}/g, minutes)
.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

@@ -2,6 +2,8 @@ const StringFormatDefinition = {
name: 'String Format',
docs: 'https://docs.noodl.net/nodes/string-manipulation/string-format',
category: 'String Manipulation',
usePortAsLabel: 'format',
portLabelTruncationMode: 'length',
initialize() {
const internal = this._internal;
internal.format = '';

View File

@@ -3,6 +3,6 @@
"version": "2.7.0",
"main": "src/index.d.ts",
"description": "",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net"
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io"
}

View File

@@ -1,7 +1,7 @@
{
"name": "@noodl/cloud-runtime",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"author": "Fluxscape <contact@fluxscape.io>",
"homepage": "https://fluxscape.io",
"version": "0.6.3",
"license": "MIT",
"main": "dist/main.js",

View File

@@ -74,6 +74,59 @@ declare namespace Noodl {
*/
const Object: any;
type RecordQuery<T> =
{
lessThan: T
} |
{
lessThanOrEqualTo: T
} |
{
greaterThan: T
} |
{
greaterThanOrEqualTo: T
} |
{
equalTo: T
} |
{
notEqualTo: T
} |
{
containedIn: T
} |
{
notContainedIn : T
} |
{
exists: T
} |
{
matchesRegex: T
} |
{
text: T
} |
{
idEqualTo: T
} |
{
idContainedIn: T
} |
{
pointsTo: T
} |
{
relatedTo: T
};
type RecordQueryField<T> = T extends RecordQuery<any> ?
{ [K in keyof T]: { [P in K]: T[P] } & Partial<Record<Exclude<keyof T, K>, never>> }[keyof T]
: never;
type RecordSortKey<T extends string> = (`${T}` | `-${T}`)[];
interface RecordsApi {
/**
* This is an async function that will query the database using the query
@@ -115,15 +168,17 @@ declare namespace Noodl {
* })
* ```
*/
query(
className: RecordClassName,
query?: any,
query<TClassName extends RecordClassName>(
className: TClassName,
query?:
RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
{ and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
options?: {
limit?: number;
skip?: number;
sort?: string[];
include?: any;
select?: any;
sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
include?: string | (keyof DatabaseSchema[TClassName])[];
select?: string | (keyof DatabaseSchema[TClassName])[];
}
): Promise<any>;
@@ -172,6 +227,9 @@ declare namespace Noodl {
objectOrId: string | { getId(): string; },
options?: {
className?: RecordClassName;
keys?: string[] | string;
include?: string[] | string;
excludeKeys?: string[] | string;
}
): Promise<any>;

View File

@@ -1,9 +1,9 @@
const path = require('path');
module.exports = {
// 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.
outPath: process.env.OUT_PATH || path.resolve(__dirname, '../../noodl-editor/src/external'),
runtimeVersion: 'cloud-runtime-' + require('../package.json').version.replaceAll('.', '-')
};
const path = require('path');
module.exports = {
// 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.
outPath: process.env.OUT_PATH || path.resolve(__dirname, '../../noodl-editor/src/external'),
runtimeVersion: 'cloud-runtime-' + require('../package.json').version.replaceAll('.', '-')
};

View File

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

View File

@@ -2,5 +2,6 @@ const { merge } = require('webpack-merge');
const common = require('./webpack.viewer.common.js');
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 navigation = {
/**
* This is set by "packages/noodl-viewer-react/src/noodl-js-api.js"
* @type {NoodlRuntime}
*/
_noodlRuntime: undefined,
async showPopup(componentPath, params) {
return new Promise((resolve) => {
navigation._noodlRuntime.context.showPopup(componentPath, params, {
onClosePopup: (action, results) => {
resolve({
action: action.replace('closeAction-', ''),
action: action?.replace('closeAction-', ''),
parameters: results
});
}

View File

@@ -7,6 +7,8 @@ import { Noodl, Slot } from '../../../types';
export interface ButtonProps extends Noodl.ReactProps {
enabled: boolean;
buttonType: 'button' | 'submit';
attrs: React.Attributes;
textStyle: Noodl.TextStyle;
@@ -96,6 +98,7 @@ export function Button(props: ButtonProps) {
return (
<button
{...props.attrs}
className={className}
disabled={!props.enabled}
{...Utils.controlEvents(props)}

View File

@@ -9,6 +9,8 @@ export interface CheckboxProps extends Noodl.ReactProps {
enabled: boolean;
checked: boolean;
attrs: React.Attributes;
useLabel: boolean;
label: string;
labelSpacing: string;
@@ -47,6 +49,7 @@ export function Checkbox(props: CheckboxProps) {
Layout.align(style, props);
const inputProps = {
...props.attrs,
id: props.id,
className: [props.className, 'ndl-controls-checkbox-2'].join(' '),
disabled: !props.enabled,

View File

@@ -10,6 +10,8 @@ export interface RadioButtonProps extends Noodl.ReactProps {
enabled: boolean;
value: string;
attrs: React.Attributes;
useLabel: boolean;
label: string;
labelSpacing: string;
@@ -47,6 +49,7 @@ export function RadioButton(props: RadioButtonProps) {
props.checkedChanged && props.checkedChanged(radioButtonGroup ? radioButtonGroup.selected === props.value : false);
const inputProps = {
...props.attrs,
id: props.id,
disabled: !props.enabled,
className: [props.className, 'ndl-controls-radio-2'].join(' '),

View File

@@ -12,6 +12,8 @@ export interface SelectProps extends Noodl.ReactProps {
textStyle: Noodl.TextStyle;
items: TSFixme;
attrs: React.Attributes;
placeholder: string;
placeholderOpacity: string;
@@ -81,6 +83,7 @@ export function Select(props: SelectProps) {
}
const inputProps = {
...props.attrs,
id: props.id,
className: props.className,
style: {

View File

@@ -9,6 +9,8 @@ export interface SliderProps extends Noodl.ReactProps {
id: string;
enabled: boolean;
attrs: React.Attributes;
value: number;
min: number;
max: number;
@@ -103,6 +105,7 @@ export function Slider(props: SliderProps) {
const className = `ndl-controls-range2 ${instanceClassId} ${props.className ? props.className : ''} `;
const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
...props.attrs,
id: props.id,
style: {
width: '100%',

View File

@@ -17,6 +17,8 @@ export interface TextInputProps extends Noodl.ReactProps {
type: 'text' | 'textArea' | 'email' | 'number' | 'password' | 'url';
textStyle: Noodl.TextStyle;
attrs: React.Attributes;
enabled: boolean;
placeholder: string;
@@ -149,6 +151,7 @@ export class TextInput extends React.Component<TextInputProps, State> {
inputStyles.color = props.noodlNode.context.styles.resolveColor(inputStyles.color);
const inputProps = {
...props.attrs,
id: props.id,
value: this.state.value,
...Utils.controlEvents(props),

View File

@@ -7,6 +7,8 @@ export interface ColumnsProps extends Noodl.ReactProps {
marginX: string;
marginY: string;
attrs: React.Attributes;
justifyContent: 'flex-start' | 'flex-end' | 'center';
direction: 'row' | 'column';
minWidth: string;
@@ -115,7 +117,10 @@ export function Columns(props: ColumnsProps) {
// ForEachCompoent breaks the layout but is needed to send onMount/onUnmount
if (!Array.isArray(props.children)) {
children = [props.children];
// @ts-expect-error props.children.type is any
if (props.children.type !== ForEachComponent) {
children = [props.children]
}
} else {
children = props.children.filter((child) => child.type !== ForEachComponent);
forEachComponent = props.children.find((child) => child.type === ForEachComponent);
@@ -123,9 +128,11 @@ export function Columns(props: ColumnsProps) {
return (
<div
{...props.attrs}
className={['columns-container', props.className].join(' ')}
ref={containerRef}
style={{
visibility: containerWidth === null ? "hidden" : "visible",
marginTop: parseFloat(props.marginY) * -1,
marginLeft: parseFloat(props.marginX) * -1,
display: 'flex',

View File

@@ -19,6 +19,8 @@ BScroll.use(Slide);
export interface GroupProps extends Noodl.ReactProps {
as?: keyof JSX.IntrinsicElements | React.ComponentType<unknown>;
attrs: React.Attributes;
scrollSnapEnabled: boolean;
showScrollbar: boolean;
scrollEnabled: boolean;
@@ -271,6 +273,7 @@ export class Group extends React.Component<GroupProps> {
<Component
// @ts-expect-error Lets hope that the type passed here is always static!
className={props.className}
{...props.attrs}
{...props.dom}
{...PointerListeners(props)}
style={style}

View File

@@ -10,6 +10,7 @@ export interface ImageProps extends Noodl.ReactProps {
src: string;
onLoad?: () => void;
};
attrs: React.Attributes;
}
export function Image(props: ImageProps) {
@@ -30,5 +31,5 @@ export function Image(props: ImageProps) {
}
}
return <img className={props.className} {...props.dom} {...PointerListeners(props)} style={style} />;
return <img {...props.attrs} className={props.className} {...props.dom} {...PointerListeners(props)} style={style} />;
}

View File

@@ -7,6 +7,8 @@ import { Noodl } from '../../../types';
export interface TextProps extends Noodl.ReactProps {
as?: keyof JSX.IntrinsicElements | React.ComponentType<unknown>;
attrs: React.Attributes;
textStyle: Noodl.TextStyle;
text: string;
@@ -48,6 +50,7 @@ export function Text(props: TextProps) {
return (
<Component
className={['ndl-visual-text', props.className].join(' ')}
{...props.attrs}
{...props.dom}
{...PointerListeners(props)}
style={style}

View File

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

View File

@@ -30,11 +30,24 @@ const ButtonNode = {
]
},
initialize() {
this.props.attrs = {};
this.props.layout = 'row'; //Used to tell child nodes what layout to expect
},
getReactComponent() {
return Button;
},
inputs: {
testId: {
index: 100009,
displayName: 'Test ID Attribute',
group: 'Advanced HTML',
type: 'string',
set(value) {
this.props.attrs["data-testid"] = value;
this.forceUpdate();
}
}
},
inputCss: {
backgroundColor: {
index: 100,

View File

@@ -31,6 +31,7 @@ const CheckBoxNode = {
]
},
initialize() {
this.props.attrs = {};
this.props.sizeMode = 'explicit';
this.props.id = 'input-' + guid();
this.props.checked = this._internal.checked = false;
@@ -94,6 +95,16 @@ const CheckBoxNode = {
this.flagOutputDirty('checked');
this._updateVisualState();
}
},
testId: {
index: 100009,
displayName: 'Test ID Attribute',
group: 'Advanced HTML',
type: 'string',
set(value) {
this.props.attrs["data-testid"] = value;
this.forceUpdate();
}
}
},
inputCss: {

View File

@@ -31,6 +31,7 @@ const OptionsNode = {
]
},
initialize: function () {
this.props.attrs = {};
this._itemsChanged = () => {
this.forceUpdate();
};
@@ -90,6 +91,16 @@ const OptionsNode = {
this.flagOutputDirty('value');
}
}
},
testId: {
index: 100009,
displayName: 'Test ID Attribute',
group: 'Advanced HTML',
type: 'string',
set(value) {
this.props.attrs["data-testid"] = value;
this.forceUpdate();
}
}
},
inputProps: {

View File

@@ -31,6 +31,7 @@ const RadioButtonNode = {
]
},
initialize() {
this.props.attrs = {};
this.props.sizeMode = 'explicit';
this.props.id = 'input-' + guid();
@@ -61,6 +62,16 @@ const RadioButtonNode = {
set(value) {
this.setStyle({ backgroundColor: value }, 'fill');
}
},
testId: {
index: 100009,
displayName: 'Test ID Attribute',
group: 'Advanced HTML',
type: 'string',
set(value) {
this.props.attrs["data-testid"] = value;
this.forceUpdate();
}
}
},
inputProps: {

Some files were not shown because too many files have changed in this diff Show More