rename History -> Editor
This commit is contained in:
parent
532fc0c006
commit
5ec5574488
@ -11,6 +11,9 @@
|
|||||||
"pages": {
|
"pages": {
|
||||||
"screening": {
|
"screening": {
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"base": {
|
||||||
|
"remove": "削除"
|
||||||
|
},
|
||||||
"fetch_ToshoListed": {
|
"fetch_ToshoListed": {
|
||||||
"title": "東証上場銘柄一覧"
|
"title": "東証上場銘柄一覧"
|
||||||
},
|
},
|
||||||
|
@ -1,38 +1,59 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { Background, Controls, ReactFlow, Node, useNodesState, useEdgesState, addEdge, Connection } from "@xyflow/react";
|
import { Background, Controls, Edge, ReactFlow, Node, useNodesState, useEdgesState, Connection } from "@xyflow/react";
|
||||||
import * as node from "./node/";
|
import * as node from "./node/";
|
||||||
|
|
||||||
import "@xyflow/react/dist/style.css";
|
import "@xyflow/react/dist/style.css";
|
||||||
|
|
||||||
import { History, Impl as HistoryImpl } from "./context/History";
|
import { Editor, Impl as EditorImpl } from "./context/Editor";
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
fetch_ToshoListed: node.fetch.ToshoListed,
|
fetch_ToshoListed: node.fetch.ToshoListed,
|
||||||
present_TableDisplay: node.present.TableDisplay,
|
present_TableDisplay: node.present.TableDisplay,
|
||||||
};
|
};
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
{id: "n1", type: "fetch_ToshoListed", position: {x:0, y:0}, data: {prime: true, standard: true, growth: true}},
|
{
|
||||||
{id: "n2", type: "present_TableDisplay", position: {x:0, y:100}, data: {}},
|
id: "n1",
|
||||||
{id: "n3", position: {x:0, y:200}, data: {label: "hello"}},
|
type: "fetch_ToshoListed",
|
||||||
|
position: {x:0, y:0},
|
||||||
|
data: {prime: true, standard: true, growth: true},
|
||||||
|
deletable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "n2",
|
||||||
|
type: "present_TableDisplay",
|
||||||
|
position: {x:0, y:100},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "n3",
|
||||||
|
position: {x:0, y:200},
|
||||||
|
data: {label: "hello"},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Screening() {
|
export default function Screening() {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([] as Edge[]);
|
||||||
|
|
||||||
|
const editor = useMemo(()=>new EditorImpl(setNodes, setEdges, 100), []);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
|
(conn: Connection)=>editor.connect(conn), []);
|
||||||
[]
|
const onDisconnect = useCallback(
|
||||||
);
|
(conns: Edge[])=>editor.disconnect(conns.map((x)=>x as Connection)), []);
|
||||||
|
const onNodesDelete = useCallback(
|
||||||
|
(nodes: Node[])=>editor.remove(nodes.map((x)=>x.id)), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<History.Provider value={new HistoryImpl(setNodes)}>
|
<Editor.Provider value={editor}>
|
||||||
<div style={{ position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }}>
|
<div style={{ position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
|
onNodesDelete={onNodesDelete}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesDelete={onDisconnect}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
fitView
|
fitView
|
||||||
@ -41,6 +62,6 @@ export default function Screening() {
|
|||||||
<Background />
|
<Background />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
</History.Provider>
|
</Editor.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
72
src/pages/context/Editor.tsx
Normal file
72
src/pages/context/Editor.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Dispatch, SetStateAction, createContext } from "react";
|
||||||
|
import { Connection, EdgeType, NodeType, addEdge } from "@xyflow/react";
|
||||||
|
|
||||||
|
type Pos = {x: int, y: int,};
|
||||||
|
|
||||||
|
export interface Iface {
|
||||||
|
add(type: string, pos: Pos, data: any): string;
|
||||||
|
remove(id: string[]): void;
|
||||||
|
modify(id: string, data: any): void;
|
||||||
|
|
||||||
|
connect(conn: Connection): void;
|
||||||
|
disconnect(conn: Connection[]): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Editor = createContext<Iface|undefined>(undefined);
|
||||||
|
export default Editor;
|
||||||
|
|
||||||
|
type SetNodes = Dispatch<SetStateAction<NodeType[]>>;
|
||||||
|
type SetEdges = Dispatch<SetStateAction<EdgeType[]>>;
|
||||||
|
export class Impl implements Iface {
|
||||||
|
_setNodes: SetNodes;
|
||||||
|
_setEdges: SetEdges;
|
||||||
|
_nextId: number = 0;
|
||||||
|
|
||||||
|
constructor(setNodes: SetNodes, setEdges: SetEdges, nextId: number = 0) {
|
||||||
|
this._setNodes = setNodes;
|
||||||
|
this._setEdges = setEdges;
|
||||||
|
this._nextId = nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(type: string, pos: Pos, data: any): string {
|
||||||
|
const ret = this._genNextId();
|
||||||
|
this._setNodes((nds)=>[...nds, {
|
||||||
|
id: ret,
|
||||||
|
type: type,
|
||||||
|
pos: pos,
|
||||||
|
data: data,
|
||||||
|
}]);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
remove(ids: string[]): void {
|
||||||
|
this._setNodes((nds)=>nds.filter((x)=>!ids.includes(x.id)));
|
||||||
|
}
|
||||||
|
modify(id: string, data: any): void {
|
||||||
|
this._setNodes(
|
||||||
|
(nds)=>nds.map((n)=>n.id === id? {...n, data: data}: n)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(c: Connection): void {
|
||||||
|
this._setEdges((eds)=>addEdge({
|
||||||
|
...c,
|
||||||
|
deletable: true,
|
||||||
|
}, eds));
|
||||||
|
}
|
||||||
|
disconnect(cs: Connection[]): void {
|
||||||
|
this._setEdges((eds)=>eds.filter((e)=>!cs.some(c=>matchEdgeAndConn(e, c))));
|
||||||
|
}
|
||||||
|
|
||||||
|
_genNextId(): number {
|
||||||
|
return "n"+(_nextId++);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function matchEdgeAndConn(e: Edge, c: Connection) {
|
||||||
|
return (
|
||||||
|
(e.source === c.source) &&
|
||||||
|
((e.sourceHandle??null) == (c.sourceHandle??null)) &&
|
||||||
|
(e.target == c.target) &&
|
||||||
|
((e.targetHandle??null) == (c.targetHandle??null))
|
||||||
|
);
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
import { Dispatch, SetStateAction, createContext } from "react";
|
|
||||||
import { Node, } from "@xyflow/react";
|
|
||||||
|
|
||||||
export interface Iface {
|
|
||||||
push(id: string, data: any): void,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const History = createContext<Iface|undefined>(undefined);
|
|
||||||
export default History;
|
|
||||||
|
|
||||||
export class Impl implements Iface {
|
|
||||||
setNodes: Dispatch<SetStateAction<Node[]>>;
|
|
||||||
|
|
||||||
constructor(setNodes: Dispatch<SetStateAction<Node[]>>) {
|
|
||||||
this.setNodes = setNodes;
|
|
||||||
}
|
|
||||||
push(id: string, data: any): void {
|
|
||||||
this.setNodes(
|
|
||||||
(nds)=>nds.map((n)=>n.id == id? {...n, data: data}: n)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,14 +1,39 @@
|
|||||||
import { ReactNode } from "react";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ReactNode, useContext, useCallback } from "react";
|
||||||
|
import { NodeProps } from "@xyflow/react";
|
||||||
|
|
||||||
|
import style from "./style.module.scss";
|
||||||
|
|
||||||
|
import Editor from "../context/Editor";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
title: ReactNode,
|
title: ReactNode,
|
||||||
children: ReactNode,
|
children: ReactNode,
|
||||||
|
node: NodeProps,
|
||||||
};
|
};
|
||||||
export default function Base(p: Params) {
|
export default function Base(p: Params) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const editor = useContext(Editor);
|
||||||
|
if (editor === undefined) {
|
||||||
|
throw new Error("no editor context");
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDelete = useCallback(()=>editor.remove([p.node.id]), [p.node]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={style.node}>
|
||||||
<div>{p.title}</div>
|
<div className={style.header}>
|
||||||
|
<div className={style.title}>{p.title}</div>
|
||||||
|
<div className={style.buttons}>
|
||||||
|
<button onClick={onDelete} disabled={!p.node.deletable}>
|
||||||
|
{t("pages.screening.nodes.base.remove")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.body}>
|
||||||
{p.children}
|
{p.children}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,30 +3,35 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Handle, Node, NodeProps, Position } from "@xyflow/react";
|
import { Handle, Node, NodeProps, Position } from "@xyflow/react";
|
||||||
import Base from "./Base";
|
import Base from "./Base";
|
||||||
|
|
||||||
import History from "../context/History";
|
import Editor from "../context/Editor";
|
||||||
|
|
||||||
export type ToshoListed = Node<{
|
export type ToshoListed = Node<{
|
||||||
prime: boolean,
|
prime: boolean,
|
||||||
standard: boolean,
|
standard: boolean,
|
||||||
growth: boolean,
|
growth: boolean,
|
||||||
}, "fetch_ToshoListed">;
|
}, "fetch_ToshoListed">;
|
||||||
export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
export function ToshoListed(node: NodeProps<ToshoListed>) {
|
||||||
|
const {id, data} = node;
|
||||||
|
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const history = useContext(History);
|
const editor = useContext(Editor);
|
||||||
if (history === undefined) {
|
if (editor === undefined) {
|
||||||
throw new Error("no History context");
|
throw new Error("no editor context");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base title={t("pages.screening.nodes.fetch_ToshoListed.title")}>
|
<Base
|
||||||
|
node={node}
|
||||||
|
title={t("pages.screening.nodes.fetch_ToshoListed.title")}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div><label>
|
<div><label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="prime"
|
name="prime"
|
||||||
checked={data.prime}
|
checked={data.prime}
|
||||||
onChange={(e)=>history.push(id, {...data, prime: e.target.checked})}
|
onChange={(e)=>editor.modify(id, {...data, prime: e.target.checked})}
|
||||||
/>
|
/>
|
||||||
{t("terms.toshoPrime")}
|
{t("terms.toshoPrime")}
|
||||||
</label></div>
|
</label></div>
|
||||||
@ -35,7 +40,7 @@ export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="standard"
|
name="standard"
|
||||||
checked={data.standard}
|
checked={data.standard}
|
||||||
onChange={(e)=>history.push(id, {...data, standard: e.target.checked})}
|
onChange={(e)=>editor.modify(id, {...data, standard: e.target.checked})}
|
||||||
/>
|
/>
|
||||||
{t("terms.toshoStandard")}
|
{t("terms.toshoStandard")}
|
||||||
</label></div>
|
</label></div>
|
||||||
@ -44,7 +49,7 @@ export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="growth"
|
name="growth"
|
||||||
checked={data.growth}
|
checked={data.growth}
|
||||||
onChange={(e)=>history.push(id, {...data, growth: e.target.checked})}
|
onChange={(e)=>editor.modify(id, {...data, growth: e.target.checked})}
|
||||||
/>
|
/>
|
||||||
{t("terms.toshoGrowth")}
|
{t("terms.toshoGrowth")}
|
||||||
</label></div>
|
</label></div>
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Handle, Position } from "@xyflow/react";
|
import { Handle, NodeProps, Position } from "@xyflow/react";
|
||||||
import Base from "./Base";
|
import Base from "./Base";
|
||||||
|
|
||||||
export function TableDisplay() {
|
export function TableDisplay(node: NodeProps) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Base title={t("pages.screening.nodes.present_TableDisplay.title")}>
|
<Base
|
||||||
|
node={node}
|
||||||
|
title={t("pages.screening.nodes.present_TableDisplay.title")}
|
||||||
|
removable={true}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="#">{t("pages.screening.nodes.present_TableDisplay.open")}</a></div>
|
<div><a href="#">{t("pages.screening.nodes.present_TableDisplay.open")}</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
21
src/pages/node/style.module.scss
Normal file
21
src/pages/node/style.module.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.node {
|
||||||
|
background-color: #FFF9E5;
|
||||||
|
border: 1px solid #4A9782;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #DCD0A8;
|
||||||
|
display: flex;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user