aboutsummaryrefslogtreecommitdiff
path: root/extension/src/features/util.ts
blob: 53f6ffdd531df8ba5035bd3a122abfb25adb1e51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 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 <https://www.gnu.org/licenses/>.


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 = <T extends Readonly<WatchSource<unknown>[]>>(source: [...T]):Promise<void> => {
    return new Promise((resolve) => watchOnce<any>(source, () => defer(() => resolve()), { deep: true }))
}

export const waitForChangeFn = <T extends Readonly<WatchSource<unknown>[]>>(source: [...T]) => {
    return (): Promise<void> => 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 = <T>(storage: any & chrome.storage.StorageArea, key: string, initialValue: MaybeRefOrGetter<T>): RemovableRef<T> => {

    const wrapper: StorageLikeAsync = {

        async getItem(key: string): Promise<any | undefined> {
            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<void> {
            //pass the raw value to storage
            await storage.set({ [key]: value });
        },
        async removeItem(key: string): Promise<void> {
            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<T> = {
        async read(value: any) {
            return value as T
        },
        async write(value: any) {
            if (isProxy(value)) {
                return toRaw(value)
            }
            return value;
        }
    }

    return useStorageAsync<T>(key, initialValue, wrapper, { serializer, shallow: true });
}

export const onWatchableChange = ({ waitForChange }: Watchable, onChangeCallback: () => Promise<any>, controls?: { immediate: boolean }) => {

    defer(async () => {
        if (controls?.immediate) {
            await onChangeCallback();
        }

        while (true) {
            await waitForChange();
            await onChangeCallback();
        }
    })
}

export const push = <T>(arr: MaybeRef<T[]>, 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 = <T>(arr: MaybeRef<T[]>, 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<string | null>(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
    }
}