Skip to content

Commit f0b57ca

Browse files
t3-code[bot]juliusmarmingecodex
authored
feat(web): add Open VSX theme search (#5654)
Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 2db0845 commit f0b57ca

11 files changed

Lines changed: 2566 additions & 134 deletions

apps/web/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@formkit/auto-animate": "^0.9.0",
2424
"@legendapp/list": "catalog:",
2525
"@lexical/react": "^0.41.0",
26+
"@noble/hashes": "catalog:",
2627
"@pierre/diffs": "catalog:",
2728
"@pierre/trees": "1.0.0-beta.4",
2829
"@t3tools/client-runtime": "workspace:*",
@@ -34,6 +35,8 @@
3435
"culori": "^4.0.2",
3536
"effect": "catalog:",
3637
"jose": "catalog:",
38+
"jsonc-parser": "3.3.1",
39+
"jszip": "3.10.1",
3740
"lexical": "^0.41.0",
3841
"lucide-react": "^0.564.0",
3942
"react": "19.2.6",

apps/web/src/components/settings/ThemeEditorPanel.tsx

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ export function ThemeEditorPanel({
610610
[activeAppearance, editingTheme, selectedRole],
611611
);
612612

613-
const handleSubmit = useCallback(() => {
613+
const handleSubmit = () => {
614614
if (!name.trim()) {
615615
setError("Name your theme first.");
616616
return;
@@ -645,8 +645,8 @@ export function ThemeEditorPanel({
645645
return;
646646
}
647647
mergedAppearance = editedModes[0] ?? null;
648-
savedTheme = updateCustomTheme(
649-
parseThemeFile({
648+
savedTheme = updateCustomTheme({
649+
...parseThemeFile({
650650
version: THEME_FILE_VERSION,
651651
id: mergeTarget.id,
652652
name: mergeTarget.label,
@@ -658,7 +658,8 @@ export function ThemeEditorPanel({
658658
},
659659
...(mergeTarget.managed === true && !isAdvanced ? { managed: true } : {}),
660660
}),
661-
);
661+
...(mergeTarget.collection ? { collection: mergeTarget.collection } : {}),
662+
});
662663
retiredTheme = editingTheme;
663664
try {
664665
removeCustomTheme(editingTheme.id);
@@ -676,8 +677,8 @@ export function ThemeEditorPanel({
676677
} else if (editingTheme) {
677678
const baseAppearance = editingTheme.appearance;
678679
const variantAppearance = baseAppearance === "light" ? "dark" : "light";
679-
savedTheme = updateCustomTheme(
680-
parseThemeFile({
680+
savedTheme = updateCustomTheme({
681+
...parseThemeFile({
681682
version: THEME_FILE_VERSION,
682683
id: editingTheme.id,
683684
name,
@@ -688,7 +689,8 @@ export function ThemeEditorPanel({
688689
: {}),
689690
...(isAdvanced ? {} : { managed: true }),
690691
}),
691-
);
692+
...(editingTheme.collection ? { collection: editingTheme.collection } : {}),
693+
});
692694
} else if (mergeTarget) {
693695
if (takenAppearances.includes(activeAppearance)) {
694696
setError(
@@ -701,8 +703,8 @@ export function ThemeEditorPanel({
701703
// survives when every palette in the theme came from the guided
702704
// editor.
703705
mergedAppearance = activeAppearance;
704-
savedTheme = updateCustomTheme(
705-
parseThemeFile({
706+
savedTheme = updateCustomTheme({
707+
...parseThemeFile({
706708
version: THEME_FILE_VERSION,
707709
id: mergeTarget.id,
708710
name: mergeTarget.label,
@@ -714,7 +716,8 @@ export function ThemeEditorPanel({
714716
},
715717
...(mergeTarget.managed === true && !isAdvanced ? { managed: true } : {}),
716718
}),
717-
);
719+
...(mergeTarget.collection ? { collection: mergeTarget.collection } : {}),
720+
});
718721
} else {
719722
savedTheme = installCustomTheme(
720723
parseThemeFile({
@@ -762,19 +765,7 @@ export function ThemeEditorPanel({
762765
: "Could not create the theme.",
763766
);
764767
}
765-
}, [
766-
activeAppearance,
767-
colorsByAppearance,
768-
editingTheme,
769-
isAdvanced,
770-
isEditing,
771-
mergeTarget,
772-
name,
773-
onOpenChange,
774-
onSaved,
775-
simpleColorsDirtyByAppearance,
776-
takenAppearances,
777-
]);
768+
};
778769

779770
const renderNameField = () => (
780771
<label className="grid grid-cols-[minmax(0,1fr)_minmax(0,2fr)] items-center gap-3">

apps/web/src/components/settings/ThemeImportDialog.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
DialogPopup,
2929
DialogTitle,
3030
} from "../ui/dialog";
31+
import { ThemeSearchSection } from "./ThemeSearchSection";
3132

3233
/**
3334
* A full theme export is a few KB, so anything past this is not a theme file.
@@ -360,9 +361,16 @@ export function ThemeImportDialog({
360361
: null;
361362
for (const theme of conflicts) {
362363
try {
364+
const existingTheme =
365+
mode === "update"
366+
? getCustomThemes().find((candidate) => candidate.id === theme.id)
367+
: undefined;
368+
const themeToUpdate = existingTheme?.collection
369+
? { ...theme, collection: existingTheme.collection }
370+
: theme;
363371
resolved.push(
364372
mode === "update"
365-
? updateCustomTheme(theme)
373+
? updateCustomTheme(themeToUpdate)
366374
: installCustomTheme(versionedCopy(theme, preferredName)),
367375
);
368376
} catch (cause) {
@@ -426,7 +434,23 @@ export function ThemeImportDialog({
426434
<DialogHeader>
427435
<DialogTitle>Add a theme</DialogTitle>
428436
</DialogHeader>
429-
<DialogPanel className="space-y-4">
437+
<DialogPanel className="space-y-5">
438+
<ThemeSearchSection
439+
onInstalled={(themes, context) => {
440+
onImportedMany(themes, context);
441+
onOpenChange(false);
442+
}}
443+
open={open}
444+
/>
445+
446+
<div className="flex items-center gap-3" aria-hidden>
447+
<div className="h-px flex-1 bg-border" />
448+
<span className="text-muted-foreground text-[11px] uppercase tracking-wider">
449+
or import a file
450+
</span>
451+
<div className="h-px flex-1 bg-border" />
452+
</div>
453+
430454
{(() => {
431455
const dropHandlers = {
432456
onDragEnter: (event: DragEvent<HTMLDivElement>) => {

0 commit comments

Comments
 (0)