improves handling of user data
This commit is contained in:
+8
-4
@@ -37,7 +37,11 @@
|
||||
"svelte": "^3.54.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"just-clamp": "^4.2.0"
|
||||
"just-clamp": "^4.2.0",
|
||||
"just-filter-object": "^3.2.0",
|
||||
"just-map-keys": "^2.3.0",
|
||||
"just-map-values": "^3.2.0",
|
||||
"svelte-local-storage-store": "^0.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.0",
|
||||
@@ -47,14 +51,14 @@
|
||||
"@sveltejs/package": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"eslint": "^8.28.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"publint": "^0.1.9",
|
||||
"svelte-check": "^3.0.1",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-check": "^3.0.1",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { user_preferences } from '$lib/user';
|
||||
import { onMount } from 'svelte';
|
||||
import { readable } from 'svelte/store';
|
||||
import { get, readable } from 'svelte/store';
|
||||
|
||||
export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
||||
if (!browser) return;
|
||||
const ID = 'svelte-podcast-generated-audio-element';
|
||||
|
||||
onMount(() => {
|
||||
const el = document.createElement('audio');
|
||||
const existing_element = document.getElementById(ID) as HTMLAudioElement | null;
|
||||
const el = existing_element || document.createElement('audio');
|
||||
|
||||
el.id = ID;
|
||||
el.setAttribute('preload', 'metadata');
|
||||
el.muted = false;
|
||||
el.autoplay = true;
|
||||
el.controls = true;
|
||||
document.body.appendChild(el);
|
||||
el.controls = false;
|
||||
|
||||
const preferences = get(user_preferences);
|
||||
el.playbackRate = preferences.playback_rate;
|
||||
el.volume = preferences.volume;
|
||||
|
||||
if (!existing_element) document.body.appendChild(el);
|
||||
set(el);
|
||||
});
|
||||
|
||||
@@ -21,5 +30,3 @@ export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
||||
element ? element.remove() : null;
|
||||
};
|
||||
});
|
||||
|
||||
export type AudioElementStore = typeof audio_element;
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { audio_element } from '$lib/audio/audio-element';
|
||||
import { load_podcast_state } from '$lib/utility';
|
||||
import { info } from '$lib/utility/package/log';
|
||||
import { user_preferences } from '$lib/user';
|
||||
import { info, warn } from '$lib/utility/package/log';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
$: $audio_element;
|
||||
/* onMount(() => {
|
||||
return audio_element.subscribe((el) => {
|
||||
if (el) info('mounted audio element :: ', el);
|
||||
});
|
||||
}); */
|
||||
|
||||
onMount(() => {
|
||||
load_podcast_state();
|
||||
return audio_element.subscribe((el) => {
|
||||
info('loaded audio :: ', el);
|
||||
return user_preferences.subscribe((prefs) => {
|
||||
const el = get(audio_element);
|
||||
if (!el) return warn('no audio element found');
|
||||
info('setting preferences :: ', prefs);
|
||||
el.volume = prefs.volume;
|
||||
el.playbackRate = prefs.playback_rate;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { audio_element, type AudioElementStore } from '$lib/audio/audio-element';
|
||||
import { episode_details, type EpisodeDetailsStore } from '$lib/audio/episode-details';
|
||||
import { user_preferences } from '$lib/preferences';
|
||||
import { podcast_progress } from '$lib/progress';
|
||||
import { audio_element } from '$lib/audio/audio-element';
|
||||
import { episode_details } from '$lib/audio/episode-details';
|
||||
import type { EpisodeAttributes, EpisodeDetails } from '$lib/types';
|
||||
import { user_preferences, user_progress } from '$lib/user';
|
||||
import { warn } from '$lib/utility/package/log';
|
||||
import clamp from 'just-clamp';
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { derived, get, type Readable } from 'svelte/store';
|
||||
|
||||
const default_episode_attributes = {
|
||||
will_autoplay: false,
|
||||
@@ -15,10 +14,9 @@ const default_episode_attributes = {
|
||||
details: null,
|
||||
} satisfies Omit<EpisodeAttributes, 'src'>;
|
||||
|
||||
const episode_attributes = derived<
|
||||
[AudioElementStore, EpisodeDetailsStore],
|
||||
EpisodeAttributes | null
|
||||
>([audio_element, episode_details], ([$audio, $details], set) => {
|
||||
type EpisodeAttributesStore = Readable<EpisodeAttributes | null>;
|
||||
|
||||
const episode_attributes = derived([audio_element, episode_details], ([$audio, $details], set) => {
|
||||
if (!$audio) return set(null);
|
||||
|
||||
function set_value() {
|
||||
@@ -30,7 +28,7 @@ const episode_attributes = derived<
|
||||
details: $details,
|
||||
will_autoplay: $audio.autoplay,
|
||||
is_paused: $audio.paused,
|
||||
start_at: podcast_progress.get_episode($audio.src)?.start_at ?? 0,
|
||||
start_at: user_progress.get($audio.src) ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,7 +41,7 @@ const episode_attributes = derived<
|
||||
$audio.removeEventListener('pause', set_value);
|
||||
$audio.removeEventListener('playing', set_value);
|
||||
};
|
||||
});
|
||||
}) satisfies EpisodeAttributesStore;
|
||||
|
||||
type HandleType = 'toggle' | 'set';
|
||||
|
||||
@@ -52,22 +50,25 @@ const no_element = (action: string) => warn(`could not ${action} :: no audio ele
|
||||
export const episode_audio = {
|
||||
subscribe: episode_attributes.subscribe,
|
||||
load: (src: string, details: EpisodeDetails) => {
|
||||
podcast_progress.stash_episode();
|
||||
user_progress.save();
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('load');
|
||||
const progress = podcast_progress.get_episode(src);
|
||||
el.src = src;
|
||||
el.currentTime = progress?.start_at || 0;
|
||||
el.muted = false;
|
||||
|
||||
// using user_progress
|
||||
const start_at = user_progress.get(src) || 0;
|
||||
el.currentTime = start_at;
|
||||
|
||||
// using user_preferences
|
||||
const preferences = get(user_preferences);
|
||||
el.playbackRate = preferences.playback_rate;
|
||||
el.volume = preferences.volume;
|
||||
el.muted = false;
|
||||
|
||||
episode_details.set(details);
|
||||
},
|
||||
unload: () => {
|
||||
podcast_progress.stash_episode();
|
||||
user_progress.save();
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('unload');
|
||||
el.src = '';
|
||||
@@ -84,7 +85,7 @@ export const episode_audio = {
|
||||
}
|
||||
},
|
||||
pause: (t?: HandleType) => {
|
||||
podcast_progress.stash_episode();
|
||||
user_progress.save();
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('pause');
|
||||
|
||||
|
||||
@@ -2,4 +2,3 @@ import type { EpisodeDetails } from '$lib/types';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const episode_details = writable<EpisodeDetails | null>(null);
|
||||
export type EpisodeDetailsStore = typeof episode_details;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { audio_element, type AudioElementStore } from '$lib/audio/audio-element';
|
||||
import { audio_element } from '$lib/audio/audio-element';
|
||||
import type { EpisodeProgress } from '$lib/types';
|
||||
import { secondsToTimestamp } from '$lib/utility';
|
||||
import { derived } from 'svelte/store';
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
|
||||
const default_episode_progress = {
|
||||
current_time: 0,
|
||||
@@ -9,9 +9,9 @@ const default_episode_progress = {
|
||||
has_ended: false,
|
||||
} satisfies EpisodeProgress;
|
||||
|
||||
export const episode_progress = derived<AudioElementStore, EpisodeProgress>(
|
||||
audio_element,
|
||||
($audio, set) => {
|
||||
type ProgressStore = Readable<EpisodeProgress>;
|
||||
|
||||
export const episode_progress = derived(audio_element, ($audio, set) => {
|
||||
if (!$audio) return set(default_episode_progress);
|
||||
|
||||
function set_value() {
|
||||
@@ -25,5 +25,4 @@ export const episode_progress = derived<AudioElementStore, EpisodeProgress>(
|
||||
|
||||
$audio.addEventListener('timeupdate', () => set_value());
|
||||
return () => $audio.removeEventListener('timeupdate', () => set_value());
|
||||
},
|
||||
);
|
||||
}) satisfies ProgressStore;
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export * from '$lib/audio';
|
||||
export * from '$lib/preferences';
|
||||
export * from '$lib/progress';
|
||||
export * from '$lib/user';
|
||||
export * from '$lib/utility';
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { user_preferences } from '$lib/preferences/db-state';
|
||||
|
||||
import type { UserPreferences } from '$lib/types';
|
||||
import { info, warn } from '$lib/utility/package/log';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
const USER_PREFERENCE_KEY = 'USER_PREFERENCE' as const;
|
||||
|
||||
export function save_preferences() {
|
||||
if (!browser || !localStorage) {
|
||||
warn('localStorage not available, skipping save');
|
||||
return;
|
||||
}
|
||||
|
||||
const preferences = get(user_preferences);
|
||||
info(`Saving user preferences to localStorage`, preferences);
|
||||
localStorage.setItem(USER_PREFERENCE_KEY, JSON.stringify(preferences));
|
||||
}
|
||||
|
||||
export function load_preferences() {
|
||||
if (!browser || !localStorage) {
|
||||
warn('localStorage not available, skipping load');
|
||||
return;
|
||||
}
|
||||
const data = localStorage.getItem(USER_PREFERENCE_KEY);
|
||||
|
||||
if (!data) {
|
||||
info('No saved user preferences found');
|
||||
return;
|
||||
}
|
||||
|
||||
const preferences = JSON.parse(data) as UserPreferences;
|
||||
user_preferences.edit(preferences);
|
||||
|
||||
info(`Loaded user preferences from localStorage`, preferences);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { UserPreferences } from '$lib/types';
|
||||
import clamp from 'just-clamp';
|
||||
import { writable } from 'svelte/store';
|
||||
import { load_preferences, save_preferences } from './db-local-storage';
|
||||
|
||||
const _default_user_preferences = { playback_rate: 1, volume: 1 } satisfies UserPreferences;
|
||||
const _user_preferences = writable<UserPreferences>(_default_user_preferences);
|
||||
|
||||
const clamp_preferences = (prefs: UserPreferences) => ({
|
||||
playback_rate: clamp(prefs.playback_rate, 0.5, 5),
|
||||
volume: clamp(prefs.volume, 0, 1),
|
||||
});
|
||||
|
||||
const edit = (prefs: Partial<UserPreferences>) => {
|
||||
_user_preferences.update((prev) => clamp_preferences({ ...prev, ...prefs }));
|
||||
save_preferences();
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
_user_preferences.set(_default_user_preferences);
|
||||
save_preferences();
|
||||
};
|
||||
|
||||
export const user_preferences = {
|
||||
subscribe: _user_preferences.subscribe,
|
||||
edit,
|
||||
clear,
|
||||
load: load_preferences,
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './db-state';
|
||||
@@ -1 +0,0 @@
|
||||
export const _episode_progress_map = new Map<string, number>();
|
||||
@@ -1,48 +0,0 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { _episode_progress_map } from '$lib/progress/_private';
|
||||
import { error, info, warn } from '$lib/utility/package/log';
|
||||
|
||||
type SavedEpisodeProgress = { src: string; current_time: number };
|
||||
const EPISODE_PROGRESS_KEY = 'EPISODE_PROGRESS' as const;
|
||||
|
||||
export function save_podcast_progress() {
|
||||
if (!browser || !localStorage) {
|
||||
warn('localStorage not available, skipping save');
|
||||
return;
|
||||
}
|
||||
const items = [..._episode_progress_map];
|
||||
|
||||
console.log('items', items);
|
||||
|
||||
const episodes = items.reduce((prev, [src, current_time]) => {
|
||||
return [...prev, { src, current_time }];
|
||||
}, [] as SavedEpisodeProgress[]);
|
||||
|
||||
info(`Saving progress for ${episodes.length} episodes to localStorage`, episodes);
|
||||
localStorage.setItem(EPISODE_PROGRESS_KEY, JSON.stringify(episodes));
|
||||
}
|
||||
|
||||
export function load_podcast_progress() {
|
||||
if (!browser || !localStorage) {
|
||||
warn('localStorage not available, skipping load');
|
||||
return;
|
||||
}
|
||||
const data = localStorage.getItem(EPISODE_PROGRESS_KEY);
|
||||
|
||||
if (!data) {
|
||||
info('No saved episode progress found');
|
||||
return;
|
||||
}
|
||||
|
||||
const episodes = JSON.parse(data) as SavedEpisodeProgress[];
|
||||
|
||||
try {
|
||||
episodes.forEach(({ src, current_time }) => {
|
||||
_episode_progress_map.set(src, current_time);
|
||||
});
|
||||
info(`Loaded ${_episode_progress_map.size} episodes progress from localStorage`);
|
||||
} catch (e) {
|
||||
error('Error loading episode progress', e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { episode_audio, episode_progress } from '$lib/audio';
|
||||
import { _episode_progress_map } from '$lib/progress/_private';
|
||||
import type { AudioLoadOptions } from '$lib/types';
|
||||
import { info } from '$lib/utility/package/log';
|
||||
import { get } from 'svelte/store';
|
||||
import { load_podcast_progress, save_podcast_progress } from './db-local-storage';
|
||||
|
||||
const get_src_pathname = (src: string) => {
|
||||
if (src.startsWith('http')) return new URL(src).pathname;
|
||||
else return new URL(src, 'https://svelte.dev').pathname;
|
||||
};
|
||||
|
||||
const stash_episode_progress = () => {
|
||||
const audio = get(episode_audio);
|
||||
console.log('audio', audio);
|
||||
if (!audio?.src) return;
|
||||
info('saving progress: ', audio.src);
|
||||
const pathname = get_src_pathname(audio.src);
|
||||
_episode_progress_map.set(pathname, get(episode_progress).current_time);
|
||||
save_podcast_progress();
|
||||
};
|
||||
|
||||
const get_episode_progress = (src: string): Pick<AudioLoadOptions, 'start_at'> | null => {
|
||||
const pathname = get_src_pathname(src);
|
||||
const start_at = _episode_progress_map.get(pathname);
|
||||
|
||||
if (!start_at) return null;
|
||||
|
||||
info('found saved progress: ', pathname, start_at);
|
||||
|
||||
return { start_at: start_at };
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
_episode_progress_map.clear();
|
||||
save_podcast_progress();
|
||||
};
|
||||
|
||||
export const podcast_progress = {
|
||||
stash_episode: stash_episode_progress,
|
||||
get_episode: get_episode_progress,
|
||||
load: load_podcast_progress,
|
||||
clear,
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './db-state';
|
||||
@@ -22,3 +22,7 @@ export interface UserPreferences {
|
||||
playback_rate: number;
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export type UserProgress = {
|
||||
[key: string]: number;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './preferences';
|
||||
export * from './progress';
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { UserPreferences } from '$lib/types';
|
||||
import clamp from 'just-clamp';
|
||||
import { persisted } from 'svelte-local-storage-store';
|
||||
|
||||
const _default_user_preferences = { playback_rate: 1, volume: 1 } satisfies UserPreferences;
|
||||
const _user_preferences = persisted<UserPreferences>('USER_PREFERENCE', _default_user_preferences);
|
||||
|
||||
export const user_preferences = {
|
||||
subscribe: _user_preferences.subscribe,
|
||||
set: {
|
||||
playback_rate: (value: number) => {
|
||||
const playback_rate = clamp(value, 0.5, 5);
|
||||
return _user_preferences.update((prefs) => ({ ...prefs, playback_rate }));
|
||||
},
|
||||
volume: (value: number) => {
|
||||
const volume = clamp(value, 0, 1);
|
||||
return _user_preferences.update((prefs) => ({ ...prefs, volume }));
|
||||
},
|
||||
},
|
||||
clear: () => _user_preferences.set(_default_user_preferences),
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { episode_audio, episode_progress } from '$lib/audio';
|
||||
import type { UserProgress } from '$lib/types';
|
||||
import { get_pathname_from_url } from '$lib/utility/get-pathname-from-url';
|
||||
import { info } from '$lib/utility/package/log';
|
||||
import { persisted } from 'svelte-local-storage-store';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
const _default_user_progress = {} satisfies UserProgress;
|
||||
const _user_progress = persisted<UserProgress>('USER_PROGRESS', _default_user_progress);
|
||||
|
||||
const save = () => {
|
||||
const audio = get(episode_audio);
|
||||
if (!audio?.src) return;
|
||||
|
||||
info('saving progress: ', audio.src);
|
||||
|
||||
const pathname = get_pathname_from_url(audio.src);
|
||||
const current_time = get(episode_progress).current_time;
|
||||
_user_progress.update((prev) => ({ ...prev, [pathname]: current_time }));
|
||||
};
|
||||
|
||||
export const user_progress = {
|
||||
subscribe: _user_progress.subscribe,
|
||||
get: (src: string) => get(_user_progress)[get_pathname_from_url(src)],
|
||||
save,
|
||||
clear: () => _user_progress.set(_default_user_progress),
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export const get_pathname_from_url = (src: string) => {
|
||||
if (src.startsWith('http')) return new URL(src).pathname;
|
||||
else return new URL(src, 'https://svelte.dev').pathname;
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from './seconds-to-timestamp';
|
||||
export * from './use-state';
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { user_preferences } from '$lib/preferences';
|
||||
import { podcast_progress } from '$lib/progress';
|
||||
|
||||
export function load_podcast_state() {
|
||||
podcast_progress.load();
|
||||
user_preferences.load();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { podcast_progress, user_preferences } from '$lib';
|
||||
import { episode_audio, episode_progress } from '$lib/audio';
|
||||
import { episode_audio, episode_progress, user_preferences, user_progress } from '$lib';
|
||||
|
||||
const sources = {
|
||||
syntax: {
|
||||
@@ -31,7 +30,7 @@
|
||||
|
||||
<h1>Demo</h1>
|
||||
<a href="/another-page">Another Page</a>
|
||||
<button type="button" on:click={podcast_progress.clear}>Clear progress for all episodes</button>
|
||||
<button type="button" on:click={user_progress.clear}>Clear progress for all episodes</button>
|
||||
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
||||
|
||||
<h5>Load Audio</h5>
|
||||
@@ -77,7 +76,7 @@
|
||||
<h6>Playback Rate</h6>
|
||||
|
||||
{#each [0.5, 1, 2, 3] as rate}
|
||||
<button type="button" on:click={() => user_preferences.edit({ playback_rate: rate })}>
|
||||
<button type="button" on:click={() => user_preferences.set.playback_rate(rate)}>
|
||||
{rate}x
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
@@ -1910,6 +1910,21 @@ just-clamp@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/just-clamp/-/just-clamp-4.2.0.tgz#4dafc300d2f44685082b686f13deaa84d1c53b21"
|
||||
integrity sha512-ssHiAxuN7R+VP7jS1XyUP7ju5YC4bTQwQdzYtuZkASTrKe9KsI/X0t+5BJeD6Un6z/dNMDFVNQpQEgUphpXa0w==
|
||||
|
||||
just-filter-object@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/just-filter-object/-/just-filter-object-3.2.0.tgz#e8bc4d6d758595cdfad2fc5b4e4d3630792d755b"
|
||||
integrity sha512-OeorYJxmp2zhy/0LxjS1UjbJ7XMY8M4gVa1RRKxnIVheCYmng2E2hE0lEbDGv4aRh/HI7FgNUXtOMnmNxpoXRQ==
|
||||
|
||||
just-map-keys@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/just-map-keys/-/just-map-keys-2.3.0.tgz#8539e396160a0c54498dc7b76716b211e7b4a677"
|
||||
integrity sha512-enWodahIp6kDnt+/dw5Ps1baTmuuc9AVFOuwwLhn3mufOyP4aWF3+4uF6oEFKVH15H0ymb7MjuO9W6j8HFQ8ng==
|
||||
|
||||
just-map-values@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/just-map-values/-/just-map-values-3.2.0.tgz#5642366be1a79b1c35963846fada7d556c0d7c35"
|
||||
integrity sha512-TyqCKtK3NxiUgOjRYMIKURvBTHesi3XzomDY0QVPZ3rYzLCF+nNq5rSi0B/L5aOd/WMTZo6ukzA4wih4HUbrDg==
|
||||
|
||||
kind-of@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
@@ -2799,6 +2814,11 @@ svelte-hmr@^0.15.1:
|
||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.1.tgz#d11d878a0bbb12ec1cba030f580cd2049f4ec86b"
|
||||
integrity sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==
|
||||
|
||||
svelte-local-storage-store@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-local-storage-store/-/svelte-local-storage-store-0.4.0.tgz#965e342582555b606102045e86662d6c5864e1e6"
|
||||
integrity sha512-ctPykTt4S3BE5bF0mfV0jKiUR1qlmqLvnAkQvYHLeb9wRyO1MdIFDVI23X+TZEFleATHkTaOpYZswIvf3b2tWA==
|
||||
|
||||
svelte-preprocess@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz#3dd21a17eb508347d4b26a0d98059d23e2d1b9a0"
|
||||
|
||||
Reference in New Issue
Block a user