Skip to content

Commit 71a88c2

Browse files
committed
🦄 refactor: Refactor shortcut
1 parent f013192 commit 71a88c2

File tree

9 files changed

+91
-68
lines changed

9 files changed

+91
-68
lines changed

packages/chili-core/src/command/commandKeys.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import type { I18nKeys } from "../i18n";
55

6+
export const CommandPrefix = "command.";
7+
68
export type CommandKeys = {
7-
[P in I18nKeys]: P extends `command.${infer K}` ? K : never;
9+
[P in I18nKeys]: P extends `${typeof CommandPrefix}${infer K}` ? K : never;
810
}[I18nKeys];

packages/chili-core/src/command/decarator.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@ export class CommandUtils {
4242
}
4343

4444
static getAllCommands(): CommandData[] {
45-
const commands: CommandData[] = [];
46-
for (const ctor of commandRegistry.values()) {
47-
if (ctor.prototype.data) {
48-
commands.push(ctor.prototype.data);
49-
}
50-
}
51-
return commands;
45+
return Array.from(
46+
commandRegistry
47+
.values()
48+
.map((ctor) => ctor.prototype.data)
49+
.filter((x) => x),
50+
);
5251
}
5352
}

packages/chili-core/src/command/shortcutProfiles.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const Chili3dShortcuts: ShortcutMap = {
2020

2121
// Primitives
2222
"create.box": "b",
23-
"create.pyramid": "p",
2423
"create.sphere": "s",
2524
"create.cylinder": "y",
2625
"create.cone": "n",
@@ -30,11 +29,11 @@ export const Chili3dShortcuts: ShortcutMap = {
3029
"modify.trim": "t",
3130
"create.offset": "o",
3231
"modify.rotate": "shift+r",
33-
"create.extrude": "shift+e",
34-
"modify.move": "shift+m",
32+
"create.extrude": "p",
33+
"modify.move": "m",
3534
"modify.array": "shift+a",
3635
"boolean.common": "shift+i",
37-
"modify.explode": "shift+x",
36+
"modify.explode": "x",
3837
"modify.chamfer": "shift+c",
3938
"modify.fillet": "shift+f",
4039
};

packages/chili-core/src/i18n/i18n.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export class I18n {
7272
console.warn(`No translation for ${key} in ${language}`);
7373
return key;
7474
}
75+
return I18n.translateLanguage(language, key, ...args);
76+
}
77+
78+
static translateLanguage(language: Locale, key: I18nKeys, ...args: any[]) {
7579
let text = language.translation[key] ?? languages.get("en")!.translation[key];
7680
if (text === undefined) {
7781
console.warn(`No translation for ${key} in ${language}`);
@@ -97,14 +101,18 @@ export class I18n {
97101

98102
static changeLanguage(newLanguage: string) {
99103
if (newLanguage === _currentLanguage) return;
104+
const oldLanguage = _currentLanguage;
100105
_currentLanguage = newLanguage;
101106

102107
document.querySelectorAll(`[data-${I18nId}]`).forEach((e) => {
103108
const html = e as HTMLElement;
104109
const data = html?.dataset[I18nId]?.split(DATASET_LINK_KEY);
105110
if (data?.length !== 2) return;
106111
const args = I18nArgs.get(html) ?? [];
107-
html[data[1] as I18nPath] = I18n.translate(data[0] as I18nKeys, ...args);
112+
const [key, path] = data as [I18nKeys, I18nPath];
113+
const oldText = I18n.translateLanguage(languages.get(oldLanguage!)!, key, ...args);
114+
const newText = I18n.translate(key, ...args);
115+
html[path] = html[path].replace(oldText, newText);
108116
});
109117
}
110118
}

packages/chili-core/src/ui/button.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Part of the Chili3d Project, under the AGPL-3.0 License.
22
// See LICENSE file in the project root for full license information.
33

4+
import type { CommandKeys } from "../command";
45
import type { I18nKeys } from "../i18n";
56

67
export enum ButtonSize {
@@ -9,7 +10,8 @@ export enum ButtonSize {
910
}
1011

1112
export interface Button {
12-
display: I18nKeys;
13+
command: CommandKeys;
14+
display?: I18nKeys;
1315
icon: string;
1416
size: ButtonSize;
1517
onClick: () => void;

packages/chili-ui/src/ribbon/ribbon.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ButtonSize,
88
type CommandKeys,
99
CommandUtils,
10+
Config,
1011
I18n,
1112
type IApplication,
1213
type ICommand,
@@ -255,20 +256,30 @@ export class Ribbon extends HTMLElement {
255256
});
256257
return stack;
257258
} else {
258-
return new RibbonButton(item.display, item.icon, ButtonSize.large, item.onClick);
259+
return new RibbonButton(item.command, item.icon, ButtonSize.large, item.onClick, item.display);
259260
}
260261
}
261262

262263
connectedCallback(): void {
263264
PubSub.default.sub("openCommandContext", this.openContext);
264265
PubSub.default.sub("closeCommandContext", this.closeContext);
266+
Config.instance.onPropertyChanged(this.handleConfigChanged);
265267
}
266268

267269
disconnectedCallback(): void {
268270
PubSub.default.remove("openCommandContext", this.openContext);
269271
PubSub.default.remove("closeCommandContext", this.closeContext);
272+
Config.instance.removePropertyChanged(this.handleConfigChanged);
270273
}
271274

275+
private readonly handleConfigChanged = (prop: keyof Config) => {
276+
if (prop === "navigation3D") {
277+
this.querySelectorAll(customElements.getName(RibbonButton)!).forEach((x) => {
278+
(x as RibbonButton).updateShortcut();
279+
});
280+
}
281+
};
282+
272283
private readonly openContext = (command: ICommand) => {
273284
if (this.commandContext) {
274285
this.closeContext();

packages/chili-ui/src/ribbon/ribbonButton.ts

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,33 @@ import {
77
type CommandData,
88
type CommandKeys,
99
CommandUtils,
10-
DefaultShortcuts,
10+
Config,
1111
I18n,
1212
type I18nKeys,
1313
type IConverter,
1414
Localize,
1515
Logger,
1616
PubSub,
1717
Result,
18+
ShortcutProfiles,
1819
} from "chili-core";
1920
import style from "./ribbonButton.module.css";
2021

2122
export class RibbonButton extends HTMLElement {
22-
private observer?: MutationObserver;
23+
#shortcut?: string;
24+
get shortcut() {
25+
return this.#shortcut;
26+
}
2327

2428
constructor(
25-
display: I18nKeys,
29+
readonly commandName: CommandKeys,
2630
icon: string,
2731
size: ButtonSize,
2832
readonly onClick: () => void,
29-
shortcut?: string,
33+
display?: I18nKeys,
3034
) {
3135
super();
32-
this.initHTML(display, icon, size, shortcut);
36+
this.initHTML(display ?? `command.${commandName}`, icon, size);
3337
this.addEventListener("click", onClick);
3438
}
3539

@@ -43,26 +47,16 @@ export class RibbonButton extends HTMLElement {
4347
return new RibbonToggleButton(data, size);
4448
}
4549

46-
const shortcutData = DefaultShortcuts[commandName];
47-
const shortcut = Array.isArray(shortcutData) ? shortcutData[0] : shortcutData;
48-
49-
return new RibbonButton(
50-
`command.${data.key}`,
51-
data.icon,
52-
size,
53-
() => {
54-
PubSub.default.pub("executeCommand", commandName);
55-
},
56-
shortcut,
57-
);
50+
return new RibbonButton(data.key, data.icon, size, () => {
51+
PubSub.default.pub("executeCommand", commandName);
52+
});
5853
}
5954

6055
dispose(): void {
6156
this.removeEventListener("click", this.onClick);
62-
this.observer?.disconnect();
6357
}
6458

65-
private initHTML(display: I18nKeys, icon: string, size: ButtonSize, shortcut?: string) {
59+
private initHTML(display: I18nKeys, icon: string, size: ButtonSize) {
6660
const image = svg({ icon });
6761
this.className = size === ButtonSize.large ? style.normal : style.small;
6862
image.classList.add(size === ButtonSize.large ? style.icon : style.smallIcon);
@@ -72,26 +66,26 @@ export class RibbonButton extends HTMLElement {
7266
});
7367

7468
I18n.set(this, "title", display);
69+
this.updateShortcut();
70+
71+
this.append(image, text);
72+
}
73+
74+
updateShortcut() {
75+
const shortcutData = ShortcutProfiles[Config.instance.navigation3D][this.commandName];
76+
const shortcut = Array.isArray(shortcutData) ? shortcutData.join("; ") : shortcutData;
7577

7678
if (shortcut) {
77-
const updateTitle = () => {
78-
const current = this.getAttribute("title");
79-
if (current && !current.includes(`(${shortcut})`)) {
80-
this.setAttribute("title", `${current} (${shortcut})`);
81-
}
82-
};
83-
updateTitle();
84-
this.observer = new MutationObserver((mutations) => {
85-
for (const m of mutations) {
86-
if (m.type === "attributes" && m.attributeName === "title") {
87-
updateTitle();
88-
}
89-
}
90-
});
91-
this.observer.observe(this, { attributes: true });
79+
if (this.#shortcut) {
80+
this.title = this.title.replace(this.#shortcut, shortcut);
81+
} else {
82+
this.title += ` (${shortcut})`;
83+
}
84+
this.#shortcut = shortcut;
85+
} else if (this.#shortcut) {
86+
this.title = this.title.replace(this.#shortcut, "");
87+
this.#shortcut = undefined;
9288
}
93-
94-
this.append(image, text);
9589
}
9690
}
9791

@@ -109,7 +103,7 @@ class ToggleConverter implements IConverter {
109103

110104
export class RibbonToggleButton extends RibbonButton {
111105
constructor(data: CommandData, size: ButtonSize) {
112-
super(`command.${data.key}`, data.icon, size, () => {
106+
super(data.key, data.icon, size, () => {
113107
PubSub.default.pub("executeCommand", data.key);
114108
});
115109

packages/chili/src/services/commandService.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
type CommandKeys,
66
CommandUtils,
77
type IApplication,
8-
ICommand,
98
type IService,
109
type IView,
1110
isCancelableCommand,

packages/chili/src/services/hotkeyService.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface HotkeyMap {
2323
}
2424

2525
export class HotkeyService implements IService {
26+
protected keys: string[] = [];
2627
private app?: IApplication;
2728
private readonly _keyMap = new Map<string, CommandKeys>();
2829

@@ -52,19 +53,25 @@ export class HotkeyService implements IService {
5253
}
5354

5455
start(): void {
56+
PubSub.default.sub("executeCommand", this.executeCommand);
5557
window.addEventListener("keydown", this.eventHandlerKeyDown);
5658
window.addEventListener("keydown", this.commandKeyDown);
5759
Config.instance.onPropertyChanged(this.handleConfigChanged);
5860
Logger.info(`${HotkeyService.name} started`);
5961
}
6062

6163
stop(): void {
64+
PubSub.default.remove("executeCommand", this.executeCommand);
6265
window.removeEventListener("keydown", this.eventHandlerKeyDown);
6366
window.removeEventListener("keydown", this.commandKeyDown);
6467
Config.instance.removePropertyChanged(this.handleConfigChanged);
6568
Logger.info(`${HotkeyService.name} stoped`);
6669
}
6770

71+
private readonly executeCommand = (_commandName: CommandKeys) => {
72+
this.keys = [];
73+
};
74+
6875
private readonly handleConfigChanged = (prop: keyof Config) => {
6976
if (prop === "navigation3D") {
7077
this.loadProfile();
@@ -114,22 +121,24 @@ export class HotkeyService implements IService {
114121
}
115122
};
116123

117-
getKey(keys: Keys): string {
118-
let key = keys.key;
119-
if (keys.ctrlKey) key = "ctrl+" + key;
120-
if (keys.shiftKey) key = "shift+" + key;
121-
if (keys.altKey) key = "alt+" + key;
122-
return key;
123-
}
124-
125-
map(command: CommandKeys, keys: Keys) {
126-
const key = this.getKey(keys);
127-
this._keyMap.set(key, command);
128-
}
129-
130124
getCommand(keys: Keys): CommandKeys | undefined {
131-
const key = this.getKey(keys);
132-
return this._keyMap.get(key);
125+
const maxKeyLength = 20;
126+
const totleLength = this.keys.length + keys.key.length;
127+
if (totleLength > maxKeyLength) {
128+
this.keys = this.keys.slice(totleLength - maxKeyLength);
129+
}
130+
this.keys.push(keys.key);
131+
132+
for (let i = 0; i < this.keys.length; i++) {
133+
let key = this.keys.slice(i).join("+");
134+
if (keys.ctrlKey) key = "ctrl+" + key;
135+
if (keys.shiftKey) key = "shift+" + key;
136+
if (keys.altKey) key = "alt+" + key;
137+
if (this._keyMap.has(key)) {
138+
return this._keyMap.get(key);
139+
}
140+
}
141+
return undefined;
133142
}
134143

135144
addMap(map: HotkeyMap) {

0 commit comments

Comments
 (0)