Added ablity to export/import themes

This commit is contained in:
WardPearce
2026-04-09 01:38:56 +12:00
parent c06b942a6b
commit 12206bd15f
7 changed files with 110 additions and 20 deletions
@@ -6,10 +6,21 @@
import { importSubscriptions } from '$lib/importExport';
import { postSubscribeInvidious } from '$lib/api/invidious/subscribe';
import { getSubscriptionsInvidious } from '$lib/api/invidious/feed';
import { backendInUseStore, invidiousAuthStore, invidiousInstanceStore } from '$lib/store';
import {
backendInUseStore,
interfaceAdvancedThemingStore,
interfaceBorderRadiusStore,
invidiousAuthStore,
invidiousInstanceStore
} from '$lib/store';
import { Clipboard } from '@capacitor/clipboard';
import { loadSettingsFromFile, settingsToJson } from '$lib/externalSettings';
import {
bookmarkletSaveToUrl,
loadSettingsFromFile,
settingsToJson
} from '$lib/externalSettings';
import { downloadStringAsFile } from '$lib/misc';
import { zNumber, zThemeColors } from '$lib/externalSettings/settings';
async function importInvidiousSubs() {
const importedSubs = await getSubscriptionsInvidious();
@@ -179,3 +190,79 @@
type="file"
/>
</div>
<h3>{$_('layout.theme.theme')}</h3>
<div class="space"></div>
<button
class="no-margin surface-container-highest"
onclick={async () => {
await Clipboard.write({
string: bookmarkletSaveToUrl([
{
name: 'advancedTheming',
store: interfaceAdvancedThemingStore,
schema: zThemeColors,
serialize: JSON.stringify
},
{
name: 'borderRadius',
store: interfaceBorderRadiusStore,
schema: zNumber
}
])
});
addToast({
data: {
text: $_('player.share.copiedSuccess')
}
});
}}
>
<i>content_copy</i>
<span>{$_('layout.export.exportToShareUrl')}</span>
</button>
<div class="space"></div>
<button
class="no-margin surface-container-highest"
onclick={async () => {
downloadStringAsFile(
settingsToJson([
{
name: 'advancedTheming',
store: interfaceAdvancedThemingStore,
schema: zThemeColors,
serialize: JSON.stringify
},
{
name: 'borderRadius',
store: interfaceBorderRadiusStore,
schema: zNumber
}
]),
'materialious-theme.json'
);
}}
>
<i>file_export</i>
<span>{$_('layout.export.exportToFile')}</span>
</button>
<div class="space"></div>
<div>
<button class="surface-container-highest">
<i>attach_file</i>
<span>{$_('layout.export.importFromFile')}</span>
</button>
<input
onchange={async (event: Event) => {
const files = (event.target as HTMLInputElement).files;
if (files?.length === 0 || !files) return;
await loadSettingsFromFile(files[0]);
}}
accept=".json"
type="file"
/>
</div>
@@ -2,7 +2,7 @@ import { page } from '$app/state';
import { get } from 'svelte/store';
import { z } from 'zod';
import { persistedStores } from './settings';
import { persistedStores, type PersistedStore } from './settings';
import { isOwnBackend } from '$lib/shared';
import { addOrUpdateKeyValue, getKeyValue } from '$lib/api/backend/keyvalue';
@@ -121,10 +121,10 @@ export function loadSettingsFromEnv() {
}
}
export function bookmarkletSaveToUrl(): string {
export function bookmarkletSaveToUrl(stores: PersistedStore<any>[] = persistedStores): string {
const url = new URL(location.origin);
for (const { name, store, serialize, excludeFromBookmarklet } of persistedStores) {
for (const { name, store, serialize, excludeFromBookmarklet } of stores) {
const value = get(store);
const encoded = serialize ? serialize(value) : value?.toString();
@@ -136,10 +136,10 @@ export function bookmarkletSaveToUrl(): string {
return url.toString();
}
export function settingsToJson(): string {
export function settingsToJson(stores: PersistedStore<any>[] = persistedStores): string {
const settings: Record<string, string> = {};
for (const { name, store, excludeFromBookmarklet } of persistedStores) {
for (const { name, store, excludeFromBookmarklet } of stores) {
const value = get(store);
if (!excludeFromBookmarklet) {
settings[name] = value;
@@ -60,7 +60,7 @@ import {
import { isOwnBackend } from '$lib/shared';
import { SUPPORTED_THEME_KEYS } from '$lib/shared/theme';
type PersistedStore<T> = {
export type PersistedStore<T> = {
name: string;
store: Writable<T>;
schema: z.ZodType<T>;
@@ -69,18 +69,18 @@ type PersistedStore<T> = {
excludeFromBackendSync?: boolean; // Won't be sync'd to account cloud
};
const zBoolean = z.coerce.boolean();
const zNumber = z.coerce.number();
const zFloat = z.coerce.number().min(0).max(1);
const zString = z.string();
const zArray = z.array(z.string());
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
const zAuth = z.object({
export const zBoolean = z.coerce.boolean();
export const zNumber = z.coerce.number();
export const zFloat = z.coerce.number().min(0).max(1);
export const zString = z.string();
export const zArray = z.array(z.string());
export const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
export const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
export const zAuth = z.object({
username: z.string(),
token: z.string()
});
const zThemeColors = z.record(
export const zThemeColors = z.record(
z.string().refine((val) => SUPPORTED_THEME_KEYS.includes(val), {
message: 'CSS property not allowed'
}),
+2 -1
View File
@@ -295,7 +295,8 @@
"exportSub": "Export subscriptions",
"exportToClipboard": "Export to clipboard",
"exportToFile": "Export to file",
"importFromFile": "Import from file"
"importFromFile": "Import from file",
"exportToShareUrl": "Export to share URL"
},
"deArrow": {
"title": "DeArrow",
+1 -1
View File
@@ -221,7 +221,7 @@ export async function exportSubscriptionsAsFile(exportType: 'InvidiousJSON' | 'O
if (data) {
downloadStringAsFile(
data,
`materialious-export-${new Date().toDateString().replaceAll(' ', '')}.${exportType === 'InvidiousJSON' ? 'json' : 'opml'}`
`materialious-export.${exportType === 'InvidiousJSON' ? 'json' : 'opml'}`
);
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ export function downloadStringAsFile(content: string, filename: string) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.download = `${new Date().toDateString().replaceAll(' ', '')}-${filename}`;
document.body.appendChild(a);
a.click();
+2
View File
@@ -133,6 +133,8 @@
// Should always be loaded after env settings
// So user preferences overwrite instance preferences.
bookmarkletLoadFromUrl();
setThemeColors($interfaceAdvancedThemingStore);
});
let syncToSettingsInitialized = false;