Skip to content

Commit 874dcfd

Browse files
authored
feat: add command to change app and editor theme from command palette (#1197)
* fix: Palette not opening when called from existing palette The palette wasn't opening when triggered from an already open palette. * feat: Add command to change editor and app theme via command palette - Added functionality to change editor and app themes directly from the command palette. - Improved user experience for keyboard-centric users by allowing theme changes without visiting the theme settings. - Included visual indication for the currently selected theme
1 parent 05f8680 commit 874dcfd

File tree

5 files changed

+159
-12
lines changed

5 files changed

+159
-12
lines changed

src/ace/commands.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ const commands = [
325325
},
326326
readOnly: true,
327327
},
328+
{
329+
name: "changeAppTheme",
330+
description: "Change App Theme",
331+
exec() {
332+
acode.exec("change-app-theme");
333+
},
334+
readOnly: true,
335+
},
336+
{
337+
name: "changeEditorTheme",
338+
description: "Change Editor Theme",
339+
exec() {
340+
acode.exec("change-editor-theme");
341+
},
342+
readOnly: true,
343+
},
328344
];
329345

330346
export function setCommands(editor) {

src/components/palette/index.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,18 @@ This shows that using keyboardHideStart event is faster than not using it.
4545
* @param {()=>string} onsSelectCb Callback to call when a hint is selected
4646
* @param {string} placeholder Placeholder for input
4747
* @param {function} onremove Callback to call when palette is removed
48+
* @param {function} onHighlight Callback to call when hint is highlighted
4849
* @returns {void}
4950
*/
50-
export default function palette(getList, onsSelectCb, placeholder, onremove) {
51+
export default function palette(
52+
getList,
53+
onsSelectCb,
54+
placeholder,
55+
onremove,
56+
onHighlight,
57+
) {
58+
let isRemoving = false;
59+
5160
/**@type {HTMLInputElement} */
5261
const $input = (
5362
<input
@@ -63,16 +72,16 @@ export default function palette(getList, onsSelectCb, placeholder, onremove) {
6372
const $palette = <div id="palette">{$input}</div>;
6473

6574
// Create a palette with input and hints
66-
inputhints($input, generateHints, onSelect);
75+
inputhints($input, generateHints, onSelect, onHighlight);
6776

6877
// Removes the darkened color from status bar and navigation bar
6978
restoreTheme(true);
7079

7180
// Remove palette when input is blurred
72-
$input.addEventListener("blur", remove);
81+
$input.addEventListener("blur", handleBlur);
7382
// Don't wait for input to blur when keyboard hides, remove is
7483
// as soon as keyboard starts to hide
75-
keyboardHandler.on("keyboardHideStart", remove);
84+
keyboardHandler.on("keyboardHideStart", handleKeyboardHide);
7685

7786
// Add to DOM
7887
app.append($palette, $mask);
@@ -91,8 +100,23 @@ export default function palette(getList, onsSelectCb, placeholder, onremove) {
91100
* @param {string} value
92101
*/
93102
function onSelect(value) {
94-
onsSelectCb(value);
103+
isRemoving = true;
95104
remove();
105+
setTimeout(() => {
106+
onsSelectCb(value);
107+
}, 0);
108+
}
109+
110+
function handleBlur() {
111+
if (!isRemoving) {
112+
remove();
113+
}
114+
}
115+
116+
function handleKeyboardHide() {
117+
if (!isRemoving) {
118+
remove();
119+
}
96120
}
97121

98122
/**
@@ -119,9 +143,12 @@ export default function palette(getList, onsSelectCb, placeholder, onremove) {
119143
* Removes the palette
120144
*/
121145
function remove() {
146+
if (isRemoving) return;
147+
isRemoving = true;
148+
122149
actionStack.remove("palette");
123-
keyboardHandler.off("keyboardHideStart", remove);
124-
$input.removeEventListener("blur", remove);
150+
keyboardHandler.off("keyboardHideStart", handleKeyboardHide);
151+
$input.removeEventListener("blur", handleBlur);
125152

126153
restoreTheme();
127154
$palette.remove();
@@ -133,12 +160,8 @@ export default function palette(getList, onsSelectCb, placeholder, onremove) {
133160
}
134161

135162
const { activeFile, editor } = editorManager;
136-
if (activeFile.wasFocused) {
163+
if (activeFile?.wasFocused) {
137164
editor.focus();
138165
}
139-
140-
remove = () => {
141-
window.log("warn", "Palette already removed.");
142-
};
143166
}
144167
}

src/lib/commands.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import plugins from "pages/plugins";
1111
import Problems from "pages/problems/problems";
1212
import changeEncoding from "palettes/changeEncoding";
1313
import changeMode from "palettes/changeMode";
14+
import changeTheme from "palettes/changeTheme";
1415
import commandPalette from "palettes/commandPalette";
1516
import findFile from "palettes/findFile";
1617
import browser from "plugins/browser";
@@ -281,6 +282,12 @@ export default {
281282
syntax() {
282283
changeMode();
283284
},
285+
"change-app-theme"() {
286+
changeTheme("app");
287+
},
288+
"change-editor-theme"() {
289+
changeTheme("editor");
290+
},
284291
"toggle-fullscreen"() {
285292
app.classList.toggle("fullscreen-mode");
286293
this["resize-editor"]();

src/palettes/changeTheme/index.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import "./style.scss";
2+
import palette from "components/palette";
3+
import appSettings from "lib/settings";
4+
import themes from "theme/list";
5+
6+
export default function changeTheme(type = "editor") {
7+
palette(
8+
() => generateHints(type),
9+
(value) => onselect(value),
10+
strings[type === "editor" ? "editor theme" : "app theme"],
11+
undefined,
12+
(value) => onselect(value),
13+
);
14+
}
15+
16+
function generateHints(type) {
17+
if (type === "editor") {
18+
const themeList = ace.require("ace/ext/themelist");
19+
const currentTheme = appSettings.value.editorTheme;
20+
const themePrefix = "ace/theme/";
21+
22+
return themeList.themes.map((theme) => {
23+
const isCurrent =
24+
theme.theme ===
25+
(currentTheme.startsWith(themePrefix)
26+
? currentTheme
27+
: themePrefix + currentTheme);
28+
29+
return {
30+
value: JSON.stringify({ type: "editor", theme: theme.theme }),
31+
text: `<div class="theme-item">
32+
<span>${theme.caption}</span>
33+
${isCurrent ? '<span class="current">current</span>' : ""}
34+
</div>`,
35+
};
36+
});
37+
}
38+
39+
// App themes
40+
const currentTheme = appSettings.value.appTheme;
41+
const availableThemes = themes
42+
.list()
43+
.filter((theme) => !(theme.version === "paid" && IS_FREE_VERSION));
44+
45+
return availableThemes.map((theme) => {
46+
const isCurrent = theme.id === currentTheme;
47+
48+
return {
49+
value: JSON.stringify({
50+
type: "app",
51+
theme: theme.id,
52+
}),
53+
text: `<div class="theme-item">
54+
<span>${theme.name}</span>
55+
${isCurrent ? '<span class="current">current</span>' : ""}
56+
</div>`,
57+
};
58+
});
59+
}
60+
61+
function onselect(value) {
62+
if (!value) return;
63+
64+
const selection = JSON.parse(value);
65+
66+
if (selection.type === "editor") {
67+
editorManager.editor.setTheme(selection.theme);
68+
appSettings.update(
69+
{
70+
editorTheme: selection.theme,
71+
},
72+
false,
73+
);
74+
} else {
75+
if (selection.theme === "custom") {
76+
CustomTheme();
77+
return;
78+
}
79+
themes.apply(selection.theme, true);
80+
}
81+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.theme-item {
2+
display: flex;
3+
align-items: center;
4+
justify-content: space-between;
5+
padding: 4px;
6+
width: 100%;
7+
8+
span {
9+
font-size: 1rem;
10+
}
11+
12+
.current {
13+
color: var(--error-text-color);
14+
background-color: var(--primary-color);
15+
border-radius: 5px;
16+
padding: 2px 6px;
17+
font-size: 0.8rem;
18+
margin-left: 8px;
19+
}
20+
}

0 commit comments

Comments
 (0)