Skip to content

Commit

Permalink
🦄 refactor: Increase the readability of the UI module
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangechen committed Oct 28, 2023
1 parent 85a5c0b commit ce01869
Show file tree
Hide file tree
Showing 32 changed files with 474 additions and 602 deletions.
24 changes: 22 additions & 2 deletions packages/chili-core/src/base/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export class ObservableCollection<T> implements ICollectionChanged, IDisposable
#callbacks: Set<(args: CollectionChangedArgs) => void> = new Set();
#items: T[];

constructor(items?: readonly T[]) {
this.#items = items ? [...items] : [];
constructor(...items: T[]) {
this.#items = [...items];
}

add(...items: T[]) {
Expand Down Expand Up @@ -106,14 +106,34 @@ export class ObservableCollection<T> implements ICollectionChanged, IDisposable
}
}

forEach(callback: (item: T, index: number) => void) {
this.items.forEach(callback);
}

map(callback: (item: T, index: number) => any) {
return this.items.map(callback);
}

get items() {
return [...this.#items];
}

[Symbol.iterator]() {
return this.items[Symbol.iterator]();
}

item(index: number) {
return this.#items[index];
}

at(index: number) {
return this.#items.at(index);
}

indexOf(item: T, fromIndex: number | undefined) {
return this.#items.indexOf(item, fromIndex);
}

contains(item: T) {
return this.#items.indexOf(item) !== -1;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/chili-core/src/converter/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { Result } from "../base";

export interface IConverter<TFrom = any, TTo = string> {
convert(value: TFrom): Result<TTo>;
convertBack(value: TTo): Result<TFrom>;
convertBack?(value: TTo): Result<TFrom>;
}
15 changes: 9 additions & 6 deletions packages/chili-core/src/utils/debounce.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

export function debounce(fun: Function, ms: number): Function {
let timeout: any;
return function (this: any, ...args: any[]) {
export const debounce = <F extends (...args: any[]) => void, P extends Parameters<F>>(
fun: F,
ms: number,
) => {
let timeout: number | undefined;
return (...args: P) => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
fun.apply(this, args);
timeout = window.setTimeout(() => {
fun(...args);
timeout = undefined;
}, ms);
};
}
};
6 changes: 3 additions & 3 deletions packages/chili-core/test/collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("ObservableCollection test", () => {
});

test("test remove", () => {
let collection = new ObservableCollection<number>([1, 2, 3]);
let collection = new ObservableCollection<number>(1, 2, 3);
collection.onCollectionChanged((arg: CollectionChangedArgs) => {
if (arg.action === CollectionAction.remove) {
expect(arg.items).toStrictEqual([1, 3]);
Expand All @@ -24,7 +24,7 @@ describe("ObservableCollection test", () => {
});

test("test move", () => {
let collection = new ObservableCollection<number>([1, 2, 3]);
let collection = new ObservableCollection<number>(1, 2, 3);
collection.onCollectionChanged((arg: CollectionChangedArgs) => {
if (arg.action === CollectionAction.move) {
expect(collection.items).toStrictEqual([2, 1, 3]);
Expand All @@ -37,7 +37,7 @@ describe("ObservableCollection test", () => {
});

test("test replace", () => {
let collection = new ObservableCollection<number>([1, 2, 3]);
let collection = new ObservableCollection<number>(1, 2, 3);
collection.onCollectionChanged((arg: CollectionChangedArgs) => {
if (arg.action === CollectionAction.replace) {
expect(collection.items).toStrictEqual([1, 3, 2, 3]);
Expand Down
4 changes: 2 additions & 2 deletions packages/chili-three/src/threeViewEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const MIDDLE = 4;

export class ThreeViewHandler implements IEventHandler {
private _lastDown: MouseDownData | undefined;
private _clearDownId: any;
private _clearDownId: number | undefined;

dispose() {}

Expand Down Expand Up @@ -60,7 +60,7 @@ export class ThreeViewHandler implements IEventHandler {

pointerUp(view: IView, event: PointerEvent): void {
if (event.buttons === MIDDLE && this._lastDown) {
this._clearDownId = setTimeout(() => {
this._clearDownId = window.setTimeout(() => {
this._lastDown = undefined;
this._clearDownId = undefined;
}, 500);
Expand Down
19 changes: 13 additions & 6 deletions packages/chili-ui/src/components/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,31 @@ import { CollectionAction, CollectionChangedArgs, ObservableCollection } from "c
import { Props, setProps } from "../controls";

export interface ItemsProps extends Props {
sources: ObservableCollection<any>;
template: (item: any) => HTMLDivElement;
sources: ObservableCollection<any> | Array<any>;
template: (item: any) => HTMLElement | SVGSVGElement;
}

export class ItemsElement extends HTMLElement {
#itemMap = new Map<any, HTMLElement>();
#itemMap = new Map<any, HTMLElement | SVGSVGElement>();
constructor(readonly props: ItemsProps) {
super();
setProps(props, this);
this.append(...this.#mapItems(props.sources.items));
const items = Array.isArray(props.sources) ? props.sources : props.sources.items;
this.append(...this.#mapItems(items));
}

getItem(item: any): HTMLElement | SVGSVGElement | undefined {
return this.#itemMap.get(item);
}

connectedCallback() {
this.props.sources.onCollectionChanged(this.#onCollectionChanged);
if (this.props.sources instanceof ObservableCollection)
this.props.sources.onCollectionChanged(this.#onCollectionChanged);
}

disconnectedCallback() {
this.props.sources.removeCollectionChanged(this.#onCollectionChanged);
if (this.props.sources instanceof ObservableCollection)
this.props.sources.removeCollectionChanged(this.#onCollectionChanged);
}

#onCollectionChanged = (args: CollectionChangedArgs) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/chili-ui/src/controls/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Binding } from "./binding";
export abstract class BindableElement extends HTMLElement implements IDisposable {
readonly #bindings: Binding<any>[] = [];

bind<T extends IPropertyChanged>(dataContext: T, path: keyof T, converter?: IConverter) {
bind<T extends IPropertyChanged>(dataContext: T, path: keyof T, converter?: IConverter): Binding {
let binding = new Binding(dataContext, path, converter);
this.#bindings.push(binding);
return binding;
return binding as any;
}

connectedCallback() {
Expand Down
11 changes: 9 additions & 2 deletions packages/chili-ui/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

import { CommandKeys } from "chili-core";
import { div } from "./controls";
import style from "./editor.module.css";
import { ProjectView } from "./project";
import { PropertyView } from "./property";
import { Ribbon } from "./ribbon";
import { Ribbon, RibbonDataContent } from "./ribbon";
import { Statusbar } from "./statusbar";
import { Viewport } from "./viewport";
import { RibbonTabData } from "./ribbon/ribbonData";
import { DefaultRibbon } from "./profile/ribbon";

let quickCommands: CommandKeys[] = ["doc.save", "edit.undo", "edit.redo"];
let ribbonTabs = DefaultRibbon.map((p) => RibbonTabData.fromProfile(p));
let content = new RibbonDataContent(quickCommands, ribbonTabs);

export const Editor = () =>
div(
{ className: style.panel },
new Ribbon(),
new Ribbon(content),
div(
{ className: style.content },
div(
Expand Down
4 changes: 2 additions & 2 deletions packages/chili-ui/src/home/home.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
display: flex;
flex-direction: column;

& .button {
& button {
display: inline-block;
height: 48px;
font-size: 16px;
Expand All @@ -44,7 +44,7 @@
flex-direction: column;
color: var(--foreground-color);

& .link {
& a {
display: inline-block;
font-size: 16px;
margin: 0px 32px;
Expand Down
17 changes: 5 additions & 12 deletions packages/chili-ui/src/home/home.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

import { ObservableCollection, PubSub, RecentDocumentDTO } from "chili-core";
import { Constants, IApplication, PubSub, RecentDocumentDTO } from "chili-core";
import { LanguageSelector } from "../components";
import { a, button, div, img, items, label, localize, span } from "../controls";
import style from "./home.module.css";

export interface HomeOption {
documents: ObservableCollection<RecentDocumentDTO>;
onDocumentClick: (document: RecentDocumentDTO) => void;
}
export const Home = async (app: IApplication) => {
let documents = await app.storage.page(Constants.DBName, Constants.RecentTable, 0);

export const Home = (options: HomeOption) => {
return div(
{ className: style.root },
div(
{ className: style.left },
div(
{ className: style.top },
button({
className: style.button,
textContent: localize("command.document.new"),
onclick: () => PubSub.default.pub("executeCommand", "doc.new"),
}),
button({
className: style.button,
textContent: localize("command.document.open"),
onclick: () => PubSub.default.pub("executeCommand", "doc.open"),
}),
),
div(
{ className: style.bottom },
a({
className: style.link,
textContent: "Github",
href: "https://github.com/xiangechen/chili3d",
target: "_blank",
}),
a({
className: style.link,
textContent: "Gitee",
href: "https://gitee.com/chenxiange/chili3d",
target: "_blank",
Expand All @@ -50,12 +43,12 @@ export const Home = (options: HomeOption) => {
div({ className: style.recent, textContent: localize("home.recent") }),
items({
className: style.documents,
sources: options.documents,
sources: documents,
template: (item: RecentDocumentDTO) =>
div(
{
className: style.document,
onclick: () => options.onDocumentClick(item),
onclick: () => app.openDocument(item.id),
},
img({ className: style.img, src: item.image }),
div(
Expand Down
85 changes: 24 additions & 61 deletions packages/chili-ui/src/mainWindow.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,51 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

import {
Constants,
IApplication,
Lazy,
Observable,
ObservableCollection,
PubSub,
RecentDocumentDTO,
} from "chili-core";
import { IApplication, PubSub, debounce } from "chili-core";
import { Dialog } from "./dialog";
import { Editor } from "./editor";
import { Home } from "./home";
import { Toast } from "./toast";
import { Dialog } from "./dialog";

document.oncontextmenu = (e) => e.preventDefault();

class MainWindowViewModel extends Observable {
private _displayHome: boolean = true;
get displayHome() {
return this._displayHome;
}
set displayHome(value: boolean) {
this.setProperty("displayHome", value);
}

constructor() {
super();
PubSub.default.sub("activeDocumentChanged", () => (this.displayHome = false));
PubSub.default.sub("showHome", () => (this.displayHome = true));
}
}

export class MainWindow {
static readonly #lazy = new Lazy(() => new MainWindow());
static readonly #instance = new MainWindow();
static get instance() {
return this.#lazy.value;
return this.#instance;
}

#app?: IApplication;
#home: HTMLElement;
#editor: HTMLElement;
#toast: Toast;
readonly #vm: MainWindowViewModel = new MainWindowViewModel();
readonly #documents = new ObservableCollection<RecentDocumentDTO>();
#home?: HTMLElement;

private constructor() {
this.#home = Home({ documents: this.#documents, onDocumentClick: this.onDocumentClick });
this.#editor = Editor();
this.#toast = new Toast();
}

async init(app: IApplication, root: HTMLElement) {
this.#app = app;
this.setTheme("light");
root.append(this.#home, this.#editor, this.#toast);
document.body.append(Editor());
}

this.#vm.onPropertyChanged(this.onPropertyChanged);
this.setHomeDisplay();
PubSub.default.sub("showToast", this.#toast.show);
async init(app: IApplication) {
this._initHome(app);
const displayHome = debounce(this.displayHome, 100);
PubSub.default.sub("showToast", Toast.show);
PubSub.default.sub("showDialog", Dialog.show);
PubSub.default.sub("activeDocumentChanged", (doc) => displayHome(app, doc === undefined));
PubSub.default.sub("showHome", () => displayHome(app, true));
}

private onDocumentClick = (document: RecentDocumentDTO) => {
this.#app?.openDocument(document.id);
};

private onPropertyChanged = (p: keyof MainWindowViewModel) => {
if (p === "displayHome") {
this.setHomeDisplay();
private displayHome = (app: IApplication, displayHome: boolean) => {
if (this.#home) {
this.#home.remove();
this.#home = undefined;
}
if (displayHome) {
this._initHome(app);
}
};

private async setHomeDisplay() {
this.#home.style.display = this.#vm.displayHome ? "" : "none";
if (this.#vm.displayHome && this.#app) {
this.#documents.clear();
let datas = await this.#app.storage.page(Constants.DBName, Constants.RecentTable, 0);
this.#documents.add(...datas);
}
private async _initHome(app: IApplication) {
this.#home = await Home(app);
document.body.append(this.#home);
}

setTheme(theme: "light" | "dark") {
let doc = document.documentElement;
doc.setAttribute("theme", theme);
document.documentElement.setAttribute("theme", theme);
}
}
Loading

0 comments on commit ce01869

Please sign in to comment.