1 Commits

Author SHA1 Message Date
Eric Tuvesson
1190dbdf89 feat: Slider add children to thumb
I am not super happy with how this works as the child is inside the thumb div. Ideally it should be moving around the thumb, but then that might be a tooltip lib?
2024-05-21 16:47:16 +02:00
39 changed files with 186 additions and 495 deletions

View File

@@ -1,21 +1,21 @@
# Fluxscape
# Noodl
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.
[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.
## Documentation
Documentation for how to use Fluxscape can be found here:
[Fluxscape Documentation](https://docs.fluxscape.io)
Documentation for how to use Noodl can be found here:
[https://noodlapp.github.io/noodl-docs/](https://noodlapp.github.io/noodl-docs/)
## Community
Main support channel is Discord: [Fluxscape Discord](https://discord.gg/fXNW9EXa6A)
Main support channel is Discord: [https://www.noodl.net/community](https://www.noodl.net/community)
## Download releases
Pre-built binaries can be [downloaded from Github](https://github.com/fluxscape/fluxscape/releases)
Pre-built binaries can be [downloaded from Github](https://github.com/noodlapp/noodl/releases)
## Note for users who are migrating from the deprecated closed source version
- [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/)
- [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)
## Building from source
@@ -23,24 +23,24 @@ Pre-built binaries can be [downloaded from Github](https://github.com/fluxscape/
# Install all dependencies
$ npm install
# 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)
# 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)
$ npm start
# 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)
# 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)
# This is ideal for a quick workflow when doing changes on the runtimes.
$ npm run dev
# Start Fluxscape Editor test runner
# Start Noodl Editor test runner
$ npm run test:editor
```
## Licenses
This repository contains two different licenses for different parts of the Fluxscape platform.
This repository contains two different licenses for different parts of the Noodl platform.
- 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
- 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
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.
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.
Packaged licensed under MIT:
- `noodl-runtime`

View File

@@ -2,8 +2,8 @@
"private": true,
"name": "@noodl/repo",
"description": "Low-code for when experience matter",
"author": "Fluxscape <contact@fluxcsape.io>",
"homepage": "https://fluxscape.io",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net",
"version": "1.0.0",
"workspaces": [
"packages/*"

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -1,9 +1,9 @@
import { Environment, ExternalCloudService } from '@noodl-models/CloudServices/ExternalCloudService';
import {
CloudServiceEvent,
CloudServiceEvents,
CreateEnvironment,
CreateEnvironmentRequest,
Environment,
ICloudBackendService,
ICloudService,
UpdateEnvironmentRequest
@@ -12,9 +12,6 @@ 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;
@@ -23,9 +20,7 @@ export type CloudQueueItem = {
class CloudBackendService implements ICloudBackendService {
private _isLoading = false;
private _collection?: Environment[];
private _globalProvider = new GlobalCloudService();
private _projectProvider = new ProjectCloudService();
private _localExternal = new ExternalCloudService();
get isLoading(): boolean {
return this._isLoading;
@@ -37,14 +32,12 @@ class CloudBackendService implements ICloudBackendService {
constructor(private readonly service: CloudService) {}
async fetch(project: ProjectModel): Promise<Environment[]> {
async fetch(): Promise<Environment[]> {
this._isLoading = true;
try {
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)));
// Fetch environments from local machine
const localEnvironments = await this._localExternal.list();
this._collection = localEnvironments.map((x) => new Environment(x));
} finally {
this._isLoading = false;
this.service.notifyListeners(CloudServiceEvent.BackendUpdated);
@@ -55,7 +48,7 @@ class CloudBackendService implements ICloudBackendService {
async fromProject(project: ProjectModel): Promise<Environment> {
const activeCloudServices = getCloudServices(project);
if (!this._collection) {
await this.fetch(project);
await this.fetch();
}
return this.items.find((b) => {
@@ -63,16 +56,16 @@ class CloudBackendService implements ICloudBackendService {
});
}
async create(project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
return await this._projectProvider.create(project, options);
async create(options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
return await this._localExternal.create(options);
}
async update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean> {
return await this._projectProvider.update(project, options);
async update(options: UpdateEnvironmentRequest): Promise<boolean> {
return await this._localExternal.update(options);
}
async delete(project: ProjectModel, id: string): Promise<boolean> {
return await this._projectProvider.delete(project, id);
async delete(id: string): Promise<boolean> {
return await this._localExternal.delete(id);
}
}
@@ -102,11 +95,15 @@ export class CloudService extends Model<CloudServiceEvent, CloudServiceEvents> i
*/
public async prefetch() {
this.reset();
await this.backend.fetch(ProjectModel.instance);
await this.fetch();
}
public async fetch() {
await this.backend.fetch();
}
public async getActiveEnvironment(project: ProjectModel): Promise<Environment> {
await this.backend.fetch(project);
await this.backend.fetch();
return this.backend.fromProject(project);
}
}

View File

@@ -1,21 +1,25 @@
import { JSONStorage } from '@noodl/platform';
import {
CreateEnvironment,
CreateEnvironmentRequest,
EnvironmentDataFormat,
ICloudServiceProvider,
UpdateEnvironmentRequest
} from '@noodl-models/CloudServices';
import { ProjectModel } from '@noodl-models/projectmodel';
import { CreateEnvironment, CreateEnvironmentRequest, UpdateEnvironmentRequest } from '@noodl-models/CloudServices';
export class GlobalCloudService implements ICloudServiceProvider {
async list(_project: ProjectModel): Promise<EnvironmentDataFormat[]> {
/** 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[]> {
const local = await JSONStorage.get('externalBrokers');
return local.brokers || [];
}
async create(_project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
async create(options: CreateEnvironmentRequest): Promise<CreateEnvironment> {
const id = `${options.url}-${options.appId}`;
const newBroker: EnvironmentDataFormat = {
@@ -40,7 +44,7 @@ export class GlobalCloudService implements ICloudServiceProvider {
};
}
async update(_project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean> {
async update(options: UpdateEnvironmentRequest): Promise<boolean> {
const local = await JSONStorage.get('externalBrokers');
const brokers: EnvironmentDataFormat[] = local.brokers || [];
@@ -59,7 +63,7 @@ export class GlobalCloudService implements ICloudServiceProvider {
return true;
}
async delete(_project: ProjectModel, id: string): Promise<boolean> {
async delete(id: string): Promise<boolean> {
const local = await JSONStorage.get('externalBrokers');
const brokers: EnvironmentDataFormat[] = local.brokers || [];
@@ -75,3 +79,25 @@ export class GlobalCloudService implements ICloudServiceProvider {
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;
}
}

View File

@@ -1,2 +1,3 @@
export * from './CloudService';
export * from './type';
export * from './ExternalCloudService';

View File

@@ -1,101 +0,0 @@
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;
}
}

View File

@@ -1,3 +1,4 @@
import { Environment } from '@noodl-models/CloudServices';
import { ProjectModel } from '@noodl-models/projectmodel';
import { IModel } from '@noodl-utils/model';
@@ -29,11 +30,11 @@ export interface ICloudBackendService {
get isLoading(): boolean;
get items(): Environment[];
fetch(project: ProjectModel): Promise<Environment[]>;
fetch(): Promise<Environment[]>;
fromProject(project: ProjectModel): Promise<Environment> | undefined;
create(project: ProjectModel, options: CreateEnvironmentRequest): Promise<CreateEnvironment>;
update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise<boolean>;
delete(project: ProjectModel, id: string): Promise<boolean>;
create(options: CreateEnvironmentRequest): Promise<CreateEnvironment>;
update(options: UpdateEnvironmentRequest): Promise<boolean>;
delete(id: string): Promise<boolean>;
}
export enum CloudServiceEvent {
@@ -54,54 +55,3 @@ 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>;
}

View File

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

View File

@@ -1,5 +1,4 @@
import { CloudService } from '@noodl-models/CloudServices';
import { ProjectModel } from '@noodl-models/projectmodel';
import SchemaModel from '@noodl-models/schemamodel';
class FormCollection {
@@ -194,7 +193,7 @@ export default class CloudFormation {
}) {
// Create new cloud services if needed
if (options.cloudServices.id === undefined) {
CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => {
CloudService.instance.backend.fetch().then((collection) => {
// TODO(OS): Cloud formation Cloud Service
// // Make sure we have a unique name for the cloud services
// const orgName = options.cloudServices.name;

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ export default class SchemaHandler {
return; // No project broker
}
CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => {
CloudService.instance.backend.fetch().then((collection) => {
// Find by the Url / Endpoint and app id
let environment = collection.find((b) => {
return b.url === activeBroker.endpoint && b.appId === activeBroker.appId;

View File

@@ -1,16 +1,13 @@
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';
@@ -87,7 +84,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 }} />
{environment.typeDisplayName + ' '}
{'Self hosted '}
{errorMessage && <span className={css['ArchivedDisplay']}>({errorMessage})</span>}
{isEditorEnvironment && <span className={css['UsedInEditorDisplay']}>(Used in editor)</span>}
</div>
@@ -121,21 +118,12 @@ export function CloudServiceCard({
</div>
{isEditorEnvironment && (
<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>
<PrimaryButton
label="Open dashboard"
size={PrimaryButtonSize.Small}
onClick={onDashboardClicked}
isGrowing
/>
)}
</div>
</div>

View File

@@ -5,7 +5,6 @@ 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;
@@ -21,7 +20,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(ProjectModel.instance, environment.id);
const response: boolean = await CloudService.instance.backend.delete(environment.id);
return {
type: ToastType.Success,
message: 'Cloud service deleted'

View File

@@ -12,7 +12,6 @@ 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;
@@ -64,7 +63,7 @@ export function CloudServiceCreateModal({ isVisible, onClose }: CloudServiceCrea
async function onCreate() {
await runActivity('Creating Cloud Service...', async () => {
await cloudService.backend.create(ProjectModel.instance, {
await cloudService.backend.create({
name,
description,
masterKey: masterKey ? masterKey : undefined,

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FeedbackType } from '@noodl-constants/FeedbackType';
import { CloudService, Environment } from '@noodl-models/CloudServices';
@@ -12,7 +12,6 @@ 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;
@@ -61,7 +60,7 @@ function AsSelfHosted({
}
CloudService.instance.backend
.update(ProjectModel.instance, {
.update({
id: environment.id,
name,
description,
@@ -71,7 +70,7 @@ function AsSelfHosted({
})
.then(() => {
ToastLayer.showSuccess(`Updated Cloud Service`);
CloudService.instance.backend.fetch(ProjectModel.instance);
CloudService.instance.backend.fetch();
})
.catch(() => {
ToastLayer.showError(`Failed to update Cloud Service`);

View File

@@ -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(ProjectModel.instance);
await cloudService.backend.fetch();
}
});

View File

@@ -5,8 +5,6 @@ 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';
@@ -56,7 +54,7 @@ export function SearchPanel() {
}
}, [debouncedSearchTerm]);
function onSearchItemClicked(searchResult: SearchResultItem) {
function onSearchItemClicked(searchResult) {
if (searchResult.type === 'Component') {
NodeGraphContextTmp.switchToComponent(searchResult.componentTarget, {
breadcrumbs: false,
@@ -87,7 +85,29 @@ export function SearchPanel() {
<div className={css.SearchResults}>
{searchResults.map((component) => (
<SearchItem key={component.componentId} component={component} onSearchItemClicked={onSearchItemClicked} />
<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>
))}
{searchResults.length === 0 && debouncedSearchTerm.length > 0 && (
<Container hasXSpacing hasYSpacing>
@@ -98,51 +118,3 @@ 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>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,6 @@ 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,
@@ -40,7 +39,6 @@ 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,
@@ -62,7 +60,6 @@ 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,
@@ -85,7 +82,6 @@ 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,
@@ -108,7 +104,6 @@ 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;
@@ -131,7 +126,6 @@ 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;
@@ -155,7 +149,6 @@ 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;
@@ -186,7 +179,6 @@ 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,
@@ -205,7 +197,6 @@ 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;
@@ -274,7 +265,7 @@ function createRecordsAPI(modelScope) {
resolve();
},
error: (err) => {
reject(Error(err || 'Failed to add relation.'));
reject(Error(rr || 'Failed to add relation.'));
}
});
});

View File

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

View File

@@ -59,7 +59,6 @@ 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);
@@ -69,7 +68,6 @@ 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);

View File

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

View File

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

View File

@@ -74,59 +74,6 @@ 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
@@ -168,17 +115,15 @@ declare namespace Noodl {
* })
* ```
*/
query<TClassName extends RecordClassName>(
className: TClassName,
query?:
RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
{ and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
query(
className: RecordClassName,
query?: any,
options?: {
limit?: number;
skip?: number;
sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
include?: string | (keyof DatabaseSchema[TClassName])[];
select?: string | (keyof DatabaseSchema[TClassName])[];
sort?: string[];
include?: any;
select?: any;
}
): Promise<any>;

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import Layout from '../../../layout';
import Utils from '../../../nodes/controls/utils';
import { Noodl } from '../../../types';
import { Noodl, Slot } from '../../../types';
export interface SliderProps extends Noodl.ReactProps {
_nodeId: string;
@@ -24,6 +24,8 @@ export interface SliderProps extends Noodl.ReactProps {
onClick: () => void;
updateOutputValue: (value: number) => void;
children: Slot;
}
function _styleTemplate(_class: string, props: SliderProps) {
@@ -168,7 +170,9 @@ export function Slider(props: SliderProps) {
return (
<div style={divStyle}>
<div style={trackStyle} />
<div style={thumbStyle} />
<div style={thumbStyle}>
{props.children}
</div>
<input
className={className}
{...Utils.controlEvents(props)}

View File

@@ -11,7 +11,6 @@ const RangeNode = {
name: 'net.noodl.controls.range',
displayNodeName: 'Slider',
docs: 'https://docs.noodl.net/nodes/ui-controls/slider',
allowChildren: false,
noodlNodeAsProp: true,
connectionPanel: {
groupPriority: [

View File

@@ -72,10 +72,8 @@ const Navigate = {
backCallback: (action, results) => {
this._internal.backResults = results;
for (const key in results) {
if (this.hasOutput('backResult-' + key)) {
this.flagOutputDirty('backResult-' + key);
}
for (var key in results) {
if (this.hasOutput('backResult-' + key)) this.flagOutputDirty('backResult-' + key);
}
if (action !== undefined) this.sendSignalOnOutput(action);
@@ -116,23 +114,22 @@ 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)) {
@@ -177,24 +174,18 @@ function setup(context, graphModel) {
if (Transitions[transition]) ports = ports.concat(Transitions[transition].ports(node.parameters));
}
const pageStacks = graphModel.getNodesWithType('Page Stack');
const pageStack = pageStacks.find(
// if(node.parameters['stack'] !== undefined) {
var pageStacks = graphModel.getNodesWithType('Page Stack');
var pageStack = pageStacks.find(
(ps) => (ps.parameters['name'] || 'Main') === (node.parameters['stack'] || 'Main')
);
if (pageStack !== undefined) {
const pages = pageStack.parameters['pages'];
var 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',
@@ -202,14 +193,14 @@ function setup(context, graphModel) {
});
// See if there is a target page with component
const targetPageId = node.parameters['target'] || pages[0].id;
const targetComponentName = pageStack.parameters['pageComp-' + targetPageId];
var targetPageId = node.parameters['target'] || pages[0].id;
var 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 (const inputName in component.inputPorts) {
for (var inputName in component.inputPorts) {
ports.push({
name: 'pm-' + inputName,
displayName: inputName,
@@ -254,6 +245,7 @@ function setup(context, graphModel) {
}
}
}
// }
context.editorConnection.sendDynamicPorts(node.id, ports);
}

View File

@@ -6,7 +6,7 @@ export default {
group: 'General',
plug: 'input',
type: 'string',
default: 'Fluxscape Viewer',
default: 'Noodl Viewer',
tooltip: 'The title that web browsers show',
ignoreInExport: true
},

View File

@@ -131,59 +131,6 @@ 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
@@ -225,17 +172,15 @@ declare namespace Noodl {
* })
* ```
*/
query<TClassName extends RecordClassName>(
className: TClassName,
query?:
RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
{ and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
query(
className: RecordClassName,
query?: any,
options?: {
limit?: number;
skip?: number;
sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
include?: string | (keyof DatabaseSchema[TClassName])[];
select?: string | (keyof DatabaseSchema[TClassName])[];
sort?: string[];
include?: any;
select?: any;
}
): Promise<any>;