aboutsummaryrefslogtreecommitdiff
path: root/resources/assets/javascripts/lib/icon-loader.ts
blob: 0b95e2f58e2f941f003534f023d14260574ce01e (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
type CacheOption = 'off' | 'session' | 'local';
type CachedIcon = {
    isSvg: boolean,
    content: string
};

class IconLoader
{
    readonly #cacheKey: string = 'studip/icons';

    #baseUrl: string;
    #useCache: CacheOption = 'off';

    #cache: Map<string, string>;
    #promises: Map<string, Promise<CachedIcon>>;

    constructor(baseUrl: string, useCache: CacheOption = 'off')
    {
        this.#baseUrl = baseUrl;
        this.#useCache = useCache;

        this.#cache = new Map<string, string>(this.#initialState());
        this.#promises = new Map<string, Promise<CachedIcon>>();
    }

    async load(shape: string): Promise<CachedIcon>
    {
        if (this.#cache.has(shape)) {
            return JSON.parse(this.#cache.get(shape)!);
        }

        if (this.#promises.has(shape)) {
            return this.#promises.get(shape)!;
        }

        const containsUrl = (shape: string): boolean => /\bhttps?:\/\/[^\s]+/i.test(shape);

        const url = containsUrl(shape) ? shape : `${this.#baseUrl}images/icons/blue/${shape}.svg`;

        const promise = (async () => {
            try {
                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`IconLoader: HTTP ${response.status} ${response.statusText}`);
                }

                const icon: CachedIcon = {
                    isSvg: response.headers.get('Content-Type')?.includes('image/svg+xml') ?? false,
                    content: ''
                };

                if (icon.isSvg) {
                    let svg = await response.text();
                    svg = svg.replace(/fill="(?!none)[^"]+"/g, 'fill="currentColor"');
                    svg = svg.replace(/(width|height)="[^"]+"/g, '');
                    icon.content = svg;
                } else {
                    const blob = await response.blob();
                    icon.content = await (new Promise(resolve => {
                        const reader = new FileReader();
                        reader.onload = () => resolve(reader.result as string);
                        reader.readAsDataURL(blob);
                    }));
                }

                this.store(shape, icon);

                return icon;
            } catch(error) {
                console.error(`IconLoader: Fehler beim Laden von ${shape}`, error);
                return {
                    isSvg: true,
                    content: ''
                } as CachedIcon;
            } finally {
                this.#promises.delete(shape);
            }
        })();

        this.#promises.set(shape, promise);

        return promise;
    }

    store(shape: string, icon: CachedIcon): void
    {
        this.#cache.set(shape, JSON.stringify(icon));

        this.#getStorage()?.setItem(
            this.#cacheKey,
            JSON.stringify([...this.#cache])
        )
    }

    #getStorage(): Storage|null
    {
        if (this.#useCache === 'off') {
            return null;
        }
        return this.#useCache === 'session' ? sessionStorage : localStorage;
    }

    #initialState(): [string, string][]
    {
        const cached = this.#getStorage()?.getItem(this.#cacheKey);
        if (!cached) {
            return [];
        }

        try {
            return JSON.parse(cached);
        } catch {
            return [];
        }
    }
}

const defaultLoader = new IconLoader(window.STUDIP.ASSETS_URL, 'session');

export default defaultLoader;
export { IconLoader, CachedIcon };