mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-10 14:22:53 +01:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50677fd529 | ||
|
|
307a13f7be | ||
|
|
4e96d23585 | ||
|
|
2fd4b67a08 | ||
|
|
50e266e3e4 | ||
|
|
7ca69b809a | ||
|
|
80c7c01805 | ||
|
|
fc89bd9ce1 | ||
|
|
2cfd36147a | ||
|
|
0e13a8b033 | ||
|
|
680bd58442 | ||
|
|
95db9f6528 | ||
|
|
6205d08451 | ||
|
|
14786b2144 | ||
|
|
e25155556f | ||
|
|
016837f466 | ||
|
|
fff03c05bf | ||
|
|
5dbb11bac8 | ||
|
|
d80870e835 | ||
|
|
a98e381f8c | ||
|
|
72aec29e27 | ||
|
|
2eb18acfef | ||
|
|
e1a1b31213 | ||
|
|
5febc490b4 | ||
|
|
48541347f0 | ||
|
|
cc79ea5f7e | ||
|
|
46f6cb2da9 | ||
|
|
34c3d07112 | ||
|
|
d85dce8d02 | ||
|
|
89ed2d602f |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -48981,7 +48981,7 @@
|
||||
},
|
||||
"packages/noodl-editor": {
|
||||
"name": "fluxscape-editor",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.1.2",
|
||||
"@jaames/iro": "^5.5.2",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Node-Based App Builder for Scalability & Rapid Development, a fork of Noodl",
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"main": "src/main/main.js",
|
||||
"scripts": {
|
||||
"build": "npx ts-node -P ./tsconfig.build.json ./scripts/build.ts",
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './NodeReferencesContext';
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import { platform } from '@noodl/platform';
|
||||
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
@@ -64,7 +65,9 @@ export function DeployPopup(props: DeployPopupProps) {
|
||||
}
|
||||
|
||||
function FluxscapeDeployTab() {
|
||||
const params = {};
|
||||
const params = {
|
||||
version: platform.getVersion()
|
||||
};
|
||||
|
||||
const projectId = ProjectModel.instance.id;
|
||||
if (projectId) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -5,10 +5,8 @@ import { useSidePanelKeyboardCommands } from '@noodl-hooks/useKeyboardCommands';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
|
||||
import { 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';
|
||||
@@ -21,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
|
||||
@@ -56,7 +54,7 @@ export function SearchPanel() {
|
||||
}
|
||||
}, [debouncedSearchTerm]);
|
||||
|
||||
function onSearchItemClicked(searchResult: SearchResultItem) {
|
||||
function onSearchItemClicked(searchResult: SearchResult) {
|
||||
if (searchResult.type === 'Component') {
|
||||
NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, {
|
||||
breadcrumbs: false,
|
||||
@@ -99,21 +97,9 @@ export function SearchPanel() {
|
||||
);
|
||||
}
|
||||
|
||||
type SearchResultItem = {
|
||||
componentTarget: ComponentModel;
|
||||
label: string;
|
||||
nodeTarget: NodeGraphNode;
|
||||
type: string;
|
||||
userLabel: string;
|
||||
};
|
||||
|
||||
type SearchItemProps = {
|
||||
component: {
|
||||
componentName: string;
|
||||
componentId: string;
|
||||
results: SearchResultItem[];
|
||||
};
|
||||
onSearchItemClicked: (item: SearchResultItem) => void;
|
||||
component: ReturnType<typeof performSearch>[0];
|
||||
onSearchItemClicked: (item: SearchResult) => void;
|
||||
};
|
||||
|
||||
function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
|
||||
@@ -130,11 +116,11 @@ function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
|
||||
<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']
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => onSearchItemClicked(result)}
|
||||
>
|
||||
<Label variant={TextType.Proud}>
|
||||
|
||||
@@ -73,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'];
|
||||
@@ -191,13 +192,13 @@ class CloudStore {
|
||||
// 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));
|
||||
@@ -257,11 +258,34 @@ class CloudStore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* objectId: string;
|
||||
* collection: string;
|
||||
* keys?: string[] | string;
|
||||
* include?: string[] | string;
|
||||
* excludeKeys?: string[] | string;
|
||||
* success: (data: unknown) => void;
|
||||
* error: (error: unknown) => void;
|
||||
* }} options
|
||||
*/
|
||||
fetch(options) {
|
||||
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('&') : ''),
|
||||
@@ -433,6 +457,8 @@ class CloudStore {
|
||||
* file: {
|
||||
* name: string;
|
||||
* }
|
||||
* success: (data: unknown) => void;
|
||||
* error: (error: unknown) => void;
|
||||
* }} options
|
||||
*/
|
||||
deleteFile(options) {
|
||||
@@ -445,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;
|
||||
}
|
||||
@@ -518,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;
|
||||
|
||||
@@ -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(','));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ function createRecordsAPI(modelScope) {
|
||||
|
||||
return {
|
||||
async query(className, query, options) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
if (typeof className === 'undefined') throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().query({
|
||||
collection: className,
|
||||
@@ -27,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) => {
|
||||
@@ -40,7 +40,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async count(className, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
if (typeof className === 'undefined') throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().count({
|
||||
collection: className,
|
||||
@@ -62,7 +62,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async distinct(className, property, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
if (typeof className === 'undefined') throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().distinct({
|
||||
collection: className,
|
||||
@@ -85,7 +85,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async aggregate(className, group, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
if (typeof className === 'undefined') throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().aggregate({
|
||||
collection: className,
|
||||
@@ -107,20 +107,35 @@ function createRecordsAPI(modelScope) {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string | { getId(): string; }} objectOrId
|
||||
* @param {{
|
||||
* className: string;
|
||||
* keys?: string[] | string;
|
||||
* include?: string[] | string;
|
||||
* excludeKeys?: string[] | string;
|
||||
* }} options
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async fetch(objectOrId, options) {
|
||||
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) {
|
||||
@@ -186,7 +201,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async create(className, properties, options) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
if (typeof className === 'undefined') throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().create({
|
||||
collection: className,
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,6 +227,9 @@ declare namespace Noodl {
|
||||
objectOrId: string | { getId(): string; },
|
||||
options?: {
|
||||
className?: RecordClassName;
|
||||
keys?: string[] | string;
|
||||
include?: string[] | string;
|
||||
excludeKeys?: string[] | string;
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
|
||||
@@ -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('.', '-')
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -40,8 +40,8 @@ const ClosePopupNode = {
|
||||
this._internal.closeCallback = cb;
|
||||
},
|
||||
scheduleClose: function () {
|
||||
var _this = this;
|
||||
var internal = this._internal;
|
||||
const _this = this;
|
||||
const internal = this._internal;
|
||||
if (!internal.hasScheduledClose) {
|
||||
internal.hasScheduledClose = true;
|
||||
this.scheduleAfterInputsHaveUpdated(function () {
|
||||
@@ -51,8 +51,9 @@ const ClosePopupNode = {
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
if (this._internal.closeCallback)
|
||||
if (this._internal.closeCallback) {
|
||||
this._internal.closeCallback(this._internal.closeAction, this._internal.resultValues);
|
||||
}
|
||||
},
|
||||
closeActionTriggered: function (name) {
|
||||
this._internal.closeAction = name;
|
||||
@@ -112,9 +113,8 @@ module.exports = {
|
||||
var closeActions = node.parameters['closeActions'];
|
||||
if (closeActions) {
|
||||
closeActions = closeActions ? closeActions.split(',') : undefined;
|
||||
for (var i in closeActions) {
|
||||
var p = closeActions[i];
|
||||
|
||||
for (const i in closeActions) {
|
||||
const p = closeActions[i];
|
||||
ports.push({
|
||||
type: 'signal',
|
||||
plug: 'input',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -53,15 +53,24 @@ const ShowPopupNode = {
|
||||
|
||||
this.context.showPopup(this._internal.target, this._internal.popupParams, {
|
||||
senderNode: this.nodeScope.componentOwner,
|
||||
/**
|
||||
* @param {string | undefined} action
|
||||
* @param {*} results
|
||||
*/
|
||||
onClosePopup: (action, results) => {
|
||||
this._internal.closeResults = results;
|
||||
|
||||
for (var key in results) {
|
||||
if (this.hasOutput('closeResult-' + key)) this.flagOutputDirty('closeResult-' + key);
|
||||
for (const key in results) {
|
||||
if (this.hasOutput('closeResult-' + key)) {
|
||||
this.flagOutputDirty('closeResult-' + key);
|
||||
}
|
||||
}
|
||||
|
||||
if (!action) this.sendSignalOnOutput('Closed');
|
||||
else this.sendSignalOnOutput(action);
|
||||
if (!action) {
|
||||
this.sendSignalOnOutput('Closed');
|
||||
} else {
|
||||
this.sendSignalOnOutput(action);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -3,6 +3,17 @@ const CloudStore = require('@noodl/runtime/src/api/cloudstore');
|
||||
|
||||
('use strict');
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {{
|
||||
* endpoint: string;
|
||||
* method: string;
|
||||
* appId: string;
|
||||
* content: any;
|
||||
* success: (json: object) => void;
|
||||
* error: (json: object | undefined) => void;
|
||||
* }} options
|
||||
*/
|
||||
function _makeRequest(path, options) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
@@ -15,7 +26,9 @@ function _makeRequest(path, options) {
|
||||
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
options.success(json);
|
||||
} else options.error(json);
|
||||
} else {
|
||||
options.error(json);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -191,7 +204,7 @@ var CloudFunctionNode = {
|
||||
this.sendSignalOnOutput('success');
|
||||
},
|
||||
error: (e) => {
|
||||
const error = typeof e === 'string' ? e : e.error || 'Failed running cloud function.';
|
||||
const error = typeof e === 'string' ? e : e?.error || 'Failed running cloud function.';
|
||||
this._internal.lastCallResult = {
|
||||
status: 'failure',
|
||||
error
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const { Node } = require('@noodl/runtime');
|
||||
|
||||
const Model = require('@noodl/runtime/src/model');
|
||||
const Collection = require('@noodl/runtime/src/collection');
|
||||
|
||||
var SetVariableNodeDefinition = {
|
||||
const SetVariableNodeDefinition = {
|
||||
name: 'Set Variable',
|
||||
docs: 'https://docs.noodl.net/nodes/data/variable/set-variable',
|
||||
category: 'Data',
|
||||
usePortAsLabel: 'name',
|
||||
color: 'data',
|
||||
initialize: function () {
|
||||
var internal = this._internal;
|
||||
|
||||
const internal = this._internal;
|
||||
internal.variablesModel = Model.get('--ndl--global-variables');
|
||||
},
|
||||
getInspectInfo() {
|
||||
if (this._internal.name) {
|
||||
return this._internal.variablesModel.get(this._internal.name);
|
||||
}
|
||||
|
||||
return '[No value set]';
|
||||
},
|
||||
outputs: {
|
||||
done: {
|
||||
type: 'signal',
|
||||
@@ -74,17 +78,22 @@ var SetVariableNodeDefinition = {
|
||||
if (this.hasScheduledStore) return;
|
||||
this.hasScheduledStore = true;
|
||||
|
||||
var internal = this._internal;
|
||||
const internal = this._internal;
|
||||
this.scheduleAfterInputsHaveUpdated(function () {
|
||||
this.hasScheduledStore = false;
|
||||
|
||||
var value = internal.setWith === 'emptyString' ? '' : internal.value;
|
||||
let value = internal.setWith === 'emptyString' ? '' : internal.value;
|
||||
|
||||
// Can set arrays with "id" or array
|
||||
if (internal.setWith === 'object' && typeof value === 'string') value = Model.get(value);
|
||||
|
||||
// Can set arrays with "id" or array
|
||||
if (internal.setWith === 'array' && typeof value === 'string') value = Collection.get(value);
|
||||
|
||||
if (internal.setWith === 'object' && typeof value === 'string') value = Model.get(value); // Can set arrays with "id" or array
|
||||
if (internal.setWith === 'array' && typeof value === 'string') value = Collection.get(value); // Can set arrays with "id" or array
|
||||
if (internal.setWith === 'boolean') value = !!value;
|
||||
|
||||
//use forceChange to always trigger Variable nodes to send the value on their output, even if it's the same value twice
|
||||
// use forceChange to always trigger Variable nodes to send the value on
|
||||
// their output, even if it's the same value twice
|
||||
internal.variablesModel.set(internal.name, value, {
|
||||
forceChange: true
|
||||
});
|
||||
@@ -96,10 +105,11 @@ var SetVariableNodeDefinition = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'value')
|
||||
if (name === 'value') {
|
||||
this.registerInput(name, {
|
||||
set: this.setValue.bind(this)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -500,8 +500,8 @@ function onFrameStart() {
|
||||
}
|
||||
|
||||
function _typename(type) {
|
||||
if (typeof type === 'string') return type;
|
||||
else return type.name;
|
||||
if (typeof type === 'object') return type.name;
|
||||
return type;
|
||||
}
|
||||
|
||||
function userInputSetter(name, value) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{"url":"noodl-app.png"},
|
||||
{"url":"load_terminator.js"},
|
||||
{"url":"noodl.deploy.js"},
|
||||
{"url":"noodl.deploy.js.map"},
|
||||
{"url":"react.production.min.js"},
|
||||
{"url":"react-dom.production.min.js"}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -274,6 +274,9 @@ declare namespace Noodl {
|
||||
objectOrId: string | { getId(): string; },
|
||||
options?: {
|
||||
className?: RecordClassName;
|
||||
keys?: string[] | string;
|
||||
include?: string[] | string;
|
||||
excludeKeys?: string[] | string;
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
@@ -482,7 +485,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.
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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"),
|
||||
};
|
||||
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')
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||
fallback: {
|
||||
events: require.resolve('events/'),
|
||||
events: require.resolve('events/')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const { merge } = require("webpack-merge");
|
||||
const common = require("./webpack.deploy.common.js");
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.deploy.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
watch: true,
|
||||
mode: 'development',
|
||||
devtool: 'inline-source-map',
|
||||
watch: true
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { merge } = require("webpack-merge");
|
||||
const common = require("./webpack.deploy.common.js");
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.deploy.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "production",
|
||||
mode: 'production',
|
||||
devtool: 'source-map'
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ module.exports = {
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||
fallback: {
|
||||
events: require.resolve('events/'),
|
||||
events: require.resolve('events/')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
|
||||
@@ -2,5 +2,6 @@ const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.ssr.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production'
|
||||
mode: 'production',
|
||||
devtool: 'source-map'
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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: "production",
|
||||
mode: 'production',
|
||||
devtool: 'source-map'
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user