Skip to content

Commit

Permalink
feat(developer): ldml-editor: repertoire viewer 🗼
Browse files Browse the repository at this point in the history
- some refactoring still
- disabled the raw XML view

Fixes: #12789
  • Loading branch information
srl295 committed Dec 31, 2024
1 parent acad884 commit 6bd82e1
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 40 deletions.
17 changes: 16 additions & 1 deletion developer/src/vscode-plugin/app/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ kbd.unchosenKey {
border: 2px solid blue;
} */

.keyList {
.keyList, .repertoire {
border: 1px solid gray;
padding: 0.25em;
width: 50em;
Expand All @@ -39,6 +39,21 @@ kbd.unchosenKey {
overflow-y: scroll;
}

.repertoireItem {
padding: 1em;
background-color: gainsboro;
margin: 0.25em;
vertical-align: middle;
box-shadow: .15em .15em gray;
border: 2px solid gray;
font-size: large;
display: flow flex-shrink;
flex-grow: 1;
flex-basis: 1em;
white-space-collapse: preserve-spaces;
}


.selectedLayoutListButton {
background-color: navy;
color: white;
Expand Down
175 changes: 138 additions & 37 deletions developer/src/vscode-plugin/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,106 @@ import React from 'react';
import './App.css';
import { KMXPlus } from '@keymanapp/common-types';
import KMXPlusFile = KMXPlus.KMXPlusFile;
import { Button, Card, Input, InputNumber, Checkbox, Segmented, Skeleton, Alert } from 'antd';
import { Button, Input, InputNumber, Checkbox, Segmented, Skeleton, Alert, Collapse, TabsProps, Tabs, Spin } from 'antd';
/** Ant's Card had an import problem, so we use this workaround */
import { FixedCard as Card } from './FixedCard.js';
import { isGapKey, layerTitle, listTitle, touchWidth, WIDTH_HARDWARE } from './utils.js';
import { FakeRepertoire, RepertoireEntry, SAMPLE_REPERTOIRE } from './fakerepertoire.js';
// import { RawSource } from './RawSource';

/**
* 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[];
}

/** 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 (
<Card title="Raw XML Source" bordered={true}>
<Button type="primary" onClick={() => setShown(true)}>Show</Button>
</Card>
// -------- repertoire -------------

function RepertoireItem({ item } : { item : RepertoireEntry, key?: any}) {
return (
<span className="repertoireItem">{item}</span>
);
}

function AddCldrSldr() {
const [ loading, setLoading ] = React.useState(false);

return (
<Card title="Raw XML Source" bordered={true}>
<Button type="primary" onClick={() => setShown(false)}>Hide</Button>
<pre className="code">{text}</pre>
</Card>
<>
Locale: <Input value="mt"/> <br/>
<Button type="primary" onClick={() => setLoading((v) => !v)}>Load</Button>
{loading && (<Spin/>)}
</>
)
}

function AddUnicodeRange() {
return (
<>
<InputNumber value="U+1A00"/>
<InputNumber value="U+1AFF"/>
<br/>
<Button type="primary">Add</Button>
</>
);
}

function AddRepertoire() {
const items : TabsProps['items'] = [
{
key: 'sldr',
label: 'SLDR',
children: (<AddCldrSldr/>),
},
{
key: 'cldr',
label: 'CLDR',
children: (<AddCldrSldr/>),
},
{
key: 'range',
label: 'Unicode Range',
children: (<AddUnicodeRange/>),
},
{
key: 'corpus',
label: 'Corpus',
children: (<Skeleton/>),
},
];
return (<Tabs defaultActiveKey='range' items={items} />);
}

function Repertoire({ repertoire }: { repertoire : FakeRepertoire}) {

// the 'add' panel
const addItems = ([
{
key: 'add',
label: 'Add…',
children: (
<AddRepertoire/>
),
}
]);

return (
<>
<div className="repertoire">
{repertoire.map((s, key) => (
<RepertoireItem key={key} item={s}/>
))}
</div>
<Collapse items={addItems} />
</>
);
}

// -------- keybag -------------

/** An individual key in the keybag. */
function Key({ k, chosenKey, setChosenKey }: {
k: KMXPlus.KeysKeys,
Expand Down Expand Up @@ -118,15 +176,18 @@ function KeyBag() {
const [chosenKey, setChosenKey] = React.useState(keys[0]?.id?.value);
if (!kmxPlus) return; // get out
return (
<Card title="Key Bag" bordered={true}>
<>
<div className="keyListContainer">
<KeyList keys={keys} chosenKey={chosenKey} setChosenKey={setChosenKey} />
</div>
<KeyDetails chosenKey={chosenKey} />
</Card>
</>
);
}

// -------- layer -------------


/** row in the layer list */
function Row({ row }: {
row: KMXPlus.LayrRow,
Expand Down Expand Up @@ -188,16 +249,16 @@ function KeyLayouts() {
}
return (
<div className="keyLayouts">
<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} />))}
</Card>
</div>
);
}

// -------- err messages -------------

/** this shows any error or warning messages from the compiler */
function Messages({ messages }: { messages: string[] }) {
if (!messages) return;
Expand All @@ -208,6 +269,20 @@ function Messages({ messages }: { messages: string[] }) {
);
}

// -------- main editor -------------

/** 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[];
}

/** This is the main Keyboard File object. */
function KeyboardFile() {
const initialData: LoadedData = {
Expand Down Expand Up @@ -240,6 +315,7 @@ function KeyboardFile() {
return (
<div>
<h4>LDML Keyboard</h4>
{/* show something while waiting */}
<Skeleton />
</div>
);
Expand All @@ -254,20 +330,45 @@ function KeyboardFile() {
)
}

const items = [
{
key: 'repertoire',
label: 'Character Repertoire',
children: (<Repertoire repertoire={SAMPLE_REPERTOIRE} />),
},
{
key: 'key',
label: 'Key Bag',
children: (<KeyBag />),
},
{
key: 'layouts',
label: 'Layouts',
children: (<KeyLayouts />),
},
];

return (
<>
<Messages messages={data.messages} />
<KmxPlusContext.Provider value={data.kmxPlus}>
<KeyBag />
<KeyLayouts />
<Collapse items={items} defaultActiveKey={[
// default items visible in the editor
'repertoire',
// 'key',
// 'layouts',
]} />
</KmxPlusContext.Provider>
<hr />
<RawSource text={data?.text} />
{/*
TODO-EPIC-LDML: raw source isn't really needed.
Users can use the system editor view if needed.
<hr />
<RawSource text={data?.text} /> */}
</>
);
}

/** The outer App. TODO-EPIC-LDML this may be */
/** The outer App. TODO-EPIC-LDML this may be unneeded */
function App() {
return (
<div className="App">
Expand Down
18 changes: 18 additions & 0 deletions developer/src/vscode-plugin/app/FixedCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Visual editor app
*/

import { Card } from 'antd';
import React from 'react';

/** workaround Card TS linkage (and also make the border always set) */
export function FixedCard({ title, bordered, children } : { title: string, bordered?: boolean, children?: any }) {
return (
// TODO-EPIC-LDML: why the red squiggle on the next line?
<Card title={title} bordered={bordered}>
{children}
</Card>
);
}
31 changes: 31 additions & 0 deletions developer/src/vscode-plugin/app/RawSource.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* the Raw Source view
*/

import React from 'react';
import { Button } from 'antd';
/** Ant's Card had an import problem, so we use this workaround */
import { FixedCard as Card } from './FixedCard.js';


/** The "show raw source" panel */
export function RawSource({ text }) {
const [shown, setShown] = React.useState(false);
function show() {
setShown(true);
}
if (!text) return;
if (!shown) return (
<Card title="Raw XML Source" >
<Button type="primary" onClick={() => setShown(true)}>Show</Button>
</Card>
);
return (
<Card title="Raw XML Source" bordered={true}>
<Button type="primary" onClick={() => setShown(false)}>Hide</Button>
<pre className="code">{text}</pre>
</Card>
);
}
28 changes: 28 additions & 0 deletions developer/src/vscode-plugin/app/fakerepertoire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Placeholder for a dynamic repertoire object.
*/

export type RepertoireEntry = string;

export class FakeRepertoire implements Iterable<RepertoireEntry> {
constructor(private items: RepertoireEntry[]) {

}

// allow `for (x of repertoire)`
[Symbol.iterator](): Iterator<RepertoireEntry, any, any> {
// use the string array's iterator
return this.items[Symbol.iterator]();
}

// allow `repertoire.map`
map(callbackfn: (value: string, index: number, array: string[]) => any, thisArg?: any): any {
return this.items.map(callbackfn);
}
}

export const SAMPLE_REPERTOIRE = new FakeRepertoire([
..."abcdefghijklmnopqrstuvwxyz".split(''),
]);
9 changes: 7 additions & 2 deletions developer/src/vscode-plugin/app/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* This is top level JS code used to bootstrap the React app
*/

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// bootstrap react app

/** find the id="root" element from ./index.html */
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
Expand Down

0 comments on commit 6bd82e1

Please sign in to comment.