diff --git a/.github/workflows/build-noodl-editor.yml b/.github/workflows/build-noodl-editor.yml index ca0ad89..449c1a6 100644 --- a/.github/workflows/build-noodl-editor.yml +++ b/.github/workflows/build-noodl-editor.yml @@ -11,7 +11,7 @@ on: # types: [created] jobs: - build_code_crusher: + build_opennoodl: runs-on: ${{ matrix.os }} strategy: @@ -72,6 +72,6 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v3 with: - name: code-crusher-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }} + name: opennoodl-${{ matrix.platform }}-${{ github.head_ref }}-${{ github.sha }} path: publish retention-days: "12" diff --git a/package.json b/package.json index ce05dea..3ee73a5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "@thelowcodefoundation/repo", "description": "Low-code for when experience matter", "author": "The Low Code Foundation ", - "homepage": "https://code-crusher.com", + "homepage": "https://learn-noodl.com", "version": "1.0.0", "workspaces": [ "packages/*" diff --git a/packages/noodl-core-ui/src/assets/icons/icon-component/logo.svg b/packages/noodl-core-ui/src/assets/icons/icon-component/logo.svg index c1a57ad..7ed025d 100644 --- a/packages/noodl-core-ui/src/assets/icons/icon-component/logo.svg +++ b/packages/noodl-core-ui/src/assets/icons/icon-component/logo.svg @@ -4,172 +4,61 @@ Created by potrace 1.10, written by Peter Selinger 2001-2011 - - + + diff --git a/packages/noodl-core-ui/src/components/common/Logo/Logo.tsx b/packages/noodl-core-ui/src/components/common/Logo/Logo.tsx index 872a863..7151a36 100644 --- a/packages/noodl-core-ui/src/components/common/Logo/Logo.tsx +++ b/packages/noodl-core-ui/src/components/common/Logo/Logo.tsx @@ -59,196 +59,134 @@ export function Logo({ const DefaultIcon = React.memo(function () { return ( - - -Created by potrace 1.10, written by Peter Selinger 2001-2011 - - - - + + + Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + ); }); const InvertedIcon = React.memo(function () { return ( - - -Created by potrace 1.10, written by Peter Selinger 2001-2011 - - - - + + + Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + ); @@ -256,180 +194,67 @@ c37 -6 85 -10 106 -10 23 0 45 -6 52 -15 8 -10 30 -15 64 -15 29 0 57 -5 63 const GrayscaleIcon = React.memo(function () { return ( - - -Created by potrace 1.10, written by Peter Selinger 2001-2011 - - - - + + + Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + ); }); diff --git a/packages/noodl-editor/build/icon.icns b/packages/noodl-editor/build/icon.icns index ba40e09..2220d5f 100644 Binary files a/packages/noodl-editor/build/icon.icns and b/packages/noodl-editor/build/icon.icns differ diff --git a/packages/noodl-editor/build/icon.ico b/packages/noodl-editor/build/icon.ico index 6c0b898..7952df8 100644 Binary files a/packages/noodl-editor/build/icon.ico and b/packages/noodl-editor/build/icon.ico differ diff --git a/packages/noodl-editor/build/icons/1024x1024.png b/packages/noodl-editor/build/icons/1024x1024.png index 54bc32a..2670833 100644 Binary files a/packages/noodl-editor/build/icons/1024x1024.png and b/packages/noodl-editor/build/icons/1024x1024.png differ diff --git a/packages/noodl-editor/build/icons/128x128.png b/packages/noodl-editor/build/icons/128x128.png index 19a3b02..af1dd2c 100644 Binary files a/packages/noodl-editor/build/icons/128x128.png and b/packages/noodl-editor/build/icons/128x128.png differ diff --git a/packages/noodl-editor/build/icons/256x256.png b/packages/noodl-editor/build/icons/256x256.png index f647ed8..e62dee5 100644 Binary files a/packages/noodl-editor/build/icons/256x256.png and b/packages/noodl-editor/build/icons/256x256.png differ diff --git a/packages/noodl-editor/build/icons/32x32.png b/packages/noodl-editor/build/icons/32x32.png index 63f8d70..6a5fd43 100644 Binary files a/packages/noodl-editor/build/icons/32x32.png and b/packages/noodl-editor/build/icons/32x32.png differ diff --git a/packages/noodl-editor/build/icons/48x48.png b/packages/noodl-editor/build/icons/48x48.png index 72d89d4..ed65485 100644 Binary files a/packages/noodl-editor/build/icons/48x48.png and b/packages/noodl-editor/build/icons/48x48.png differ diff --git a/packages/noodl-editor/build/icons/512x512.png b/packages/noodl-editor/build/icons/512x512.png index 98c3051..6bfa220 100644 Binary files a/packages/noodl-editor/build/icons/512x512.png and b/packages/noodl-editor/build/icons/512x512.png differ diff --git a/packages/noodl-editor/build/icons/64x64.png b/packages/noodl-editor/build/icons/64x64.png index 71b0f6b..4e8452e 100644 Binary files a/packages/noodl-editor/build/icons/64x64.png and b/packages/noodl-editor/build/icons/64x64.png differ diff --git a/packages/noodl-editor/build/macos-notarize.js b/packages/noodl-editor/build/macos-notarize.js index 2b12e67..992b0ea 100644 --- a/packages/noodl-editor/build/macos-notarize.js +++ b/packages/noodl-editor/build/macos-notarize.js @@ -11,7 +11,7 @@ module.exports = async function (params) { return; } - const appId = 'com.code-crusher.app'; + const appId = 'com.opennoodl.app'; const appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`); if (!fs.existsSync(appPath)) { diff --git a/packages/noodl-editor/package.json b/packages/noodl-editor/package.json index 0321822..48b5835 100644 --- a/packages/noodl-editor/package.json +++ b/packages/noodl-editor/package.json @@ -4,7 +4,7 @@ "description": "Full stack low code React app builder", "author": "The Low Code Foundation", "homepage": "https://thelowcodefoundation.com", - "version": "1.0.1", + "version": "1.1.0", "main": "src/main/main.js", "scripts": { "build": "npx ts-node -P ./tsconfig.build.json ./scripts/build.ts", @@ -15,30 +15,30 @@ "test:ci": "webpack-cli --config=webpackconfigs/webpack.test-ci.js && electron test.js" }, "build": { - "appId": "com.code-crusher.app", + "appId": "com.opennoodl.app", "afterSign": "./build/macos-notarize.js", "mac": { "hardenedRuntime": true, "entitlements": "build/entitlements.mac.plist", "extendInfo": { "LSMultipleInstancesProhibited": true, - "NSMicrophoneUsageDescription": "Allow Code-Crusher apps that you create and run to access the microphone?", - "NSCameraUsageDescription": "Allow Code-Crusher apps that you create and run to access the camera?" + "NSMicrophoneUsageDescription": "Allow OpenNoodl apps that you create and run to access the microphone?", + "NSCameraUsageDescription": "Allow OpenNoodl apps that you create and run to access the camera?" } }, "win": { "target": "nsis" }, "nsis": { - "guid": "com.code-crusher.app" + "guid": "com.opennoodl.app" }, "linux": { "target": "deb" }, "protocols": { - "name": "code-crusher", + "name": "opennoodl", "schemes": [ - "code-crusher" + "opennoodl" ] }, "npmRebuild": false, diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/_backend/ReAct.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/_backend/ReAct.ts index de7def1..230deb7 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/_backend/ReAct.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/_backend/ReAct.ts @@ -39,7 +39,7 @@ export abstract class ReActAgent { ...history ], provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0 }, onStream: (_, text) => { diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/api.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/api.ts index a714a27..03ccb57 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/api.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/api.ts @@ -38,7 +38,7 @@ export namespace AiAssistantApi { version: '0.0.0', models: [ { - name: 'gpt-4', + name: 'gpt-4o-mini', displayName: 'gpt-4 (8k context)', promptTokenCost: 0.03, completionTokenCost: 0.06 diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/interfaces.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/interfaces.ts index 1892a22..6e68fc5 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/interfaces.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/interfaces.ts @@ -13,14 +13,14 @@ export type AiCopilotTextProviders = { max_tokens?: number; } -export type ModelName = 'gpt-3.5-turbo' | 'gpt-4'; +export type ModelName = 'gpt-3.5-turbo' | 'gpt-4o-mini'; export type AiCopilotChatProviders = { model: 'gpt-3.5-turbo', temperature?: number; max_tokens?: number; } | { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature?: number; max_tokens?: number; } diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/chart.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/chart.ts index 97db00e..82c7979 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/chart.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/chart.ts @@ -49,7 +49,7 @@ export const template: AiNodeTemplate = { const fullCodeText = await chatStream({ provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-crud.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-crud.ts index 78ea0c3..5536001 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-crud.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-crud.ts @@ -71,7 +71,7 @@ export const template: AiNodeTemplate = { const fullText = await chatStreamXml({ messages, provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', // model: 'gpt-3.5-turbo', // The next context doesnt work with GPT-3.5 temperature: 0.5, diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-query-database/gpt-4-version.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-query-database/gpt-4-version.ts index b17ee55..0a159e5 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-query-database/gpt-4-version.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function-query-database/gpt-4-version.ts @@ -41,7 +41,7 @@ export async function execute( const fullCodeText = await chatStream({ provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, @@ -112,7 +112,7 @@ export async function execute( { role: 'user', content: codeText } ], provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function.ts index 61ea2f2..77fae5b 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function.ts @@ -23,7 +23,7 @@ export const template: AiNodeTemplate = { console.log('using version: ', version); try { - if ((version === 'enterprise' && OpenAiStore.getModel() === 'gpt-4') || version === 'full-beta') { + if ((version === 'enterprise' && OpenAiStore.getModel() === 'gpt-4o-mini') || version === 'full-beta') { await GPT4.execute(context); } else { await GPT3.execute(context); diff --git a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function/gpt-4-version.ts b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function/gpt-4-version.ts index b820717..bcdd73e 100644 --- a/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function/gpt-4-version.ts +++ b/packages/noodl-editor/src/editor/src/models/AiAssistant/templates/function/gpt-4-version.ts @@ -56,7 +56,7 @@ A["FUNCTION"]`; provider: { // NOTE: Tried with GPT 3.5 here before. // Then this question doesnt work: "Can you make a function that starts recording from the microphone when it gets a start signal and stops recording when it gets a stop signal" - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0 } }); @@ -100,7 +100,7 @@ A["FUNCTION"]`; const fullText = await chatStream({ provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, @@ -156,7 +156,7 @@ A["FUNCTION"]`; const fullCodeText = await chatStream({ provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, @@ -251,7 +251,7 @@ A["FUNCTION"]`; } ], provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', temperature: 0.0, max_tokens: 2048 }, diff --git a/packages/noodl-editor/src/editor/src/store/AiAssistantStore.ts b/packages/noodl-editor/src/editor/src/store/AiAssistantStore.ts index 34b03e3..9e625a2 100644 --- a/packages/noodl-editor/src/editor/src/store/AiAssistantStore.ts +++ b/packages/noodl-editor/src/editor/src/store/AiAssistantStore.ts @@ -20,7 +20,7 @@ const AI_ASSISTANT_MODEL_KEY = 'aiAssistant.model'; export type AiVersion = 'disabled' | 'full-beta' | 'enterprise'; -export type AiModel = 'gpt-3' | 'gpt-4'; +export type AiModel = 'gpt-3' | 'gpt-4o-mini'; export const OpenAiStore = { isEnabled(): boolean { diff --git a/packages/noodl-editor/src/editor/src/utils/getDocsEndpoint.ts b/packages/noodl-editor/src/editor/src/utils/getDocsEndpoint.ts index fe2efca..2b0043c 100644 --- a/packages/noodl-editor/src/editor/src/utils/getDocsEndpoint.ts +++ b/packages/noodl-editor/src/editor/src/utils/getDocsEndpoint.ts @@ -2,5 +2,5 @@ const remote = require('@electron/remote'); export default function getDocsEndpoint() { const localDocs = remote.getGlobal('useLocalDocs'); - return localDocs ? 'http://localhost:3000' : 'https://the-low-code-foundation.github.io/code-crusher-docs'; + return localDocs ? 'http://localhost:3000' : 'https://the-low-code-foundation.github.io/opennoodl-docs'; } diff --git a/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx b/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx index c15c7e5..43721b5 100644 --- a/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx +++ b/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx @@ -79,7 +79,7 @@ export default function Clippy() { const version = OpenAiStore.getVersion(); if (version === 'enterprise') { setHasApiKey(true); - setHasGPT4(OpenAiStore.getModel() === 'gpt-4'); + setHasGPT4(OpenAiStore.getModel() === 'gpt-4o-mini'); } else if (version === 'full-beta') { setHasApiKey(OpenAiStore.getIsAiApiKeyVerified()); } else { @@ -94,10 +94,10 @@ export default function Clippy() { async function doIt() { const version = OpenAiStore.getVersion(); if (version === 'enterprise') { - setHasGPT4(OpenAiStore.getModel() === 'gpt-4'); + setHasGPT4(OpenAiStore.getModel() === 'gpt-4o-mini'); } else { const models = await verifyOpenAiApiKey(OpenAiStore.getApiKey()); - setHasGPT4(!!models['gpt-4']); + setHasGPT4(!!models['gpt-4o-mini']); } } diff --git a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/SuggestCommand.tsx b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/SuggestCommand.tsx index 6c94bd1..2b77fde 100644 --- a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/SuggestCommand.tsx +++ b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/SuggestCommand.tsx @@ -44,7 +44,7 @@ export async function handleSuggestionCommand(prompt: string, statusCallback: (s { role: 'user', content: p } ]; - const response = await makeChatRequest('gpt-4', messages); + const response = await makeChatRequest('gpt-4o-mini', messages); console.log(response); return JSON.parse(response.content); diff --git a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/UICommand.tsx b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/UICommand.tsx index b11c67e..0e6b389 100644 --- a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/UICommand.tsx +++ b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/UICommand.tsx @@ -137,7 +137,7 @@ export async function handleUICommand( await ctx.chatStreamXml({ messages: messages, provider: { - model: 'gpt-4', + model: 'gpt-4o-mini', // The next context doesnt work with GPT-3.5 temperature: 0.1 }, diff --git a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/utils.tsx b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/utils.tsx index 0b8ed33..b0bedfe 100644 --- a/packages/noodl-editor/src/editor/src/views/Clippy/Commands/utils.tsx +++ b/packages/noodl-editor/src/editor/src/views/Clippy/Commands/utils.tsx @@ -70,8 +70,8 @@ export async function makeChatRequest(model: string, messages: unknown[]) { console.error(json.error); return null; } else { - const promptTokenCost = model === 'gpt-4' ? 0.03 : 0.002; - const completionTokenCost = model === 'gpt-4' ? 0.06 : 0.002; + const promptTokenCost = model === 'gpt-4o-mini' ? 0.03 : 0.002; + const completionTokenCost = model === 'gpt-4o-mini' ? 0.06 : 0.002; let cost = (json.usage.completion_tokens * completionTokenCost) / 1000 + (json.usage.prompt_tokens * promptTokenCost) / 1000; diff --git a/packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/sections/OpenAiSection.tsx b/packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/sections/OpenAiSection.tsx index 66fadbe..74b90bf 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/sections/OpenAiSection.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/sections/OpenAiSection.tsx @@ -29,7 +29,7 @@ export function OpenAiSection() { async function onVerifyApiKey() { const models = await verifyOpenAiApiKey(apiKey); if (models) { - const haveGpt4 = !!models['gpt-4']; + const haveGpt4 = !!models['gpt-4o-mini']; if (haveGpt4) { OpenAiStore.setIsAiApiKeyVerified(true); ToastLayer.showSuccess('OpenAI API Key is valid with GPT-4!'); @@ -78,7 +78,7 @@ export function OpenAiSection() { properties={{ options: [ { label: 'gpt-3', value: 'gpt-3' }, - { label: 'gpt-4', value: 'gpt-4' } + { label: 'gpt-4', value: 'gpt-4o-mini' } ] }} onChange={(value: AiModel) => { @@ -121,7 +121,7 @@ export function OpenAiSection() { properties={{ options: [ { label: 'gpt-3', value: 'gpt-3' }, - { label: 'gpt-4', value: 'gpt-4' } + { label: 'gpt-4', value: 'gpt-4o-mini' } ] }} onChange={(value: AiModel) => { diff --git a/packages/noodl-runtime/src/api/queryutils.js b/packages/noodl-runtime/src/api/queryutils.js index 9bd10bf..6ed811d 100644 --- a/packages/noodl-runtime/src/api/queryutils.js +++ b/packages/noodl-runtime/src/api/queryutils.js @@ -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} 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(',')); } diff --git a/packages/noodl-runtime/src/nodes/std-library/data/dbcollectionnode2.js b/packages/noodl-runtime/src/nodes/std-library/data/dbcollectionnode2.js index c5a1c2c..d6d3d7d 100644 --- a/packages/noodl-runtime/src/nodes/std-library/data/dbcollectionnode2.js +++ b/packages/noodl-runtime/src/nodes/std-library/data/dbcollectionnode2.js @@ -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']; diff --git a/packages/noodl-runtime/src/nodes/std-library/datetostring.js b/packages/noodl-runtime/src/nodes/std-library/datetostring.js index 93e0b3e..edf50ce 100644 --- a/packages/noodl-runtime/src/nodes/std-library/datetostring.js +++ b/packages/noodl-runtime/src/nodes/std-library/datetostring.js @@ -31,8 +31,6 @@ const DateToStringNode = { this._internal.currentInput = _value; this._format(); - this.flagOutputDirty('currentValue'); - this.sendSignalOnOutput('inputChanged'); } } }, @@ -49,30 +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 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); + 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(/\{yearShort\}/g, yearShort) - .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'); } } }; diff --git a/packages/noodl-viewer-react/src/nodes/navigation/navigate.js b/packages/noodl-viewer-react/src/nodes/navigation/navigate.js index 8090044..8c5d889 100644 --- a/packages/noodl-viewer-react/src/nodes/navigation/navigate.js +++ b/packages/noodl-viewer-react/src/nodes/navigation/navigate.js @@ -192,8 +192,7 @@ function setup(context, graphModel) { enums: pages.map((p) => ({ label: p.label, value: p.id - })), - allowEditOnly: true + })) }, group: 'General', displayName: 'Target Page', diff --git a/packages/noodl-viewer-react/src/nodes/navigation/navigation-stack.jsx b/packages/noodl-viewer-react/src/nodes/navigation/navigation-stack.jsx index 0de7d32..5a45844 100644 --- a/packages/noodl-viewer-react/src/nodes/navigation/navigation-stack.jsx +++ b/packages/noodl-viewer-react/src/nodes/navigation/navigation-stack.jsx @@ -1,3 +1,5 @@ +import React from 'react'; + import ASyncQueue from '../../async-queue'; import { createNodeFromReactComponent } from '../../react-component-node'; @@ -75,10 +77,13 @@ const PageStack = { const info = [{ type: 'text', value: 'Active Components:' }]; return info.concat( - this._internal.stack.map((p, i) => ({ - type: 'text', - value: '- ' + this._internal.pages.find((pi) => pi.id === p.pageId).label - })) + this._internal.stack.map((p) => { + const pageInfo = this._findPage(p.pageId); + return { + type: 'text', + value: '- ' + pageInfo.label + }; + }) ); }, defaultCss: { @@ -170,6 +175,7 @@ const PageStack = { topPageName: { type: 'string', displayName: 'Top Component Name', + group: 'General', get() { return this._internal.topPageName; } @@ -177,6 +183,7 @@ const PageStack = { stackDepth: { type: 'number', displayName: 'Stack Depth', + group: 'General', get() { return this._internal.stackDepth; } @@ -189,12 +196,31 @@ const PageStack = { _deregisterPageStack() { NavigationHandler.instance.deregisterPageStack(this._internal.name, this); }, - _pageNameForId(id) { - if (this._internal.pages === undefined) return; - const page = this._internal.pages.find((p) => p.id === id); - if (page === undefined) return; + /** + * @param {String} pageIdOrLabel + */ + _findPage(pageIdOrLabel) { + if (this._internal.pageInfo[pageIdOrLabel]) { + const pageInfo = this._internal.pageInfo[pageIdOrLabel]; + const pageRef = this._internal.pages.find((x) => x.id === pageIdOrLabel); + return { + component: String(pageInfo.component), + label: String(pageRef.label), + id: String(pageIdOrLabel) + }; + } - return page.label; + const pageRef = this._internal.pages.find((x) => x.label === pageIdOrLabel); + if (pageRef) { + const pageInfo = this._internal.pageInfo[pageRef.id]; + return { + component: String(pageInfo.component), + label: String(pageRef.label), + id: String(pageRef.id) + }; + } + + return undefined; }, setPageOutputs(outputs) { for (const prop in outputs) { @@ -230,8 +256,9 @@ const PageStack = { if (this._internal.pages === undefined || this._internal.pages.length === 0) return; - var startPageId, - params = {}; + let startPageId; + let params = {}; + var pageFromUrl = this.matchPageFromUrl(); if (pageFromUrl !== undefined) { // We have an url matching a page, use that page as start page @@ -239,13 +266,16 @@ const PageStack = { params = Object.assign({}, pageFromUrl.query, pageFromUrl.params); } else { - var startPageId = this._internal.startPageId; + startPageId = this._internal.startPageId; if (startPageId === undefined) startPageId = this._internal.pages[0].id; } - var pageInfo = this._internal.pageInfo[startPageId]; - - if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page + // Find the page by either ID or by Label + const pageInfo = this._findPage(startPageId); + if (pageInfo === undefined || pageInfo.component === undefined) { + // No page was found + return; + } var content = await this.nodeScope.createNode(pageInfo.component, guid()); @@ -269,7 +299,7 @@ const PageStack = { ]; this.setPageOutputs({ - topPageName: this._pageNameForId(startPageId), + topPageName: pageInfo.label, stackDepth: this._internal.stack.length }); }, @@ -458,13 +488,22 @@ const PageStack = { this._internal.asyncQueue.enqueue(this.replaceAsync.bind(this, args)); }, async replaceAsync(args) { - if (this._internal.pages === undefined || this._internal.pages.length === 0) return; + if (this._internal.pages === undefined || this._internal.pages.length === 0) { + return; + } - if (this._internal.isTransitioning) return; + if (this._internal.isTransitioning) { + return; + } - var pageId = args.target || this._internal.pages[0].id; - var pageInfo = this._internal.pageInfo[pageId]; - if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page + const pageId = args.target || this._internal.pages[0].id; + + // Find the page by either ID or by Label + const pageInfo = this._findPage(pageId); + if (pageInfo === undefined || pageInfo.component === undefined) { + // No page was found + return; + } // Remove all current pages in the stack var children = this.getChildren(); @@ -498,7 +537,7 @@ const PageStack = { ]; this.setPageOutputs({ - topPageName: this._pageNameForId(pageId), + topPageName: pageInfo.label, stackDepth: this._internal.stack.length }); @@ -510,13 +549,22 @@ const PageStack = { this._internal.asyncQueue.enqueue(this.navigateAsync.bind(this, args)); }, async navigateAsync(args) { - if (this._internal.pages === undefined || this._internal.pages.length === 0) return; + if (this._internal.pages === undefined || this._internal.pages.length === 0) { + return; + } - if (this._internal.isTransitioning) return; + if (this._internal.isTransitioning) { + return; + } - var pageId = args.target || this._internal.pages[0].id; - var pageInfo = this._internal.pageInfo[pageId]; - if (pageInfo === undefined || pageInfo.component === undefined) return; // No component specified for page + const pageId = args.target || this._internal.pages[0].id; + + // Find the page by either ID or by Label + const pageInfo = this._findPage(pageId); + if (pageInfo === undefined || pageInfo.component === undefined) { + // No page was found + return; + } // Create the container group const group = this.createPageContainer(); @@ -530,7 +578,7 @@ const PageStack = { group.addChild(content); // Connect navigate back nodes - var navigateBackNodes = content.nodeScope.getNodesWithType('PageStackNavigateBack'); + const navigateBackNodes = content.nodeScope.getNodesWithType('PageStackNavigateBack'); if (navigateBackNodes && navigateBackNodes.length > 0) { for (var j = 0; j < navigateBackNodes.length; j++) { navigateBackNodes[j]._setBackCallback(this.back.bind(this)); @@ -538,8 +586,8 @@ const PageStack = { } // Push the new top - var top = this._internal.stack[this._internal.stack.length - 1]; - var newTop = { + const top = this._internal.stack[this._internal.stack.length - 1]; + const newTop = { from: top.page, page: group, pageInfo: pageInfo, @@ -551,7 +599,7 @@ const PageStack = { }; this._internal.stack.push(newTop); this.setPageOutputs({ - topPageName: this._pageNameForId(args.target), + topPageName: pageInfo.label, stackDepth: this._internal.stack.length }); this._updateUrlWithTopPage(); @@ -584,8 +632,11 @@ const PageStack = { this.addChild(top.from, 0); top.backCallback && top.backCallback(args.backAction, args.results); + // Find the page by either ID or by Label + const pageInfo = this._findPage(this._internal.stack[this._internal.stack.length - 2].pageId); + this.setPageOutputs({ - topPageName: this._pageNameForId(this._internal.stack[this._internal.stack.length - 2].pageId), + topPageName: pageInfo.label, stackDepth: this._internal.stack.length - 1 }); diff --git a/packages/noodl-viewer-react/src/nodes/std-library/data/collectionnode2.js b/packages/noodl-viewer-react/src/nodes/std-library/data/collectionnode2.js index f32ac1e..d1502c1 100644 --- a/packages/noodl-viewer-react/src/nodes/std-library/data/collectionnode2.js +++ b/packages/noodl-viewer-react/src/nodes/std-library/data/collectionnode2.js @@ -2,8 +2,7 @@ const { Node } = require('@noodl/runtime'); -var Model = require('@noodl/runtime/src/model'), - Collection = require('@noodl/runtime/src/collection'); +const Collection = require('@noodl/runtime/src/collection'); var CollectionNode = { name: 'Collection2', @@ -27,6 +26,7 @@ var CollectionNode = { _this.scheduleAfterInputsHaveUpdated(function () { _this.sendSignalOnOutput('changed'); + _this.flagOutputDirty('firstItemId'); _this.flagOutputDirty('count'); collectionChangedScheduled = false; }); @@ -117,6 +117,17 @@ var CollectionNode = { return this._internal.collection; } }, + firstItemId: { + type: 'string', + displayName: 'First Item Id', + group: 'General', + getter: function () { + if (this._internal.collection) { + var firstItem = this._internal.collection.get(0); + if (firstItem !== undefined) return firstItem.getId(); + } + } + }, count: { type: 'number', displayName: 'Count', @@ -150,6 +161,7 @@ var CollectionNode = { collection.on('change', this._internal.collectionChangedCallback); this.flagOutputDirty('items'); + this.flagOutputDirty('firstItemId'); this.flagOutputDirty('count'); }, setSourceCollection: function (collection) { diff --git a/packages/noodl-viewer-react/static/viewer/global.d.ts.keep b/packages/noodl-viewer-react/static/viewer/global.d.ts.keep index e21e938..5461b82 100644 --- a/packages/noodl-viewer-react/static/viewer/global.d.ts.keep +++ b/packages/noodl-viewer-react/static/viewer/global.d.ts.keep @@ -482,7 +482,12 @@ declare namespace Noodl { const Records: RecordsApi; interface CurrentUserObject { - UserId: string; + id: string; + email: string; + emailVerified: boolean; + username: string; + + Properties: unknown; /** * Log out the current user and terminate the session.