change display layout to node editor
This commit is contained in:
@@ -10,23 +10,10 @@
|
||||
},
|
||||
"pages": {
|
||||
"screening": {
|
||||
"title": "銘柄スクリーニング",
|
||||
"rerun": "再実行",
|
||||
"universe": {
|
||||
"title": "ユニバース",
|
||||
"desc": "スクリーニング対象の銘柄の条件を設定してください",
|
||||
"type": {
|
||||
"listed": "全上場銘柄"
|
||||
"nodes": {
|
||||
"fetch_ToshoListed": {
|
||||
"title": "東証上場銘柄一覧"
|
||||
}
|
||||
},
|
||||
"evaluation": {
|
||||
"title": "銘柄評価",
|
||||
"desc": "銘柄一覧テーブルに表示する列を設定してください",
|
||||
"columnName": "カラム名"
|
||||
},
|
||||
"filterOut": {
|
||||
"title": "選別",
|
||||
"desc": "銘柄の選別条件を設定してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,96 +1,45 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useCallback } from "react";
|
||||
import { Background, Controls, ReactFlow, Node, useNodesState, useEdgesState, addEdge, Connection } from "@xyflow/react";
|
||||
import * as node from "./node/";
|
||||
|
||||
function Screening() {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<h1>{t("pages.screening.title")}</h1>
|
||||
<UniverseSpec/>
|
||||
<EvaluationSpec/>
|
||||
<FilterOutSpec/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default Screening;
|
||||
import "@xyflow/react/dist/style.css";
|
||||
|
||||
function UniverseSpec() {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<FilterBox
|
||||
title={t("pages.screening.universe.title")}
|
||||
desc={t("pages.screening.universe.desc")}
|
||||
>
|
||||
<select>
|
||||
<option>{t("pages.screening.universe.type.listed")}</option>
|
||||
</select>
|
||||
<label><input type="checkbox" />{t("terms.toshoGrowth")}</label>
|
||||
<label><input type="checkbox" />{t("terms.toshoStandard")}</label>
|
||||
<label><input type="checkbox" />{t("terms.toshoPrime")}</label>
|
||||
</FilterBox>
|
||||
);
|
||||
}
|
||||
import { History, Impl as HistoryImpl } from "./context/History";
|
||||
|
||||
function EvaluationSpec() {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<FilterBox
|
||||
title={t("pages.screening.evaluation.title")}
|
||||
desc={t("pages.screening.evaluation.desc")}
|
||||
>
|
||||
<ol>
|
||||
{
|
||||
[...Array(3)].map((_,i)=>(
|
||||
<li>
|
||||
<input type="text" placeholder={t("pages.screening.evaluation.columnName")} />
|
||||
=
|
||||
<select>
|
||||
<option>{t("terms.stock.volume")}</option>
|
||||
<option>{t("terms.stock.price")}</option>
|
||||
</select>
|
||||
<a href="#">remove</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
<li>
|
||||
<a href="#">add</a>
|
||||
</li>
|
||||
</ol>
|
||||
</FilterBox>
|
||||
);
|
||||
}
|
||||
|
||||
function FilterOutSpec() {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<FilterBox
|
||||
title={t("pages.screening.filterOut.title")}
|
||||
desc={t("pages.screening.filterOut.desc")}
|
||||
>
|
||||
<textarea />
|
||||
</FilterBox>
|
||||
);
|
||||
}
|
||||
|
||||
type FilterBoxProps = {
|
||||
title: string,
|
||||
desc: string,
|
||||
children: ReactNode,
|
||||
const nodeTypes = {
|
||||
fetch_ToshoListed: node.fetch.ToshoListed,
|
||||
};
|
||||
function FilterBox({title, desc, children}: FilterBoxProps) {
|
||||
const {t} = useTranslation();
|
||||
const initialNodes: Node[] = [
|
||||
{id: "n1", type: "fetch_ToshoListed", position: {x:0, y:0}, data: {prime: true, standard: true, growth: true}},
|
||||
{id: "n2", position: {x:0, y:100}, data: {label: "hi"}},
|
||||
{id: "n3", position: {x:0, y:200}, data: {label: "hello"}},
|
||||
];
|
||||
|
||||
export default function Screening() {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
<p>{desc}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#">{t("pages.screening.rerun")}</a>
|
||||
</div>
|
||||
<History.Provider value={new HistoryImpl(setNodes)}>
|
||||
<div style={{ position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }}>
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
>
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</History.Provider>
|
||||
);
|
||||
}
|
||||
|
22
src/pages/context/History.tsx
Normal file
22
src/pages/context/History.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
};
|
14
src/pages/node/Base.tsx
Normal file
14
src/pages/node/Base.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type Params = {
|
||||
title: ReactNode,
|
||||
children: ReactNode,
|
||||
};
|
||||
export default function Base(p: Params) {
|
||||
return (
|
||||
<div>
|
||||
<div>{p.title}</div>
|
||||
{p.children}
|
||||
</div>
|
||||
);
|
||||
}
|
56
src/pages/node/fetch.tsx
Normal file
56
src/pages/node/fetch.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Handle, Node, NodeProps, Position } from "@xyflow/react";
|
||||
import Base from "./Base";
|
||||
|
||||
import History from "../context/History";
|
||||
|
||||
export type ToshoListed = Node<{
|
||||
prime: boolean,
|
||||
standard: boolean,
|
||||
growth: boolean,
|
||||
}, "ToshoListed">;
|
||||
export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const history = useContext(History);
|
||||
if (history === undefined) {
|
||||
throw new Error("no History context");
|
||||
}
|
||||
|
||||
return (
|
||||
<Base title={t("pages.screening.nodes.fetch_ToshoListed.title")}>
|
||||
<div>
|
||||
<div><label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="prime"
|
||||
checked={data.prime}
|
||||
onChange={(e)=>history.push(id, {...data, prime: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoPrime")}
|
||||
</label></div>
|
||||
<div><label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="standard"
|
||||
checked={data.standard}
|
||||
onChange={(e)=>history.push(id, {...data, standard: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoStandard")}
|
||||
</label></div>
|
||||
<div><label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="growth"
|
||||
checked={data.growth}
|
||||
onChange={(e)=>history.push(id, {...data, growth: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoGrowth")}
|
||||
</label></div>
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right}/>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
2
src/pages/node/index.tsx
Normal file
2
src/pages/node/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Base } from "./Base";
|
||||
export * as fetch from "./fetch";
|
Reference in New Issue
Block a user