mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
Updated project to React 19
This commit is contained in:
@@ -1,23 +1,24 @@
|
||||
const path = require('path');
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
import path from 'path';
|
||||
|
||||
const editorDir = path.join(__dirname, '../../noodl-editor');
|
||||
const coreLibDir = path.join(__dirname, '../');
|
||||
|
||||
module.exports = {
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/preset-create-react-app',
|
||||
'@storybook/addon-measure'
|
||||
],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: '@storybook/builder-webpack5'
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {}
|
||||
},
|
||||
webpackFinal: (config) => {
|
||||
const destinationPath = path.resolve(__dirname, '../../noodl-editor');
|
||||
const addExternalPath = (rules) => {
|
||||
const addExternalPath = (rules: any[]) => {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i];
|
||||
if (rule.test && RegExp(rule.test).test('.tsx')) {
|
||||
@@ -32,17 +33,20 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
addExternalPath(config.module.rules);
|
||||
if (config.module?.rules) {
|
||||
addExternalPath(config.module.rules as any[]);
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('ts-loader')
|
||||
}
|
||||
]
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('ts-loader')
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'@noodl-core-ui': path.join(coreLibDir, 'src'),
|
||||
@@ -56,5 +60,10 @@ module.exports = {
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
typescript: {
|
||||
reactDocgen: 'react-docgen-typescript'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"name": "@noodl/noodl-core-ui",
|
||||
"version": "2.7.0",
|
||||
"scripts": {
|
||||
"start": "start-storybook -p 6006 -s public",
|
||||
"build": "build-storybook -s public"
|
||||
"start": "storybook dev -p 6006",
|
||||
"build": "storybook build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -42,14 +42,20 @@
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^8.6.14",
|
||||
"@storybook/addon-interactions": "^8.6.14",
|
||||
"@storybook/addon-links": "^8.6.14",
|
||||
"@storybook/addon-measure": "^8.6.14",
|
||||
"@storybook/react": "^8.6.14",
|
||||
"@storybook/react-webpack5": "^8.6.14",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.42",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"babel-plugin-named-exports-order": "^0.0.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"sass": "^1.90.0",
|
||||
"storybook": "^9.1.3",
|
||||
"storybook": "^8.6.14",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^3.5.2",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { ChangeEventHandler, cloneElement, FocusEventHandler, MouseEventHandler } from 'react';
|
||||
import React, { ChangeEventHandler, cloneElement, FocusEventHandler, isValidElement, MouseEventHandler, ReactElement } from 'react';
|
||||
|
||||
import { InputNotification } from '@noodl-types/globalInputTypes';
|
||||
|
||||
@@ -113,7 +113,7 @@ export function Checkbox({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{children && <div className={css['ChildContainer']}>{cloneElement(children, { isChecked })}</div>}
|
||||
{children && isValidElement(children) && <div className={css['ChildContainer']}>{cloneElement(children as ReactElement<{ isChecked?: boolean }>, { isChecked })}</div>}
|
||||
{label && <InputLabelSection label={label} />}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ export function CoreBaseDialog({
|
||||
}, 50);
|
||||
}, [isVisible]);
|
||||
|
||||
const dialogRef = useRef<HTMLDivElement>();
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const [dialogPosition, setDialogPosition] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -45,7 +45,7 @@ export function Carousel({ activeIndex, items, indicator }: CarouselProps) {
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<HStack UNSAFE_style={{ width: items.length * 100 + '%' }}>
|
||||
{items.map((item, index) => (
|
||||
<VStack key={index} ref={(ref) => (sliderRefs.current[index] = ref)} UNSAFE_style={{ width: '100%' }}>
|
||||
<VStack key={index} ref={(ref) => { sliderRefs.current[index] = ref; }} UNSAFE_style={{ width: '100%' }}>
|
||||
{item.slot}
|
||||
</VStack>
|
||||
))}
|
||||
|
||||
@@ -91,6 +91,10 @@ export function Columns({
|
||||
}}
|
||||
>
|
||||
{toArray(children).map((child, i) => {
|
||||
// Skip non-element children (null, undefined, boolean)
|
||||
if (!React.isValidElement(child)) {
|
||||
return child;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="column-item"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { cloneElement, MouseEventHandler } from 'react';
|
||||
import React, { cloneElement, isValidElement, MouseEventHandler, ReactElement } from 'react';
|
||||
|
||||
import { Icon, IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
|
||||
import { BaseDialog, BaseDialogProps } from '@noodl-core-ui/components/layout/BaseDialog';
|
||||
@@ -100,7 +100,10 @@ export function MenuDialog({
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
{item.component && cloneElement(item.component(() => onClose()))}
|
||||
{item.component && (() => {
|
||||
const component = item.component(() => onClose());
|
||||
return isValidElement(component) ? cloneElement(component as ReactElement) : component;
|
||||
})()}
|
||||
<div className={css['EndSlot']}>
|
||||
{item.endSlot && typeof item.endSlot === 'string' && <Text>{item.endSlot}</Text>}
|
||||
{item.endSlot && typeof item.endSlot !== 'string' && item.endSlot}
|
||||
|
||||
@@ -1,92 +1,91 @@
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties, useLayoutEffect, useRef, useState } from 'react';
|
||||
import css from './PopupSection.module.scss';
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Remove style prop and replace with "isInactive" prop
|
||||
*/
|
||||
|
||||
export interface PopupSectionProps {
|
||||
children?: Slot;
|
||||
title?: string;
|
||||
className?: string;
|
||||
maxContentHeight?: number;
|
||||
|
||||
hasBottomBorder?: boolean;
|
||||
hasYPadding?: boolean;
|
||||
isCenteringChildren?: boolean;
|
||||
|
||||
style?: CSSProperties;
|
||||
contentContainerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export function PopupSection({
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
maxContentHeight,
|
||||
|
||||
hasBottomBorder,
|
||||
hasYPadding,
|
||||
isCenteringChildren,
|
||||
|
||||
style,
|
||||
contentContainerStyle
|
||||
}: PopupSectionProps) {
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
const [shouldScroll, setShouldScroll] = useState(false);
|
||||
const [hasBeenCalculated, setHasBeenCalculated] = useState(false);
|
||||
|
||||
function checkIfShouldScroll() {
|
||||
if (!contentRef.current || !maxContentHeight) return false;
|
||||
if (contentRef.current.getBoundingClientRect().height >= maxContentHeight) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setShouldScroll(checkIfShouldScroll());
|
||||
setHasBeenCalculated(true);
|
||||
}
|
||||
}, [contentRef, children]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames([css['Root'], hasBottomBorder && css['has-bottom-border'], className])}
|
||||
style={{
|
||||
...style,
|
||||
overflowY: hasBeenCalculated ? undefined : 'hidden',
|
||||
height: hasBeenCalculated ? undefined : 0
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<header className={css['Header']}>
|
||||
<h2 className={css['Title']}>{title}</h2>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{children ? (
|
||||
<div
|
||||
className={classNames([
|
||||
css['Content'],
|
||||
hasYPadding && css['has-y-padding'],
|
||||
isCenteringChildren && css['is-centering-children']
|
||||
])}
|
||||
ref={contentRef}
|
||||
style={{
|
||||
...contentContainerStyle,
|
||||
height: shouldScroll ? maxContentHeight : undefined,
|
||||
// @ts-expect-error
|
||||
overflowY: shouldScroll ? 'overlay' : undefined
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div ref={contentRef} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties, useLayoutEffect, useRef, useState } from 'react';
|
||||
import css from './PopupSection.module.scss';
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Remove style prop and replace with "isInactive" prop
|
||||
*/
|
||||
|
||||
export interface PopupSectionProps {
|
||||
children?: Slot;
|
||||
title?: string;
|
||||
className?: string;
|
||||
maxContentHeight?: number;
|
||||
|
||||
hasBottomBorder?: boolean;
|
||||
hasYPadding?: boolean;
|
||||
isCenteringChildren?: boolean;
|
||||
|
||||
style?: CSSProperties;
|
||||
contentContainerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export function PopupSection({
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
maxContentHeight,
|
||||
|
||||
hasBottomBorder,
|
||||
hasYPadding,
|
||||
isCenteringChildren,
|
||||
|
||||
style,
|
||||
contentContainerStyle
|
||||
}: PopupSectionProps) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [shouldScroll, setShouldScroll] = useState(false);
|
||||
const [hasBeenCalculated, setHasBeenCalculated] = useState(false);
|
||||
|
||||
function checkIfShouldScroll() {
|
||||
if (!contentRef.current || !maxContentHeight) return false;
|
||||
if (contentRef.current.getBoundingClientRect().height >= maxContentHeight) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setShouldScroll(checkIfShouldScroll());
|
||||
setHasBeenCalculated(true);
|
||||
}
|
||||
}, [contentRef, children]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames([css['Root'], hasBottomBorder && css['has-bottom-border'], className])}
|
||||
style={{
|
||||
...style,
|
||||
overflowY: hasBeenCalculated ? undefined : 'hidden',
|
||||
height: hasBeenCalculated ? undefined : 0
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<header className={css['Header']}>
|
||||
<h2 className={css['Title']}>{title}</h2>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{children ? (
|
||||
<div
|
||||
className={classNames([
|
||||
css['Content'],
|
||||
hasYPadding && css['has-y-padding'],
|
||||
isCenteringChildren && css['is-centering-children']
|
||||
])}
|
||||
ref={contentRef}
|
||||
style={{
|
||||
...contentContainerStyle,
|
||||
height: shouldScroll ? maxContentHeight : undefined,
|
||||
overflowY: shouldScroll ? 'overlay' : undefined
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div ref={contentRef} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function PropertyPanelSelectInput({
|
||||
hasSmallText
|
||||
}: PropertyPanelSelectInputProps) {
|
||||
const [isSelectCollapsed, setIsSelectCollapsed] = useState(true);
|
||||
const rootRef = useRef<HTMLDivElement>();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const displayValue = properties?.options.find((option) => option.value === value)?.label;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export function PropertyPanelSliderInput({
|
||||
}
|
||||
|
||||
const thumbPercentage = useMemo(
|
||||
() => linearMap(parseInt(value.toString()), properties.min, properties.max, 0, 100),
|
||||
() => linearMap(parseInt(value.toString()), Number(properties.min), Number(properties.max), 0, 100),
|
||||
[value, properties]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import React, {
|
||||
JSXElementConstructor,
|
||||
ReactChild,
|
||||
ReactElement,
|
||||
ReactFragment,
|
||||
ReactPortal,
|
||||
ReactText
|
||||
} from 'react';
|
||||
|
||||
export interface UnsafeStyleProps {
|
||||
UNSAFE_className?: string;
|
||||
UNSAFE_style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// FIXME: add generics to be able to specify what exact components are allowed?
|
||||
export type SingleSlot =
|
||||
| ReactElement<TSFixme, TSFixme>
|
||||
| ReactFragment
|
||||
| ReactPortal
|
||||
| boolean
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type Slot = SingleSlot | SingleSlot[];
|
||||
import React, { ReactElement, ReactPortal } from 'react';
|
||||
|
||||
export interface UnsafeStyleProps {
|
||||
UNSAFE_className?: string;
|
||||
UNSAFE_style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// FIXME: add generics to be able to specify what exact components are allowed?
|
||||
// Note: ReactFragment removed in React 19, using React.ReactNode for fragments
|
||||
export type SingleSlot =
|
||||
| ReactElement<TSFixme, TSFixme>
|
||||
| Iterable<React.ReactNode>
|
||||
| ReactPortal
|
||||
| boolean
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type Slot = SingleSlot | SingleSlot[];
|
||||
|
||||
@@ -103,8 +103,8 @@
|
||||
"@types/checksum": "^0.1.35",
|
||||
"@types/jasmine": "^4.6.5",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/react": "^19.0.00",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/split2": "^3.2.1",
|
||||
|
||||
234
packages/noodl-editor/src/editor/src/utils/projectBackup.ts
Normal file
234
packages/noodl-editor/src/editor/src/utils/projectBackup.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Project Backup Utility
|
||||
*
|
||||
* Provides automatic backup functionality before project upgrades or migrations.
|
||||
* Creates timestamped backups of project.json files to prevent data loss.
|
||||
*
|
||||
* @module noodl-editor
|
||||
* @since 2.0.0 (OpenNoodl)
|
||||
*/
|
||||
|
||||
import { filesystem } from '@noodl/platform';
|
||||
import path from 'path';
|
||||
|
||||
export interface BackupResult {
|
||||
success: boolean;
|
||||
backupPath?: string;
|
||||
error?: string;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface BackupOptions {
|
||||
/** Maximum number of backups to keep per project (default: 5) */
|
||||
maxBackups?: number;
|
||||
/** Custom prefix for backup file names */
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_BACKUPS = 5;
|
||||
const BACKUP_FOLDER_NAME = '.noodl-backups';
|
||||
|
||||
/**
|
||||
* Creates a backup of the project.json file before migration/upgrade.
|
||||
*
|
||||
* @param projectDir - The directory containing the project
|
||||
* @param options - Backup configuration options
|
||||
* @returns BackupResult with success status and backup path
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await createProjectBackup('/path/to/project');
|
||||
* if (result.success) {
|
||||
* console.log('Backup created at:', result.backupPath);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function createProjectBackup(
|
||||
projectDir: string,
|
||||
options: BackupOptions = {}
|
||||
): Promise<BackupResult> {
|
||||
const { maxBackups = DEFAULT_MAX_BACKUPS, prefix = 'backup' } = options;
|
||||
|
||||
try {
|
||||
const projectJsonPath = path.join(projectDir, 'project.json');
|
||||
const backupDir = path.join(projectDir, BACKUP_FOLDER_NAME);
|
||||
|
||||
// Check if project.json exists
|
||||
const exists = await filesystem.exists(projectJsonPath);
|
||||
if (!exists) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'project.json not found in project directory'
|
||||
};
|
||||
}
|
||||
|
||||
// Create backup directory if it doesn't exist
|
||||
await filesystem.makeDirectory(backupDir);
|
||||
|
||||
// Generate timestamp for backup filename
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupFileName = `${prefix}-${timestamp}.json`;
|
||||
const backupPath = path.join(backupDir, backupFileName);
|
||||
|
||||
// Read original project.json
|
||||
const projectContent = await filesystem.readFile(projectJsonPath);
|
||||
|
||||
// Write backup file
|
||||
await filesystem.writeFile(backupPath, projectContent);
|
||||
|
||||
// Clean up old backups if we exceed maxBackups
|
||||
await cleanupOldBackups(backupDir, prefix, maxBackups);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
backupPath,
|
||||
timestamp
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all available backups for a project.
|
||||
*
|
||||
* @param projectDir - The directory containing the project
|
||||
* @returns Array of backup file paths sorted by date (newest first)
|
||||
*/
|
||||
export async function listProjectBackups(projectDir: string): Promise<string[]> {
|
||||
try {
|
||||
const backupDir = path.join(projectDir, BACKUP_FOLDER_NAME);
|
||||
|
||||
const exists = await filesystem.exists(backupDir);
|
||||
if (!exists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = await filesystem.listDirectory(backupDir);
|
||||
const backupFiles = files
|
||||
.filter(f => !f.isDirectory && f.name.endsWith('.json'))
|
||||
.map(f => f.name)
|
||||
.sort()
|
||||
.reverse(); // Newest first
|
||||
|
||||
return backupFiles.map(f => path.join(backupDir, f));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a project from a backup file.
|
||||
*
|
||||
* @param projectDir - The directory containing the project
|
||||
* @param backupPath - Path to the backup file to restore
|
||||
* @returns BackupResult with success status
|
||||
*/
|
||||
export async function restoreProjectBackup(
|
||||
projectDir: string,
|
||||
backupPath: string
|
||||
): Promise<BackupResult> {
|
||||
try {
|
||||
const projectJsonPath = path.join(projectDir, 'project.json');
|
||||
|
||||
// Verify backup exists
|
||||
const backupExists = await filesystem.exists(backupPath);
|
||||
if (!backupExists) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Backup file not found'
|
||||
};
|
||||
}
|
||||
|
||||
// Create a backup of the current state before restoring
|
||||
// (so user can undo the restore if needed)
|
||||
const preRestoreBackup = await createProjectBackup(projectDir, {
|
||||
prefix: 'pre-restore'
|
||||
});
|
||||
|
||||
if (!preRestoreBackup.success) {
|
||||
console.warn('Could not create pre-restore backup:', preRestoreBackup.error);
|
||||
}
|
||||
|
||||
// Read backup content
|
||||
const backupContent = await filesystem.readFile(backupPath);
|
||||
|
||||
// Write to project.json
|
||||
await filesystem.writeFile(projectJsonPath, backupContent);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
backupPath
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up old backups, keeping only the most recent ones.
|
||||
*
|
||||
* @param backupDir - Directory containing backups
|
||||
* @param prefix - Prefix to filter backups by
|
||||
* @param maxBackups - Maximum number of backups to keep
|
||||
*/
|
||||
async function cleanupOldBackups(
|
||||
backupDir: string,
|
||||
prefix: string,
|
||||
maxBackups: number
|
||||
): Promise<void> {
|
||||
try {
|
||||
const files = await filesystem.listDirectory(backupDir);
|
||||
|
||||
// Filter to only backups with our prefix and sort by name (which includes timestamp)
|
||||
const backupFileNames = files
|
||||
.filter(f => !f.isDirectory && f.name.startsWith(prefix) && f.name.endsWith('.json'))
|
||||
.map(f => f.name)
|
||||
.sort()
|
||||
.reverse(); // Newest first
|
||||
|
||||
// Remove old backups
|
||||
if (backupFileNames.length > maxBackups) {
|
||||
const filesToDelete = backupFileNames.slice(maxBackups);
|
||||
for (const fileName of filesToDelete) {
|
||||
const filePath = path.join(backupDir, fileName);
|
||||
await filesystem.removeFile(filePath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to cleanup old backups:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent backup for a project.
|
||||
*
|
||||
* @param projectDir - The directory containing the project
|
||||
* @returns Path to the most recent backup, or null if none exist
|
||||
*/
|
||||
export async function getLatestBackup(projectDir: string): Promise<string | null> {
|
||||
const backups = await listProjectBackups(projectDir);
|
||||
return backups.length > 0 ? backups[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a backup file by attempting to parse it as JSON.
|
||||
*
|
||||
* @param backupPath - Path to the backup file
|
||||
* @returns true if backup is valid JSON, false otherwise
|
||||
*/
|
||||
export async function validateBackup(backupPath: string): Promise<boolean> {
|
||||
try {
|
||||
const content = await filesystem.readFile(backupPath);
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ function CommentForeground(props) {
|
||||
|
||||
function CommentControls(props) {
|
||||
const [showColorPicker, setShowColorPicker] = useState(false);
|
||||
const colorPickerRef = useRef();
|
||||
const colorPickerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const color = getColor(props);
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ function DeployPopupChild() {
|
||||
backgroundColor: '#444444',
|
||||
position: 'relative',
|
||||
maxHeight: `calc(90vh - 40px)`,
|
||||
// @ts-expect-error https://github.com/frenic/csstype/issues/62
|
||||
overflowY: 'overlay',
|
||||
overflowY: 'overlay' as React.CSSProperties['overflowY'],
|
||||
overflowX: 'hidden'
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -72,12 +72,12 @@ export function EditorTopbar({
|
||||
deployIsDisabled
|
||||
}: EditorTopbarProps) {
|
||||
const urlBarRef = useRef<HTMLInputElement>(null);
|
||||
const deployButtonRef = useRef();
|
||||
const warningButtonRef = useRef();
|
||||
const urlInputRef = useRef();
|
||||
const zoomLevelTrigger = useRef();
|
||||
const screenSizeTrigger = useRef();
|
||||
const previewLayoutTrigger = useRef();
|
||||
const deployButtonRef = useRef<HTMLSpanElement>(null);
|
||||
const warningButtonRef = useRef<HTMLDivElement>(null);
|
||||
const urlInputRef = useRef<HTMLDivElement>(null);
|
||||
const zoomLevelTrigger = useRef<HTMLDivElement>(null);
|
||||
const screenSizeTrigger = useRef<HTMLDivElement>(null);
|
||||
const previewLayoutTrigger = useRef<HTMLDivElement>(null);
|
||||
const [isDeployVisible, setIsDeployVisible] = useState(false);
|
||||
const [isWarningsDialogVisible, setIsWarningsDialogVisible] = useState(false);
|
||||
const [isZoomDialogVisible, setIsZoomDialogVisible] = useState(false);
|
||||
@@ -162,7 +162,7 @@ export function EditorTopbar({
|
||||
onClose: () => createNewNodePanel.dispose()
|
||||
});
|
||||
}
|
||||
const rootRef = useRef<HTMLDivElement>();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const bounds = useTrackBounds(rootRef);
|
||||
const isSmall = bounds?.width < 850;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import algoliasearch from 'algoliasearch/lite';
|
||||
import { liteClient as algoliasearch } from 'algoliasearch/lite';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { InstantSearch, Hits, Highlight, useSearchBox, Configure } from 'react-instantsearch';
|
||||
import { platform } from '@noodl/platform';
|
||||
@@ -17,7 +17,7 @@ import { Title, TitleSize } from '@noodl-core-ui/components/typography/Title';
|
||||
import css from './HelpCenter.module.scss';
|
||||
|
||||
export function HelpCenter() {
|
||||
const rootRef = useRef();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const [version] = useState(platform.getVersion().slice(0, 3));
|
||||
const [isDialogVisible, setIsDialogVisible] = useState(false);
|
||||
const [isSearchModalVisible, setIsSearchModalVisible] = useState(false);
|
||||
|
||||
@@ -103,7 +103,7 @@ interface ItemProps {
|
||||
|
||||
function Item({ item, onSwitchToComponent }: ItemProps) {
|
||||
let icon = getIconFromItem(item);
|
||||
const itemRef = useRef<HTMLDivElement>();
|
||||
const itemRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// change a visual component icon to be a regular component icon in the trail
|
||||
// @ts-expect-error fix this when we refactor the component sidebar to not use the old HTML templates
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactChild, ReactNode, useEffect, useState } from 'react';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import { NodeType } from '@noodl-constants/NodeType';
|
||||
import { Collapsible } from '@noodl-core-ui/components/layout/Collapsible';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactChild, ReactNode } from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import css from './NodePickerSection.module.scss';
|
||||
|
||||
interface NodePickerSectionProps {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Text, TextSize, TextType } from '@noodl-core-ui/components/typography/Text';
|
||||
import React, { ReactChild } from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import css from './NodePickerSubCategory.module.scss';
|
||||
|
||||
interface NodePickerSubCategoryProps {
|
||||
title: string;
|
||||
children: ReactChild;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function NodePickerSubCategory({ title, children }: NodePickerSubCategoryProps) {
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SidePanel() {
|
||||
|
||||
// All the panel data
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
const [panels, setPanels] = useState<Record<string, React.ReactChild>>({});
|
||||
const [panels, setPanels] = useState<Record<string, React.ReactElement>>({});
|
||||
|
||||
useEffect(() => {
|
||||
// ---
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import { platform } from '@noodl/platform';
|
||||
|
||||
import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
|
||||
@@ -19,6 +19,8 @@ export class CanvasView extends View {
|
||||
inspectMode: boolean;
|
||||
selectedNodeId: string | null;
|
||||
|
||||
private root: Root | null = null;
|
||||
|
||||
props: {
|
||||
deviceName?: string;
|
||||
zoom?: number;
|
||||
@@ -152,7 +154,10 @@ export class CanvasView extends View {
|
||||
return this.el;
|
||||
}
|
||||
renderReact() {
|
||||
ReactDOM.render(React.createElement(VisualCanvas, this.props), this.el[0]);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(this.el[0]);
|
||||
}
|
||||
this.root.render(React.createElement(VisualCanvas, this.props as any));
|
||||
}
|
||||
setCurrentRoute(route: string) {
|
||||
const protocol = process.env.ssl ? 'https://' : 'http://';
|
||||
@@ -171,7 +176,10 @@ export class CanvasView extends View {
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this.el[0]);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
ipcRenderer.off('editor-api-response', this._onEditorApiResponse);
|
||||
}
|
||||
refresh() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getCurrentWindow, screen } from '@electron/remote';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { MenuDialog, MenuDialogWidth } from '@noodl-core-ui/components/popups/MenuDialog';
|
||||
|
||||
@@ -11,6 +11,8 @@ export function showInspectMenu(items: TSFixme) {
|
||||
const screenPoint = screen.getCursorScreenPoint();
|
||||
const [winX, winY] = getCurrentWindow().getPosition();
|
||||
|
||||
let root: Root | null = null;
|
||||
|
||||
const popout = PopupLayer.instance.showPopout({
|
||||
content: { el: $(container) },
|
||||
arrowColor: 'transparent',
|
||||
@@ -20,11 +22,15 @@ export function showInspectMenu(items: TSFixme) {
|
||||
},
|
||||
position: 'top',
|
||||
onClose: () => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
if (root) {
|
||||
root.unmount();
|
||||
root = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
root = createRoot(container);
|
||||
root.render(
|
||||
<MenuDialog
|
||||
title="Nodes"
|
||||
width={MenuDialogWidth.Large}
|
||||
@@ -34,7 +40,6 @@ export function showInspectMenu(items: TSFixme) {
|
||||
PopupLayer.instance.hidePopout(popout);
|
||||
}}
|
||||
items={items}
|
||||
/>,
|
||||
container
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { NodeGraphModel, NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
@@ -24,6 +24,7 @@ export class CreateNewNodePanel extends View {
|
||||
attachToRoot: boolean;
|
||||
pos: IVector2;
|
||||
runtimeType: string;
|
||||
root: Root | null = null;
|
||||
|
||||
static shouldShow(context: { component: ComponentModel; parentModel: NodeGraphNode }) {
|
||||
const nodeTypes = NodeLibrary.instance.getNodeTypes();
|
||||
@@ -55,11 +56,14 @@ export class CreateNewNodePanel extends View {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
ReactDOM.unmountComponentAtNode(this.el[0]);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
ipcRenderer.send('viewer-show');
|
||||
}
|
||||
|
||||
renderReact(div) {
|
||||
renderReact(div: HTMLElement) {
|
||||
const props = {
|
||||
model: this.model,
|
||||
parentModel: this.parentModel,
|
||||
@@ -72,8 +76,10 @@ export class CreateNewNodePanel extends View {
|
||||
ipcRenderer.send('viewer-hide');
|
||||
|
||||
// ... then render the picker
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
ReactDOM.render(React.createElement(NodePicker, props), div);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(div);
|
||||
}
|
||||
this.root.render(React.createElement(NodePicker, props));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -22,7 +22,7 @@ function anyToString(value: unknown) {
|
||||
}
|
||||
|
||||
export function CodeDiffDialog({ diff, onClose }: CodeDiffDialogProps) {
|
||||
const codeEditorRef = useRef();
|
||||
const codeEditorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!codeEditorRef.current) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { App } from '@noodl-models/app';
|
||||
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
|
||||
@@ -30,6 +30,7 @@ export class LessonLayer {
|
||||
steps: ILessonStep[];
|
||||
el: TSFixme;
|
||||
refreshTimeout: NodeJS.Timeout;
|
||||
root: Root | null = null;
|
||||
|
||||
constructor() {
|
||||
this.keyboardCommands = [
|
||||
@@ -116,12 +117,16 @@ export class LessonLayer {
|
||||
}
|
||||
};
|
||||
|
||||
ReactDOM.render(React.createElement(LessonLayerView, props), this.div);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(this.div);
|
||||
}
|
||||
this.root.render(React.createElement(LessonLayerView, props));
|
||||
}
|
||||
|
||||
_render() {
|
||||
if (this.div) {
|
||||
ReactDOM.unmountComponentAtNode(this.div);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
this.div = document.createElement('div');
|
||||
@@ -320,7 +325,10 @@ export class LessonLayer {
|
||||
this.model.off(this);
|
||||
EventDispatcher.instance.off(this);
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this.div);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
}
|
||||
|
||||
resize() {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { IconName } from '@noodl-core-ui/components/common/Icon';
|
||||
|
||||
@@ -230,7 +230,8 @@ class PageComponentTemplate extends ComponentTemplate {
|
||||
}
|
||||
};
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(PageComponentTemplatePopup, props), div);
|
||||
const root = createRoot(div);
|
||||
root.render(React.createElement(PageComponentTemplatePopup, props));
|
||||
|
||||
return { el: $(div) };
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export interface CodeEditorProps {
|
||||
}
|
||||
|
||||
export function CodeEditor({ model, initialSize, onSave, outEditor }: CodeEditorProps) {
|
||||
const rootRef = useRef<HTMLDivElement>();
|
||||
const editorRef = useRef<HTMLDivElement>();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor>(null);
|
||||
const [size, setSize] = useState<{ width: number; height: number }>({
|
||||
width: initialSize?.x ?? 700,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { WarningsModel } from '@noodl-models/warningsmodel';
|
||||
import { createModel } from '@noodl-utils/CodeEditor';
|
||||
@@ -66,6 +66,9 @@ export class CodeEditorType extends TypeView {
|
||||
|
||||
isPrimary: boolean;
|
||||
|
||||
propertyRoot: Root | null = null;
|
||||
popoutRoot: Root | null = null;
|
||||
|
||||
static fromPort(args): TSFixme {
|
||||
const view = new CodeEditorType();
|
||||
|
||||
@@ -97,8 +100,11 @@ export class CodeEditorType extends TypeView {
|
||||
this.model?.dispose();
|
||||
this.model = null;
|
||||
|
||||
// ReactDOM.unmountComponentAtNode(this.propertyDiv);
|
||||
ReactDOM.unmountComponentAtNode(this.popoutDiv);
|
||||
// Unmount popout root
|
||||
if (this.popoutRoot) {
|
||||
this.popoutRoot.unmount();
|
||||
this.popoutRoot = null;
|
||||
}
|
||||
|
||||
WarningsModel.instance.off(this);
|
||||
}
|
||||
@@ -107,7 +113,7 @@ export class CodeEditorType extends TypeView {
|
||||
this.el = this.bindView($(`<div></div>`), this);
|
||||
super.render();
|
||||
|
||||
const _this = this;
|
||||
const self = this;
|
||||
|
||||
const propertyProps: PropertyProps = {
|
||||
isPrimary: this.isPrimary,
|
||||
@@ -115,12 +121,13 @@ export class CodeEditorType extends TypeView {
|
||||
tooltip: this.tooltip,
|
||||
isDefault: this.isDefault,
|
||||
onClick(event) {
|
||||
_this.onLaunchClicked(_this, event.currentTarget, event);
|
||||
self.onLaunchClicked(self, event.currentTarget, event);
|
||||
}
|
||||
};
|
||||
|
||||
this.propertyDiv = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(Property, propertyProps), this.propertyDiv);
|
||||
this.propertyRoot = createRoot(this.propertyDiv);
|
||||
this.propertyRoot.render(React.createElement(Property, propertyProps));
|
||||
|
||||
return this.propertyDiv;
|
||||
}
|
||||
@@ -261,7 +268,8 @@ export class CodeEditorType extends TypeView {
|
||||
}
|
||||
|
||||
this.popoutDiv = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(CodeEditor, props), this.popoutDiv);
|
||||
this.popoutRoot = createRoot(this.popoutDiv);
|
||||
this.popoutRoot.render(React.createElement(CodeEditor, props));
|
||||
|
||||
const popoutDiv = this.popoutDiv;
|
||||
this.parent.showPopout({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
@@ -94,9 +94,9 @@ export class ColorType extends TypeView {
|
||||
bindColorPickerToView(this);
|
||||
}
|
||||
|
||||
let colorStylePickerDiv;
|
||||
let colorStylePickerDiv: HTMLDivElement | undefined;
|
||||
let colorStylePickerRoot: Root | null = null;
|
||||
const props = {};
|
||||
let isShowingColorStylePicker = false;
|
||||
|
||||
EventDispatcher.instance.on(
|
||||
'Model.stylesChanged',
|
||||
@@ -113,10 +113,10 @@ export class ColorType extends TypeView {
|
||||
});
|
||||
|
||||
this.$('input').on('click', (e) => {
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
delete props.filter; //delete filter in case the user opens/closes multiple times
|
||||
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
props.onItemSelected = (name) => {
|
||||
this.parent.setParameter(this.name, name);
|
||||
this.updateCurrentValue();
|
||||
@@ -124,36 +124,39 @@ export class ColorType extends TypeView {
|
||||
};
|
||||
|
||||
const current = this.getCurrentValue();
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
props.inputValue = current.value;
|
||||
|
||||
colorStylePickerDiv = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(ColorStylePicker, props), colorStylePickerDiv);
|
||||
colorStylePickerRoot = createRoot(colorStylePickerDiv);
|
||||
colorStylePickerRoot.render(React.createElement(ColorStylePicker, props));
|
||||
|
||||
this.parent.showPopout({
|
||||
content: { el: $(colorStylePickerDiv) },
|
||||
attachTo: this.el,
|
||||
position: 'right',
|
||||
onClose: () => {
|
||||
ReactDOM.unmountComponentAtNode(colorStylePickerDiv);
|
||||
isShowingColorStylePicker = false;
|
||||
if (colorStylePickerRoot) {
|
||||
colorStylePickerRoot.unmount();
|
||||
colorStylePickerRoot = null;
|
||||
colorStylePickerDiv = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
isShowingColorStylePicker = true;
|
||||
|
||||
e.stopPropagation(); // Stop propagation, otherwise the popup will close
|
||||
});
|
||||
|
||||
this.$('input').on('keyup', (e) => {
|
||||
if (!isShowingColorStylePicker) {
|
||||
if (!colorStylePickerRoot) {
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
this.parent.hidePopout();
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
props.filter = e.target.value;
|
||||
ReactDOM.render(React.createElement(ColorStylePicker, props), colorStylePickerDiv);
|
||||
colorStylePickerRoot.render(React.createElement(ColorStylePicker, props));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { TypeView } from '../../TypeView';
|
||||
import { getEditType } from '../../utils';
|
||||
@@ -53,7 +53,8 @@ export class CurveType extends TypeView {
|
||||
}
|
||||
};
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(require('./curveeditor.jsx'), props), div);
|
||||
const root = createRoot(div);
|
||||
root.render(React.createElement(require('./curveeditor.jsx'), props));
|
||||
|
||||
const curveEditorView = {
|
||||
el: $(div)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { NodeLibrary } from '@noodl-models/nodelibrary';
|
||||
|
||||
@@ -77,7 +77,8 @@ export class IconType extends TypeView {
|
||||
}
|
||||
};
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(IconPicker, props), div);
|
||||
const root = createRoot(div);
|
||||
root.render(React.createElement(IconPicker, props));
|
||||
|
||||
this.parent.showPopout({
|
||||
content: {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import QueryEditor from '../components/QueryEditor';
|
||||
import { TypeView } from '../TypeView';
|
||||
import { getEditType } from '../utils';
|
||||
|
||||
export class QueryFilterType extends TypeView {
|
||||
private root: Root | null = null;
|
||||
|
||||
static fromPort(args) {
|
||||
const view = new QueryFilterType();
|
||||
|
||||
@@ -33,6 +35,8 @@ export class QueryFilterType extends TypeView {
|
||||
this.isDefault = false;
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
|
||||
const renderFilters = () => {
|
||||
const props = {
|
||||
filter: this.value,
|
||||
@@ -40,10 +44,12 @@ export class QueryFilterType extends TypeView {
|
||||
onChange
|
||||
};
|
||||
|
||||
ReactDOM.render(React.createElement(QueryEditor.Filter, props), div);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(div);
|
||||
}
|
||||
this.root.render(React.createElement(QueryEditor.Filter, props));
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
renderFilters();
|
||||
|
||||
this.el = $(div);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import QueryEditor from '../components/QueryEditor';
|
||||
import { TypeView } from '../TypeView';
|
||||
import { getEditType } from '../utils';
|
||||
|
||||
export class QuerySortingType extends TypeView {
|
||||
private root: Root | null = null;
|
||||
|
||||
public static fromPort(args: TSFixme) {
|
||||
const view = new QuerySortingType();
|
||||
|
||||
@@ -34,6 +36,8 @@ export class QuerySortingType extends TypeView {
|
||||
this.isDefault = false;
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
|
||||
const renderSorting = () => {
|
||||
const props = {
|
||||
sorting: this.value,
|
||||
@@ -41,10 +45,12 @@ export class QuerySortingType extends TypeView {
|
||||
onChange
|
||||
};
|
||||
|
||||
ReactDOM.render(React.createElement(QueryEditor.Sorting, props), div);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(div);
|
||||
}
|
||||
this.root.render(React.createElement(QueryEditor.Sorting, props));
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
renderSorting();
|
||||
|
||||
this.el = $(div);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { NodeLibrary } from '@noodl-models/nodelibrary';
|
||||
import { StylesModel } from '@noodl-models/StylesModel';
|
||||
@@ -54,7 +54,8 @@ export class TextStyleType extends TypeView {
|
||||
this
|
||||
);
|
||||
|
||||
let textStylePickerDiv;
|
||||
let textStylePickerDiv: HTMLDivElement | undefined;
|
||||
let textStylePickerRoot: Root | null = null;
|
||||
|
||||
const props = {};
|
||||
|
||||
@@ -158,19 +159,21 @@ export class TextStyleType extends TypeView {
|
||||
});
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
props.inputValue = this.$('input').val();
|
||||
|
||||
textStylePickerDiv = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(TextStylePicker, props), textStylePickerDiv);
|
||||
textStylePickerRoot = createRoot(textStylePickerDiv);
|
||||
textStylePickerRoot.render(React.createElement(TextStylePicker, props));
|
||||
|
||||
this.parent.showPopout({
|
||||
content: { el: $(textStylePickerDiv) },
|
||||
attachTo: this.el,
|
||||
position: 'right',
|
||||
onClose: () => {
|
||||
if (textStylePickerDiv) {
|
||||
ReactDOM.unmountComponentAtNode(textStylePickerDiv);
|
||||
if (textStylePickerRoot) {
|
||||
textStylePickerRoot.unmount();
|
||||
textStylePickerRoot = null;
|
||||
textStylePickerDiv = undefined;
|
||||
}
|
||||
}
|
||||
@@ -180,16 +183,16 @@ export class TextStyleType extends TypeView {
|
||||
});
|
||||
|
||||
this.$('input').on('keyup', (e) => {
|
||||
if (!textStylePickerDiv) {
|
||||
if (!textStylePickerRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
this.parent.hidePopout();
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Dynamic props assignment for legacy component
|
||||
props.filter = e.target.value;
|
||||
ReactDOM.render(React.createElement(TextStylePicker, props), textStylePickerDiv);
|
||||
textStylePickerRoot.render(React.createElement(TextStylePicker, props));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
|
||||
import { IconButton, IconButtonVariant } from '@noodl-core-ui/components/inputs/IconButton';
|
||||
@@ -234,14 +234,17 @@ export class Pages extends React.Component {
|
||||
}
|
||||
};
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(AddNewPagePopup, props), div);
|
||||
const root = createRoot(div);
|
||||
root.render(React.createElement(AddNewPagePopup, props));
|
||||
|
||||
PopupLayer.instance.showPopup({
|
||||
content: { el: $(div) },
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error - Legacy class component without proper typing
|
||||
attachTo: $(this.popupAnchor),
|
||||
position: 'right',
|
||||
onClose: function () {}
|
||||
onClose: function () {
|
||||
root.unmount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Pages } from './Pages';
|
||||
|
||||
export class PagesType extends TypeView {
|
||||
el: TSFixme;
|
||||
private root: Root | null = null;
|
||||
|
||||
static fromPort(args) {
|
||||
const view = new PagesType();
|
||||
@@ -50,10 +51,19 @@ export class PagesType extends TypeView {
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(React.createElement(Pages, props), div);
|
||||
this.root = createRoot(div);
|
||||
this.root.render(React.createElement(Pages, props));
|
||||
|
||||
this.el = $(div);
|
||||
|
||||
return this.el;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
TypeView.prototype.dispose.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import _ from 'underscore';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { getComponentIconType, ComponentIconType } from '@noodl-models/nodelibrary/ComponentIcon';
|
||||
@@ -34,6 +34,7 @@ export class ComponentPicker {
|
||||
private reactMount: HTMLElement;
|
||||
private componentsToShow: ComponentPickerOptions['components'];
|
||||
private ignoreSheetName: boolean;
|
||||
private root: Root | null = null;
|
||||
|
||||
constructor(args: ComponentPickerOptions) {
|
||||
this.onItemSelected = args.onItemSelected;
|
||||
@@ -159,7 +160,10 @@ export class ComponentPicker {
|
||||
width: MenuDialogWidth.Medium
|
||||
};
|
||||
|
||||
ReactDOM.render(React.createElement(MenuDialog, props), this.reactMount);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(this.reactMount);
|
||||
}
|
||||
this.root.render(React.createElement(MenuDialog, props));
|
||||
}
|
||||
|
||||
render(target: HTMLElement) {
|
||||
@@ -168,8 +172,9 @@ export class ComponentPicker {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.reactMount) {
|
||||
ReactDOM.unmountComponentAtNode(this.reactMount);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export class QueryGroup extends React.Component<QueryGroupProps> {
|
||||
className={'queryeditor-group' + (this.props.isTopLevel ? ' toplevel' : '')}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
<div className="queryeditor-group-children" ref={(el) => (this.childContainer = el)}>
|
||||
<div className="queryeditor-group-children" ref={(el) => { this.childContainer = el; }}>
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
<div className="queryeditor-group-row">
|
||||
|
||||
@@ -77,7 +77,7 @@ export class QuerySortingEditor extends React.Component<QuerySortingEditorProps>
|
||||
{this.sorting !== undefined ? (
|
||||
<div>
|
||||
<div className="queryeditor-sorting-rules">
|
||||
<div ref={(el) => (this.childContainer = el)}>
|
||||
<div ref={(el) => { this.childContainer = el; }}>
|
||||
{this.sorting.map((s, idx) => (
|
||||
<div key={idx /* TODO: Invalid key */}>
|
||||
<QuerySortingRule
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import PopupLayer from '../../../../popuplayer';
|
||||
|
||||
export function openPopup(args) {
|
||||
let root: Root | null = null;
|
||||
|
||||
const onChange = () => {
|
||||
args.onChange && args.onChange();
|
||||
renderPopup();
|
||||
@@ -21,7 +23,10 @@ export function openPopup(args) {
|
||||
onDelete
|
||||
};
|
||||
|
||||
ReactDOM.render(React.createElement(args.reactComponent, props), div);
|
||||
if (!root) {
|
||||
root = createRoot(div);
|
||||
}
|
||||
root.render(React.createElement(args.reactComponent, props));
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
@@ -33,7 +38,10 @@ export function openPopup(args) {
|
||||
attachTo: $(args.attachTo),
|
||||
position: 'right',
|
||||
onClose() {
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
if (root) {
|
||||
root.unmount();
|
||||
root = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export class PickVariantPopup extends React.Component<PickVariantPopupProps, Sta
|
||||
<div className="variants-input-container">
|
||||
<input
|
||||
className="variants-input"
|
||||
ref={(ref) => ref && setTimeout(() => ref.focus(), 10)}
|
||||
ref={(ref) => { if (ref) setTimeout(() => ref.focus(), 10); }}
|
||||
autoFocus
|
||||
onKeyUp={this.onKeyUp.bind(this)}
|
||||
onChange={(e) => (this.newVariantName = e.target.value)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
@@ -26,6 +26,7 @@ export class VariantsEditor extends React.Component<VariantsEditorProps, State>
|
||||
model: VariantsEditorProps['model'];
|
||||
popout: any;
|
||||
popupAnchor: HTMLDivElement;
|
||||
private popupRoot: Root | null = null;
|
||||
|
||||
constructor(props: VariantsEditorProps) {
|
||||
super(props);
|
||||
@@ -128,7 +129,7 @@ export class VariantsEditor extends React.Component<VariantsEditorProps, State>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="variants-editor" ref={(el) => (this.popupAnchor = el)}>
|
||||
<div className="variants-editor" ref={(el) => { this.popupAnchor = el; }}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
@@ -185,13 +186,18 @@ export class VariantsEditor extends React.Component<VariantsEditorProps, State>
|
||||
PopupLayer.instance.hidePopout(this.popout);
|
||||
}
|
||||
};
|
||||
ReactDOM.render(React.createElement(PickVariantPopup, props), div);
|
||||
this.popupRoot = createRoot(div);
|
||||
this.popupRoot.render(React.createElement(PickVariantPopup, props));
|
||||
|
||||
this.popout = PopupLayer.instance.showPopout({
|
||||
content: { el: $(div) },
|
||||
attachTo: $(this.popupAnchor),
|
||||
position: 'right',
|
||||
onClose: function () {
|
||||
onClose: () => {
|
||||
if (this.popupRoot) {
|
||||
this.popupRoot.unmount();
|
||||
this.popupRoot = null;
|
||||
}
|
||||
this.popout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { TransitionEditor } from './TransitionEditor';
|
||||
|
||||
@@ -23,6 +23,7 @@ type State = {
|
||||
|
||||
export class VisualStates extends React.Component<VisualStatesProps, State> {
|
||||
popupAnchor: TSFixme;
|
||||
private popupRoot: Root | null = null;
|
||||
|
||||
constructor(props: VisualStatesProps) {
|
||||
super(props);
|
||||
@@ -83,13 +84,20 @@ export class VisualStates extends React.Component<VisualStatesProps, State> {
|
||||
model: this.props.model,
|
||||
visualState: this.state.selectedVisualState
|
||||
};
|
||||
ReactDOM.render(React.createElement(TransitionEditor, props), div);
|
||||
this.popupRoot = createRoot(div);
|
||||
this.popupRoot.render(React.createElement(TransitionEditor, props));
|
||||
|
||||
this.props.portsView.showPopout({
|
||||
arrowColor: '#444444',
|
||||
content: { el: $(div) },
|
||||
attachTo: $(this.popupAnchor),
|
||||
position: 'right'
|
||||
position: 'right',
|
||||
onClose: () => {
|
||||
if (this.popupRoot) {
|
||||
this.popupRoot.unmount();
|
||||
this.popupRoot = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
evt.stopPropagation();
|
||||
@@ -100,7 +108,7 @@ export class VisualStates extends React.Component<VisualStatesProps, State> {
|
||||
<div
|
||||
className="variants-section property-editor-visual-states"
|
||||
style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
|
||||
ref={(el) => (this.popupAnchor = el)}
|
||||
ref={(el) => { this.popupAnchor = el; }}
|
||||
>
|
||||
<div className="variants-name-section" onClick={this.onCurrentStateClicked.bind(this)}>
|
||||
<label>{this.state.selectedVisualState.label} state</label>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import _ from 'underscore';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { UndoQueue, UndoActionGroup } from '@noodl-models/undo-queue-model';
|
||||
@@ -27,6 +27,8 @@ export class PropertyEditor extends View {
|
||||
allowAsRoot: TSFixme;
|
||||
portsView: TSFixme;
|
||||
renderPortsViewScheduled: TSFixme;
|
||||
variantsRoot: Root | null = null;
|
||||
visualStatesRoot: Root | null = null;
|
||||
|
||||
constructor(args) {
|
||||
super();
|
||||
@@ -76,7 +78,11 @@ export class PropertyEditor extends View {
|
||||
this.$('.sidebar-property-editor').removeClass('variants-sidepanel-edit-mode');
|
||||
}
|
||||
};
|
||||
ReactDOM.render(React.createElement(VariantsEditor, props), this.$('.variants')[0]);
|
||||
const container = this.$('.variants')[0];
|
||||
if (!this.variantsRoot) {
|
||||
this.variantsRoot = createRoot(container);
|
||||
}
|
||||
this.variantsRoot.render(React.createElement(VariantsEditor, props));
|
||||
}
|
||||
}
|
||||
renderVisualStates() {
|
||||
@@ -86,7 +92,11 @@ export class PropertyEditor extends View {
|
||||
onVisualStateChanged: this.onVisualStateChanged.bind(this),
|
||||
portsView: this.portsView
|
||||
};
|
||||
ReactDOM.render(React.createElement(VisualStates, props), this.$('.visual-states')[0]);
|
||||
const container = this.$('.visual-states')[0];
|
||||
if (!this.visualStatesRoot) {
|
||||
this.visualStatesRoot = createRoot(container);
|
||||
}
|
||||
this.visualStatesRoot.render(React.createElement(VisualStates, props));
|
||||
}
|
||||
}
|
||||
onVisualStateChanged(state) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import View from './view';
|
||||
|
||||
export interface ReactViewDefaultProps {
|
||||
@@ -8,6 +8,7 @@ export interface ReactViewDefaultProps {
|
||||
|
||||
export abstract class ReactView<TProps extends ReactViewDefaultProps> extends View {
|
||||
private props: TProps;
|
||||
private root: Root | null = null;
|
||||
|
||||
public el: any;
|
||||
|
||||
@@ -31,14 +32,20 @@ export abstract class ReactView<TProps extends ReactViewDefaultProps> extends Vi
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(React.createElement(this.renderReact.bind(this), this.props), this.el[0]);
|
||||
if (!this.root) {
|
||||
this.root = createRoot(this.el[0]);
|
||||
}
|
||||
this.root.render(React.createElement(this.renderReact.bind(this), this.props));
|
||||
|
||||
return this.el;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.el && ReactDOM.unmountComponentAtNode(this.el[0]);
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract renderReact(props: TProps): JSX.Element;
|
||||
protected abstract renderReact(props: TProps): React.JSX.Element;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"@noodl/runtime": "file:../noodl-runtime"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"generate-json-webpack-plugin": "^2.0.0",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "^4.9.5"
|
||||
|
||||
@@ -6,18 +6,11 @@ const { outPath, runtimeVersion } = require('./constants.js');
|
||||
const common = require('./webpack.common.js');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const GenerateJsonPlugin = require('generate-json-webpack-plugin');
|
||||
|
||||
const noodlEditorExternalViewerPath = path.join(outPath, 'cloudruntime');
|
||||
|
||||
function stripStartDirectories(targetPath, numDirs) {
|
||||
const p = targetPath.split('/');
|
||||
p.splice(0, numDirs);
|
||||
return p.join(path.sep);
|
||||
}
|
||||
|
||||
const prefix = `const { ipcRenderer } = require('electron'); const _noodl_cloud_runtime_version = "${runtimeVersion}";`;
|
||||
|
||||
module.exports = merge(common, {
|
||||
@@ -26,22 +19,23 @@ module.exports = merge(common, {
|
||||
},
|
||||
output: {
|
||||
filename: 'sandbox.viewer.bundle.js',
|
||||
path: noodlEditorExternalViewerPath
|
||||
path: noodlEditorExternalViewerPath,
|
||||
clean: true
|
||||
},
|
||||
plugins: [
|
||||
new webpack.BannerPlugin({
|
||||
banner: prefix,
|
||||
raw: true
|
||||
}),
|
||||
new CleanWebpackPlugin(noodlEditorExternalViewerPath, {
|
||||
allowExternal: true
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'static/viewer',
|
||||
to: '.',
|
||||
noErrorOnMissing: true
|
||||
}
|
||||
]
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: 'static/viewer/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
}
|
||||
]),
|
||||
new GenerateJsonPlugin('manifest.json', {
|
||||
version: runtimeVersion
|
||||
})
|
||||
|
||||
@@ -30,20 +30,20 @@
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"babel-loader": "^8.4.1",
|
||||
"clean-webpack-plugin": "^1.0.1",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"css-loader": "^5.0.0",
|
||||
"jest": "^28.1.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^6.11.0",
|
||||
"jest": "^29.7.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-jest": "^29.4.1",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "^5.1.3",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.101.3",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-dev-server": "^4.15.2",
|
||||
"webpack-merge": "^5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,38 +3,35 @@ const { merge } = require('webpack-merge');
|
||||
const { outPath } = require('./constants.js');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const noodlEditorExternalDeployPath = path.join(outPath, 'deploy');
|
||||
|
||||
function stripStartDirectories(targetPath, numDirs) {
|
||||
const p = targetPath.split('/');
|
||||
p.splice(0, numDirs);
|
||||
return p.join(path.sep);
|
||||
}
|
||||
|
||||
module.exports = merge(common, {
|
||||
entry: {
|
||||
deploy: './index.deploy.js'
|
||||
},
|
||||
output: {
|
||||
filename: 'noodl.[name].js',
|
||||
path: noodlEditorExternalDeployPath
|
||||
path: noodlEditorExternalDeployPath,
|
||||
clean: true
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(noodlEditorExternalDeployPath, {
|
||||
allowExternal: true
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: 'static/shared/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
},
|
||||
{
|
||||
from: 'static/deploy/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
}
|
||||
])
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'static/shared',
|
||||
to: '.',
|
||||
noErrorOnMissing: true,
|
||||
info: { minimized: true }
|
||||
},
|
||||
{
|
||||
from: 'static/deploy',
|
||||
to: '.',
|
||||
noErrorOnMissing: true,
|
||||
info: { minimized: true }
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
const path = require('path');
|
||||
const { outPath } = require('./constants.js');
|
||||
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const noodlEditorExternalDeployPath = path.join(outPath, 'ssr');
|
||||
|
||||
function stripStartDirectories(targetPath, numDirs) {
|
||||
const p = targetPath.split('/');
|
||||
p.splice(0, numDirs);
|
||||
return p.join(path.sep);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
deploy: './index.ssr.js'
|
||||
},
|
||||
output: {
|
||||
filename: 'noodl.[name].js',
|
||||
path: noodlEditorExternalDeployPath
|
||||
path: noodlEditorExternalDeployPath,
|
||||
clean: true
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(noodlEditorExternalDeployPath, {
|
||||
allowExternal: true
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
// {
|
||||
// from: 'static/shared/**/*',
|
||||
// transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
// },
|
||||
{
|
||||
from: 'static/ssr/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
}
|
||||
])
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'static/ssr',
|
||||
to: '.',
|
||||
noErrorOnMissing: true,
|
||||
info: { minimized: true }
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
externals: {
|
||||
react: 'React',
|
||||
|
||||
@@ -5,38 +5,35 @@ const { merge } = require('webpack-merge');
|
||||
const { outPath } = require('./constants.js');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const noodlEditorExternalViewerPath = path.join(outPath, 'viewer');
|
||||
|
||||
function stripStartDirectories(targetPath, numDirs) {
|
||||
const p = targetPath.split('/');
|
||||
p.splice(0, numDirs);
|
||||
return p.join(path.sep);
|
||||
}
|
||||
|
||||
module.exports = merge(common, {
|
||||
entry: {
|
||||
viewer: './index.viewer.js'
|
||||
},
|
||||
output: {
|
||||
filename: 'noodl.[name].js',
|
||||
path: noodlEditorExternalViewerPath
|
||||
path: noodlEditorExternalViewerPath,
|
||||
clean: true
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(noodlEditorExternalViewerPath, {
|
||||
allowExternal: true
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: 'static/shared/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
},
|
||||
{
|
||||
from: 'static/viewer/**/*',
|
||||
transformPath: (targetPath) => stripStartDirectories(targetPath, 2)
|
||||
}
|
||||
])
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'static/shared',
|
||||
to: '.',
|
||||
noErrorOnMissing: true,
|
||||
info: { minimized: true }
|
||||
},
|
||||
{
|
||||
from: 'static/viewer',
|
||||
to: '.',
|
||||
noErrorOnMissing: true,
|
||||
info: { minimized: true }
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user