Skip to content

Commit

Permalink
Fix bug in singlerules
Browse files Browse the repository at this point in the history
  • Loading branch information
tansongchen committed Jul 5, 2024
1 parent 0892a5f commit f20a89a
Show file tree
Hide file tree
Showing 14 changed files with 533 additions and 240 deletions.
16 changes: 16 additions & 0 deletions scripts/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { readFileSync, writeFileSync } from "fs";
import type { Compound, PrimitiveCharacter } from "~/lib";

const repertoire = JSON.parse(
readFileSync("public/cache/repertoire.json", "utf-8"),
) as PrimitiveCharacter[];

const diff = [];

for (const { unicode, gb2312, tygf } of repertoire) {
if (gb2312 && tygf === 0) {
diff.push(String.fromCodePoint(unicode));
}
}

writeFileSync("public/cache/diff.txt", diff.join(""));
10 changes: 10 additions & 0 deletions src/atoms/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const customizeAtom = focusAtom(analysisAtom, (o) =>
o.prop("customize").valueOr({} as NonNullable<Analysis["customize"]>),
);

export const customizeCornersAtom = focusAtom(analysisAtom, (o) =>
o
.prop("customizeCorners")
.valueOr({} as NonNullable<Analysis["customizeCorners"]>),
);

export const classifierCustomizationAtom = focusAtom(analysisAtom, (o) =>
o.prop("classifier").valueOr({} as Record<Feature, number>),
);
Expand All @@ -40,3 +46,7 @@ export const customClassifierAtom = atom((get) => {
const customization = get(classifierCustomizationAtom);
return mergeClassifier(customization);
});

export const serializerAtom = focusAtom(analysisAtom, (o) =>
o.prop("serializer").valueOr("sequential" as "sequential" | "c3"),
);
13 changes: 6 additions & 7 deletions src/components/DetailEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Panel } from "reactflow";
import type { ConditionData, SourceData } from "./graph";
import styled from "styled-components";
import { Cascader, Flex, Form, Typography } from "antd";
import type { CodableObject } from "~/lib";
import type { CodableObject, Condition, Source, UnaryOp } from "~/lib";
import { parseList, defaultAlgebra, renderList } from "~/lib";
import { Select } from "./Utils";
import type { Op } from "~/lib";
import { ops, unaryOps } from "~/lib";
import TextArea from "antd/es/input/TextArea";
import { useAtomValue, keyboardAtom, algebraAtom } from "~/atoms";
Expand Down Expand Up @@ -35,12 +33,13 @@ export default function DetailEditor({
setData,
}: {
selected: string;
data: SourceData | ConditionData;
setData: (data: SourceData | ConditionData) => void;
data: Source | Condition | undefined;
setData: (data: Source | Condition) => void;
}) {
const { alphabet } = useAtomValue(keyboardAtom);
const algebra = useAtomValue(algebraAtom);
const customElements = useAtomValue(customElementsAtom);
if (data === undefined) return null;
const genericIndices = [...Array(10).keys()]
.map((x) => [x + 1, -(x + 1)])
.flat();
Expand Down Expand Up @@ -165,8 +164,8 @@ export default function DetailEditor({
value: v,
}))}
onChange={(event) => {
if ((unaryOps as readonly Op[]).includes(event)) {
setData({ ...data, operator: event });
if (unaryOps.includes(event as UnaryOp)) {
setData({ ...data, operator: event as UnaryOp });
} else {
setData({
...data,
Expand Down
195 changes: 106 additions & 89 deletions src/components/Node.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import { Button, Dropdown } from "antd";
import type { PropsWithChildren } from "react";
import type { Edge, Node, NodeProps } from "reactflow";
import { Handle, Position, useReactFlow } from "reactflow";
import { useContext, type PropsWithChildren } from "react";
import type { NodeProps } from "reactflow";
import { Handle, Position } from "reactflow";
import styled from "styled-components";
import type {
MenuItemGroupType,
MenuItemType,
} from "antd/es/menu/hooks/useItems";
// import {
// SubMenuType,
// } from "antd/es/menu/hooks/useItems";
import type { SourceData, ConditionData } from "./graph";
import {
makeEdge,
getLayoutedElements,
makeSourceNode,
sortNodes,
makeConditionNode,
renderType,
} from "./graph";
import { CacheContext, renderType } from "./graph";
import type { Condition, Source } from "~/lib";
import { renderName } from "~/lib";
import { blue } from "@ant-design/colors";
import type { MenuItemGroupType, MenuItemType } from "antd/es/menu/interface";

const SourceButton = styled(Button)`
width: 64px;
Expand All @@ -31,97 +19,126 @@ const SourceButton = styled(Button)`
&:focus {
color: ${blue[4]};
border-color: ${blue[4]};
outline: 2px solid ${blue[4]};
}
`;

const ConditionButton = styled(SourceButton)`
border-radius: 0;
`;

const defaultSource: Source = { object: { type: "汉字" as const }, next: null };
const defaultCondition: Condition = {
object: { type: "汉字" as const },
operator: "存在",
positive: null,
negative: null,
};

const getNewId = (sources: Record<string, any>, type: "s" | "c") => {
let newId = 0;
for (const currentId of Object.keys(sources)) {
if (currentId !== `${type}${newId}`) break;
newId += 1;
}
return `${type}${newId}`;
};

type Creator = (etype?: "positive" | "negative") => MenuItemType;

const ContextMenu = ({ id, children }: PropsWithChildren<{ id: string }>) => {
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow<
SourceData | ConditionData
>();
const nodes = getNodes();
const sourceNodes = nodes.filter((n) => n.id.startsWith("s")).sort(sortNodes);
const conditionNodes = nodes
.filter((n) => n.id.startsWith("c"))
.sort(sortNodes);
const edges = getEdges();
const setLayout = (newnodes: Node[], newedges: Edge[]) => {
const [lnodes, ledges] = getLayoutedElements(newnodes, newedges);
setNodes(lnodes);
setEdges(ledges);
const { sources, setSources, conditions, setConditions, setSelected } =
useContext(CacheContext);
if (sources[id] === undefined && conditions[id] === undefined) return null;
const createSourceNode: Creator = (etype) => {
const label = "添加源节点" + (etype ? `(${renderType[etype]})` : "");
return {
key: `create-source-${etype}`,
label,
onClick: () => {
const newId = getNewId(sources, "s");
const newSources = { ...sources, [newId]: defaultSource };
const newConditions = { ...conditions };
if (etype === undefined) {
newSources[id] = { ...newSources[id]!, next: newId };
} else {
newConditions[id] = {
...newConditions[id]!,
[etype]: newId,
};
}
setSources(newSources);
setConditions(newConditions);
setSelected(newId);
},
};
};
const createConditionNode: Creator = (etype) => {
const label = "添加条件节点" + (etype ? `(${renderType[etype]})` : "");
return {
key: `create-condition-${etype}`,
label,
onClick: () => {
const newId = getNewId(conditions, "c");
const newSources = { ...sources };
const newConditions = { ...conditions, [newId]: defaultCondition };
if (etype === undefined) {
newSources[id] = { ...newSources[id]!, next: newId };
} else {
newConditions[id] = {
...newConditions[id]!,
[etype]: newId,
};
}
setSources(newSources);
setConditions(newConditions);
setSelected(newId);
},
};
};
const createSourceNode: (etype: string | undefined) => MenuItemType = (
etype,
) => ({
key: `create-source-${etype}`,
label:
"添加源节点" +
(etype ? `(${renderType[etype as keyof typeof renderType]})` : ""),
onClick: () => {
let newid = 0;
for (const node of sourceNodes) {
if (node.id !== `s${newid}`) break;
newid += 1;
}
const newnodes = nodes.concat(
makeSourceNode({ object: { type: "汉字" } }, `s${newid}`),
);
const newedges = edges.concat(makeEdge(id, `s${newid}`, etype));
setLayout(newnodes, newedges);
},
});
const createConditionNode: (etype: string | undefined) => MenuItemType = (
etype,
) => ({
key: `create-condition-${etype}`,
label:
"添加条件节点" +
(etype ? `(${renderType[etype as keyof typeof renderType]})` : ""),
onClick: () => {
let newid = 0;
for (const node of conditionNodes) {
if (node.id !== `c${newid}`) break;
newid += 1;
}
const newnodes = nodes.concat(
makeConditionNode(
{ object: { type: "汉字" }, operator: "存在" },
`c${newid}`,
),
);
const newedges = edges.concat(makeEdge(id, `c${newid}`, etype));
setLayout(newnodes, newedges);
},
});
const deleteNode: MenuItemType = {
key: "delete",
label: "删除节点",
onClick: () => {
const newnodes = getNodes().filter((n) => n.id !== id);
const newedges = getEdges().filter(
({ source, target }) => ![source, target].includes(id),
);
setLayout(newnodes, newedges);
const newSources = { ...sources };
const newConditions = { ...conditions };
// 递归删除
const stack = [id];
while (stack.length) {
const currentId = stack.pop()!;
if (currentId[0] === "s") {
const next = newSources[currentId]?.next;
if (next) stack.push(next);
} else {
const positive = newConditions[currentId]?.positive;
if (positive) stack.push(positive);
const negative = newConditions[currentId]?.negative;
if (negative) stack.push(negative);
}
delete newSources[currentId];
delete newConditions[currentId];
}
for (const value of Object.values(newSources)) {
if (value.next === id) value.next = null;
}
for (const value of Object.values(newConditions)) {
if (value.positive === id) value.positive = null;
if (value.negative === id) value.negative = null;
}
setSelected(undefined);
setSources(newSources);
setConditions(newConditions);
},
};
const items: (MenuItemType | MenuItemGroupType)[] = [];
if (id[0] === "s") {
if (id !== "s0") items.push(deleteNode);
if (!edges.some((e) => e.source === id))
items.push(createSourceNode(undefined), createConditionNode(undefined));
if (sources[id]!.next === null)
items.push(createSourceNode(), createConditionNode());
} else {
items.push(deleteNode);
for (const label of ["positive", "negative"]) {
if (
!edges.some(
(e) =>
e.source === id &&
e.label === renderType[label as keyof typeof renderType],
)
)
for (const label of ["positive", "negative"] as const) {
if (conditions[id]![label] === null)
items.push(createSourceNode(label), createConditionNode(label));
}
}
Expand Down
Loading

0 comments on commit f20a89a

Please sign in to comment.