mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-10 14:22:53 +01:00
refactor(editor): useNodeReferences to React context (#64)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
import { type ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { type NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { type NodeLibraryNodeType } from '@noodl-models/nodelibrary';
|
||||
import { type Slot } from '@noodl-core-ui/types/global';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
|
||||
|
||||
export type NodeReference = {
|
||||
type: NodeLibraryNodeType;
|
||||
displayName: string;
|
||||
referenaces: {
|
||||
displayName: string;
|
||||
node?: NodeGraphNode;
|
||||
component: ComponentModel;
|
||||
}[];
|
||||
};
|
||||
|
||||
export interface NodeReferencesContext {
|
||||
nodeReferences: NodeReference[];
|
||||
}
|
||||
|
||||
const NodeReferencesContext = createContext<NodeReferencesContext>({
|
||||
nodeReferences: [],
|
||||
});
|
||||
|
||||
export interface NodeReferencesContextProps {
|
||||
children: Slot;
|
||||
}
|
||||
|
||||
export function NodeReferencesContextProvider({ children }: NodeReferencesContextProps) {
|
||||
const [group] = useState({});
|
||||
const [nodeReferences, setNodeReferences] = useState<NodeReference[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
function updateIndex() {
|
||||
const types: { [key: string]: NodeReference['type'] } = {};
|
||||
const references = new Map<string, NodeReference['referenaces']>();
|
||||
|
||||
function handleComponent(component: ComponentModel) {
|
||||
component.forEachNode((node: NodeGraphNode) => {
|
||||
const name = node.type.name;
|
||||
|
||||
// Add the reference
|
||||
references.set(name, [
|
||||
...(references.get(name) || []),
|
||||
{
|
||||
displayName: component.displayName || component.name,
|
||||
node,
|
||||
component
|
||||
}
|
||||
]);
|
||||
|
||||
// Repeater
|
||||
if (name === 'For Each' && node.parameters.template) {
|
||||
const templateComponent = ProjectModel.instance.getComponentWithName(node.parameters.template);
|
||||
|
||||
if (templateComponent) {
|
||||
references.set(templateComponent.fullName, [
|
||||
...(references.get(templateComponent.fullName) || []),
|
||||
{
|
||||
displayName: component.displayName || component.name,
|
||||
node,
|
||||
component
|
||||
}
|
||||
]);
|
||||
|
||||
handleComponent(templateComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Add some metadata for this node if we dont have it yet.
|
||||
if (!types[name]) {
|
||||
types[name] = node.type;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loop all the nodes in the project
|
||||
ProjectModel.instance.forEachComponent(handleComponent);
|
||||
|
||||
// Combine the result to look a little better.
|
||||
const results: NodeReference[] = Array.from(references.keys())
|
||||
.map((key) => ({
|
||||
type: types[key],
|
||||
displayName: types[key]?.displayName || key,
|
||||
referenaces: references.get(key)
|
||||
}))
|
||||
.sort((a, b) => b.referenaces.length - a.referenaces.length);
|
||||
|
||||
setNodeReferences(results);
|
||||
}
|
||||
|
||||
updateIndex();
|
||||
|
||||
EventDispatcher.instance.on(
|
||||
[
|
||||
'Model.nodeAdded',
|
||||
'Model.nodeRemoved',
|
||||
'Model.componentAdded',
|
||||
'Model.componentRemoved',
|
||||
'Model.componentRenamed'
|
||||
],
|
||||
updateIndex,
|
||||
group
|
||||
);
|
||||
|
||||
return function () {
|
||||
EventDispatcher.instance.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NodeReferencesContext.Provider
|
||||
value={{
|
||||
nodeReferences,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NodeReferencesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNodeReferencesContext() {
|
||||
const context = useContext(NodeReferencesContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useNodeReferencesContext must be a child of NodeReferencesContextProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './NodeReferencesContext';
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NodeGraphContextProvider } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import { NodeReferencesContextProvider } from '@noodl-contexts/NodeReferencesContext';
|
||||
import { PluginContextProvider } from '@noodl-contexts/PluginContext';
|
||||
import { ProjectDesignTokenContextProvider } from '@noodl-contexts/ProjectDesignTokenContext';
|
||||
import { useKeyboardCommands } from '@noodl-hooks/useKeyboardCommands';
|
||||
import { useModel } from '@noodl-hooks/useModel';
|
||||
@@ -43,7 +45,6 @@ import { BaseWindow } from '../../views/windows/BaseWindow';
|
||||
import { whatsnewRender } from '../../whats-new';
|
||||
import { IRouteProps } from '../AppRoute';
|
||||
import { useSetupSettings } from './useSetupSettings';
|
||||
import { PluginContextProvider } from '@noodl-contexts/PluginContext';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const ImportOverwritePopupTemplate = require('../../templates/importoverwritepopup.html');
|
||||
@@ -223,28 +224,30 @@ export function EditorPage({ route }: EditorPageProps) {
|
||||
|
||||
return (
|
||||
<NodeGraphContextProvider>
|
||||
<ProjectDesignTokenContextProvider>
|
||||
<PluginContextProvider>
|
||||
<BaseWindow>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<>
|
||||
<FrameDivider
|
||||
first={<SidePanel />}
|
||||
second={<ErrorBoundary>{Boolean(Document) && <Document />}</ErrorBoundary>}
|
||||
sizeMin={200}
|
||||
size={frameDividerSize}
|
||||
horizontal
|
||||
onSizeChanged={setFrameDividerSize}
|
||||
/>
|
||||
<NodeReferencesContextProvider>
|
||||
<ProjectDesignTokenContextProvider>
|
||||
<PluginContextProvider>
|
||||
<BaseWindow>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<>
|
||||
<FrameDivider
|
||||
first={<SidePanel />}
|
||||
second={<ErrorBoundary>{Boolean(Document) && <Document />}</ErrorBoundary>}
|
||||
sizeMin={200}
|
||||
size={frameDividerSize}
|
||||
horizontal
|
||||
onSizeChanged={setFrameDividerSize}
|
||||
/>
|
||||
|
||||
{Boolean(lesson) && <Frame instance={lesson} isContentSize isFitWidth />}
|
||||
</>
|
||||
)}
|
||||
</BaseWindow>
|
||||
</PluginContextProvider>
|
||||
</ProjectDesignTokenContextProvider>
|
||||
{Boolean(lesson) && <Frame instance={lesson} isContentSize isFitWidth />}
|
||||
</>
|
||||
)}
|
||||
</BaseWindow>
|
||||
</PluginContextProvider>
|
||||
</ProjectDesignTokenContextProvider>
|
||||
</NodeReferencesContextProvider>
|
||||
</NodeGraphContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import { type NodeReference, useNodeReferencesContext } from '@noodl-contexts/NodeReferencesContext';
|
||||
import { useFocusRefOnPanelActive } from '@noodl-hooks/useFocusRefOnPanelActive';
|
||||
import { useNodeLibraryLoaded } from '@noodl-hooks/useNodeLibraryLoaded';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { INodeColorScheme } from '@noodl-types/nodeTypes';
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { NodeLibrary, NodeLibraryNodeType } from '@noodl-models/nodelibrary';
|
||||
import { NodeLibrary } from '@noodl-models/nodelibrary';
|
||||
import { BasicNodeType } from '@noodl-models/nodelibrary/BasicNodeType';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
import { EditorNode } from '@noodl-core-ui/components/common/EditorNode';
|
||||
import { IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
|
||||
@@ -26,113 +24,17 @@ import { Section, SectionVariant } from '@noodl-core-ui/components/sidebar/Secti
|
||||
import { Label } from '@noodl-core-ui/components/typography/Label';
|
||||
|
||||
import { NodeReferencesPanel_ID } from '.';
|
||||
import { EventDispatcher } from '../../../../../shared/utils/EventDispatcher';
|
||||
|
||||
type ResultItem = {
|
||||
type: NodeLibraryNodeType;
|
||||
displayName: string;
|
||||
referenaces: {
|
||||
displayName: string;
|
||||
node?: NodeGraphNode;
|
||||
component: ComponentModel;
|
||||
}[];
|
||||
};
|
||||
|
||||
function useNodeReferences() {
|
||||
const [group] = useState({});
|
||||
const [result, setResult] = useState<ResultItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
function updateIndex() {
|
||||
const types: { [key: string]: ResultItem['type'] } = {};
|
||||
const references = new Map<string, ResultItem['referenaces']>();
|
||||
|
||||
function handleComponent(component: ComponentModel) {
|
||||
component.forEachNode((node: NodeGraphNode) => {
|
||||
const name = node.type.name;
|
||||
|
||||
// Add the reference
|
||||
references.set(name, [
|
||||
...(references.get(name) || []),
|
||||
{
|
||||
displayName: component.displayName || component.name,
|
||||
node,
|
||||
component
|
||||
}
|
||||
]);
|
||||
|
||||
// Repeater
|
||||
if (name === 'For Each' && node.parameters.template) {
|
||||
const templateComponent = ProjectModel.instance.getComponentWithName(node.parameters.template);
|
||||
|
||||
if (templateComponent) {
|
||||
references.set(templateComponent.fullName, [
|
||||
...(references.get(templateComponent.fullName) || []),
|
||||
{
|
||||
displayName: component.displayName || component.name,
|
||||
node,
|
||||
component
|
||||
}
|
||||
]);
|
||||
|
||||
handleComponent(templateComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Add some metadata for this node if we dont have it yet.
|
||||
if (!types[name]) {
|
||||
types[name] = node.type;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loop all the nodes in the project
|
||||
ProjectModel.instance.forEachComponent(handleComponent);
|
||||
|
||||
// Combine the result to look a little better.
|
||||
const results: ResultItem[] = Array.from(references.keys())
|
||||
.map((key) => ({
|
||||
type: types[key],
|
||||
displayName: types[key]?.displayName || key,
|
||||
referenaces: references.get(key)
|
||||
}))
|
||||
.sort((a, b) => b.referenaces.length - a.referenaces.length);
|
||||
|
||||
setResult(results);
|
||||
}
|
||||
|
||||
updateIndex();
|
||||
|
||||
EventDispatcher.instance.on(
|
||||
[
|
||||
'Model.nodeAdded',
|
||||
'Model.nodeRemoved',
|
||||
'Model.componentAdded',
|
||||
'Model.componentRemoved',
|
||||
'Model.componentRenamed'
|
||||
],
|
||||
updateIndex,
|
||||
group
|
||||
);
|
||||
|
||||
return function () {
|
||||
EventDispatcher.instance.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return [result];
|
||||
}
|
||||
|
||||
export function NodeReferencesPanel() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [includeCoreNodes, setIncludeCoreNodes] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
const [result] = useNodeReferences();
|
||||
const { nodeReferences } = useNodeReferencesContext();
|
||||
const nodeLibraryLoaded = useNodeLibraryLoaded();
|
||||
|
||||
useFocusRefOnPanelActive(inputRef, NodeReferencesPanel_ID);
|
||||
|
||||
function searchFilter(x: ResultItem) {
|
||||
function searchFilter(x: NodeReference) {
|
||||
if (x.displayName.toLowerCase().includes(searchTerm)) {
|
||||
return true;
|
||||
}
|
||||
@@ -144,7 +46,7 @@ export function NodeReferencesPanel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let filteredResult = result.filter(searchFilter);
|
||||
let filteredResult = nodeReferences.filter(searchFilter);
|
||||
if (!includeCoreNodes) {
|
||||
filteredResult = filteredResult.filter((x) => x.displayName.startsWith('/'));
|
||||
}
|
||||
@@ -185,7 +87,7 @@ export function NodeReferencesPanel() {
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
entry: ResultItem;
|
||||
entry: NodeReference;
|
||||
}
|
||||
|
||||
function Item({ entry }: ItemProps) {
|
||||
@@ -245,8 +147,8 @@ function Item({ entry }: ItemProps) {
|
||||
}
|
||||
|
||||
interface ItemReferenceProps {
|
||||
entry: ResultItem;
|
||||
referenace: ResultItem['referenaces'][0];
|
||||
entry: NodeReference;
|
||||
referenace: NodeReference['referenaces'][0];
|
||||
colors: INodeColorScheme;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user