From acad88456c662ca5ca63548e82d9e11413c08668 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 30 Dec 2024 11:38:39 -0600 Subject: [PATCH] =?UTF-8?q?chore(developer):=20ldml-editor:=20refactor,=20?= =?UTF-8?q?cleanup,=20comment=20=F0=9F=97=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor, cleanup, and comment - use @parcel/transformer-typescript-tsc for compilation Fixes: #12789 --- developer/src/vscode-plugin/.parcelrc | 6 + developer/src/vscode-plugin/app/App.tsx | 184 +++++++++-------------- developer/src/vscode-plugin/app/utils.ts | 57 +++++++ developer/src/vscode-plugin/package.json | 1 + package-lock.json | 44 ++++++ 5 files changed, 180 insertions(+), 112 deletions(-) create mode 100644 developer/src/vscode-plugin/.parcelrc create mode 100644 developer/src/vscode-plugin/app/utils.ts diff --git a/developer/src/vscode-plugin/.parcelrc b/developer/src/vscode-plugin/.parcelrc new file mode 100644 index 0000000000..a808df4c00 --- /dev/null +++ b/developer/src/vscode-plugin/.parcelrc @@ -0,0 +1,6 @@ +{ + "extends": "@parcel/config-default", + "transformers": { + "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"] + } +} diff --git a/developer/src/vscode-plugin/app/App.tsx b/developer/src/vscode-plugin/app/App.tsx index f95dee01d0..dc27b4a36b 100644 --- a/developer/src/vscode-plugin/app/App.tsx +++ b/developer/src/vscode-plugin/app/App.tsx @@ -1,27 +1,36 @@ /* * Keyman is copyright (C) SIL Global. MIT License. + * + * Visual editor app */ import React from 'react'; import './App.css'; import { KMXPlus } from '@keymanapp/common-types'; import KMXPlusFile = KMXPlus.KMXPlusFile; -import { constants } from '@keymanapp/ldml-keyboard-constants'; -import { Button, Input, InputNumber, Checkbox, Card, Segmented, Skeleton, Alert } from 'antd'; +import { Button, Card, Input, InputNumber, Checkbox, Segmented, Skeleton, Alert } from 'antd'; +import { isGapKey, layerTitle, listTitle, touchWidth, WIDTH_HARDWARE } from './utils.js'; +/** + * Used to get the VSCode global. Can only call this once. + */ +const vsCode = (global as any).acquireVsCodeApi(); +/** This is used to track data as it is loaded. */ interface LoadedData { + /** true if we think we're loaded */ loaded: boolean; + /** full XML source text */ text?: string; + /** the entire KMXPlusFile */ kmxPlus?: KMXPlusFile; + /** messages serialized to strings */ messages?: string[]; } -const noKmxPlusFile : KMXPlusFile = undefined as unknown as KMXPlusFile; - -const KmxPlusContext = React.createContext(noKmxPlusFile); - -const vsCode = (global as any).acquireVsCodeApi(); +/** this context will have the KMXPlusFile. We won't use it until the KMXPlusFile is valid. */ +const KmxPlusContext = React.createContext(undefined as unknown as KMXPlusFile) +/** The "show raw source" panel */ function RawSource({ text }) { const [shown, setShown] = React.useState(false); function show() { @@ -29,17 +38,19 @@ function RawSource({ text }) { } if (!text) return; if (!shown) return ( - + + + ); return ( -
- -

Raw Source

+ +
{text}
-
+ ); } +/** An individual key in the keybag. */ function Key({ k, chosenKey, setChosenKey }: { k: KMXPlus.KeysKeys, chosenKey: string, @@ -50,7 +61,7 @@ function Key({ k, chosenKey, setChosenKey }: { const chosen = (chosenKey == id); const className = chosen ? "Key chosenKey" : "Key unchosenKey"; // const onClick = chosen ? () => setChosenKey(k.id.value) : setChosenKey(id); - const onClick = () => {}; + const onClick = () => { }; if (k.to.value === '') { return ( setChosenKey(id)} className={className} title={id}>{id} @@ -61,6 +72,7 @@ function Key({ k, chosenKey, setChosenKey }: { ); } +/** The list of keys in the keybag */ function KeyList({ keys, chosenKey, setChosenKey }: { keys: KMXPlus.KeysKeys[], chosenKey: string, setChosenKey: any /* setter */ }) { return ( @@ -70,11 +82,11 @@ function KeyList({ keys, chosenKey, setChosenKey }: ); } -/** detail editor */ -function KeyDetails({ chosenKey } : { chosenKey : string }) { - const kmxPlus = React.useContext(KmxPlusContext); +/** keybag detail editor */ +function KeyDetails({ chosenKey }: { chosenKey: string }) { + const kmxPlus = React.useContext(KmxPlusContext) as KMXPlus.KMXPlusFile; if (!chosenKey) return; - const chosenKeys = kmxPlus.kmxplus.keys?.keys.filter(({id}) => id.value == chosenKey); + const chosenKeys = kmxPlus.kmxplus.keys?.keys.filter(({ id }) => id.value == chosenKey); if (!chosenKeys?.length) { return; // no key selecte4d } else if (chosenKeys?.length > 1) { @@ -83,16 +95,16 @@ function KeyDetails({ chosenKey } : { chosenKey : string }) { const k = chosenKeys[0]; - const {flags} = k; - const isGap = flags & constants.keys_key_flags_gap; + const { flags } = k; + const isGap = isGapKey(k); return (
Details
- ID:
- To:
- Width:
- Gap
+ ID:
+ To:
+ Width:
+ Gap

flags? 0x{k.flags.toString(16)}

); @@ -103,104 +115,48 @@ function KeyBag() { const kmxPlus = React.useContext(KmxPlusContext); const keys = kmxPlus?.kmxplus?.keys?.keys || []; /** string id of selected key */ - const [ chosenKey, setChosenKey ] = React.useState(keys[0]?.id?.value); + const [chosenKey, setChosenKey] = React.useState(keys[0]?.id?.value); if (!kmxPlus) return; // get out return ( - <> -

Key Bag

+
- - - ); -} - -/** treat hardware as width -1 for sorting */ -const WIDTH_HARDWARE = -1; - -/** get an index for a LayrList */ -function touchWidth( list: KMXPlus.LayrList ) : number { - if (list.hardware.value === constants.layr_list_hardware_touch) { - return list.minDeviceWidth; - } else { - return -1; - } -} - -function LayoutListButton({curWidth, setCurWidth, list} : - { - curWidth: number, - setCurWidth: any, - list: KMXPlus.LayrList, - key: string, - } -) { - const myWidth = touchWidth(list); - const selected = (curWidth === myWidth); - const isTouch = (list.hardware.value === constants.layr_list_hardware_touch); - const title = isTouch ? `Touch>${list.minDeviceWidth}px` : `${list.hardware.value}`; - const className = selected ? 'layoutListButton selectedLayoutListButton' : 'layoutListButton unselectedLayoutListButton'; - return ( - + +
); } -function modToStr(mod : number) { - if (mod === constants.keys_mod_none) { - return 'none'; - } - let ret : string[] = []; - constants.keys_mod_map.forEach((mask, name) => { - if(mod & mask) { - ret.push(name); - } - }); - ret.sort(); // make it deterministic - return ret.join(','); -} - -function layerTitle(layer : KMXPlus.LayrEntry) { - if (layer.id.value) return layer.id.value + modToStr(layer.mod); - return modToStr(layer.mod); -} - -function Row({row} : { +/** row in the layer list */ +function Row({ row }: { row: KMXPlus.LayrRow, key: string, }) { return ( <> - {row.keys.map(({value})=>( + {row.keys.map(({ value }) => ( {value} ))} -
+
) } -function Layer({layer} : { +/** single layer */ +function Layer({ layer }: { layer: KMXPlus.LayrEntry, key: string, }) { return ( <>
{layerTitle(layer)}
- {layer.rows.map((row,index)=> ())} + {layer.rows.map((row, index) => ())} ); } -function listTitle(list : KMXPlus.LayrList) { - const myWidth = touchWidth(list); - const isTouch = (list.hardware.value === constants.layr_list_hardware_touch); - const title = isTouch ? `Touch>${list.minDeviceWidth}px` : `${list.hardware.value}`; - return title; -} - -function LayoutList({curWidth, setCurWidth, list} : +/** a list of layouts (i.e. a form type or width) */ +function LayoutList({ curWidth, setCurWidth, list }: { curWidth: number, setCurWidth: any, @@ -213,43 +169,47 @@ function LayoutList({curWidth, setCurWidth, list} : if (!selected) return; // for now: hide collapsed layouts return (
- {list.layers.map((layer)=> ())} + {list.layers.map((layer) => ())}
); } +/** The list of Layouts */ function KeyLayouts() { const kmxPlus = React.useContext(KmxPlusContext); const lists = [...(kmxPlus.kmxplus.layr?.lists || [])]; // copy the list const [curWidth, setCurWidth] = React.useState(WIDTH_HARDWARE); - lists.sort((a,b)=>touchWidth(a)-touchWidth(b)); // sort by width - const listWidthAndTitle = lists.map((list) => [listTitle(list),touchWidth(list)]); - const listTitles = listWidthAndTitle.map(([t])=>t); // just titles + lists.sort((a, b) => touchWidth(a) - touchWidth(b)); // sort by width + const listWidthAndTitle = lists.map((list) => [listTitle(list), touchWidth(list)]); + const listTitles = listWidthAndTitle.map(([t]) => t); // just titles // search listWidthAndTitle for the width function findWidth(title) { - return listWidthAndTitle.filter(([t])=>t == title)[0][1]; + return listWidthAndTitle.filter(([t]) => t == title)[0][1]; } return (
-

Layouts

- + + options={listTitles} - onChange={(value)=>setCurWidth(findWidth(value))} /> - {lists.map((list) => ())} + onChange={(value) => setCurWidth(findWidth(value))} /> + {lists.map((list) => ())} +
); } -function Messages({messages} : {messages: string[]}) { +/** this shows any error or warning messages from the compiler */ +function Messages({ messages }: { messages: string[] }) { if (!messages) return; return (
    - {messages.map((msg,key) => (
  • {msg}
  • ))} + {messages.map((msg, key) => (
  • {msg}
  • ))}
); } -function Keyboard() { +/** This is the main Keyboard File object. */ +function KeyboardFile() { const initialData: LoadedData = { loaded: false, text: undefined, @@ -280,7 +240,7 @@ function Keyboard() { return (

LDML Keyboard

- +
); } @@ -289,29 +249,29 @@ function Keyboard() { return (
- +
) } return ( -
- - + <> + +
-
+ ); } +/** The outer App. TODO-EPIC-LDML this may be */ function App() { return (
-

LDML Editor

- +
); } diff --git a/developer/src/vscode-plugin/app/utils.ts b/developer/src/vscode-plugin/app/utils.ts new file mode 100644 index 0000000000..641088fe9b --- /dev/null +++ b/developer/src/vscode-plugin/app/utils.ts @@ -0,0 +1,57 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Utility functions + */ + +import { KMXPlus } from '@keymanapp/common-types'; +import { constants } from '@keymanapp/ldml-keyboard-constants'; + +/** + * @returns true if this is a gap key + */ +export function isGapKey(key: KMXPlus.KeysKeys) { + const {flags} = key; + return flags & constants.keys_key_flags_gap; +} + +/** convert a modifier number to a string */ +export function modToStr(mod: number) { + if (mod === constants.keys_mod_none) { + return 'none'; + } + let ret: string[] = []; + constants.keys_mod_map.forEach((mask, name) => { + if (mod & mask) { + ret.push(name); + } + }); + ret.sort(); // make it deterministic + return ret.join(','); +} + +/** treat hardware as width -1 for sorting */ +export const WIDTH_HARDWARE = -1; + +/** get an index for a LayrList */ +export function touchWidth(list: KMXPlus.LayrList): number { + if (list.hardware.value === constants.layr_list_hardware_touch) { + return list.minDeviceWidth; + } else { + return -1; + } +} + +/** convert a layer entry to a title */ +export function layerTitle(layer: KMXPlus.LayrEntry) { + if (layer.id.value) return layer.id.value + modToStr(layer.mod); + return modToStr(layer.mod); +} +/** convert a LayrList to a title */ +export function listTitle(list: KMXPlus.LayrList) { + const myWidth = touchWidth(list); + const isTouch = (list.hardware.value === constants.layr_list_hardware_touch); + const title = isTouch ? `Touch>${list.minDeviceWidth}px` : `${list.hardware.value}`; + return title; +} + diff --git a/developer/src/vscode-plugin/package.json b/developer/src/vscode-plugin/package.json index de4ede9394..d7342fc27f 100644 --- a/developer/src/vscode-plugin/package.json +++ b/developer/src/vscode-plugin/package.json @@ -41,6 +41,7 @@ "ui-watch": "parcel watch --target=default --cache-dir=.parcel-cache" }, "devDependencies": { + "@parcel/transformer-typescript-tsc": "^2.13.2", "@types/jsdom": "^21.1.7", "@types/mocha": "^10.0.0", "@types/node": "20.4.1", diff --git a/package-lock.json b/package-lock.json index 1eeb589c49..088c55a7a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2156,6 +2156,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { + "@parcel/transformer-typescript-tsc": "^2.13.2", "@types/jsdom": "^21.1.7", "@types/mocha": "^10.0.0", "@types/node": "20.4.1", @@ -5313,6 +5314,49 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/transformer-typescript-tsc": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-typescript-tsc/-/transformer-typescript-tsc-2.13.2.tgz", + "integrity": "sha512-bJq/7QeLbj7IpDCiEWM+jf1b8lMWvongM9/fYIJtm1mTGsBBIYQ0vd1jbtdtfb4fNIRK3qV2++GUvE2CgEZs9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.13.2", + "@parcel/source-map": "^2.1.1", + "@parcel/ts-utils": "2.13.2" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.13.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "typescript": ">=3.0.0" + } + }, + "node_modules/@parcel/ts-utils": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@parcel/ts-utils/-/ts-utils-2.13.2.tgz", + "integrity": "sha512-wj6trzSaZ7nHDzx5zVQfs467WrA3p7g6bos7Opr8nTw5V1qACXa03C9ccRiEUmR0i+Bk8kEZKUunswhjBiPuHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "typescript": ">=3.0.0" + } + }, "node_modules/@parcel/types": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.13.2.tgz",