diff --git a/examples/yustar.yaml b/examples/yustar.yaml index 92163f5..5cf4659 100644 --- a/examples/yustar.yaml +++ b/examples/yustar.yaml @@ -26,7 +26,6 @@ analysis: no_cross: false selector: - 根少优先 - - 全符笔顺 - 连续笔顺 - 结构完整 - 能连不交 diff --git a/package-lock.json b/package-lock.json index 05f6cae..5355a87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chai", - "version": "0.1.9", + "version": "0.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chai", - "version": "0.1.9", + "version": "0.1.10", "dependencies": { "@ant-design/colors": "^7.0.2", "@ant-design/pro-components": "^2.7.1", @@ -20,6 +20,7 @@ "deep-object-diff": "^1.1.9", "immer": "^10.1.1", "jotai": "^2.8.0", + "jotai-location": "^0.5.5", "jotai-minidb": "^0.0.8", "jotai-optics": "^0.3.2", "js-md5": "^0.8.3", @@ -3658,12 +3659,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -5215,9 +5216,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6495,6 +6496,14 @@ "react": ">=17.0.0" } }, + "node_modules/jotai-location": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/jotai-location/-/jotai-location-0.5.5.tgz", + "integrity": "sha512-6QW/7W9IJHjhbn7gRgAw4sC30k0/G6JiC4uPlKi8ZPZGYk7R7r9PyMD2eVhL4XSxxag89JxS1iSyr6BIXsB4Sw==", + "peerDependencies": { + "jotai": ">=1.11.0" + } + }, "node_modules/jotai-minidb": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/jotai-minidb/-/jotai-minidb-0.0.8.tgz", diff --git a/package.json b/package.json index 85193e7..e923c42 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "deep-object-diff": "^1.1.9", "immer": "^10.1.1", "jotai": "^2.8.0", + "jotai-location": "^0.5.5", "jotai-minidb": "^0.0.8", "jotai-optics": "^0.3.2", "js-md5": "^0.8.3", diff --git a/scripts/analyze.ts b/scripts/analyze.ts index c27d997..834f90c 100644 --- a/scripts/analyze.ts +++ b/scripts/analyze.ts @@ -100,7 +100,6 @@ const analyze = ( const estdup1 = [...map1.values()] .filter((x) => x.length > 1) .reduce((a, b) => a + estimate(b.length, alphabet), 0); - // console.log([...map2].sort((a, b) => b[1].length - a[1].length).slice(0, 10)) const estdup2 = [...map2.values()] .filter((x) => x.length > 1) .reduce((a, b) => a + estimate(b.length, alphabet * alphabet), 0); diff --git a/scripts/recursive.ts b/scripts/recursive.ts index 46d9398..179ee42 100644 --- a/scripts/recursive.ts +++ b/scripts/recursive.ts @@ -1,5 +1,6 @@ import { readFileSync, writeFileSync } from "fs"; -import { Character, Compound, PrimitiveCharacter } from "~/lib"; +import type { Compound, PrimitiveCharacter } from "~/lib"; +import { Character } from "~/lib"; const repertoire = JSON.parse( readFileSync("public/cache/repertoire.json", "utf-8"), diff --git a/spec/affine.spec.ts b/spec/affine.spec.ts index 3ea098c..586f841 100644 --- a/spec/affine.spec.ts +++ b/spec/affine.spec.ts @@ -1,4 +1,5 @@ -import { affineMerge, Compound, SVGGlyph } from "~/lib"; +import type { Compound, SVGGlyph } from "~/lib"; +import { affineMerge } from "~/lib"; import { describe, expect, it } from "vitest"; describe("affine transformations", () => { diff --git a/spec/analysis.spec.ts b/spec/analysis.spec.ts index 0d966dc..b14c9a0 100644 --- a/spec/analysis.spec.ts +++ b/spec/analysis.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; +import type { SVGGlyph } from "~/lib"; import { - SVGGlyph, analysis, assemble, classifier, @@ -9,7 +9,7 @@ import { recursiveRenderComponent, recursiveRenderCompound, } from "~/lib"; -import { primitiveRepertoire, repertoire } from "./mock"; +import { focusAnalysis, primitiveRepertoire, repertoire } from "./mock"; import { readFileSync } from "fs"; describe("e2e test", () => { @@ -17,7 +17,7 @@ describe("e2e test", () => { const config = examples["mswb"]; const analysisResult = analysis( repertoire, - config, + focusAnalysis(config, repertoire), Object.keys(repertoire).filter(isValidCJKChar), ); const { componentError, compoundError } = analysisResult; @@ -27,7 +27,11 @@ describe("e2e test", () => { const assemblyResult = assemble( repertoire, - config, + { + algebra: config.algebra, + encoder: config.encoder, + keyboard: config.form, + }, characters, [], analysisResult, diff --git a/spec/component.spec.ts b/spec/component.spec.ts index 8764b22..55a4def 100644 --- a/spec/component.spec.ts +++ b/spec/component.spec.ts @@ -11,7 +11,12 @@ import { renderRootList, } from "~/lib"; import type { DerivedComponent, PrimitiveRepertoire } from "~/lib"; -import { primitiveRepertoire, repertoire, computedComponents } from "./mock"; +import { + primitiveRepertoire, + repertoire, + computedComponents, + focusAnalysis, +} from "./mock"; describe("pruning", () => { const strokes = 4; @@ -44,13 +49,21 @@ describe("get component scheme", () => { name: "", data: "国标五分类", keyboard: "米十五笔", - encoder: "形音码", + encoder: "形音码(米十五笔)", }); - - const rootList = renderRootList(repertoire, config); + const analysisConfig = focusAnalysis(config, repertoire); + const rootList = renderRootList(repertoire, [ + ...analysisConfig.primaryRoots, + ...analysisConfig.secondaryRoots, + ]); it("can get component scheme", () => { const 天 = computedComponents.天!; - const scheme = getComponentScheme(天, rootList, config, classifier); + const scheme = getComponentScheme( + 天, + [...rootList.values()], + analysisConfig, + classifier, + ); expect(scheme).toHaveProperty("sequence"); }); }); diff --git a/spec/degenerator.spec.ts b/spec/degenerator.spec.ts index 0c00dcf..3ab1f84 100644 --- a/spec/degenerator.spec.ts +++ b/spec/degenerator.spec.ts @@ -4,12 +4,11 @@ import { binaryToIndices, defaultDegenerator, generateSliceBinaries, - defaultConfig, } from "~/lib"; import { describe, it, expect } from "vitest"; import { create, all } from "mathjs"; import { computedGlyphs2 as renderedGlyphs, computedComponents } from "./mock"; -import { RenderedGlyph } from "~/lib"; +import type { RenderedGlyph } from "~/lib"; const { randomInt } = create(all!, { randomSeed: "a", @@ -46,39 +45,43 @@ describe("bi-directional conversion", () => { describe("generate slice binaries", () => { it("should find multiple occurence of a root", () => { const { 丰, 十 } = computedComponents; - expect(generateSliceBinaries(defaultConfig, 丰!, 十!)).toEqual([9, 5, 3]); + expect(generateSliceBinaries(defaultDegenerator, 丰!, 十!)).toEqual([ + 9, 5, 3, + ]); }); it("should be able to distinguish 土 and 士", () => { const { 土, 士, 王, 壬 } = computedComponents; - expect(generateSliceBinaries(defaultConfig, 王!, 土!)).toEqual([7]); - expect(generateSliceBinaries(defaultConfig, 王!, 士!)).toEqual([]); - expect(generateSliceBinaries(defaultConfig, 壬!, 土!)).toEqual([]); - expect(generateSliceBinaries(defaultConfig, 壬!, 士!)).toEqual([7]); + expect(generateSliceBinaries(defaultDegenerator, 王!, 土!)).toEqual([7]); + expect(generateSliceBinaries(defaultDegenerator, 王!, 士!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 壬!, 土!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 壬!, 士!)).toEqual([7]); }); it("should be able to distinguish 未 and 末", () => { const { 未, 末, 朱, 耒 } = computedComponents; - expect(generateSliceBinaries(defaultConfig, 朱!, 未!)).toEqual([31]); - expect(generateSliceBinaries(defaultConfig, 耒!, 未!)).toEqual([47, 31]); - expect(generateSliceBinaries(defaultConfig, 朱!, 末!)).toEqual([]); - expect(generateSliceBinaries(defaultConfig, 耒!, 末!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 朱!, 未!)).toEqual([31]); + expect(generateSliceBinaries(defaultDegenerator, 耒!, 未!)).toEqual([ + 47, 31, + ]); + expect(generateSliceBinaries(defaultDegenerator, 朱!, 末!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 耒!, 末!)).toEqual([]); }); it("should be able to distinguish 口 and 囗", () => { const { 口, 囗, 中, 日 } = computedComponents; - expect(generateSliceBinaries(defaultConfig, 中!, 口!)).toEqual([14]); - expect(generateSliceBinaries(defaultConfig, 中!, 囗!)).toEqual([]); - expect(generateSliceBinaries(defaultConfig, 日!, 口!)).toEqual([]); - expect(generateSliceBinaries(defaultConfig, 日!, 囗!)).toEqual([13]); + expect(generateSliceBinaries(defaultDegenerator, 中!, 口!)).toEqual([14]); + expect(generateSliceBinaries(defaultDegenerator, 中!, 囗!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 日!, 口!)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 日!, 囗!)).toEqual([13]); }); it("should be able to distinguish 木无十 and 全字头", () => { const 木无十 = computedComponents["\ue087"]!; const 全字头 = computedComponents["\ue43d"]!; const { 朱 } = computedComponents; - expect(generateSliceBinaries(defaultConfig, 朱!, 木无十)).toEqual([3]); - expect(generateSliceBinaries(defaultConfig, 朱!, 全字头)).toEqual([]); + expect(generateSliceBinaries(defaultDegenerator, 朱!, 木无十)).toEqual([3]); + expect(generateSliceBinaries(defaultDegenerator, 朱!, 全字头)).toEqual([]); }); }); diff --git a/spec/mock.ts b/spec/mock.ts index 47a9f2e..cb7b3b5 100644 --- a/spec/mock.ts +++ b/spec/mock.ts @@ -1,5 +1,11 @@ import rawrepertoire from "../public/cache/repertoire.json"; -import type { BasicComponent, PrimitiveCharacter } from "~/lib"; +import type { + AnalysisConfig, + BasicComponent, + Config, + PrimitiveCharacter, + Repertoire, +} from "~/lib"; import { listToObject, determine, computeComponent } from "~/lib"; export const primitiveRepertoire = Object.fromEntries( @@ -22,3 +28,16 @@ export const computedGlyphs2 = Object.fromEntries( return [k, v.glyph]; }), ); + +export const focusAnalysis = (config: Config, repertoire: Repertoire) => { + const result: AnalysisConfig = { + analysis: config.analysis ?? {}, + primaryRoots: new Set( + Object.keys(config.form.mapping).filter((x) => repertoire[x]), + ), + secondaryRoots: new Set( + Object.keys(config.form.grouping ?? []).filter((x) => repertoire[x]), + ), + }; + return result; +}; diff --git a/spec/selector.spec.ts b/spec/selector.spec.ts index 5db9441..3ae1424 100644 --- a/spec/selector.spec.ts +++ b/spec/selector.spec.ts @@ -1,43 +1,50 @@ import { describe, expect, it } from "vitest"; -import { length, bias, order, crossing, attaching, Scheme } from "~/lib"; +import type { Environment, Scheme } from "~/lib"; +import { length, bias, order, crossing, attaching, analysis } from "~/lib"; import { select } from "~/lib"; -import { computedComponents, repertoire } from "./mock"; +import { computedComponents, focusAnalysis, repertoire } from "./mock"; import { defaultConfig } from "~/lib"; import { Config } from "~/lib"; +const analysisConfig = focusAnalysis(defaultConfig, repertoire); const { 天 } = computedComponents as any; +const env: Environment = { + component: 天, + rootMap: new Map(), + ...analysisConfig, +}; const rootMap = new Map(); describe("length", () => { it("should measure the length of scheme", () => { - expect(length.key([8, 7], 天, defaultConfig, rootMap)).toBe(2); + expect(length.key([8, 7], env)).toBe(2); }); }); describe("bias", () => { it("should measure the bias of scheme", () => { - expect(bias.key([8, 7], 天, defaultConfig, rootMap)).toEqual([-1, -3]); + expect(bias.key([8, 7], env)).toEqual([-1, -3]); }); }); describe("order", () => { it("should measure the order of scheme", () => { - expect(order.key([8, 7], 天, defaultConfig, rootMap)).toEqual(0); + expect(order.key([8, 7], env)).toEqual(0); }); }); describe("crossing", () => { it("should measure the crossing of scheme", () => { - expect(crossing.key([8, 7], 天, defaultConfig, rootMap)).toBe(0); - expect(crossing.key([12, 3], 天, defaultConfig, rootMap)).toBe(1); + expect(crossing.key([8, 7], env)).toBe(0); + expect(crossing.key([12, 3], env)).toBe(1); }); }); describe("attaching", () => { it("should measure the attaching of scheme", () => { - expect(attaching.key([8, 7], 天, defaultConfig, rootMap)).toBe(1); - expect(attaching.key([12, 3], 天, defaultConfig, rootMap)).toBe(0); + expect(attaching.key([8, 7], env)).toBe(1); + expect(attaching.key([12, 3], env)).toBe(0); }); }); @@ -46,7 +53,7 @@ describe("select", () => { expect( ( select( - defaultConfig, + analysisConfig, 天, [ [12, 3], diff --git a/spec/topology.spec.ts b/spec/topology.spec.ts index cc3fb7e..14db64b 100644 --- a/spec/topology.spec.ts +++ b/spec/topology.spec.ts @@ -1,7 +1,7 @@ import { expect, describe, it } from "vitest"; -import type { StrokeRelation } from "~/lib"; +import type { StrokeRelation, CubicCurve, LinearCurve } from "~/lib"; import { findTopology, curveRelation, renderSVGGlyph } from "~/lib"; -import { CubicCurve, LinearCurve, area, render } from "~/lib"; +import { area, render } from "~/lib"; import type { Draw, Point } from "~/lib"; import { computedGlyphs2 as computedGlyphs } from "./mock"; import { getIntervalPosition, makeCurve } from "~/lib"; diff --git a/src/atoms/analysis.ts b/src/atoms/analysis.ts index 513d225..ddafd78 100644 --- a/src/atoms/analysis.ts +++ b/src/atoms/analysis.ts @@ -1,7 +1,13 @@ -import type { Analysis, Degenerator, Selector, SieveName } from "~/lib"; +import type { + Analysis, + Degenerator, + Selector, + SieveName, + Feature, +} from "~/lib"; import { focusAtom } from "jotai-optics"; import { analysisAtom } from "./config"; -import { Feature, mergeClassifier } from "~/lib"; +import { mergeClassifier } from "~/lib"; import { atom } from "jotai"; export const degeneratorAtom = focusAtom(analysisAtom, (o) => diff --git a/src/atoms/assets.ts b/src/atoms/assets.ts index f0488e9..4b56692 100644 --- a/src/atoms/assets.ts +++ b/src/atoms/assets.ts @@ -1,5 +1,5 @@ import { atomWithStorage } from "jotai/utils"; -import { Dictionary, Distribution, Equivalence, Frequency } from "."; +import type { Dictionary, Distribution, Equivalence, Frequency } from "."; import { MiniDb } from "jotai-minidb"; const db = new MiniDb(); diff --git a/src/atoms/cache.ts b/src/atoms/cache.ts index 78c22ff..f0c6b81 100644 --- a/src/atoms/cache.ts +++ b/src/atoms/cache.ts @@ -1,10 +1,92 @@ import { atom } from "jotai"; -import { AssemblyResult } from "~/lib"; -import { AnalysisResult } from "~/lib"; -import { EncodeResult } from "."; +import type { + AnalysisConfig, + AssemblyResult, + PronunciationElementTypes, +} from "~/lib"; +import { applyRules, defaultAlgebra } from "~/lib"; +import type { AnalysisResult } from "~/lib"; +import type { EncodeResult } from "."; +import { + Thread, + algebraAtom, + analysisAtom, + assetsAtom, + charactersAtom, + dictionaryAtom, + encoderAtom, + groupingAtom, + keyboardAtom, + mappingAtom, + repertoireAtom, +} from "."; +import { customElementsAtom } from "./assets"; -export const analysisResultAtom = atom(null); +const mergedAlgebraAtom = atom((get) => { + const algebra = get(algebraAtom); + return { ...algebra, ...defaultAlgebra }; +}); -export const assemblyResultAtom = atom(null); +export const phonemeEnumerationAtom = atom((get) => { + const repertoire = get(repertoireAtom); + const syllables = [ + ...new Set( + Object.values(repertoire) + .map((x) => x.readings.map((y) => y.pinyin)) + .flat(), + ), + ]; + const mergedAlgebras = Object.entries(get(mergedAlgebraAtom)); + const content: Map = new Map( + mergedAlgebras.map(([name, rules]) => { + const list = [ + ...new Set(syllables.map((s) => applyRules(name, rules, s))), + ].sort(); + return [name as PronunciationElementTypes, list]; + }), + ); + return content; +}); + +const jsThread = new Thread("js"); + +export const analysisResultAtom = atom(async (get) => { + const repertoire = get(repertoireAtom); + const analysisConfig: AnalysisConfig = { + analysis: get(analysisAtom), + primaryRoots: new Set( + Object.keys(get(mappingAtom)).filter((x) => repertoire[x]), + ), + secondaryRoots: new Set( + Object.keys(get(groupingAtom)).filter((x) => repertoire[x]), + ), + }; + const characters = get(charactersAtom); + return await jsThread.spawn("analysis", [ + repertoire, + analysisConfig, + characters, + ]); +}); + +export const assemblyResultAtom = atom(async (get) => { + const repertoire = get(repertoireAtom); + const algebra = get(algebraAtom); + const encoder = get(encoderAtom); + const keyboard = get(keyboardAtom); + const characters = get(charactersAtom); + const dictionary = await get(dictionaryAtom); + const analysisResult = await get(analysisResultAtom); + const customElements = get(customElementsAtom); + const config = { algebra, encoder, keyboard }; + return await jsThread.spawn("assembly", [ + repertoire, + config, + characters, + dictionary, + analysisResult, + customElements, + ]); +}); export const encodeResultAtom = atom(null); diff --git a/src/atoms/config.ts b/src/atoms/config.ts index d793ffe..e5e76f2 100644 --- a/src/atoms/config.ts +++ b/src/atoms/config.ts @@ -1,28 +1,29 @@ import { atom } from "jotai"; -import { atomWithStorage } from "jotai/utils"; +import { atomFamily, atomWithStorage } from "jotai/utils"; +import type { Analysis, Data, EncoderConfig } from "~/lib"; import { defaultOptimization, type Algebra, type Config, type Info, - Analysis, - Data, - EncoderConfig, + defaultConfig, } from "~/lib"; import { focusAtom } from "jotai-optics"; +import { atomWithLocation } from "jotai-location"; -/** 需要在根组件里提前修改它 */ -export const configIdAtom = atom(""); -configIdAtom.debugLabel = "id"; +const locationAtom = atomWithLocation(); -const configStorageAtomAtom = atom((get) => { - const id = get(configIdAtom); - return atomWithStorage(id, {} as Config); -}); +export const idAtom = atom( + (get) => get(locationAtom).pathname?.split("/")[1] ?? "", +); + +const configStorage = atomFamily((id: string) => + atomWithStorage(id, defaultConfig), +); export const configAtom = atom( - (get) => get(get(configStorageAtomAtom)), - (get, set, value: Config) => set(get(configStorageAtomAtom), value), + (get) => get(configStorage(get(idAtom))), + (get, set, value: Config) => set(configStorage(get(idAtom)), value), ); configAtom.debugLabel = "config"; diff --git a/src/atoms/constants.ts b/src/atoms/constants.ts index 2caefde..708bcd2 100644 --- a/src/atoms/constants.ts +++ b/src/atoms/constants.ts @@ -1,5 +1,10 @@ import { atom } from "jotai"; -import type { PrimitiveRepertoire } from "~/lib"; +import { + getDictFromTSV, + getDistributionFromTSV, + getRecordFromTSV, + type PrimitiveRepertoire, +} from "~/lib"; import { produce } from "immer"; import { userFrequencyAtom, @@ -64,10 +69,18 @@ export type Frequency = Record; export type Distribution = Record; export type Equivalence = Record; -export const defaultDictionaryAtom = atom([]); -export const frequencyAtom = atom({}); -export const keyDistributionAtom = atom({}); -export const pairEquivalenceAtom = atom({}); +export const defaultDictionaryAtom = atom>(async () => + getDictFromTSV(await fetchAsset("dictionary", "txt")), +); +export const frequencyAtom = atom>(async () => + getRecordFromTSV(await fetchAsset("frequency", "txt")), +); +export const keyDistributionAtom = atom>(async () => + getDistributionFromTSV(await fetchAsset("key_distribution", "txt")), +); +export const pairEquivalenceAtom = atom>(async () => + getRecordFromTSV(await fetchAsset("pair_equivalence", "txt")), +); export interface Assets { frequency: Frequency; @@ -75,12 +88,12 @@ export interface Assets { pair_equivalence: Equivalence; } -export const assetsAtom = atom((get) => { - const frequency = get(userFrequencyAtom) ?? get(frequencyAtom); +export const assetsAtom = atom(async (get) => { + const frequency = get(userFrequencyAtom) ?? (await get(frequencyAtom)); const key_distribution = - get(userKeyDistributionAtom) ?? get(keyDistributionAtom); + get(userKeyDistributionAtom) ?? (await get(keyDistributionAtom)); const pair_equivalence = - get(userPairEquivalenceAtom) ?? get(pairEquivalenceAtom); + get(userPairEquivalenceAtom) ?? (await get(pairEquivalenceAtom)); const assets: Assets = { frequency, key_distribution, @@ -89,6 +102,6 @@ export const assetsAtom = atom((get) => { return assets; }); -export const dictionaryAtom = atom((get) => { - return get(userDictionaryAtom) ?? get(defaultDictionaryAtom); +export const dictionaryAtom = atom(async (get) => { + return get(userDictionaryAtom) ?? (await get(defaultDictionaryAtom)); }); diff --git a/src/atoms/data.ts b/src/atoms/data.ts index 3ee88c1..e1a739c 100644 --- a/src/atoms/data.ts +++ b/src/atoms/data.ts @@ -1,18 +1,16 @@ import { atom, useAtomValue } from "jotai"; import { primitiveRepertoireAtom } from "./constants"; -import { +import type { CharacterSetSpecifier, CustomReadings, PrimitiveCharacter, - isPUA, - isValidCJKBasicChar, - isValidCJKChar, } from "~/lib"; +import { isPUA, isValidCJKBasicChar, isValidCJKChar } from "~/lib"; import { recursiveRenderCompound } from "~/lib"; import { dataAtom } from "."; import { focusAtom } from "jotai-optics"; -import { PrimitiveRepertoire, SVGGlyph } from "~/lib"; -import { CustomGlyph } from "~/lib"; +import type { PrimitiveRepertoire, SVGGlyph } from "~/lib"; +import type { CustomGlyph } from "~/lib"; import { determine } from "~/lib"; import { classifier } from "~/lib"; diff --git a/src/atoms/encoder.ts b/src/atoms/encoder.ts index 5ea4323..3ea6f6c 100644 --- a/src/atoms/encoder.ts +++ b/src/atoms/encoder.ts @@ -1,6 +1,7 @@ import { focusAtom } from "jotai-optics"; import { encoderAtom } from "."; -import { ShortCodeRule, ShortCodeScheme, WordRule } from "~/lib"; +import type { ShortCodeRule, WordRule } from "~/lib"; +import { ShortCodeScheme } from "~/lib"; export const maxLengthAtom = focusAtom(encoderAtom, (o) => o.prop("max_length"), diff --git a/src/atoms/hooks.ts b/src/atoms/hooks.ts index 0cc3e4c..72e9f93 100644 --- a/src/atoms/hooks.ts +++ b/src/atoms/hooks.ts @@ -1,14 +1,34 @@ -import { Config, Key, exportYAML, isValidCJKChar } from "~/lib"; +import type { Config } from "~/lib"; +import { isValidCJKChar, makeWorker } from "~/lib"; import useTitle from "ahooks/es/useTitle"; import init, { validate } from "libchai"; -import { LibchaiOutputEvent } from "~/worker"; +import type { LibchaiOutputEvent } from "~/worker"; import { notification } from "antd"; -import { Err } from "~/api"; +import type { Err } from "~/api"; import { createContext } from "react"; import { isEqual } from "lodash-es"; import { diff } from "deep-object-diff"; import { load } from "js-yaml"; +export class Thread { + private worker: Worker; + constructor(type: "js" | "wasm") { + const url = type === "js" ? "../jsworker.ts" : undefined; + this.worker = makeWorker(url); + } + + async spawn(func: string, args: any[]): Promise { + return await new Promise((resolve) => { + const channel = new MessageChannel(); + channel.port1.onmessage = ({ data }) => { + channel.port1.close(); + resolve(data); + }; + this.worker.postMessage({ func, args }, [channel.port2]); + }); + } +} + export const RemoteContext = createContext(true); export async function validateConfig(config: Config) { diff --git a/src/atoms/index.ts b/src/atoms/index.ts index 91930c8..d8444d7 100644 --- a/src/atoms/index.ts +++ b/src/atoms/index.ts @@ -1,4 +1,5 @@ -import { WritableAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; +import type { WritableAtom } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; export * from "jotai"; export * from "./constants"; @@ -10,7 +11,7 @@ export * from "./encoder"; export * from "./analysis"; export * from "./optimization"; import * as O from "optics-ts/standalone"; -import { SetStateAction } from "react"; +import type { SetStateAction } from "react"; export function useAddAtom( atom: WritableAtom, [SetStateAction>], void>, diff --git a/src/atoms/keyboard.ts b/src/atoms/keyboard.ts index 5b6091a..8cf4cc9 100644 --- a/src/atoms/keyboard.ts +++ b/src/atoms/keyboard.ts @@ -1,6 +1,6 @@ import { focusAtom } from "jotai-optics"; import { keyboardAtom } from "./config"; -import { Grouping } from "~/lib"; +import type { Grouping } from "~/lib"; export const alphabetAtom = focusAtom(keyboardAtom, (o) => o.prop("alphabet")); diff --git a/src/components/Action.tsx b/src/components/Action.tsx index dedb308..44edefa 100644 --- a/src/components/Action.tsx +++ b/src/components/Action.tsx @@ -8,13 +8,8 @@ import { Popover, Space, } from "antd"; -import { - ForwardedRef, - createContext, - forwardRef, - useContext, - useState, -} from "react"; +import type { ForwardedRef } from "react"; +import { createContext, forwardRef, useContext, useState } from "react"; import { remoteCreate, remoteCreateWithoutUnicode, @@ -23,13 +18,13 @@ import { remoteMutate, } from "~/api"; import { DeleteButton, Select } from "~/components/Utils"; +import type { Reading } from "~/lib"; import { chars, isValidCJKChar, getDummyBasicComponent, getDummyCompound, getDummyDerivedComponent, - Reading, } from "~/lib"; import { useAtomValue, @@ -48,10 +43,10 @@ import { RemoteContext, customReadingsAtom, } from "~/atoms"; -import { PrimitiveCharacter, Compound, Component } from "~/lib"; +import type { PrimitiveCharacter, Compound, Component } from "~/lib"; import ComponentForm from "./ComponentForm"; import CompoundForm from "./CompoundForm"; -import { MenuProps } from "antd/lib"; +import type { MenuProps } from "antd/lib"; import * as O from "optics-ts/standalone"; import ReadingForm from "./ReadingForm"; @@ -349,7 +344,7 @@ export const EditReading = ({ key: -1, label: ( diff --git a/src/components/Algebra.tsx b/src/components/Algebra.tsx index 5da3d25..4891f5a 100644 --- a/src/components/Algebra.tsx +++ b/src/components/Algebra.tsx @@ -7,7 +7,7 @@ import { } from "@ant-design/pro-components"; import { Button, Form, Space, notification } from "antd"; import { CloseCircleOutlined, CopyOutlined } from "@ant-design/icons"; -import { Rule } from "~/lib"; +import type { Rule } from "~/lib"; import { algebraAtom, useAddAtom } from "~/atoms"; import { defaultAlgebra } from "~/lib"; diff --git a/src/components/CharacterQuery.tsx b/src/components/CharacterQuery.tsx index e47ff81..23ff621 100644 --- a/src/components/CharacterQuery.tsx +++ b/src/components/CharacterQuery.tsx @@ -1,11 +1,6 @@ import { Form, Input } from "antd"; -import { - CharacterFilter, - Operator, - PrimitiveRepertoire, - Repertoire, - operators, -} from "~/lib"; +import type { CharacterFilter } from "~/lib"; +import { Operator, PrimitiveRepertoire, Repertoire, operators } from "~/lib"; import { ProFormDigit, ProFormSelect, @@ -28,6 +23,7 @@ export default function CharacterQuery({ setFilter }: StrokeSearchProps) { labelWidth="auto" submitter={false} style={{ maxWidth: 1080 }} + autoFocusFirstInput={false} > diff --git a/src/components/CharacterSelect.tsx b/src/components/CharacterSelect.tsx index acd53cc..49ff2fc 100644 --- a/src/components/CharacterSelect.tsx +++ b/src/components/CharacterSelect.tsx @@ -2,9 +2,11 @@ import { useAtomValue } from "jotai"; import { useEffect, useState } from "react"; import { displayAtom, sequenceAtom, sortedRepertoireAtom } from "~/atoms"; import { Select } from "./Utils"; -import { SelectProps } from "antd"; -import { PrimitiveCharacter, Character } from "~/lib"; -import { ProFormSelect, ProFormSelectProps } from "@ant-design/pro-components"; +import type { SelectProps } from "antd"; +import type { Character } from "~/lib"; +import { PrimitiveCharacter } from "~/lib"; +import type { ProFormSelectProps } from "@ant-design/pro-components"; +import { ProFormSelect } from "@ant-design/pro-components"; interface ItemSelectProps extends SelectProps { customFilter?: (e: [string, Character]) => boolean; diff --git a/src/components/CharacterTable.tsx b/src/components/CharacterTable.tsx index 7890d2c..ce58d14 100644 --- a/src/components/CharacterTable.tsx +++ b/src/components/CharacterTable.tsx @@ -38,12 +38,12 @@ import ComponentForm from "./ComponentForm"; import CompoundForm from "./CompoundForm"; import { remoteUpdate } from "~/api"; import { DeleteButton, PlusButton } from "./Utils"; -import Root from "./Element"; +import Element from "./Element"; import * as O from "optics-ts/standalone"; import CharacterQuery from "./CharacterQuery"; import TagPicker from "./TagPicker"; import { findGlyphIndex } from "~/lib"; -import { TourProps } from "antd/lib"; +import type { TourProps } from "antd/lib"; import { QuestionCircleOutlined } from "@ant-design/icons"; type Column = ColumnType; @@ -52,7 +52,7 @@ function ReadingList({ readings }: { readings: Reading[] }) { return ( {readings.map((reading, index) => { - const core = {reading.pinyin}; + const core = {reading.pinyin}; if (readings.length === 1) return core; return {core}; })} @@ -197,7 +197,7 @@ export default function CharacterTable() { ) : ( { @@ -266,7 +266,7 @@ export default function CharacterTable() { /> ) : ( { diff --git a/src/components/Classifier.tsx b/src/components/Classifier.tsx index 2916c75..b89fb24 100644 --- a/src/components/Classifier.tsx +++ b/src/components/Classifier.tsx @@ -7,12 +7,12 @@ import { useSetAtom, } from "~/atoms"; import { Button, Flex, Space, notification } from "antd"; -import Root from "~/components/Element"; +import Element from "~/components/Element"; import { DndContext, useDraggable, useDroppable } from "@dnd-kit/core"; import type { PropsWithChildren } from "react"; import { useState } from "react"; import { blue } from "@ant-design/colors"; -import { Feature } from "~/lib"; +import type { Feature } from "~/lib"; function Draggable({ name }: { name: string }) { const { attributes, listeners, setNodeRef, transform } = useDraggable({ @@ -25,9 +25,9 @@ function Draggable({ name }: { name: string }) { : undefined; return ( - + {name} - + ); } @@ -82,7 +82,7 @@ export default function Classifier() { {items.map(([x, v]) => ( - {x} + {x} {v.map((s) => ( diff --git a/src/components/ComponentForm.tsx b/src/components/ComponentForm.tsx index 23a7412..5586b49 100644 --- a/src/components/ComponentForm.tsx +++ b/src/components/ComponentForm.tsx @@ -1,35 +1,32 @@ -import { - Button, - Flex, - Form, - Dropdown, - notification, - FormListFieldData, -} from "antd"; +import type { FormListFieldData } from "antd"; +import { Button, Flex, Form, Dropdown, notification } from "antd"; import { EditorColumn, EditorRow, NumberInput } from "./Utils"; -import { MutableRefObject, ReactNode, useRef } from "react"; +import type { MutableRefObject, ReactNode } from "react"; +import { useRef } from "react"; import type { PrimitiveCharacter, Component, Character } from "~/lib"; import type { Feature } from "~/lib"; import { getDummySVGStroke, schema } from "~/lib"; import { getDummyReferenceStroke, isComponent } from "~/lib"; import { allRepertoireAtom, useAtomValue } from "~/atoms"; import { GlyphSelect } from "./CharacterSelect"; +import type { + ProFormInstance, + ProFormListProps, +} from "@ant-design/pro-components"; import { ModalForm, ProFormDependency, ProFormDigit, ProFormGroup, - ProFormInstance, ProFormList, - ProFormListProps, ProFormSelect, } from "@ant-design/pro-components"; import styled from "styled-components"; import { CommonForm } from "./CompoundForm"; -import Root from "./Element"; +import Element from "./Element"; import { recursiveRenderComponent } from "~/lib"; import { Box, StrokesView } from "./GlyphView"; -import { BaseOptionType } from "antd/es/select"; +import type { BaseOptionType } from "antd/es/select"; const Digit = ({ name }: { name: (string | number)[] }) => ( @@ -184,7 +181,7 @@ export default function ComponentForm({ const trigger = noButton ? ( {title} ) : ( - {title} + {title} ); const isValidSource = ([name, _]: [string, Character]) => { let component: Component | undefined = diff --git a/src/components/CompoundForm.tsx b/src/components/CompoundForm.tsx index 3f63cf4..68d7a13 100644 --- a/src/components/CompoundForm.tsx +++ b/src/components/CompoundForm.tsx @@ -13,7 +13,7 @@ import { import { InlineRender, StaticList } from "./ComponentForm"; import { useAtomValue } from "jotai"; import { repertoireAtom, tagsAtom } from "~/atoms"; -import Root from "./Element"; +import Element from "./Element"; import { EditorColumn, EditorRow } from "./Utils"; import { Box, StrokesView } from "./GlyphView"; import { recursiveRenderCompound } from "~/lib"; @@ -79,7 +79,7 @@ export default function CompoundForm({ const trigger = noButton ? ( {title} ) : ( - {title} + {title} ); return ( @@ -119,7 +119,7 @@ export default function CompoundForm({ { const newLength = value === "⿲" || value === "⿳" ? 3 : 2; const newList = list.concat("一").slice(0, newLength); @@ -128,8 +128,8 @@ export default function CompoundForm({ options={operators.map((x) => ({ value: x, label: x }))} style={{ width: "96px" }} allowClear={false} - > - + /> + {(meta, i) => ( @@ -139,7 +139,7 @@ export default function CompoundForm({ 归并至 setMain(event)} /> {/* ({ value: x, label: display(x), }))} - value={char} - onChange={onChange} filterOption={(input, option) => { if (option === undefined) return false; const value = option.value; diff --git a/src/components/KeySelect.tsx b/src/components/KeySelect.tsx index 9789b3f..8226b0a 100644 --- a/src/components/KeySelect.tsx +++ b/src/components/KeySelect.tsx @@ -8,7 +8,8 @@ import { sequenceAtom, } from "~/atoms"; import { Select } from "./Utils"; -import { Key, renderSuperScript } from "~/lib"; +import type { Key } from "~/lib"; +import { renderSuperScript } from "~/lib"; export interface KeySelectProps { value: Key; diff --git a/src/components/Mapping.tsx b/src/components/Mapping.tsx index 1d45e54..fa1bf52 100644 --- a/src/components/Mapping.tsx +++ b/src/components/Mapping.tsx @@ -15,7 +15,7 @@ import { useRemoveAtom, } from "~/atoms"; -import Root from "./Element"; +import Element from "./Element"; import Char from "./Character"; import { isPUA, joinKeys, renderMapped } from "~/lib"; import { DeleteButton, Select, Uploader } from "./Utils"; @@ -23,7 +23,7 @@ import { range } from "lodash-es"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import ElementSelect from "./ElementSelect"; import KeySelect from "./KeySelect"; -import { Key } from "~/lib"; +import type { Key } from "~/lib"; interface MappedInfo { name: string; @@ -69,7 +69,7 @@ const KeysEditor = ({ const display = useAtomValue(displayAtom); return ( - {display(name)} + {display(name)} {keys.map((key, index) => { return ( @@ -127,7 +127,7 @@ const AdjustableRootPopoverContent = ({ 归并元素 {affiliates.map((x) => ( - {display(x)} + {display(x)} - ) : ( - )} - + + } + > + + + )} ); diff --git a/src/components/SelectRules.tsx b/src/components/SelectRules.tsx index db81cfd..81a4866 100644 --- a/src/components/SelectRules.tsx +++ b/src/components/SelectRules.tsx @@ -1,9 +1,9 @@ import { Button, Flex, Form, Input } from "antd"; import { useAtom, useAtomValue } from "jotai"; import { + alphabetAtom, autoSelectLengthAtom, autoSelectPatternAtom, - keyboardAtom, maxLengthAtom, selectKeysAtom, } from "~/atoms"; @@ -17,7 +17,7 @@ export default function SelectRules() { autoSelectPatternAtom, ); const [selectKeys, setSelectKeys] = useAtom(selectKeysAtom); - const { alphabet } = useAtomValue(keyboardAtom); + const alphabet = useAtomValue(alphabetAtom); const allowedSelectKeys = printableAscii.filter((x) => !alphabet.includes(x)); return ( diff --git a/src/components/SequenceTable.tsx b/src/components/SequenceTable.tsx index 8163639..4010624 100644 --- a/src/components/SequenceTable.tsx +++ b/src/components/SequenceTable.tsx @@ -1,37 +1,22 @@ -import { useState } from "react"; import { Alert, Button, Flex, Input, Space } from "antd"; +import type { DictEntry } from "~/atoms"; import { useAtomValue, configAtom, displayAtom, - repertoireAtom, - useAtom, assetsAtom, - dictionaryAtom, priorityShortCodesAtom, maxLengthAtom, useSetAtom, makeEncodeCallback, - DictEntry, - charactersAtom, } from "~/atoms"; import type { Assembly, IndexedElement } from "~/lib"; -import { - assemble, - getPriorityMap, - stringifySequence, - summarize, - analysis, -} from "~/lib"; +import { assemble, getPriorityMap, stringifySequence, summarize } from "~/lib"; import { exportTSV, makeWorker, renderIndexed, renderSuperScript } from "~/lib"; -import { - analysisResultAtom, - assemblyResultAtom, - encodeResultAtom, -} from "~/atoms/cache"; -import { ProColumns, ProTable } from "@ant-design/pro-components"; +import { assemblyResultAtom, encodeResultAtom } from "~/atoms/cache"; +import type { ProColumns } from "@ant-design/pro-components"; +import { ProTable } from "@ant-design/pro-components"; import ProrityShortCodeSelector from "./ProrityShortCodeSelector"; -import { customElementsAtom } from "~/atoms/assets"; interface MainEntry { key: string; @@ -45,39 +30,6 @@ interface MainEntry { [n: number]: IndexedElement; } -const RecomputeAssembly = () => { - const repertoire = useAtomValue(repertoireAtom); - const config = useAtomValue(configAtom); - const characters = useAtomValue(charactersAtom); - const [analysisResult, setAnalysisResult] = useAtom(analysisResultAtom); - const [assemblyResult, setAssemblyResult] = useAtom(assemblyResultAtom); - const customElements = useAtomValue(customElementsAtom); - const dictionary = useAtomValue(dictionaryAtom); - return ( - - ); -}; - const ExportAssembly = () => { const assemblyResult = useAtomValue(assemblyResultAtom) ?? []; const priorityShortCodes = useAtomValue(priorityShortCodesAtom); @@ -104,7 +56,7 @@ const ExportAssembly = () => { exportTSV(tsv, "elements.txt"); }} > - 导出拆分表 + 导出元素序列表 ); }; @@ -342,12 +294,7 @@ export default function SequenceTable() { }, ); - const toolbar = [ - , - , - , - , - ]; + const toolbar = [, , ]; return ( <> diff --git a/src/components/ShortCodeRules.tsx b/src/components/ShortCodeRules.tsx index 53c929f..cfd1f57 100644 --- a/src/components/ShortCodeRules.tsx +++ b/src/components/ShortCodeRules.tsx @@ -1,6 +1,7 @@ import { Button, Cascader, Flex, Form } from "antd"; import { DeleteButton, Select } from "./Utils"; -import { Config, ShortCodeRule, wordLengthArray } from "~/lib"; +import type { ShortCodeRule } from "~/lib"; +import { Config, wordLengthArray } from "~/lib"; import { useListAtom } from "~/atoms"; import { shortCodeConfigAtom } from "~/atoms/encoder"; diff --git a/src/components/SolverForm.tsx b/src/components/SolverForm.tsx index 56090e4..6b29a31 100644 --- a/src/components/SolverForm.tsx +++ b/src/components/SolverForm.tsx @@ -1,15 +1,15 @@ +import type { ProFormInstance } from "@ant-design/pro-components"; import { ProForm, ProFormDependency, ProFormDigit, ProFormGroup, - ProFormInstance, ProFormSelect, } from "@ant-design/pro-components"; import { Form, Switch } from "antd"; import { useRef } from "react"; import { metaheuristicAtom, useAtom } from "~/atoms"; -import { Solver } from "~/lib"; +import type { Solver } from "~/lib"; export default function SolverForm() { const [metaheuristic, setMetaheuristic] = useAtom(metaheuristicAtom); @@ -25,119 +25,113 @@ export default function SolverForm() { random_full_key_swap: 0.01, }; return ( - <> - - title="求解算法" - formRef={formRef} - initialValues={metaheuristic} - onFinish={async (values) => setMetaheuristic(values)} - layout="horizontal" - onValuesChange={(_, values) => setMetaheuristic(values)} - submitter={false} - > - - - - - {({ parameters }) => ( - - { - formRef?.current?.setFieldValue( - "parameters", - value ? undefined : defaultParams, - ); - formRef?.current?.setFieldValue( - "runtime", - value ? 10 : undefined, - ); - formRef?.current?.submit(); - }} - /> - - )} - - + + title="求解算法" + formRef={formRef} + initialValues={metaheuristic} + onFinish={async (values) => setMetaheuristic(values)} + layout="horizontal" + onValuesChange={(_, values) => setMetaheuristic(values)} + submitter={false} + > + + + - {({ parameters }) => - parameters === undefined ? ( + {({ parameters }) => ( + + { + formRef?.current?.setFieldValue( + "parameters", + value ? undefined : defaultParams, + ); + formRef?.current?.setFieldValue( + "runtime", + value ? 10 : undefined, + ); + formRef?.current?.submit(); + }} + /> + + )} + + + + {({ parameters }) => + parameters === undefined ? ( + + ) : ( + - ) : ( - + + + + ) + } + + + {({ search_method }) => ( + <> + + { + formRef?.current?.setFieldValue( + "search_method", + value ? undefined : defaultSerachMethod, + ); + formRef?.current?.submit(); + }} + /> + + {search_method !== undefined ? ( + - ) - } - - - {({ search_method }) => ( - <> - - { - formRef?.current?.setFieldValue( - "search_method", - value ? undefined : defaultSerachMethod, - ); - formRef?.current?.submit(); - }} - /> - - {search_method !== undefined ? ( - - - - - - ) : null} - - )} - - - + ) : null} + + )} + + ); } diff --git a/src/components/Utils.tsx b/src/components/Utils.tsx index 32dacde..0bafe80 100644 --- a/src/components/Utils.tsx +++ b/src/components/Utils.tsx @@ -15,7 +15,7 @@ import { useState } from "react"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import PlusOutlined from "@ant-design/icons/PlusOutlined"; import MinusOutlined from "@ant-design/icons/MinusOutlined"; -import Root from "./Element"; +import Element from "./Element"; const ScrollableRow = styled(Row)` height: 100%; @@ -84,7 +84,10 @@ export const Uploader = ({ ); }; -type Click = { onClick: () => void; disabled?: boolean }; +interface Click { + onClick: () => void; + disabled?: boolean; +} export const PlusButton = ({ onClick }: Click) => { return ( @@ -135,7 +138,7 @@ export const KeyList = ({ {keys.map((x, index) => ( - {x} + {x} ))} diff --git a/src/components/WordRules.tsx b/src/components/WordRules.tsx index 17da3aa..397d154 100644 --- a/src/components/WordRules.tsx +++ b/src/components/WordRules.tsx @@ -1,7 +1,8 @@ import { Flex, Select, Form, Cascader, Input, Space, Button } from "antd"; import { useListAtom, wordRulesAtom } from "~/atoms"; import { DeleteButton } from "./Utils"; -import { Config, wordLengthArray } from "~/lib"; +import type { Config } from "~/lib"; +import { wordLengthArray } from "~/lib"; const defaultRules: NonNullable = [ { length_equal: 2, formula: "AaAbBaBb" }, diff --git a/src/jsworker.ts b/src/jsworker.ts new file mode 100644 index 0000000..8c27120 --- /dev/null +++ b/src/jsworker.ts @@ -0,0 +1,29 @@ +import type { AnalysisResult, AssemblyResult } from "./lib"; +import { analysis, assemble } from "./lib"; + +export interface JsInputEvent { + func: "analysis" | "assembly"; + args: any[]; +} + +export type JsOutputEvent = AnalysisResult | AssemblyResult; + +self.onmessage = async (event: MessageEvent) => { + const channel = event.ports[0]!; + try { + switch (event.data.func) { + case "analysis": + const [repertoire, config, characters] = event.data.args; + channel.postMessage(analysis(repertoire, config, characters)); + break; + case "assembly": + const [arg1, arg2, arg3, arg4, arg5, arg6] = event.data.args; + channel.postMessage(assemble(arg1, arg2, arg3, arg4, arg5, arg6)); + break; + } + } catch (error) { + channel.postMessage({ error: error }); + } +}; + +export {}; diff --git a/src/lib/affine.ts b/src/lib/affine.ts index 6854277..ef6847c 100644 --- a/src/lib/affine.ts +++ b/src/lib/affine.ts @@ -1,5 +1,12 @@ import { add } from "mathjs"; -import { Compound, Draw, Operator, Point, SVGGlyph, SVGStroke } from "./data"; +import type { + Compound, + Draw, + Operator, + Point, + SVGGlyph, + SVGStroke, +} from "./data"; import { cloneDeep } from "lodash-es"; class Affine { diff --git a/src/lib/assembly.ts b/src/lib/assembly.ts index 0ec637d..ceb46a0 100644 --- a/src/lib/assembly.ts +++ b/src/lib/assembly.ts @@ -1,8 +1,10 @@ import type { - BinaryCondition, + Algebra, Condition, Config, + EncoderConfig, Grouping, + Keyboard, Mapping, Op, Source, @@ -12,11 +14,12 @@ import type { ComponentAnalysis } from "./component"; import { recursiveRenderCompound, type CompoundAnalysis } from "./compound"; import type { Extra } from "./element"; import { algebraCache, findElement } from "./element"; -import { Repertoire } from "./data"; -import { AnalysisResult, analysis } from "./repertoire"; +import type { Repertoire } from "./data"; +import type { AnalysisResult } from "./repertoire"; +import { analysis } from "./repertoire"; import { mergeClassifier } from "./classifier"; -import { Dictionary } from "~/atoms"; -import { CustomElementMap } from "~/atoms/assets"; +import type { Dictionary } from "~/atoms"; +import type { CustomElementMap } from "~/atoms/assets"; export const getPriorityMap = ( priorityShortCodes: [string, string, number][], @@ -70,7 +73,7 @@ export type CharacterResult = (ComponentAnalysis | CompoundAnalysis) & { const satisfy = ( condition: Condition, result: CharacterResult, - config: Config, + config: Algebra, extra: Extra, totalMapping: Record, ) => { @@ -102,27 +105,31 @@ const merge = (mapping: Mapping, grouping: Grouping) => { }; export type IndexedElement = string | { element: string; index: number }; -export type Assembly = { +export interface Assembly { name: string; pinyin_list: string[]; sequence: IndexedElement[]; importance: number; level?: number; -}; +} export type AssemblyResult = Assembly[]; -const compile = (config: Config) => { - const { mapping, grouping, alphabet } = config.form; +const compile = ( + keyboard: Keyboard, + encoder: EncoderConfig, + algebra: Algebra, +) => { + const { mapping, grouping, alphabet } = keyboard; const totalMapping = merge(mapping, grouping ?? {}); return (result: CharacterResult, data: Repertoire, extra: Extra) => { let node: string | null = "s0"; const codes = [] as IndexedElement[]; while (node) { if (node.startsWith("s")) { - const source: Source = config.encoder.sources[node]!; + const source: Source = encoder.sources[node]!; const { object, next, index } = source; if (node !== "s0") { - const element = findElement(object!, result, config, extra); + const element = findElement(object!, result, algebra, extra); // 检查元素或键位是否有效 if (element === undefined) { node = next; @@ -163,44 +170,15 @@ const compile = (config: Config) => { } node = next; } else { - const condition: Condition = config.encoder.conditions[node]!; - if (satisfy(condition, result, config, extra, totalMapping)) { + const condition: Condition = encoder.conditions[node]!; + if (satisfy(condition, result, algebra, extra, totalMapping)) { node = condition.positive; } else { node = condition.negative; } } } - return codes.slice(0, config.encoder.max_length ?? codes.length); - }; -}; - -const extraAnalysis = function (repertoire: Repertoire, config: Config): Extra { - const { mapping, grouping } = config.form; - const classifier = mergeClassifier(config.analysis?.classifier); - const findSequence = (x: string) => { - if (x.match(/[0-9]+/)) { - return [...x].map(Number); - } - const glyph = repertoire[x]?.glyph; - if (glyph === undefined) { - return []; - } - if (glyph.type === "basic_component") { - return glyph.strokes.map((s) => classifier[s.feature]); - } else { - const sequence = recursiveRenderCompound(glyph, repertoire); - if (sequence instanceof Error) return []; - return sequence.map((s) => classifier[s.feature]); - } - }; - const rootSequence = new Map(); - const roots = Object.keys(mapping).concat(Object.keys(grouping ?? {})); - for (const root of roots) { - rootSequence.set(root, findSequence(root)); - } - return { - rootSequence, + return codes.slice(0, encoder.max_length ?? codes.length); }; }; @@ -238,6 +216,12 @@ const gather = (totalElements: IndexedElement[][], rules: WordRule[]) => { if (matched) return result; }; +interface AssembleConfig { + encoder: EncoderConfig; + keyboard: Keyboard; + algebra: Algebra; +} + /** * 给定一个拆分结果,返回所有可能的编码 * @@ -250,15 +234,15 @@ const gather = (totalElements: IndexedElement[][], rules: WordRule[]) => { */ export const assemble = ( repertoire: Repertoire, - config: Config, + config: AssembleConfig, characters: string[], dictionary: Dictionary, analysisResult: AnalysisResult, customElements: Record, ) => { const { customized, compoundResults } = analysisResult; - const extra = extraAnalysis(repertoire, config); - const func = compile(config); + const extra = { rootSequence: analysisResult.rootSequence }; + const func = compile(config.keyboard, config.encoder, config.algebra); const result: AssemblyResult = []; algebraCache.clear(); const characterCache = new Map(); diff --git a/src/lib/component.ts b/src/lib/component.ts index 2df1897..938f288 100644 --- a/src/lib/component.ts +++ b/src/lib/component.ts @@ -5,13 +5,14 @@ import type { SVGGlyph, Component, } from "./data"; -import { generateSliceBinaries } from "./degenerator"; +import { defaultDegenerator, generateSliceBinaries } from "./degenerator"; import { select } from "./selector"; import { bisectLeft, bisectRight } from "d3-array"; import type { RenderedGlyph, Topology } from "./topology"; import { findTopology, renderSVGGlyph } from "./topology"; import { mergeClassifier, type Classifier } from "./classifier"; import { isComponent, isValidCJKChar } from "./utils"; +import type { AnalysisConfig } from "."; import { recursiveRenderCompound } from "."; export class InvalidGlyphError extends Error {} @@ -106,11 +107,13 @@ export class MultipleSchemeError extends Error {} export const getComponentScheme = function ( component: ComputedComponent, rootData: ComputedComponent[], - config: Config, + config: AnalysisConfig, classifier: Classifier, ): ComponentAnalysis | NoSchemeError | MultipleSchemeError { - const { mapping } = config.form; - if (mapping[component.name]) + if ( + config.primaryRoots.has(component.name) || + config.secondaryRoots.has(component.name) + ) return { strokes: component.glyph.length, sequence: [component.name], @@ -122,7 +125,11 @@ export const getComponentScheme = function ( }), ); for (const root of rootData) { - const binaries = generateSliceBinaries(config, component, root); + const binaries = generateSliceBinaries( + config.analysis?.degenerator ?? defaultDegenerator, + component, + root, + ); binaries.forEach((binary) => rootMap.set(binary, root.name)); } const roots = Array.from(rootMap.keys()).sort((a, b) => a - b); @@ -250,9 +257,7 @@ export const computeComponent = (name: string, glyph: SVGGlyph) => { * * @returns 所有计算后字根的列表 */ -export const renderRootList = (repertoire: Repertoire, config: Config) => { - const { mapping, grouping } = config.form; - const elements = [...Object.keys(mapping), ...Object.keys(grouping ?? {})]; +export const renderRootList = (repertoire: Repertoire, elements: string[]) => { const rootList: Map = new Map(); for (const root of elements) { const glyph = repertoire[root]?.glyph; @@ -270,10 +275,9 @@ export const renderRootList = (repertoire: Repertoire, config: Config) => { const getLeafComponents = ( repertoire: Repertoire, - config: Config, + config: AnalysisConfig, characters: string[], ) => { - const { mapping, grouping } = config.form; const queue = [...characters]; const leafSet = new Set(); const knownSet = new Set(characters); @@ -281,11 +285,11 @@ const getLeafComponents = ( const char = queue.shift()!; const glyph = repertoire[char]!.glyph!; if (!glyph) { - console.log(char, char.codePointAt(0)?.toString(16)); continue; } if (glyph.type === "compound") { - if (mapping[char] || grouping?.[char]) continue; + if (config.primaryRoots.has(char) || config.secondaryRoots.has(char)) + continue; glyph.operandList.forEach((x) => { if (!knownSet.has(x)) { knownSet.add(x); @@ -309,11 +313,14 @@ const getLeafComponents = ( */ export const disassembleComponents = function ( repertoire: Repertoire, - config: Config, + config: AnalysisConfig, characters: string[], ): [ComponentResults, string[]] { const leafSet = getLeafComponents(repertoire, config, characters); - const rootMap = renderRootList(repertoire, config); + const rootMap = renderRootList(repertoire, [ + ...config.primaryRoots, + ...config.secondaryRoots, + ]); const rootList = [...rootMap.values()]; const classifier = mergeClassifier(config.analysis?.classifier); const result: [string, ComponentAnalysis][] = []; diff --git a/src/lib/compound.ts b/src/lib/compound.ts index c997de4..b804624 100644 --- a/src/lib/compound.ts +++ b/src/lib/compound.ts @@ -1,19 +1,17 @@ +import type { AnalysisConfig } from "."; import { affineMerge } from "."; -import { - ComponentResults, - ComponentAnalysis, - InvalidGlyphError, -} from "./component"; +import type { ComponentResults, ComponentAnalysis } from "./component"; +import { InvalidGlyphError } from "./component"; import { Config } from "./config"; -import { +import type { Block, Compound, Character, Repertoire, Operator, - PrimitiveRepertoire, SVGGlyph, } from "./data"; +import { PrimitiveRepertoire } from "./data"; export type CompoundResults = Map; @@ -152,11 +150,10 @@ const assembleSequence = ( */ export const disassembleCompounds = ( repertoire: Repertoire, - config: Config, + config: AnalysisConfig, componentResults: ComponentResults, characters: string[], ) => { - const { mapping, grouping } = config.form; const knownCharacters = new Set(characters); const compounds = topologicalSort(repertoire, characters); const compoundResults: CompoundResults = new Map(); @@ -165,7 +162,7 @@ export const disassembleCompounds = ( return componentResults.get(s) || compoundResults.get(s); }; for (const [char, glyph] of compounds.entries()) { - if (mapping[char] || grouping?.[char]) { + if (config.primaryRoots.has(char) || config.secondaryRoots.has(char)) { // 复合体本身是一个字根 compoundResults.set(char, { sequence: [char] }); continue; diff --git a/src/lib/config.ts b/src/lib/config.ts index 81f27bd..34f2e7c 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -23,13 +23,13 @@ export interface Info { export const characterSetSpecifiers = ["general", "basic", "extended"] as const; export type CharacterSetSpecifier = (typeof characterSetSpecifiers)[number]; -export type Data = { +export interface Data { character_set?: CharacterSetSpecifier; repertoire?: PrimitiveRepertoire; glyph_customization?: CustomGlyph; reading_customization?: CustomReadings; tags?: string[]; -}; +} export type CustomGlyph = Record; export type CustomReadings = Record; diff --git a/src/lib/degenerator.ts b/src/lib/degenerator.ts index 835b5e2..34d9ddd 100644 --- a/src/lib/degenerator.ts +++ b/src/lib/degenerator.ts @@ -1,5 +1,6 @@ import { isEqual } from "lodash-es"; -import { findTopology, RenderedGlyph } from "./topology"; +import type { RenderedGlyph } from "./topology"; +import { findTopology } from "./topology"; import type { Interval } from "./bezier"; import { curveLength, @@ -7,9 +8,10 @@ import { isCollinear, sortTwoNumbers, } from "./bezier"; -import { Config, Degenerator } from "./config"; -import { Feature } from "./classifier"; -import { ComputedComponent } from "./component"; +import type { Degenerator } from "./config"; +import { Config } from "./config"; +import type { Feature } from "./classifier"; +import type { ComputedComponent } from "./component"; export const indicesToBinary = (n: number) => (indices: number[]) => { let binaryCode = 0; @@ -113,11 +115,10 @@ const verifySpecialRoots = ( * @param root 字根 */ export const generateSliceBinaries = ( - config: Config, + degenerator: Degenerator, component: ComputedComponent, root: ComputedComponent, ) => { - const degenerator = config.analysis?.degenerator ?? defaultDegenerator; const { glyph: cglyph, topology: ctopology } = component; const { glyph: rglyph, topology: rtopology } = root; if (cglyph.length < rglyph.length) return []; diff --git a/src/lib/element.ts b/src/lib/element.ts index 35f73f7..17382af 100644 --- a/src/lib/element.ts +++ b/src/lib/element.ts @@ -1,4 +1,4 @@ -import type { Config, Rule } from "./config"; +import type { Algebra, Config, Rule } from "./config"; import type { CharacterResult } from "./assembly"; export interface Extra { @@ -203,7 +203,7 @@ export const algebraCache = new Map(); export const findElement = ( object: CodableObject, result: CharacterResult, - config: Config, + algebra: Algebra, extra: Extra, ) => { const { pinyin, sequence } = result; @@ -223,7 +223,7 @@ export const findElement = ( const name = object.subtype; const hash = name + ":" + pinyin; if (algebraCache.has(hash)) return algebraCache.get(hash); - const rules = defaultAlgebra[name] || config.algebra?.[name]; + const rules = defaultAlgebra[name] || algebra?.[name]; const transformed = applyRules(name, rules, pinyin); algebraCache.set(hash, transformed); return transformed; diff --git a/src/lib/repertoire.ts b/src/lib/repertoire.ts index 7063de4..1813b45 100644 --- a/src/lib/repertoire.ts +++ b/src/lib/repertoire.ts @@ -1,12 +1,11 @@ -import { - ComponentResults, - ComponentAnalysis, - disassembleComponents, - recursiveRenderComponent, -} from "./component"; -import { CompoundResults, disassembleCompounds } from "./compound"; -import { Config, CustomGlyph, CustomReadings } from "./config"; -import { +import { mergeClassifier } from "."; +import type { ComponentResults, ComponentAnalysis } from "./component"; +import { disassembleComponents, recursiveRenderComponent } from "./component"; +import type { CompoundResults } from "./compound"; +import { disassembleCompounds, recursiveRenderCompound } from "./compound"; +import type { Analysis, CustomGlyph, CustomReadings } from "./config"; +import { Config } from "./config"; +import type { Compound, Character, Repertoire, @@ -88,8 +87,47 @@ export interface AnalysisResult { customized: ComponentResults; compoundResults: CompoundResults; compoundError: string[]; + rootSequence: Map; } +export interface AnalysisConfig { + analysis: Analysis; + primaryRoots: Set; + secondaryRoots: Set; +} + +const getRootSequence = function ( + repertoire: Repertoire, + config: AnalysisConfig, +) { + const classifier = mergeClassifier(config.analysis?.classifier); + const findSequence = (x: string) => { + if (x.match(/[0-9]+/)) { + return [...x].map(Number); + } + const glyph = repertoire[x]?.glyph; + if (glyph === undefined) { + return []; + } + if (glyph.type === "basic_component") { + return glyph.strokes.map((s) => classifier[s.feature]); + } else { + const sequence = recursiveRenderCompound(glyph, repertoire); + if (sequence instanceof Error) return []; + return sequence.map((s) => classifier[s.feature]); + } + }; + const rootSequence = new Map(); + const roots = new Set([...config.primaryRoots, ...config.secondaryRoots]); + for (const root of roots) { + rootSequence.set(root, findSequence(root)); + } + for (const num of Object.values(classifier)) { + rootSequence.set(num.toString(), [num]); + } + return rootSequence; +}; + /** * 对整个字符集中的字符进行拆分 * @@ -98,7 +136,7 @@ export interface AnalysisResult { */ export const analysis = function ( repertoire: Repertoire, - config: Config, + config: AnalysisConfig, characters: string[], ): AnalysisResult { const [componentResults, componentError] = disassembleComponents( @@ -124,6 +162,7 @@ export const analysis = function ( customized, characters, ); + const rootSequence = getRootSequence(repertoire, config); return { componentResults, componentError, @@ -131,5 +170,6 @@ export const analysis = function ( customized, compoundResults, compoundError, + rootSequence, }; }; diff --git a/src/lib/selector.ts b/src/lib/selector.ts index 9d5a703..fdf3ec4 100644 --- a/src/lib/selector.ts +++ b/src/lib/selector.ts @@ -3,11 +3,12 @@ import { type ComputedComponent, NoSchemeError, } from "./component"; -import type { Config, SieveName } from "./config"; +import type { Analysis, Config, SieveName } from "./config"; import { binaryToIndices } from "./degenerator"; -import { type CurveRelation } from "./topology"; +import type { CurveRelation } from "./topology"; import { isEqual } from "lodash-es"; import { sortTwoNumbers } from "./bezier"; +import type { AnalysisConfig } from "."; export const defaultSelector: SieveName[] = [ "结构完整", @@ -22,14 +23,17 @@ export type Scheme = number[]; type Comparable = number | number[]; +export interface Environment { + component: ComputedComponent; + rootMap: Map; + analysis: Analysis; + primaryRoots: Set; + secondaryRoots: Set; +} + interface Sieve { title: SieveName; - key: ( - scheme: Scheme, - component: ComputedComponent, - config: Config, - rootMap: Map, - ) => T; + key: (scheme: Scheme, environment: Environment) => T; display?: (data: T) => string; } @@ -88,7 +92,7 @@ export const unbias: Sieve = { */ export const order: Sieve = { title: "全符笔顺", - key: (scheme, component) => { + key: (scheme, { component }) => { const indices = scheme .map((x) => binaryToIndices(component.glyph.length)(x)) .flat(); @@ -106,7 +110,7 @@ export const order: Sieve = { */ export const order2: Sieve = { title: "连续笔顺", - key: (scheme, component) => { + key: (scheme, { component }) => { const indices = scheme.map((x) => binaryToIndices(component.glyph.length)(x), ); @@ -126,9 +130,9 @@ export const order2: Sieve = { */ export const similar: Sieve = { title: "非形近根", - key: (scheme, _, config, rootMap) => { + key: (scheme, { rootMap, secondaryRoots }) => { const roots = scheme.map((x) => rootMap.get(x)!); - return roots.filter((x) => config.form.grouping?.[x] !== undefined).length; + return roots.filter((x) => secondaryRoots.has(x)).length; }, }; @@ -139,8 +143,8 @@ export const similar: Sieve = { */ export const strong: Sieve = { title: "多强字根", - key: (scheme, _, config, rootMap) => { - const strong = config.analysis?.strong || []; + key: (scheme, { rootMap, analysis }) => { + const strong = analysis?.strong || []; const roots = scheme.map((x) => rootMap.get(x)!); return -roots.filter((x) => strong.includes(x)).length; }, @@ -153,8 +157,8 @@ export const strong: Sieve = { */ export const weak: Sieve = { title: "少弱字根", - key: (scheme, _, config, rootMap) => { - const weak = config.analysis?.weak || []; + key: (scheme, { rootMap, analysis }) => { + const weak = analysis?.weak || []; const roots = scheme.map((x) => rootMap.get(x)!); return roots.filter((x) => weak.includes(x)).length; }, @@ -165,7 +169,7 @@ const makeTopologySieve = function ( avoidRelationType: CurveRelation["type"][], title: SieveName, ): Sieve { - const key: Sieve["key"] = (scheme, component) => { + const key: Sieve["key"] = (scheme, { component }) => { const parsedScheme = scheme.map((x) => binaryToIndices(component.glyph.length)(x), ); @@ -212,7 +216,7 @@ export const attaching = makeTopologySieve("连", ["交"], "能散不连"); */ export const orientation: Sieve = { title: "同向笔画", - key: (scheme, component) => { + key: (scheme, { component }) => { const n = component.glyph.length; const parsedScheme = scheme.map(binaryToIndices(n)); let totalCrosses = 0; @@ -229,7 +233,7 @@ export const orientation: Sieve = { r ||= oriented; } } - totalCrosses += +r; + totalCrosses += Number(r); } } return totalCrosses; @@ -252,7 +256,7 @@ const contains = (b1: number, b2: number) => (b1 | b2) === b1; */ export const integrity: Sieve = { title: "结构完整", - key: (scheme, _, __, rootMap) => { + key: (scheme, { rootMap }) => { const priorities = [ "口", "囗", @@ -300,7 +304,7 @@ export const sieveMap = new Map | Sieve>( * @param rootMap 字根映射,从切片的二进制表示到字根名称的映射 */ export const select = ( - config: Config, + config: AnalysisConfig, component: ComputedComponent, schemeList: Scheme[], rootMap: Map, @@ -310,12 +314,17 @@ export const select = ( evaluation: new Map(), excluded: false, })); - for (const sieveName of config.analysis?.selector ?? defaultSelector) { + const environment: Environment = { + component, + rootMap, + ...config, + }; + for (const sieveName of config.analysis.selector ?? defaultSelector) { const sieve = sieveMap.get(sieveName)!; let min: number | number[] | undefined; for (const data of schemeData) { if (data.excluded) continue; - const value = sieve.key(data.scheme, component, config, rootMap); + const value = sieve.key(data.scheme, environment); data.evaluation.set(sieve.title, value); if (min === undefined) { min = value; diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 6a6b5c5..7fc6b2f 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -1,4 +1,4 @@ -import { Classifier } from "./classifier"; +import type { Classifier } from "./classifier"; import type { Analysis, Config, Keyboard } from "./config"; import { defaultDegenerator } from "./degenerator"; import { defaultSelector } from "./selector"; @@ -68,8 +68,19 @@ export interface StarterType { } export const createConfig = function (starter: StarterType): Config { + const form = keyboardMap[starter.keyboard]; + const classifier = classifierMap[starter.data]; + + // 确保笔画都在 mapping 里 + for (const value of Object.values(classifier)) { + const element = value.toString(); + if (!form.mapping[element]) { + form.mapping[element] = form.alphabet[0]!; + } + } + return { - version: APP_VERSION, + version: "0.1", source: null, info: { name: starter.name, @@ -78,11 +89,11 @@ export const createConfig = function (starter: StarterType): Config { description: "从模板创建", }, analysis: { - classifier: classifierMap[starter.data]!, + classifier, degenerator: defaultDegenerator, selector: defaultSelector, }, - form: keyboardMap[starter.keyboard], + form, encoder: encoderMap[starter.encoder], }; }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b4abafa..5d8ea5a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,5 @@ -import { Distribution, Frequency } from "~/atoms"; +import type { Distribution } from "~/atoms"; +import { Frequency } from "~/atoms"; import type { Feature } from "./classifier"; import { schema } from "./classifier"; import type { @@ -15,7 +16,8 @@ import type { } from "./data"; import { range } from "lodash-es"; import { dump } from "js-yaml"; -import { IndexedElement, Key, summarize } from "."; +import type { IndexedElement, Key } from "."; +import { summarize } from "."; import { Combined } from "~/components/SequenceTable"; export const printableAscii = range(33, 127).map((x) => @@ -259,8 +261,8 @@ export const renderMapped = (mapped: string | Key[]) => { }); }; -export const makeWorker = () => { - return new Worker(new URL("../worker.ts", import.meta.url), { +export const makeWorker = (url = "../worker.ts") => { + return new Worker(new URL(url, import.meta.url), { type: "module", }); }; @@ -325,6 +327,7 @@ export interface CharacterFilter { export const makeFilter = (input: string, form: Repertoire, sequence: Map) => (char: string) => { + if ((sequence.get(char)?.length ?? 0) <= 1) return false; let name = form[char]?.name ?? ""; let seq = sequence.get(char) ?? ""; return ( diff --git a/src/pages/[id].tsx b/src/pages/[id].tsx index 16c4197..d128301 100644 --- a/src/pages/[id].tsx +++ b/src/pages/[id].tsx @@ -14,13 +14,13 @@ import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"; import CusSpin from "~/components/CustomSpin"; import { DevTools } from "jotai-devtools"; import { - configIdAtom, infoAtom, useSetAtom, useAtomValue, useAtom, fetchAsset, primitiveRepertoireAtom, + configAtom, } from "~/atoms"; import { listToObject } from "~/lib"; import { @@ -176,31 +176,18 @@ function EditorLayout() { } export default function Contextualized() { - const { pathname } = useLocation(); - const id = pathname.split("/")[1]!; - const [configId, setConfigId] = useAtom(configIdAtom); + const id = location.pathname.split("/")[1] ?? ""; const setRepertoire = useSetAtom(primitiveRepertoireAtom); - const setF = useSetAtom(frequencyAtom); - const setW = useSetAtom(defaultDictionaryAtom); - const setKE = useSetAtom(keyDistributionAtom); - const setPE = useSetAtom(pairEquivalenceAtom); + + console.log("root mounted"); useEffect(() => { - setConfigId(id); fetchAsset("repertoire").then((value) => setRepertoire(listToObject(value)), ); - fetchAsset("frequency", "txt").then((x) => setF(getRecordFromTSV(x))); - fetchAsset("dictionary", "txt").then((x) => setW(getDictFromTSV(x))); - fetchAsset("key_distribution", "txt").then((x) => - setKE(getDistributionFromTSV(x)), - ); - fetchAsset("pair_equivalence", "txt").then((x) => - setPE(getRecordFromTSV(x)), - ); - }, [id]); + }, []); - if (!(id in localStorage) || !configId) { + if (!(id in localStorage)) { return ; } diff --git a/src/pages/[id]/analysis.tsx b/src/pages/[id]/analysis.tsx index 94e1577..04935a8 100644 --- a/src/pages/[id]/analysis.tsx +++ b/src/pages/[id]/analysis.tsx @@ -2,12 +2,12 @@ import { Alert, Button, Col, - Empty, Flex, Modal, Pagination, Radio, Row, + Skeleton, Space, Statistic, Switch, @@ -17,15 +17,13 @@ import { sequenceAtom, displayAtom, repertoireAtom, - configAtom, - useAtom, useChaifenTitle, customizeAtom, charactersAtom, } from "~/atoms"; import { Collapse } from "antd"; import ResultDetail from "~/components/ResultDetail"; -import { useState } from "react"; +import { Suspense, useState } from "react"; import type { ComponentResults, AnalysisResult, CharacterFilter } from "~/lib"; import type { CompoundResults } from "~/lib"; import { analysis, exportTSV, makeCharacterFilter } from "~/lib"; @@ -78,24 +76,16 @@ const ConfigureRules = () => { ); }; -export default function Analysis() { - useChaifenTitle("拆分"); - const [filter, setFilter] = useState({}); +const AnalysisResult = ({ filter }: { filter: CharacterFilter }) => { const [step, setStep] = useState(0 as 0 | 1); const repertoire = useAtomValue(repertoireAtom); const sequenceMap = useAtomValue(sequenceAtom); - const [analysisResult, setAnalysisResult] = useAtom(analysisResultAtom); - const componentResults: ComponentResults = - analysisResult?.componentResults ?? new Map(); - const compoundResults: CompoundResults = - analysisResult?.compoundResults ?? new Map(); - const componentError = analysisResult?.componentError ?? []; - const compoundError = analysisResult?.compoundError ?? []; + const analysisResult = useAtomValue(analysisResultAtom); + const { componentResults, compoundResults, componentError, compoundError } = + analysisResult; const characters = useAtomValue(charactersAtom); - - const config = useAtomValue(configAtom); const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(100); + const [pageSize, setPageSize] = useState(50); const display = useAtomValue(displayAtom); const customize = useAtomValue(customizeAtom); const filterFn = makeCharacterFilter(filter, repertoire, sequenceMap); @@ -138,9 +128,8 @@ export default function Analysis() { }); const displays = [componentDisplay, compoundDisplay] as const; - return ( - + <> {componentError.length + compoundError.length > 0 ? ( ) : null} - - + setStep(e.target.value as 0)} @@ -163,81 +151,80 @@ export default function Analysis() { 复合体拆分 - - - {step === 0 && ( - - - - - - - - - - - - - - - - 只显示自定义 - setCustomizedOnly((x) => !x)} - /> - - - - )} - {displays[step].length ? ( - <> - + + + + + + + + - { - setPage(page); - setPageSize(pageSize); - }} - total={displays[step].length} - pageSize={pageSize} + + + - - ) : ( - - )} + + + + 只显示自定义 + setCustomizedOnly((x) => !x)} + /> + + + + + { + setPage(page); + setPageSize(pageSize); + }} + total={displays[step].length} + pageSize={pageSize} + /> + + ); +}; + +export default function Analysis() { + useChaifenTitle("拆分"); + const [filter, setFilter] = useState({}); + + return ( + + + + + + }> + + ); } diff --git a/src/pages/[id]/assembly.tsx b/src/pages/[id]/assembly.tsx index 8ae0973..4caa1a1 100644 --- a/src/pages/[id]/assembly.tsx +++ b/src/pages/[id]/assembly.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { Button, Flex, Modal } from "antd"; +import { Suspense, useState } from "react"; +import { Button, Flex, Modal, Skeleton } from "antd"; import EncoderGraph from "~/components/EncoderGraph"; import { ReactFlowProvider } from "reactflow"; import WordRules from "~/components/WordRules"; @@ -62,7 +62,9 @@ export default function Assembly() { return ( - + }> + + ); } diff --git a/src/pages/[id]/index.tsx b/src/pages/[id]/index.tsx index 7b6b8c5..9ac6b38 100644 --- a/src/pages/[id]/index.tsx +++ b/src/pages/[id]/index.tsx @@ -60,7 +60,7 @@ function AssetUploader({ dumper, }: { atom: WritableAtom], void>; - defaultAtom: Atom; + defaultAtom: Atom>; title: string; description: string; parser: (text: string) => V; diff --git a/src/pages/[id]/optimization.tsx b/src/pages/[id]/optimization.tsx index 088fbe6..f1e559c 100644 --- a/src/pages/[id]/optimization.tsx +++ b/src/pages/[id]/optimization.tsx @@ -22,15 +22,15 @@ import { KeyList, Select, } from "~/components/Utils"; -import { +import type { AtomicConstraint, Constraints, LevelWeights, Objective, PartialWeights, - Solver, TierWeights, } from "~/lib"; +import { Solver } from "~/lib"; const AtomicObjective = ({ title, diff --git a/src/pages/[id]/statistics.tsx b/src/pages/[id]/statistics.tsx index 3501b5e..babb4cd 100644 --- a/src/pages/[id]/statistics.tsx +++ b/src/pages/[id]/statistics.tsx @@ -14,12 +14,14 @@ import { } from "@ant-design/pro-components"; import { Form, Space, Typography } from "antd"; import { useAtomValue } from "jotai"; -import { Frequency, maxLengthAtom } from "~/atoms"; -import { AnalyzerForm, AssemblyResult, renderIndexed } from "~/lib"; +import type { Frequency } from "~/atoms"; +import { maxLengthAtom } from "~/atoms"; +import type { AnalyzerForm, AssemblyResult } from "~/lib"; +import { renderIndexed } from "~/lib"; import { Select } from "~/components/Utils"; import { useState } from "react"; import { assemblyResultAtom } from "~/atoms/cache"; -import { ColumnsType } from "antd/es/table"; +import type { ColumnsType } from "antd/es/table"; import { range, sum, sumBy } from "lodash-es"; const numbers = [ @@ -131,6 +133,7 @@ const SubStatistics = ({ init }: { init: AnalyzerForm }) => { submitter={false} initialValues={analyzer} onValuesChange={(_, values) => setAnalyzer(values)} + autoFocusFirstInput={false} > { const rootMap = new Map(); for (const another of toCompare) { if (another.name === record.name) continue; - const slices = generateSliceBinaries(defaultConfig, record, another); + const slices = generateSliceBinaries( + defaultDegenerator, + record, + another, + ); if (slices.length) { rootMap.set(another.name, slices); } @@ -58,7 +66,7 @@ const DegeneratorTable = () => { {rootList.map(([name, slices]) => { return ( - {display(name)} + {display(name)} {slices.map((x) => `(${convert(x).join(", ")})`).join(", ")} );