mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-11 14:52:54 +01:00
Compare commits
15 Commits
feature/sl
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41711c3934 | ||
|
|
0ee55c26eb | ||
|
|
3a31b86d48 | ||
|
|
2f06952e4a | ||
|
|
44a40aef96 | ||
|
|
0a69765460 | ||
|
|
5d8b2d5bba | ||
|
|
2ebd57b29a | ||
|
|
5225d26870 | ||
|
|
eb71536cb0 | ||
|
|
d67afd3c60 | ||
|
|
1a048587d9 | ||
|
|
8e16d5f9d4 | ||
|
|
cdeb4b1c15 | ||
|
|
851dbc98d3 |
32
README.md
32
README.md
@@ -1,21 +1,21 @@
|
||||
# Noodl
|
||||
# Fluxscape
|
||||
|
||||
[Noodl](https://noodl.net) is a low-code platform where designers and developers build custom applications and experiences. Designed as a visual programming environment, it aims to expedite your development process. It promotes the swift and efficient creation of applications, requiring minimal coding knowledge.
|
||||
Fluxscape is a low-code platform where designers and developers build custom applications and experiences. Designed as a visual programming environment, it aims to expedite your development process. It promotes the swift and efficient creation of applications, requiring minimal coding knowledge.
|
||||
|
||||
## Documentation
|
||||
Documentation for how to use Noodl can be found here:
|
||||
[https://noodlapp.github.io/noodl-docs/](https://noodlapp.github.io/noodl-docs/)
|
||||
Documentation for how to use Fluxscape can be found here:
|
||||
[Fluxscape Documentation](https://docs.fluxscape.io)
|
||||
|
||||
## Community
|
||||
Main support channel is Discord: [https://www.noodl.net/community](https://www.noodl.net/community)
|
||||
Main support channel is Discord: [Fluxscape Discord](https://discord.gg/fXNW9EXa6A)
|
||||
|
||||
## Download releases
|
||||
Pre-built binaries can be [downloaded from Github](https://github.com/noodlapp/noodl/releases)
|
||||
Pre-built binaries can be [downloaded from Github](https://github.com/fluxscape/fluxscape/releases)
|
||||
|
||||
## Note for users who are migrating from the deprecated closed source version
|
||||
- [Migrating the project files and workspaces to a Git provider](https://noodlapp.github.io/noodl-docs/docs/guides/collaboration/migrating-from-noodl-hosted-git)
|
||||
- [Migrate backend and database](https://noodlapp.github.io/noodl-docs/docs/guides/deploy/using-an-external-backend#migrating-from-a-noodl-cloud-service)
|
||||
- [Self-host frontend](https://noodlapp.github.io/noodl-docs/docs/guides/deploy/hosting-frontend)
|
||||
- [Migrating the project files and workspaces to a Git provider](https://docs.fluxscape.io/docs/guides/collaboration/migrating-from-noodl-hosted-git/)
|
||||
- [Migrate backend and database](https://docs.fluxscape.io/docs/guides/deploy/using-an-external-backend/#migrating-from-a-noodl-cloud-service)
|
||||
- [Self-host frontend](https://docs.fluxscape.io/docs/guides/deploy/hosting-frontend/)
|
||||
|
||||
## Building from source
|
||||
|
||||
@@ -23,24 +23,24 @@ Pre-built binaries can be [downloaded from Github](https://github.com/noodlapp/n
|
||||
# Install all dependencies
|
||||
$ npm install
|
||||
|
||||
# Start the Noodl Editor and build a production version of the cloud and react runtime (useful when running Noodl from source but want to deploy to production)
|
||||
# Start the Fluxscape Editor and build a production version of the cloud and react runtime (useful when running Fluxscape from source but want to deploy to production)
|
||||
$ npm start
|
||||
|
||||
# Start the Noodl Editor and watch the filesystem for changes to the runtimes. Development versions of the runtimes, not meant for production (mostly due to source maps and file size)
|
||||
# Start the Fluxscape Editor and watch the filesystem for changes to the runtimes. Development versions of the runtimes, not meant for production (mostly due to source maps and file size)
|
||||
# This is ideal for a quick workflow when doing changes on the runtimes.
|
||||
$ npm run dev
|
||||
|
||||
# Start Noodl Editor test runner
|
||||
# Start Fluxscape Editor test runner
|
||||
$ npm run test:editor
|
||||
```
|
||||
|
||||
## Licenses
|
||||
This repository contains two different licenses for different parts of the Noodl platform.
|
||||
This repository contains two different licenses for different parts of the Fluxscape platform.
|
||||
|
||||
- Components related to the editor, used to edit Noodl projects, are under GPLv3
|
||||
- Components related to the end applications, used by the applications Noodl deploys, are under MIT
|
||||
- Components related to the editor, used to edit Fluxscape projects, are under GPLv3
|
||||
- Components related to the end applications, used by the applications Fluxscape deploys, are under MIT
|
||||
|
||||
All of the source code of applications created with Noodl are under MIT. This means you can do project specific changes to the runtime without having to redistribute your changes.
|
||||
All of the source code of applications created with Fluxscape are under MIT. This means you can do project specific changes to the runtime without having to redistribute your changes.
|
||||
|
||||
Packaged licensed under MIT:
|
||||
- `noodl-runtime`
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"private": true,
|
||||
"name": "@noodl/repo",
|
||||
"description": "Low-code for when experience matter",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net",
|
||||
"author": "Fluxscape <contact@fluxcsape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"version": "1.0.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
||||
@@ -11,7 +11,7 @@ import css from './Tooltip.module.scss';
|
||||
|
||||
export interface TooltipProps extends UnsafeStyleProps {
|
||||
content: SingleSlot;
|
||||
fineType?: string;
|
||||
fineType?: string | string[];
|
||||
children: Slot;
|
||||
showAfterMs?: number;
|
||||
|
||||
@@ -79,9 +79,17 @@ export function Tooltip({
|
||||
|
||||
{fineType && (
|
||||
<div className={css['FineType']}>
|
||||
<Label size={LabelSize.Small} variant={TextType.Secondary}>
|
||||
{fineType}
|
||||
</Label>
|
||||
{Array.isArray(fineType) ? (
|
||||
fineType.map((x) => (
|
||||
<Label size={LabelSize.Small} variant={TextType.Secondary}>
|
||||
{x}
|
||||
</Label>
|
||||
))
|
||||
) : (
|
||||
<Label size={LabelSize.Small} variant={TextType.Secondary}>
|
||||
{fineType}
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</BaseDialog>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Noodl</title>
|
||||
<title>Fluxscape</title>
|
||||
<link href="../assets/lib/fontawesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="../assets/css/style.css" rel="stylesheet" />
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ import { Keybinding } from '@noodl-utils/keyboard/Keybinding';
|
||||
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
|
||||
|
||||
export namespace Keybindings {
|
||||
export const SEARCH = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_F);
|
||||
|
||||
export const CLOUD_SERVICE_OPEN_DASHBOARD = new Keybinding(KeyMod.CtrlCmd, KeyMod.Shift, KeyCode.KEY_P);
|
||||
export const CLOUD_SERVICE_OPEN_DASHBOARD_BROWSER = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_P);
|
||||
|
||||
export const REFRESH_PREVIEW = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_R);
|
||||
export const OPEN_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_D);
|
||||
export const OPEN_CLOUD_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyMod.Shift, KeyCode.KEY_R);
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function execute(command: Command, event: MessageEvent) {
|
||||
|
||||
// Deploy to temp folder
|
||||
await compilation.deployToFolder(tempDir, {
|
||||
environment: command.cloudService ? new Environment(command.cloudService) : undefined
|
||||
environment: command.cloudService ? new Environment("", command.cloudService) : undefined
|
||||
});
|
||||
|
||||
// Upload to S3
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Environment, ExternalCloudService } from '@noodl-models/CloudServices/ExternalCloudService';
|
||||
import {
|
||||
CloudServiceEvent,
|
||||
CloudServiceEvents,
|
||||
CreateEnvironment,
|
||||
CreateEnvironmentRequest,
|
||||
Environment,
|
||||
ICloudBackendService,
|
||||
ICloudService,
|
||||
UpdateEnvironmentRequest
|
||||
@@ -12,6 +12,9 @@ import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { getCloudServices } from '@noodl-models/projectmodel.editor';
|
||||
import { Model } from '@noodl-utils/model';
|
||||
|
||||
import { GlobalCloudService } from './providers/GlobalCloudService';
|
||||
import { ProjectCloudService } from './providers/ProjectCloudService';
|
||||
|
||||
export type CloudQueueItem = {
|
||||
frontendId: string;
|
||||
environmentId: string;
|
||||
@@ -20,7 +23,9 @@ export type CloudQueueItem = {
|
||||
class CloudBackendService implements ICloudBackendService {
|
||||
private _isLoading = false;
|
||||
private _collection?: Environment[];
|
||||
private _localExternal = new ExternalCloudService();
|
||||
|
||||
private _globalProvider = new GlobalCloudService();
|
||||
private _projectProvider = new ProjectCloudService();
|
||||
|
||||
get isLoading(): boolean {
|
||||
return this._isLoading;
|
||||
@@ -32,12 +37,14 @@ class CloudBackendService implements ICloudBackendService {
|
||||
|
||||
constructor(private readonly service: CloudService) {}
|
||||
|
||||
async fetch(): Promise<Environment[]> {
|
||||
async fetch(project: ProjectModel): Promise<Environment[]> {
|
||||
this._isLoading = true;
|
||||
try {
|
||||
// Fetch environments from local machine
|
||||
const localEnvironments = await this._localExternal.list();
|
||||
this._collection = localEnvironments.map((x) => new Environment(x));
|
||||
const projectResults = await this._projectProvider.list(project);
|
||||
this._collection = projectResults.map((x) => new Environment("project", x));
|
||||
|
||||
const globalResults = await this._globalProvider.list(project);
|
||||
this._collection = this._collection.concat(globalResults.map((x) => new Environment("global", x)));
|
||||
} finally {
|
||||
this._isLoading = false;
|
||||
this.service.notifyListeners(CloudServiceEvent.BackendUpdated);
|
||||
@@ -48,7 +55,7 @@ class CloudBackendService implements ICloudBackendService {
|
||||
async fromProject(project: ProjectModel): Promise<Environment> {
|
||||
const activeCloudServices = getCloudServices(project);
|
||||
if (!this._collection) {
|
||||
await this.fetch();
|
||||
await this.fetch(project);
|
||||
}
|
||||
|
||||
return this.items.find((b) => {
|
||||
@@ -56,16 +63,16 @@ class CloudBackendService implements ICloudBackendService {
|
||||
});
|
||||
}
|
||||
|
||||
async create(options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
|
||||
return await this._localExternal.create(options);
|
||||
async create(project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
|
||||
return await this._projectProvider.create(project, options);
|
||||
}
|
||||
|
||||
async update(options: UpdateEnvironmentRequest): Promise<boolean> {
|
||||
return await this._localExternal.update(options);
|
||||
async update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean> {
|
||||
return await this._projectProvider.update(project, options);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
return await this._localExternal.delete(id);
|
||||
async delete(project: ProjectModel, id: string): Promise<boolean> {
|
||||
return await this._projectProvider.delete(project, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,15 +102,11 @@ export class CloudService extends Model<CloudServiceEvent, CloudServiceEvents> i
|
||||
*/
|
||||
public async prefetch() {
|
||||
this.reset();
|
||||
await this.fetch();
|
||||
}
|
||||
|
||||
public async fetch() {
|
||||
await this.backend.fetch();
|
||||
await this.backend.fetch(ProjectModel.instance);
|
||||
}
|
||||
|
||||
public async getActiveEnvironment(project: ProjectModel): Promise<Environment> {
|
||||
await this.backend.fetch();
|
||||
await this.backend.fetch(project);
|
||||
return this.backend.fromProject(project);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './CloudService';
|
||||
export * from './type';
|
||||
export * from './ExternalCloudService';
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
import { JSONStorage } from '@noodl/platform';
|
||||
|
||||
import { CreateEnvironment, CreateEnvironmentRequest, UpdateEnvironmentRequest } from '@noodl-models/CloudServices';
|
||||
import {
|
||||
CreateEnvironment,
|
||||
CreateEnvironmentRequest,
|
||||
EnvironmentDataFormat,
|
||||
ICloudServiceProvider,
|
||||
UpdateEnvironmentRequest
|
||||
} from '@noodl-models/CloudServices';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
/** The data format is separated from our internal model. */
|
||||
export type EnvironmentDataFormat = {
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
masterKey: string;
|
||||
appId: string;
|
||||
endpoint: string;
|
||||
};
|
||||
|
||||
export class ExternalCloudService {
|
||||
async list(): Promise<EnvironmentDataFormat[]> {
|
||||
export class GlobalCloudService implements ICloudServiceProvider {
|
||||
async list(_project: ProjectModel): Promise<EnvironmentDataFormat[]> {
|
||||
const local = await JSONStorage.get('externalBrokers');
|
||||
return local.brokers || [];
|
||||
}
|
||||
|
||||
async create(options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
|
||||
async create(_project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
|
||||
const id = `${options.url}-${options.appId}`;
|
||||
|
||||
const newBroker: EnvironmentDataFormat = {
|
||||
@@ -44,7 +40,7 @@ export class ExternalCloudService {
|
||||
};
|
||||
}
|
||||
|
||||
async update(options: UpdateEnvironmentRequest): Promise<boolean> {
|
||||
async update(_project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean> {
|
||||
const local = await JSONStorage.get('externalBrokers');
|
||||
const brokers: EnvironmentDataFormat[] = local.brokers || [];
|
||||
|
||||
@@ -63,7 +59,7 @@ export class ExternalCloudService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
async delete(_project: ProjectModel, id: string): Promise<boolean> {
|
||||
const local = await JSONStorage.get('externalBrokers');
|
||||
const brokers: EnvironmentDataFormat[] = local.brokers || [];
|
||||
|
||||
@@ -79,25 +75,3 @@ export class ExternalCloudService {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Environment {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
masterKeyUpdatedAt: string;
|
||||
masterKey: string;
|
||||
appId: string;
|
||||
url: string;
|
||||
|
||||
constructor(item: EnvironmentDataFormat) {
|
||||
this.id = item.id;
|
||||
this.name = item.name;
|
||||
this.description = item.description;
|
||||
this.createdAt = '';
|
||||
this.masterKeyUpdatedAt = '';
|
||||
this.masterKey = item.masterKey;
|
||||
this.appId = item.appId;
|
||||
this.url = item.endpoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { filesystem } from '@noodl/platform';
|
||||
|
||||
import {
|
||||
CreateEnvironment,
|
||||
CreateEnvironmentRequest,
|
||||
EnvironmentDataFormat,
|
||||
ICloudServiceProvider,
|
||||
UpdateEnvironmentRequest
|
||||
} from '@noodl-models/CloudServices';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
/**
|
||||
* Store the Cloud Service relative to the project folder.
|
||||
*/
|
||||
export class ProjectCloudService implements ICloudServiceProvider {
|
||||
private async load(project: ProjectModel) {
|
||||
const dirpath = filesystem.resolve(project._retainedProjectDirectory, ".noodl");
|
||||
const filepath = filesystem.resolve(dirpath, "cloudservices.json");
|
||||
if (!filesystem.exists(filepath)) {
|
||||
return []
|
||||
}
|
||||
|
||||
// TODO: Validate file content
|
||||
return filesystem.readJson(filepath);
|
||||
}
|
||||
|
||||
private async save(project: ProjectModel, items: EnvironmentDataFormat[]) {
|
||||
const dirpath = filesystem.resolve(project._retainedProjectDirectory, ".noodl");
|
||||
const filepath = filesystem.resolve(dirpath, "cloudservices.json");
|
||||
if (!filesystem.exists(filepath)) {
|
||||
await filesystem.makeDirectory(dirpath);
|
||||
}
|
||||
|
||||
await filesystem.writeJson(filepath, items);
|
||||
}
|
||||
|
||||
async list(project: ProjectModel): Promise<EnvironmentDataFormat[]> {
|
||||
if (!project || !project._retainedProjectDirectory) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await this.load(project);
|
||||
}
|
||||
|
||||
async create(project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
|
||||
const id = `${options.url}-${options.appId}`;
|
||||
|
||||
const newBroker: EnvironmentDataFormat = {
|
||||
enabled: true,
|
||||
id,
|
||||
name: options.name,
|
||||
description: options.description,
|
||||
masterKey: options.masterKey,
|
||||
appId: options.appId,
|
||||
endpoint: options.url
|
||||
};
|
||||
|
||||
const current = await this.load(project);
|
||||
await this.save(project, [...current, newBroker]);
|
||||
|
||||
return {
|
||||
id: newBroker.id,
|
||||
appId: newBroker.appId,
|
||||
url: newBroker.endpoint,
|
||||
masterKey: newBroker.masterKey
|
||||
};
|
||||
}
|
||||
|
||||
async update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean> {
|
||||
const current = await this.load(project);
|
||||
|
||||
// Find and update
|
||||
const broker = current.find((x) => x.id === options.id);
|
||||
if (!broker) return false;
|
||||
|
||||
if (typeof options.name !== undefined) broker.name = options.name;
|
||||
if (typeof options.description !== undefined) broker.description = options.description;
|
||||
if (typeof options.appId !== undefined) broker.appId = options.appId;
|
||||
if (typeof options.masterKey !== undefined) broker.masterKey = options.masterKey;
|
||||
if (typeof options.url !== undefined) broker.endpoint = options.url;
|
||||
|
||||
await this.save(project, current);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async delete(project: ProjectModel, id: string): Promise<boolean> {
|
||||
const current = await this.load(project);
|
||||
|
||||
// Find the environment
|
||||
const found = current.find((b) => b.id === id);
|
||||
if (found) {
|
||||
// Delete the environment
|
||||
current.splice(current.indexOf(found), 1);
|
||||
}
|
||||
|
||||
// Save the list
|
||||
await this.save(project, current);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Environment } from '@noodl-models/CloudServices';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { IModel } from '@noodl-utils/model';
|
||||
|
||||
@@ -30,11 +29,11 @@ export interface ICloudBackendService {
|
||||
get isLoading(): boolean;
|
||||
get items(): Environment[];
|
||||
|
||||
fetch(): Promise<Environment[]>;
|
||||
fetch(project: ProjectModel): Promise<Environment[]>;
|
||||
fromProject(project: ProjectModel): Promise<Environment> | undefined;
|
||||
create(options: CreateEnvironmentRequest): Promise<CreateEnvironment>;
|
||||
update(options: UpdateEnvironmentRequest): Promise<boolean>;
|
||||
delete(id: string): Promise<boolean>;
|
||||
create(project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment>;
|
||||
update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean>;
|
||||
delete(project: ProjectModel, id: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export enum CloudServiceEvent {
|
||||
@@ -55,3 +54,54 @@ export interface ICloudService extends IModel<CloudServiceEvent, CloudServiceEve
|
||||
|
||||
get backend(): ICloudBackendService;
|
||||
}
|
||||
|
||||
/** The data format is separated from our internal model. */
|
||||
export type EnvironmentDataFormat = {
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
masterKey: string;
|
||||
appId: string;
|
||||
endpoint: string;
|
||||
};
|
||||
|
||||
export class Environment {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
masterKeyUpdatedAt: string;
|
||||
masterKey: string;
|
||||
appId: string;
|
||||
url: string;
|
||||
|
||||
provider: string;
|
||||
|
||||
get typeDisplayName() {
|
||||
switch (this.provider) {
|
||||
case "project": return "Self hosted";
|
||||
default: return "Global Self hosted"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(provider: string, item: EnvironmentDataFormat) {
|
||||
this.provider = provider;
|
||||
|
||||
this.id = item.id;
|
||||
this.name = item.name;
|
||||
this.description = item.description;
|
||||
this.createdAt = '';
|
||||
this.masterKeyUpdatedAt = '';
|
||||
this.masterKey = item.masterKey;
|
||||
this.appId = item.appId;
|
||||
this.url = item.endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICloudServiceProvider {
|
||||
list(project: ProjectModel | undefined): Promise<EnvironmentDataFormat[]>;
|
||||
create(project: ProjectModel | undefined, options: CreateEnvironmentRequest): Promise<CreateEnvironment>;
|
||||
update(project: ProjectModel | undefined, options: UpdateEnvironmentRequest): Promise<boolean>;
|
||||
delete(project: ProjectModel | undefined, id: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AppRegistry } from '@noodl-models/app_registry';
|
||||
import { SidebarModel } from '@noodl-models/sidebar';
|
||||
import { Keybinding } from '@noodl-utils/keyboard/Keybinding';
|
||||
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
|
||||
import { Keybindings } from '@noodl-constants/Keybindings';
|
||||
|
||||
import { IconName } from '@noodl-core-ui/components/common/Icon';
|
||||
|
||||
@@ -69,7 +68,7 @@ export function installSidePanel({ isLesson }: SetupEditorOptions) {
|
||||
SidebarModel.instance.register({
|
||||
id: 'search',
|
||||
name: 'Search',
|
||||
fineType: new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_F).label,
|
||||
fineType: Keybindings.SEARCH.label,
|
||||
order: 2,
|
||||
icon: IconName.Search,
|
||||
panel: SearchPanel
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CloudService } from '@noodl-models/CloudServices';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import SchemaModel from '@noodl-models/schemamodel';
|
||||
|
||||
class FormCollection {
|
||||
@@ -193,7 +194,7 @@ export default class CloudFormation {
|
||||
}) {
|
||||
// Create new cloud services if needed
|
||||
if (options.cloudServices.id === undefined) {
|
||||
CloudService.instance.backend.fetch().then((collection) => {
|
||||
CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => {
|
||||
// TODO(OS): Cloud formation Cloud Service
|
||||
// // Make sure we have a unique name for the cloud services
|
||||
// const orgName = options.cloudServices.name;
|
||||
|
||||
@@ -41,7 +41,7 @@ export class HtmlProcessor {
|
||||
baseUrl = baseUrl + '/';
|
||||
}
|
||||
|
||||
const title = parameters.title || settings.htmlTitle || 'Noodl Viewer';
|
||||
const title = parameters.title || settings.htmlTitle || 'Fluxscape Viewer';
|
||||
let headCode = settings.headCode || '';
|
||||
|
||||
if (parameters.headCode) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ITemplateProvider, ProgressCallback, TemplateItem, TemplateListFilter }
|
||||
*/
|
||||
export class NoodlDocsTemplateProvider implements ITemplateProvider {
|
||||
get name(): string {
|
||||
return 'https://docs.noodl.net';
|
||||
return this.getDocsEndpoint() || 'https://docs.fluxscape.io';
|
||||
}
|
||||
|
||||
constructor(private readonly getDocsEndpoint: () => string) {}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class SchemaHandler {
|
||||
return; // No project broker
|
||||
}
|
||||
|
||||
CloudService.instance.backend.fetch().then((collection) => {
|
||||
CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => {
|
||||
// Find by the Url / Endpoint and app id
|
||||
let environment = collection.find((b) => {
|
||||
return b.url === activeBroker.endpoint && b.appId === activeBroker.appId;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Keybindings } from '@noodl-constants/Keybindings';
|
||||
import { Environment } from '@noodl-models/CloudServices';
|
||||
import ParseDashboardServer from '@noodl-utils/parsedashboardserver';
|
||||
|
||||
import { Icon, IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
|
||||
import { IconButton, IconButtonState, IconButtonVariant } from '@noodl-core-ui/components/inputs/IconButton';
|
||||
import { PrimaryButton, PrimaryButtonSize, PrimaryButtonVariant } from '@noodl-core-ui/components/inputs/PrimaryButton';
|
||||
import { DialogRenderDirection } from '@noodl-core-ui/components/layout/BaseDialog';
|
||||
import { Collapsible } from '@noodl-core-ui/components/layout/Collapsible';
|
||||
import { Tooltip } from '@noodl-core-ui/components/popups/Tooltip';
|
||||
import { Label, LabelSpacingSize } from '@noodl-core-ui/components/typography/Label';
|
||||
import { Text, TextType } from '@noodl-core-ui/components/typography/Text';
|
||||
|
||||
@@ -84,7 +87,7 @@ export function CloudServiceCard({
|
||||
<div className={css['MetaBar']}>
|
||||
<div className={classNames([css['TypeDisplay'], isEditorEnvironment && css['is-editor-environment']])}>
|
||||
<Icon icon={IconName.CloudCheck} size={IconSize.Small} UNSAFE_style={{ marginRight: 4 }} />
|
||||
{'Self hosted '}
|
||||
{environment.typeDisplayName + ' '}
|
||||
{errorMessage && <span className={css['ArchivedDisplay']}>({errorMessage})</span>}
|
||||
{isEditorEnvironment && <span className={css['UsedInEditorDisplay']}>(Used in editor)</span>}
|
||||
</div>
|
||||
@@ -118,12 +121,21 @@ export function CloudServiceCard({
|
||||
</div>
|
||||
|
||||
{isEditorEnvironment && (
|
||||
<PrimaryButton
|
||||
label="Open dashboard"
|
||||
size={PrimaryButtonSize.Small}
|
||||
onClick={onDashboardClicked}
|
||||
isGrowing
|
||||
/>
|
||||
<Tooltip
|
||||
content="Open the Parse Dashboard"
|
||||
fineType={[
|
||||
`In Window: ${Keybindings.CLOUD_SERVICE_OPEN_DASHBOARD.label}`,
|
||||
`In Browser: ${Keybindings.CLOUD_SERVICE_OPEN_DASHBOARD_BROWSER.label}`
|
||||
]}
|
||||
renderDirection={DialogRenderDirection.Below}
|
||||
>
|
||||
<PrimaryButton
|
||||
label="Open dashboard"
|
||||
size={PrimaryButtonSize.Small}
|
||||
onClick={onDashboardClicked}
|
||||
isGrowing
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CloudService, Environment } from '@noodl-models/CloudServices';
|
||||
import { ToastType } from '../../../ToastLayer/components/ToastCard';
|
||||
import { CloudServiceCard } from '../CloudServiceCard';
|
||||
import { useCloudServiceContext } from '../CloudServicePanel.context';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
export interface CloudServiceCardItemProps {
|
||||
environment: Environment;
|
||||
@@ -20,7 +21,7 @@ export function CloudServiceCardItem({ environment, deleteEnvironment }: CloudSe
|
||||
async function onDelete() {
|
||||
await deleteEnvironment();
|
||||
await runActivity('Deleting cloud service...', async () => {
|
||||
const response: boolean = await CloudService.instance.backend.delete(environment.id);
|
||||
const response: boolean = await CloudService.instance.backend.delete(ProjectModel.instance, environment.id);
|
||||
return {
|
||||
type: ToastType.Success,
|
||||
message: 'Cloud service deleted'
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Text, TextType } from '@noodl-core-ui/components/typography/Text';
|
||||
|
||||
import { ToastType } from '../../../ToastLayer/components/ToastCard';
|
||||
import { useCloudServiceContext } from '../CloudServicePanel.context';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
function isValidParseUrl(url: string) {
|
||||
if (!url) return false;
|
||||
@@ -63,7 +64,7 @@ export function CloudServiceCreateModal({ isVisible, onClose }: CloudServiceCrea
|
||||
|
||||
async function onCreate() {
|
||||
await runActivity('Creating Cloud Service...', async () => {
|
||||
await cloudService.backend.create({
|
||||
await cloudService.backend.create(ProjectModel.instance, {
|
||||
name,
|
||||
description,
|
||||
masterKey: masterKey ? masterKey : undefined,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FeedbackType } from '@noodl-constants/FeedbackType';
|
||||
import { CloudService, Environment } from '@noodl-models/CloudServices';
|
||||
@@ -12,6 +12,7 @@ import { HStack, VStack } from '@noodl-core-ui/components/layout/Stack';
|
||||
import { Text } from '@noodl-core-ui/components/typography/Text';
|
||||
|
||||
import { ToastLayer } from '../../../ToastLayer';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
export interface CloudServiceModalProps {
|
||||
isVisible: boolean;
|
||||
@@ -60,7 +61,7 @@ function AsSelfHosted({
|
||||
}
|
||||
|
||||
CloudService.instance.backend
|
||||
.update({
|
||||
.update(ProjectModel.instance, {
|
||||
id: environment.id,
|
||||
name,
|
||||
description,
|
||||
@@ -70,7 +71,7 @@ function AsSelfHosted({
|
||||
})
|
||||
.then(() => {
|
||||
ToastLayer.showSuccess(`Updated Cloud Service`);
|
||||
CloudService.instance.backend.fetch();
|
||||
CloudService.instance.backend.fetch(ProjectModel.instance);
|
||||
})
|
||||
.catch(() => {
|
||||
ToastLayer.showError(`Failed to update Cloud Service`);
|
||||
|
||||
@@ -35,7 +35,7 @@ export function CloudServiceContextProvider({ children }) {
|
||||
const { hasActivity, runActivity } = useActivityQueue({
|
||||
onSuccess: async () => {
|
||||
// Always fetch all the backends after something have changed
|
||||
await cloudService.backend.fetch();
|
||||
await cloudService.backend.fetch(ProjectModel.instance);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +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';
|
||||
|
||||
@@ -54,7 +56,7 @@ export function SearchPanel() {
|
||||
}
|
||||
}, [debouncedSearchTerm]);
|
||||
|
||||
function onSearchItemClicked(searchResult) {
|
||||
function onSearchItemClicked(searchResult: SearchResultItem) {
|
||||
if (searchResult.type === 'Component') {
|
||||
NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, {
|
||||
breadcrumbs: false,
|
||||
@@ -85,29 +87,7 @@ export function SearchPanel() {
|
||||
|
||||
<div className={css.SearchResults}>
|
||||
{searchResults.map((component) => (
|
||||
<Section
|
||||
title={`${component.componentName} (${component.results.length} result${
|
||||
component.results.length > 1 ? 's' : ''
|
||||
})`}
|
||||
key={component.componentId}
|
||||
variant={SectionVariant.Panel}
|
||||
>
|
||||
{component.results.map((result, index) => (
|
||||
<div
|
||||
className={classNames(
|
||||
css.SearchResultItem
|
||||
// lastActiveComponentId === result.componentTarget.id && css['is-active']
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => onSearchItemClicked(result)}
|
||||
>
|
||||
<Label variant={TextType.Proud}>
|
||||
{result.userLabel ? result.type + ' - ' + result.userLabel : result.type}
|
||||
</Label>
|
||||
<Text>{result.label}</Text>
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
<SearchItem key={component.componentId} component={component} onSearchItemClicked={onSearchItemClicked} />
|
||||
))}
|
||||
{searchResults.length === 0 && debouncedSearchTerm.length > 0 && (
|
||||
<Container hasXSpacing hasYSpacing>
|
||||
@@ -118,3 +98,51 @@ export function SearchPanel() {
|
||||
</BasePanel>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
function SearchItem({ component, onSearchItemClicked }: SearchItemProps) {
|
||||
const resultCountText = `${component.results.length} result${component.results.length > 1 ? 's' : ''}`;
|
||||
let titleText = `${component.componentName} (${resultCountText})`;
|
||||
|
||||
// We expect there to always be at least one result, to get the full component name.
|
||||
const isInCloudFunctions = component.results[0].componentTarget.name.startsWith('/#__cloud__/');
|
||||
if (isInCloudFunctions) {
|
||||
titleText += ' (Cloud Function)';
|
||||
}
|
||||
|
||||
return (
|
||||
<Section title={titleText} variant={SectionVariant.Panel}>
|
||||
{component.results.map((result, index) => (
|
||||
<div
|
||||
className={classNames(
|
||||
css.SearchResultItem
|
||||
// lastActiveComponentId === result.componentTarget.id && css['is-active']
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => onSearchItemClicked(result)}
|
||||
>
|
||||
<Label variant={TextType.Proud}>
|
||||
{result.userLabel ? result.type + ' - ' + result.userLabel : result.type}
|
||||
</Label>
|
||||
<Text>{result.label}</Text>
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Noodl Viewer</title>
|
||||
<title>Fluxscape Viewer</title>
|
||||
<link href="../../assets/lib/fontawesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="assets/style.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="../../assets/lib/jquery-min.js"></script>
|
||||
|
||||
@@ -67,7 +67,7 @@ function startServer(app, projectGetSettings, projectGetInfo, projectGetComponen
|
||||
ProjectModules.instance.injectIntoHtml(info.projectDirectory, data, '/', function (injected) {
|
||||
projectGetSettings((settings) => {
|
||||
settings = settings || {};
|
||||
injected = injected.replace('{{#title#}}', settings.htmlTitle || 'Noodl Viewer');
|
||||
injected = injected.replace('{{#title#}}', settings.htmlTitle || 'Fluxscape Viewer');
|
||||
injected = injected.replace('{{#customHeadCode#}}', settings.headCode || '');
|
||||
|
||||
response.writeHead(200, {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": "2.7.0",
|
||||
"main": "src/index.ts",
|
||||
"description": "",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net",
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"dependencies": {
|
||||
"desktop-trampoline": "https://github.com/desktop/desktop-trampoline/archive/refs/tags/v0.9.8.tar.gz",
|
||||
"dugite": "^1.106.0",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": "2.7.0",
|
||||
"main": "src/index.ts",
|
||||
"description": "Cross platform implementation of platform specific features.",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net",
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"dependencies": {
|
||||
"@noodl/platform": "file:../noodl-platform",
|
||||
"@noodl/platform-node": "file:../noodl-platform-node"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": "2.7.0",
|
||||
"main": "src/index.ts",
|
||||
"description": "Cross platform implementation of platform specific features.",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net",
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage"
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"version": "2.7.0",
|
||||
"main": "src/index.ts",
|
||||
"description": "Cross platform implementation of platform specific features.",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net"
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ function createRecordsAPI(modelScope) {
|
||||
|
||||
return {
|
||||
async query(className, query, options) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().query({
|
||||
collection: className,
|
||||
@@ -39,6 +40,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async count(className, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().count({
|
||||
collection: className,
|
||||
@@ -60,6 +62,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async distinct(className, property, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().distinct({
|
||||
collection: className,
|
||||
@@ -82,6 +85,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async aggregate(className, group, query) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().aggregate({
|
||||
collection: className,
|
||||
@@ -104,6 +108,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
@@ -126,6 +131,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async increment(objectOrId, properties, options) {
|
||||
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
@@ -149,6 +155,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async save(objectOrId, properties, options) {
|
||||
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
@@ -179,6 +186,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async create(className, properties, options) {
|
||||
if (typeof className === "undefined") throw new Error("'className' is undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().create({
|
||||
collection: className,
|
||||
@@ -197,6 +205,7 @@ function createRecordsAPI(modelScope) {
|
||||
},
|
||||
|
||||
async delete(objectOrId, options) {
|
||||
if (typeof objectOrId === 'undefined') return Promise.reject(new Error("'objectOrId' is undefined."));
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
@@ -265,7 +274,7 @@ function createRecordsAPI(modelScope) {
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(rr || 'Failed to add relation.'));
|
||||
reject(Error(err || 'Failed to add relation.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,12 +75,12 @@ var SetDbModelPropertiedNodeDefinition = {
|
||||
_this.setError('Missing Record Id');
|
||||
return;
|
||||
}
|
||||
var model = internal.model;
|
||||
|
||||
for (var i in internal.inputValues) {
|
||||
model.set(i, internal.inputValues[i], { resolve: true });
|
||||
|
||||
const model = internal.model;
|
||||
for (const key in internal.inputValues) {
|
||||
model.set(key, internal.inputValues[key], { resolve: true });
|
||||
}
|
||||
|
||||
|
||||
CloudStore.forScope(_this.nodeScope.modelScope).save({
|
||||
collection: internal.collectionId,
|
||||
objectId: model.getId(), // Get the objectId part of the model id
|
||||
|
||||
@@ -59,6 +59,7 @@ const DateToStringNode = {
|
||||
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);
|
||||
@@ -68,6 +69,7 @@ const DateToStringNode = {
|
||||
.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);
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"version": "2.7.0",
|
||||
"main": "src/index.d.ts",
|
||||
"description": "",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net"
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@noodl/cloud-runtime",
|
||||
"author": "Noodl <info@noodl.net>",
|
||||
"homepage": "https://noodl.net",
|
||||
"author": "Fluxscape <contact@fluxscape.io>",
|
||||
"homepage": "https://fluxscape.io",
|
||||
"version": "0.6.3",
|
||||
"license": "MIT",
|
||||
"main": "dist/main.js",
|
||||
|
||||
@@ -74,6 +74,59 @@ declare namespace Noodl {
|
||||
*/
|
||||
const Object: any;
|
||||
|
||||
type RecordQuery<T> =
|
||||
{
|
||||
lessThan: T
|
||||
} |
|
||||
{
|
||||
lessThanOrEqualTo: T
|
||||
} |
|
||||
{
|
||||
greaterThan: T
|
||||
} |
|
||||
{
|
||||
greaterThanOrEqualTo: T
|
||||
} |
|
||||
{
|
||||
equalTo: T
|
||||
} |
|
||||
{
|
||||
notEqualTo: T
|
||||
} |
|
||||
{
|
||||
containedIn: T
|
||||
} |
|
||||
{
|
||||
notContainedIn : T
|
||||
} |
|
||||
{
|
||||
exists: T
|
||||
} |
|
||||
{
|
||||
matchesRegex: T
|
||||
} |
|
||||
{
|
||||
text: T
|
||||
} |
|
||||
{
|
||||
idEqualTo: T
|
||||
} |
|
||||
{
|
||||
idContainedIn: T
|
||||
} |
|
||||
{
|
||||
pointsTo: T
|
||||
} |
|
||||
{
|
||||
relatedTo: T
|
||||
};
|
||||
|
||||
type RecordQueryField<T> = T extends RecordQuery<any> ?
|
||||
{ [K in keyof T]: { [P in K]: T[P] } & Partial<Record<Exclude<keyof T, K>, never>> }[keyof T]
|
||||
: never;
|
||||
|
||||
type RecordSortKey<T extends string> = (`${T}` | `-${T}`)[];
|
||||
|
||||
interface RecordsApi {
|
||||
/**
|
||||
* This is an async function that will query the database using the query
|
||||
@@ -115,15 +168,17 @@ declare namespace Noodl {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
query(
|
||||
className: RecordClassName,
|
||||
query?: any,
|
||||
query<TClassName extends RecordClassName>(
|
||||
className: TClassName,
|
||||
query?:
|
||||
RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
|
||||
{ and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
|
||||
options?: {
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
sort?: string[];
|
||||
include?: any;
|
||||
select?: any;
|
||||
sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
|
||||
include?: string | (keyof DatabaseSchema[TClassName])[];
|
||||
select?: string | (keyof DatabaseSchema[TClassName])[];
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
|
||||
@@ -72,8 +72,10 @@ const Navigate = {
|
||||
backCallback: (action, results) => {
|
||||
this._internal.backResults = results;
|
||||
|
||||
for (var key in results) {
|
||||
if (this.hasOutput('backResult-' + key)) this.flagOutputDirty('backResult-' + key);
|
||||
for (const key in results) {
|
||||
if (this.hasOutput('backResult-' + key)) {
|
||||
this.flagOutputDirty('backResult-' + key);
|
||||
}
|
||||
}
|
||||
|
||||
if (action !== undefined) this.sendSignalOnOutput(action);
|
||||
@@ -114,22 +116,23 @@ const Navigate = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'target')
|
||||
if (name === 'target') {
|
||||
return this.registerInput(name, {
|
||||
set: this.setTargetPageId.bind(this)
|
||||
});
|
||||
else if (name === 'transition')
|
||||
} else if (name === 'transition') {
|
||||
return this.registerInput(name, {
|
||||
set: this.setTransition.bind(this)
|
||||
});
|
||||
else if (name.startsWith('tr-'))
|
||||
} else if (name.startsWith('tr-')) {
|
||||
return this.registerInput(name, {
|
||||
set: this.setTransitionParam.bind(this, name.substring('tr-'.length))
|
||||
});
|
||||
else if (name.startsWith('pm-'))
|
||||
} else if (name.startsWith('pm-')) {
|
||||
return this.registerInput(name, {
|
||||
set: this.setPageParam.bind(this, name.substring('pm-'.length))
|
||||
});
|
||||
}
|
||||
},
|
||||
registerOutputIfNeeded: function (name) {
|
||||
if (this.hasOutput(name)) {
|
||||
@@ -174,18 +177,24 @@ function setup(context, graphModel) {
|
||||
if (Transitions[transition]) ports = ports.concat(Transitions[transition].ports(node.parameters));
|
||||
}
|
||||
|
||||
// if(node.parameters['stack'] !== undefined) {
|
||||
var pageStacks = graphModel.getNodesWithType('Page Stack');
|
||||
var pageStack = pageStacks.find(
|
||||
const pageStacks = graphModel.getNodesWithType('Page Stack');
|
||||
const pageStack = pageStacks.find(
|
||||
(ps) => (ps.parameters['name'] || 'Main') === (node.parameters['stack'] || 'Main')
|
||||
);
|
||||
|
||||
if (pageStack !== undefined) {
|
||||
var pages = pageStack.parameters['pages'];
|
||||
const pages = pageStack.parameters['pages'];
|
||||
if (pages !== undefined && pages.length > 0) {
|
||||
ports.push({
|
||||
plug: 'input',
|
||||
type: { name: 'enum', enums: pages.map((p) => ({ label: p.label, value: p.id })), allowEditOnly: true },
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: pages.map((p) => ({
|
||||
label: p.label,
|
||||
value: p.id
|
||||
})),
|
||||
allowEditOnly: true
|
||||
},
|
||||
group: 'General',
|
||||
displayName: 'Target Page',
|
||||
name: 'target',
|
||||
@@ -193,14 +202,14 @@ function setup(context, graphModel) {
|
||||
});
|
||||
|
||||
// See if there is a target page with component
|
||||
var targetPageId = node.parameters['target'] || pages[0].id;
|
||||
var targetComponentName = pageStack.parameters['pageComp-' + targetPageId];
|
||||
const targetPageId = node.parameters['target'] || pages[0].id;
|
||||
const targetComponentName = pageStack.parameters['pageComp-' + targetPageId];
|
||||
if (targetComponentName !== undefined) {
|
||||
const component = graphModel.components[targetComponentName];
|
||||
|
||||
if (component !== undefined) {
|
||||
// Make all inputs of the component to inputs of this navigation node
|
||||
for (var inputName in component.inputPorts) {
|
||||
for (const inputName in component.inputPorts) {
|
||||
ports.push({
|
||||
name: 'pm-' + inputName,
|
||||
displayName: inputName,
|
||||
@@ -245,7 +254,6 @@ function setup(context, graphModel) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
context.editorConnection.sendDynamicPorts(node.id, ports);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export default {
|
||||
group: 'General',
|
||||
plug: 'input',
|
||||
type: 'string',
|
||||
default: 'Noodl Viewer',
|
||||
default: 'Fluxscape Viewer',
|
||||
tooltip: 'The title that web browsers show',
|
||||
ignoreInExport: true
|
||||
},
|
||||
|
||||
@@ -131,6 +131,59 @@ declare namespace Noodl {
|
||||
*/
|
||||
const Events: EventsApi;
|
||||
|
||||
type RecordQuery<T> =
|
||||
{
|
||||
lessThan: T
|
||||
} |
|
||||
{
|
||||
lessThanOrEqualTo: T
|
||||
} |
|
||||
{
|
||||
greaterThan: T
|
||||
} |
|
||||
{
|
||||
greaterThanOrEqualTo: T
|
||||
} |
|
||||
{
|
||||
equalTo: T
|
||||
} |
|
||||
{
|
||||
notEqualTo: T
|
||||
} |
|
||||
{
|
||||
containedIn: T
|
||||
} |
|
||||
{
|
||||
notContainedIn : T
|
||||
} |
|
||||
{
|
||||
exists: T
|
||||
} |
|
||||
{
|
||||
matchesRegex: T
|
||||
} |
|
||||
{
|
||||
text: T
|
||||
} |
|
||||
{
|
||||
idEqualTo: T
|
||||
} |
|
||||
{
|
||||
idContainedIn: T
|
||||
} |
|
||||
{
|
||||
pointsTo: T
|
||||
} |
|
||||
{
|
||||
relatedTo: T
|
||||
};
|
||||
|
||||
type RecordQueryField<T> = T extends RecordQuery<any> ?
|
||||
{ [K in keyof T]: { [P in K]: T[P] } & Partial<Record<Exclude<keyof T, K>, never>> }[keyof T]
|
||||
: never;
|
||||
|
||||
type RecordSortKey<T extends string> = (`${T}` | `-${T}`)[];
|
||||
|
||||
interface RecordsApi {
|
||||
/**
|
||||
* This is an async function that will query the database using the query
|
||||
@@ -172,15 +225,17 @@ declare namespace Noodl {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
query(
|
||||
className: RecordClassName,
|
||||
query?: any,
|
||||
query<TClassName extends RecordClassName>(
|
||||
className: TClassName,
|
||||
query?:
|
||||
RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
|
||||
{ and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
|
||||
options?: {
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
sort?: string[];
|
||||
include?: any;
|
||||
select?: any;
|
||||
sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
|
||||
include?: string | (keyof DatabaseSchema[TClassName])[];
|
||||
select?: string | (keyof DatabaseSchema[TClassName])[];
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user