// Copyright (C) 2024 Vaughn Nugent // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { defer, filter, isEqual } from "lodash"; import { RemovableRef, SerializerAsync, StorageLikeAsync, useStorageAsync, watchOnce, get, set } from "@vueuse/core"; import { type MaybeRefOrGetter, type WatchSource, isProxy, toRaw, MaybeRef, shallowRef } from "vue"; import type { Watchable } from "./types"; export const waitForChange = []>>(source: [...T]):Promise => { return new Promise((resolve) => watchOnce(source, () => defer(() => resolve()), { deep: true })) } export const waitForChangeFn = []>>(source: [...T]) => { return (): Promise => waitForChange(source) } /** * Waits for a change to occur on the given watch source * once. * @returns A promise that resolves when the change occurs. */ export const waitOne = waitForChange; export const useStorage = (storage: any & chrome.storage.StorageArea, key: string, initialValue: MaybeRefOrGetter): RemovableRef => { const wrapper: StorageLikeAsync = { async getItem(key: string): Promise { const value = await storage.get(key) //pass the raw value to the serializer return value[key] as T; }, async setItem(key: string, value: any): Promise { //pass the raw value to storage await storage.set({ [key]: value }); }, async removeItem(key: string): Promise { await storage.remove(key); } } /** * Custom sealizer that passes the raw * values to the storage, the storage * wrapper above will store the raw values * as is. */ const serializer: SerializerAsync = { async read(value: any) { return value as T }, async write(value: any) { if (isProxy(value)) { return toRaw(value) } return value; } } return useStorageAsync(key, initialValue, wrapper, { serializer, shallow: true }); } export const onWatchableChange = ({ waitForChange }: Watchable, onChangeCallback: () => Promise, controls?: { immediate: boolean }) => { defer(async () => { if (controls?.immediate) { await onChangeCallback(); } while (true) { await waitForChange(); await onChangeCallback(); } }) } export const push = (arr: MaybeRef, item: T | T[]) => { //get the reactuve value first const current = get(arr) if (Array.isArray(item)) { //push the items current.push(...item) } else { //push the item current.push(item) } //set the value set(arr, current) } export const remove = (arr: MaybeRef, item: T) => { //get the reactuve value first const current = get(arr) //Get all items that are not the item const wo = filter(current, (i) => !isEqual(i, item)) //set the value set(arr, wo) } export const useQuery = (query: string) => { const get = () => { const args = new URLSearchParams(window.location.search) return args.get(query) } const set = (value: string) => { const args = new URLSearchParams(window.location.search); args.set(query, value); (window as any).customHistory.replaceState({}, '', `${window.location.pathname}?${args.toString()}`) } const mutable = shallowRef(get()) //Setup custom historu if (!('customHistory' in window)) { (window as any).customHistory = { replaceState: (...args: any[]) => { window.history.replaceState(...args) window.dispatchEvent(new Event('replaceState')) } } } //Listen for custom history events and update the mutable state window.addEventListener('replaceState', () => mutable.value = get()) return{ get, set, asRef: mutable } }