mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat(settings): add background selector
This commit is contained in:
@@ -76,6 +76,7 @@
|
|||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"dompurify": "^3.0.8",
|
"dompurify": "^3.0.8",
|
||||||
"framer-motion": "^11.0.25",
|
"framer-motion": "^11.0.25",
|
||||||
|
"idb": "^8.0.0",
|
||||||
"kolorist": "^1.8.0",
|
"kolorist": "^1.8.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -30,10 +30,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
|
||||||
"resources": ["seqta/ui/background/background.html"],
|
|
||||||
"matches": ["*://*/*"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resources": ["*://*/*"],
|
"resources": ["*://*/*"],
|
||||||
"matches": ["*://*/*"]
|
"matches": ["*://*/*"]
|
||||||
|
|||||||
@@ -1,13 +1,104 @@
|
|||||||
import browser from 'webextension-polyfill';
|
import { getDataById, isIndexedDBSupported } from '@/svelte-interface/hooks/BackgroundDataLoader';
|
||||||
|
|
||||||
export async function appendBackgroundToUI() {
|
export async function appendBackgroundToUI() {
|
||||||
const parent = document.getElementById('container');
|
const parent = document.getElementById('container');
|
||||||
|
|
||||||
// embed background.html
|
// embed background.html - old method
|
||||||
const background = document.createElement('iframe');
|
/* const background = document.createElement('iframe');
|
||||||
background.id = 'background';
|
background.id = 'background';
|
||||||
background.classList.add('imageBackground');
|
background.classList.add('imageBackground');
|
||||||
background.setAttribute('excludeDarkCheck', 'true');
|
background.setAttribute('excludeDarkCheck', 'true');
|
||||||
background.src = browser.runtime.getURL('seqta/ui/background/background.html');
|
background.src = browser.runtime.getURL('seqta/ui/background/background.html');
|
||||||
parent!.appendChild(background);
|
parent!.appendChild(background); */
|
||||||
|
if (!parent) return;
|
||||||
|
|
||||||
|
const backgroundContainer = document.createElement('div');
|
||||||
|
backgroundContainer.classList.add('imageBackground');
|
||||||
|
backgroundContainer.setAttribute('excludeDarkCheck', 'true');
|
||||||
|
|
||||||
|
const mediaContainer = document.createElement('div');
|
||||||
|
mediaContainer.id = 'media-container';
|
||||||
|
backgroundContainer.appendChild(mediaContainer);
|
||||||
|
|
||||||
|
parent.appendChild(backgroundContainer);
|
||||||
|
|
||||||
|
// Add styles
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#media-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#media-container video, #media-container img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Load and display the background
|
||||||
|
await loadBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadBackground() {
|
||||||
|
if (!isIndexedDBSupported()) {
|
||||||
|
console.error("IndexedDB is not supported. Unable to load background.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selectedBackgroundId = localStorage.getItem('selectedBackground');
|
||||||
|
if (!selectedBackgroundId) {
|
||||||
|
const backgroundContainer = document.querySelector('.imageBackground');
|
||||||
|
if (backgroundContainer) {
|
||||||
|
backgroundContainer.remove();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const background = await getDataById(selectedBackgroundId);
|
||||||
|
if (!background) return;
|
||||||
|
|
||||||
|
let backgroundContainer = document.querySelector('.imageBackground');
|
||||||
|
if (!backgroundContainer) {
|
||||||
|
backgroundContainer = document.createElement('div');
|
||||||
|
backgroundContainer.classList.add('imageBackground');
|
||||||
|
backgroundContainer.setAttribute('excludeDarkCheck', 'true');
|
||||||
|
const parent = document.getElementById('container');
|
||||||
|
if (parent) {
|
||||||
|
parent.appendChild(backgroundContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaContainer = document.getElementById('media-container');
|
||||||
|
if (!mediaContainer) {
|
||||||
|
mediaContainer = document.createElement('div');
|
||||||
|
mediaContainer.id = 'media-container';
|
||||||
|
backgroundContainer.appendChild(mediaContainer);
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaContainer = document.getElementById('media-container');
|
||||||
|
if (!mediaContainer) return;
|
||||||
|
|
||||||
|
mediaContainer.innerHTML = '';
|
||||||
|
|
||||||
|
const mediaElement = background.type === 'video'
|
||||||
|
? document.createElement('video')
|
||||||
|
: document.createElement('img');
|
||||||
|
|
||||||
|
mediaElement.src = URL.createObjectURL(background.blob);
|
||||||
|
mediaElement.classList.add('background');
|
||||||
|
|
||||||
|
if (mediaElement instanceof HTMLVideoElement) {
|
||||||
|
mediaElement.loop = true;
|
||||||
|
mediaElement.muted = true;
|
||||||
|
mediaElement.autoplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaContainer.appendChild(mediaElement);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading background:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Background Fetcher</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
video, img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Container for the media -->
|
|
||||||
<div id="media-container"></div>
|
|
||||||
|
|
||||||
<script type="module" src="./background.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
interface Data {
|
|
||||||
blob: Blob;
|
|
||||||
type: 'image' | 'video';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatabaseEventTarget extends EventTarget {
|
|
||||||
result: IDBDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatabaseEvent extends Event {
|
|
||||||
target: DatabaseEventTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
const openDB = (): Promise<IDBDatabase> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open('MyDatabase', 1);
|
|
||||||
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
request.onsuccess = () => resolve(request.result);
|
|
||||||
|
|
||||||
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
|
||||||
// @ts-expect-error - The event type is not recognized by TypeScript
|
|
||||||
event?.target?.result.createObjectStore('backgrounds', { keyPath: 'id' });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const readData = async (): Promise<Data | null> => {
|
|
||||||
const selectedBackground = localStorage.getItem('selectedBackground');
|
|
||||||
|
|
||||||
//const selectedBackground = localStorage.getItem('selectedBackground');
|
|
||||||
if (!selectedBackground || selectedBackground === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await openDB();
|
|
||||||
const tx = db.transaction('backgrounds', 'readonly');
|
|
||||||
const store = tx.objectStore('backgrounds');
|
|
||||||
const request = store.get(selectedBackground);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.onsuccess = () => resolve(request.result as Data);
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateBackground = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const data = await readData();
|
|
||||||
if (!data) {
|
|
||||||
const container = document.getElementById('media-container');
|
|
||||||
const currentMedia = container?.querySelector('.current-media');
|
|
||||||
if (currentMedia) {
|
|
||||||
currentMedia.remove();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(data.blob);
|
|
||||||
const container = document.getElementById('media-container');
|
|
||||||
|
|
||||||
// Create new element and set properties
|
|
||||||
let newElement;
|
|
||||||
if (data.type === 'image') {
|
|
||||||
newElement = document.createElement('img');
|
|
||||||
newElement.src = url;
|
|
||||||
newElement.alt = 'Uploaded content';
|
|
||||||
} else if (data.type === 'video') {
|
|
||||||
newElement = document.createElement('video');
|
|
||||||
newElement.src = url;
|
|
||||||
newElement.autoplay = true;
|
|
||||||
newElement.loop = true;
|
|
||||||
newElement.muted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the old element for removal
|
|
||||||
const oldElement = container?.querySelector('.current-media');
|
|
||||||
if (oldElement) {
|
|
||||||
oldElement.classList.remove('current-media');
|
|
||||||
oldElement.classList.add('old-media');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new element and mark it as current
|
|
||||||
newElement?.classList.add('current-media');
|
|
||||||
container?.appendChild(newElement as Node);
|
|
||||||
|
|
||||||
// Delay removal of old element
|
|
||||||
setTimeout(() => {
|
|
||||||
const oldMedia = container?.querySelector('.old-media');
|
|
||||||
if (oldMedia) {
|
|
||||||
oldMedia.remove();
|
|
||||||
}
|
|
||||||
}, 100); // 0.1 second delay
|
|
||||||
} catch (error) {
|
|
||||||
console.error('An error occurred:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main function to run on page load
|
|
||||||
const main = async (): Promise<void> => {
|
|
||||||
await updateBackground();
|
|
||||||
|
|
||||||
// Listen for changes to local storage
|
|
||||||
try {
|
|
||||||
window.addEventListener('storage', async (event) => {
|
|
||||||
if (event.key === 'selectedBackground') {
|
|
||||||
await updateBackground();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('An error occurred:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
main()
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Background {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
blob: Blob;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { bg, isSelected, isEditMode, onClick, onDelete } = $props<{ bg: Background, isSelected: boolean, isEditMode: boolean, onClick: () => void, onDelete: () => void }>();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onclick={onClick}
|
||||||
|
onkeydown={onClick}
|
||||||
|
tabindex="-1"
|
||||||
|
role="button"
|
||||||
|
class="relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 {isEditMode ? 'animate-shake' : ''} {isSelected ? 'dark:ring-2 ring-4' : 'ring-0'}"
|
||||||
|
>
|
||||||
|
{#if isEditMode}
|
||||||
|
<div
|
||||||
|
tabindex="-1"
|
||||||
|
role="button"
|
||||||
|
class="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
|
||||||
|
onclick={onDelete}
|
||||||
|
onkeydown={onDelete}
|
||||||
|
>
|
||||||
|
<div class="w-4 h-0.5 bg-white"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if bg.type === 'image'}
|
||||||
|
<img class="object-cover w-full h-full rounded-xl" src={bg.url} alt="swatch" />
|
||||||
|
{:else if bg.type === 'video'}
|
||||||
|
<video muted loop autoplay src={bg.url} class="object-cover w-full h-full rounded-xl"></video>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { hasEnoughStorageSpace, isIndexedDBSupported, writeData, openDatabase, readAllData, deleteData } from '@/svelte-interface/hooks/BackgroundDataLoader'
|
||||||
|
import BackgroundUploader from './BackgroundUploader.svelte';
|
||||||
|
import BackgroundItem from './BackgroundItem.svelte'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { loadBackground } from '@/seqta/ui/ImageBackgrounds'
|
||||||
|
|
||||||
|
let { isEditMode, selectNoBackground = $bindable(), selectedBackground = $bindable() } = $props<{ isEditMode: boolean, selectNoBackground: () => void, selectedBackground: string | null }>();
|
||||||
|
let backgrounds = $state<{ id: string; type: string; blob: Blob; url?: string }[]>([]);
|
||||||
|
let isLoading = $state<boolean>(false);
|
||||||
|
let error = $state<string | null>(null);
|
||||||
|
|
||||||
|
let imageBackgrounds = $derived(backgrounds.filter(bg => bg.type === 'image'));
|
||||||
|
let videoBackgrounds = $derived(backgrounds.filter(bg => bg.type === 'video'));
|
||||||
|
|
||||||
|
async function getTheme() {
|
||||||
|
return localStorage.getItem('selectedBackground');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setTheme(theme: string) {
|
||||||
|
localStorage.setItem('selectedBackground', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFileChange(file: File): Promise<void> {
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isIndexedDBSupported()) {
|
||||||
|
throw new Error("Your browser doesn't support IndexedDB. Unable to save backgrounds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSpace = await hasEnoughStorageSpace(file.size);
|
||||||
|
if (!hasSpace) {
|
||||||
|
throw new Error("Not enough storage space to save this background.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileId = `${Date.now()}-${file.name}`;
|
||||||
|
const fileType = file.type.split('/')[0];
|
||||||
|
const blob = new Blob([file], { type: file.type });
|
||||||
|
|
||||||
|
await writeData(fileId, fileType, blob);
|
||||||
|
backgrounds = [...backgrounds, { id: fileId, type: fileType, blob, url: URL.createObjectURL(blob) }];
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
error = e.message;
|
||||||
|
} else {
|
||||||
|
error = 'An unknown error occurred';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBackgrounds(): Promise<void> {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
if (!isIndexedDBSupported()) {
|
||||||
|
throw new Error("Your browser doesn't support IndexedDB. Unable to load backgrounds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await openDatabase();
|
||||||
|
const data = await readAllData();
|
||||||
|
const dataWithUrls = data.map(bg => ({ ...bg, url: URL.createObjectURL(bg.blob) }));
|
||||||
|
backgrounds = dataWithUrls;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
error = e.message;
|
||||||
|
} else {
|
||||||
|
error = 'An unknown error occurred';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectBackground(fileId: string): void {
|
||||||
|
if (selectedBackground === fileId) {
|
||||||
|
selectNoBackground();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedBackground = fileId;
|
||||||
|
setTheme(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteBackground(fileId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await deleteData(fileId);
|
||||||
|
backgrounds = backgrounds.filter(bg => bg.id !== fileId);
|
||||||
|
|
||||||
|
if (selectedBackground === fileId) {
|
||||||
|
selectNoBackground();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
error = `Failed to delete background: ${e.message}`;
|
||||||
|
} else {
|
||||||
|
error = 'An unknown error occurred';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNoBackground = () => {
|
||||||
|
selectedBackground = null;
|
||||||
|
setTheme('');
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
loadBackground();
|
||||||
|
selectedBackground
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await loadBackgrounds();
|
||||||
|
selectedBackground = await getTheme();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="relative px-1 py-2">
|
||||||
|
<h2 class="pb-2 text-lg font-bold">Background Images</h2>
|
||||||
|
<div class="flex flex-wrap gap-4 mb-4">
|
||||||
|
{#if !isEditMode}
|
||||||
|
<BackgroundUploader on:fileChange={e => handleFileChange(e.detail)} />
|
||||||
|
{/if}
|
||||||
|
{#each imageBackgrounds as bg (bg.id)}
|
||||||
|
<BackgroundItem
|
||||||
|
{bg}
|
||||||
|
isSelected={selectedBackground === bg.id}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
onClick={() => selectBackground(bg.id)}
|
||||||
|
onDelete={() => deleteBackground(bg.id)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="py-2 text-lg font-bold">Background Videos</h2>
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
{#if !isEditMode}
|
||||||
|
<BackgroundUploader on:fileChange={e => handleFileChange(e.detail)} />
|
||||||
|
{/if}
|
||||||
|
{#each videoBackgrounds as bg (bg.id)}
|
||||||
|
<BackgroundItem
|
||||||
|
{bg}
|
||||||
|
isSelected={selectedBackground === bg.id}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
onClick={() => selectBackground(bg.id)}
|
||||||
|
onDelete={() => deleteBackground(bg.id)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function handleFileChange(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const file = input.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
dispatch('fileChange', file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="relative w-16 h-16 overflow-hidden transition rounded-xl bg-zinc-100 dark:bg-zinc-900">
|
||||||
|
<div class="flex items-center justify-center w-full h-full text-3xl font-bold text-gray-400 transition font-IconFamily hover:text-gray-500">
|
||||||
|
<!-- Plus icon -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*, video/*"
|
||||||
|
on:change={handleFileChange}
|
||||||
|
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { progress } = $props<{ progress: number }>();
|
||||||
|
|
||||||
|
let circumference = $derived(2 * Math.PI * 14);
|
||||||
|
let offset = $derived(circumference * (1 - (progress / 100)));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="absolute top-0 left-0 z-20 flex items-center justify-center w-full h-full">
|
||||||
|
<svg class="w-full h-full text-zinc-100 dark:text-zinc-700" viewBox="0 0 36 36">
|
||||||
|
<circle stroke="currentColor" fill="none" stroke-width="4" stroke-linecap="round" cx="18" cy="18" r="10" stroke-dasharray="{circumference} {circumference}" stroke-dashoffset="0" transform="rotate(-90 18 18)"></circle>
|
||||||
|
<circle stroke="#3B82F6" fill="none" stroke-width="4" stroke-linecap="round" cx="18" cy="18" r="10" stroke-dasharray="{circumference} {circumference}" stroke-dashoffset="{offset}" transform="rotate(-90 18 18)"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { openDB, type IDBPDatabase, type DBSchema } from 'idb';
|
||||||
|
|
||||||
|
interface BackgroundDB extends DBSchema {
|
||||||
|
backgrounds: {
|
||||||
|
key: string;
|
||||||
|
value: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
blob: Blob;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let db: IDBPDatabase<BackgroundDB> | null = null;
|
||||||
|
|
||||||
|
export async function openDatabase(): Promise<IDBPDatabase<BackgroundDB>> {
|
||||||
|
if (db) return db;
|
||||||
|
|
||||||
|
db = await openDB<BackgroundDB>('BackgroundDB', 1, {
|
||||||
|
upgrade(db: IDBPDatabase<BackgroundDB>) {
|
||||||
|
db.createObjectStore('backgrounds', { keyPath: 'id' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readAllData(): Promise<Array<{ id: string; type: string; blob: Blob }>> {
|
||||||
|
const db = await openDatabase();
|
||||||
|
return db.getAll('backgrounds');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeData(id: string, type: string, blob: Blob): Promise<void> {
|
||||||
|
const db = await openDatabase();
|
||||||
|
await db.put('backgrounds', { id, type, blob });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteData(id: string): Promise<void> {
|
||||||
|
const db = await openDatabase();
|
||||||
|
await db.delete('backgrounds', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clearAllData(): Promise<void> {
|
||||||
|
const db = await openDatabase();
|
||||||
|
await db.clear('backgrounds');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDataById(id: string): Promise<{ id: string; type: string; blob: Blob } | undefined> {
|
||||||
|
const db = await openDatabase();
|
||||||
|
return db.get('backgrounds', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeDatabase(): void {
|
||||||
|
if (db) {
|
||||||
|
db.close();
|
||||||
|
db = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if IndexedDB is supported
|
||||||
|
export function isIndexedDBSupported(): boolean {
|
||||||
|
return 'indexedDB' in window;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if there's enough storage space
|
||||||
|
export async function hasEnoughStorageSpace(requiredSpace: number): Promise<boolean> {
|
||||||
|
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
||||||
|
const { quota, usage } = await navigator.storage.estimate();
|
||||||
|
if (quota !== undefined && usage !== undefined) {
|
||||||
|
return (quota - usage) > requiredSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we can't determine, assume there's enough space
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export let selectedBackground = $state<string | null>(null);
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import BackgroundSelector from "@/svelte-interface/components/themes/BackgroundSelector.svelte"
|
||||||
|
|
||||||
|
// backgrounds
|
||||||
|
let selectedBackground = $state<string | null>(null);
|
||||||
|
let selectNoBackground = $state<() => void>(() => { });
|
||||||
|
|
||||||
|
let clearTheme = $derived(selectedBackground !== null);
|
||||||
|
let editMode = $state<boolean>(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pt-4">
|
<div class="py-4">
|
||||||
<button
|
<button
|
||||||
|
onclick={() => selectNoBackground()}
|
||||||
class="w-full px-4 py-2 mb-4 text-[13px] text-white transition rounded-xl bg-zinc-700/50">
|
class="w-full px-4 py-2 mb-4 text-[13px] text-white transition rounded-xl bg-zinc-700/50">
|
||||||
Clear Theme
|
{ clearTheme ? 'Clear Theme' : 'Select Theme' }
|
||||||
</button>
|
</button>
|
||||||
|
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1 +0,0 @@
|
|||||||
<h1>HI THERE!!!!</h1>
|
|
||||||
@@ -12,3 +12,5 @@ export function createStandalone() {
|
|||||||
setStandalone
|
setStandalone
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const standalone = createStandalone();
|
||||||
+5
-5
@@ -7,11 +7,11 @@ export default {
|
|||||||
content: [
|
content: [
|
||||||
"./src/**/*.{js,ts,jsx,tsx,html,svelte}",
|
"./src/**/*.{js,ts,jsx,tsx,html,svelte}",
|
||||||
],
|
],
|
||||||
safelist: [
|
//safelist: [
|
||||||
{
|
//{
|
||||||
pattern: / */,
|
// pattern: / */,
|
||||||
}
|
//}
|
||||||
],
|
//],
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
theme: {
|
theme: {
|
||||||
fontSize: {
|
fontSize: {
|
||||||
|
|||||||
+1
-2
@@ -58,8 +58,7 @@ export default defineConfig({
|
|||||||
minify: false,
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
settings: join(__dirname, 'src', 'svelte-interface', 'index.html'),
|
settings: join(__dirname, 'src', 'svelte-interface', 'index.html')
|
||||||
backgrounds: join(__dirname, 'src', 'seqta', 'ui', 'background', 'background.html')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user