5 Commits

Author SHA1 Message Date
Eric Tuvesson
b4ef346d25 chore: Combine search keybinding definitions 2024-11-18 17:50:41 +01:00
Eric Tuvesson
6205d08451 fix: showPopup replace error (#82)
Error: "TypeError: Cannot read properties of undefined (reading 'replace')"
2024-11-18 17:06:10 +01:00
Eric Tuvesson
14786b2144 chore(runtime): Add some JSDocs (#81) 2024-11-18 17:01:49 +01:00
Eric Tuvesson
e25155556f feat(runtime): 'Set Variable' node, add editor getInspectInfo (#80)
It is very nice to be able to inspect the value inside the variable even via the "Set Variable" node. Most of the time I see an additional variable node placed above the "Set Variable" node to just inspect the current value.
2024-11-13 13:35:03 +01:00
Eric Tuvesson
016837f466 feat(runtime): Add "keys" and "excludeKeys" to fetch record api (#79)
Adding support for some more options when fetching a record.
91f9aca25b/src/Routers/ClassesRouter.js (L60-L68)
2024-11-12 15:50:49 +01:00
12 changed files with 157 additions and 81 deletions

View File

@@ -8,6 +8,7 @@ import { ipcRenderer } from 'electron';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { platform } from '@noodl/platform'; import { platform } from '@noodl/platform';
import { Keybindings } from '@noodl-constants/Keybindings';
import { App } from '@noodl-models/app'; import { App } from '@noodl-models/app';
import { AppRegistry } from '@noodl-models/app_registry'; import { AppRegistry } from '@noodl-models/app_registry';
import { CloudService } from '@noodl-models/CloudServices'; import { CloudService } from '@noodl-models/CloudServices';
@@ -156,7 +157,7 @@ export function EditorPage({ route }: EditorPageProps) {
useKeyboardCommands(() => [ useKeyboardCommands(() => [
{ {
handler: () => SidebarModel.instance.switch('search'), handler: () => SidebarModel.instance.switch('search'),
keybinding: KeyMod.CtrlCmd | KeyCode.KEY_F keybinding: Keybindings.SEARCH.hash
}, },
{ {
handler: () => EventDispatcher.instance.emit('viewer-open-devtools'), handler: () => EventDispatcher.instance.emit('viewer-open-devtools'),

View File

@@ -5,9 +5,9 @@ import { useSidePanelKeyboardCommands } from '@noodl-hooks/useKeyboardCommands';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Keybindings } from '@noodl-constants/Keybindings';
import { ComponentModel } from '@noodl-models/componentmodel'; import { ComponentModel } from '@noodl-models/componentmodel';
import { NodeGraphNode } from '@noodl-models/nodegraphmodel'; import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
import { performSearch } from '@noodl-utils/universal-search'; import { performSearch } from '@noodl-utils/universal-search';
import { SearchInput } from '@noodl-core-ui/components/inputs/SearchInput'; import { SearchInput } from '@noodl-core-ui/components/inputs/SearchInput';
@@ -40,7 +40,7 @@ export function SearchPanel() {
inputRef.current.focus(); inputRef.current.focus();
inputRef.current.select(); inputRef.current.select();
}, },
keybinding: KeyMod.CtrlCmd | KeyCode.KEY_F keybinding: Keybindings.SEARCH.hash
} }
], ],
'search' 'search'

View File

@@ -263,7 +263,9 @@ class CloudStore {
* @param {{ * @param {{
* objectId: string; * objectId: string;
* collection: string; * collection: string;
* keys?: string[] | string;
* include?: string[] | string; * include?: string[] | string;
* excludeKeys?: string[] | string;
* success: (data: unknown) => void; * success: (data: unknown) => void;
* error: (error: unknown) => void; * error: (error: unknown) => void;
* }} options * }} options
@@ -275,6 +277,16 @@ class CloudStore {
args.push('include=' + (Array.isArray(options.include) ? options.include.join(',') : options.include)); args.push('include=' + (Array.isArray(options.include) ? options.include.join(',') : options.include));
} }
if (options.keys) {
args.push('keys=' + (Array.isArray(options.keys) ? options.keys.join(',') : options.keys));
}
if (options.excludeKeys) {
args.push(
'excludeKeys=' + (Array.isArray(options.excludeKeys) ? options.excludeKeys.join(',') : options.excludeKeys)
);
}
this._makeRequest( this._makeRequest(
'/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''), '/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''),
{ {

View File

@@ -112,7 +112,9 @@ function createRecordsAPI(modelScope) {
* @param {string | { getId(): string; }} objectOrId * @param {string | { getId(): string; }} objectOrId
* @param {{ * @param {{
* className: string; * className: string;
* keys?: string[] | string;
* include?: string[] | string; * include?: string[] | string;
* excludeKeys?: string[] | string;
* }} options * }} options
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
@@ -129,7 +131,9 @@ function createRecordsAPI(modelScope) {
cloudstore().fetch({ cloudstore().fetch({
collection: className, collection: className,
objectId: objectOrId, objectId: objectOrId,
include: options ? options.include : undefined, keys: options?.keys,
include: options?.include,
excludeKeys: options?.excludeKeys,
success: function (response) { success: function (response) {
const record = cloudstore()._fromJSON(response, className); const record = cloudstore()._fromJSON(response, className);
resolve(record); resolve(record);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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