Skip to content

Commit

Permalink
chore(developer): ldml-editor: refactor, cleanup, comment 🗼
Browse files Browse the repository at this point in the history
- refactor, cleanup, and comment
- use @parcel/transformer-typescript-tsc for compilation

Fixes: #12789
  • Loading branch information
srl295 committed Dec 30, 2024
1 parent 57cafb3 commit acad884
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 112 deletions.
6 changes: 6 additions & 0 deletions developer/src/vscode-plugin/.parcelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}
184 changes: 72 additions & 112 deletions developer/src/vscode-plugin/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,56 @@
/*
* 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() {
setShown(true);
}
if (!text) return;
if (!shown) return (
<Button type="primary" onClick={() => setShown(true)}>Show Raw Source</Button>
<Card title="Raw XML Source" bordered={true}>
<Button type="primary" onClick={() => setShown(true)}>Show</Button>
</Card>
);
return (
<div>
<Button type="primary" onClick={() => setShown(false)}>Hide Raw Source</Button>
<h4>Raw Source</h4>
<Card title="Raw XML Source" bordered={true}>
<Button type="primary" onClick={() => setShown(false)}>Hide</Button>
<pre className="code">{text}</pre>
</div>
</Card>
);
}

/** An individual key in the keybag. */
function Key({ k, chosenKey, setChosenKey }: {
k: KMXPlus.KeysKeys,
chosenKey: string,
Expand All @@ -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 (
<i key={id} onClick={() => setChosenKey(id)} className={className} title={id}>{id}</i>
Expand All @@ -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 (
Expand All @@ -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) {
Expand All @@ -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 (
<Card title="Details" bordered={true}>
<h5>Details</h5>
<b>ID:</b> <Input value={k.id.value} /> <br/>
<b>To:</b> <Input value={k.to.value} /> <br/>
<b>Width:</b> <InputNumber min={0.1} max={99.0} step={0.1} value={k.width / 10} /> <br/>
<Checkbox checked={isGap}>Gap</Checkbox> <br/>
<b>ID:</b> <Input value={k.id.value} /> <br />
<b>To:</b> <Input value={k.to.value} /> <br />
<b>Width:</b> <InputNumber min={0.1} max={99.0} step={0.1} value={k.width / 10} /> <br />
<Checkbox checked={isGap}>Gap</Checkbox> <br />
<p>flags? 0x{k.flags.toString(16)}</p>
</Card>
);
Expand All @@ -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 (
<>
<h4>Key Bag</h4>
<Card title="Key Bag" bordered={true}>
<div className="keyListContainer">
<KeyList keys={keys} chosenKey={chosenKey} setChosenKey={setChosenKey} />
</div>
<KeyDetails chosenKey={chosenKey}/>
</>
);
}

/** 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 (
<button className={className} onClick={() => setCurWidth(myWidth)}>
{title}
</button>
<KeyDetails chosenKey={chosenKey} />
</Card>
);
}

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 }) => (
<kbd key={value}>{value}</kbd>
))}
<br/>
<br />
</>
)
}

function Layer({layer} : {
/** single layer */
function Layer({ layer }: {
layer: KMXPlus.LayrEntry,
key: string,
}) {
return (
<>
<h5>{layerTitle(layer)}</h5>
{layer.rows.map((row,index)=> (<Row row={row} key={index.toString()}/>))}
{layer.rows.map((row, index) => (<Row row={row} key={index.toString()} />))}
</>
);
}

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,
Expand All @@ -213,43 +169,47 @@ function LayoutList({curWidth, setCurWidth, list} :
if (!selected) return; // for now: hide collapsed layouts
return (
<div className="layoutList">
{list.layers.map((layer)=> (<Layer key={layerTitle(layer)} layer={layer}/>))}
{list.layers.map((layer) => (<Layer key={layerTitle(layer)} layer={layer} />))}
</div>
);
}

/** 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 (
<div className="keyLayouts">
<h4>Layouts</h4>
<Segmented<string>
<Card title="Layouts" bordered={true}>
<Segmented<string>
options={listTitles}
onChange={(value)=>setCurWidth(findWidth(value))} />
{lists.map((list) => (<LayoutList key={touchWidth(list).toString()} curWidth={curWidth} setCurWidth={setCurWidth} list={list}/>))}
onChange={(value) => setCurWidth(findWidth(value))} />
{lists.map((list) => (<LayoutList key={touchWidth(list).toString()} curWidth={curWidth} setCurWidth={setCurWidth} list={list} />))}
</Card>
</div>
);
}

function Messages({messages} : {messages: string[]}) {
/** this shows any error or warning messages from the compiler */
function Messages({ messages }: { messages: string[] }) {
if (!messages) return;
return (
<ul>
{messages.map((msg,key) => (<li key={key}>{msg}</li>))}
{messages.map((msg, key) => (<li key={key}>{msg}</li>))}
</ul>
);
}

function Keyboard() {
/** This is the main Keyboard File object. */
function KeyboardFile() {
const initialData: LoadedData = {
loaded: false,
text: undefined,
Expand Down Expand Up @@ -280,7 +240,7 @@ function Keyboard() {
return (
<div>
<h4>LDML Keyboard</h4>
<Skeleton/>
<Skeleton />
</div>
);
}
Expand All @@ -289,29 +249,29 @@ function Keyboard() {
return (
<div>
<Alert type="error" showIcon message="Could not read keyboard file. Correct these issues and reload the file." />
<Messages messages={data.messages}/>
<Messages messages={data.messages} />
</div>
)
}

return (
<div>
<Messages messages={data.messages}/>
<KmxPlusContext.Provider value={data.kmxPlus}>
<>
<Messages messages={data.messages} />
<KmxPlusContext.Provider value={data.kmxPlus}>
<KeyBag />
<KeyLayouts />
</KmxPlusContext.Provider>
<hr />
<RawSource text={data?.text} />
</div>
</>
);
}

/** The outer App. TODO-EPIC-LDML this may be */
function App() {
return (
<div className="App">
<h4>LDML Editor</h4>
<Keyboard />
<KeyboardFile />
</div>
);
}
Expand Down
Loading

0 comments on commit acad884

Please sign in to comment.