|
| 1 | +import { useSyncExternalStore } from "react"; |
| 2 | +import { readFlag, writeFlag } from "./storageFlags"; |
| 3 | + |
| 4 | +const SHOW_REMAINING_USAGE_KEY = "monocode.showRemainingUsage"; |
| 5 | +const MASK_EMAILS_KEY = "monocode.maskEmails"; |
| 6 | + |
| 7 | +export const SHOW_REMAINING_USAGE_DEFAULT = false; |
| 8 | +export const MASK_EMAILS_DEFAULT = false; |
| 9 | + |
| 10 | +/** Fired on `window` whenever the usage meter direction flips (detail: boolean). */ |
| 11 | +export const SHOW_REMAINING_USAGE_CHANGE_EVENT = |
| 12 | + "monocode:showremainingusagechange"; |
| 13 | +/** Fired on `window` whenever email masking flips (detail: boolean). */ |
| 14 | +export const MASK_EMAILS_CHANGE_EVENT = "monocode:maskemailschange"; |
| 15 | + |
| 16 | +function flagStore(key: string, fallback: boolean, event: string) { |
| 17 | + // Holds a saved value only while storage failed to keep it, so the switch |
| 18 | + // still flips for this window when localStorage is unavailable. |
| 19 | + let unsaved: boolean | null = null; |
| 20 | + const load = () => unsaved ?? readFlag(key) ?? fallback; |
| 21 | + const save = (value: boolean) => { |
| 22 | + writeFlag(key, value); |
| 23 | + unsaved = readFlag(key) === value ? null : value; |
| 24 | + if (typeof window === "undefined") return; |
| 25 | + window.dispatchEvent(new CustomEvent<boolean>(event, { detail: value })); |
| 26 | + }; |
| 27 | + const subscribe = (onStoreChange: () => void) => { |
| 28 | + if (typeof window === "undefined") return () => {}; |
| 29 | + // Other windows write the same localStorage; `storage` reports their saves. |
| 30 | + const onStorage = (storage: StorageEvent) => { |
| 31 | + if (storage.key !== key && storage.key !== null) return; |
| 32 | + unsaved = null; |
| 33 | + onStoreChange(); |
| 34 | + }; |
| 35 | + window.addEventListener(event, onStoreChange); |
| 36 | + window.addEventListener("storage", onStorage); |
| 37 | + return () => { |
| 38 | + window.removeEventListener(event, onStoreChange); |
| 39 | + window.removeEventListener("storage", onStorage); |
| 40 | + }; |
| 41 | + }; |
| 42 | + const useFlag = () => useSyncExternalStore(subscribe, load, () => fallback); |
| 43 | + return { load, save, subscribe, useFlag }; |
| 44 | +} |
| 45 | + |
| 46 | +const showRemainingUsage = flagStore( |
| 47 | + SHOW_REMAINING_USAGE_KEY, |
| 48 | + SHOW_REMAINING_USAGE_DEFAULT, |
| 49 | + SHOW_REMAINING_USAGE_CHANGE_EVENT, |
| 50 | +); |
| 51 | +const maskEmails = flagStore( |
| 52 | + MASK_EMAILS_KEY, |
| 53 | + MASK_EMAILS_DEFAULT, |
| 54 | + MASK_EMAILS_CHANGE_EVENT, |
| 55 | +); |
| 56 | + |
| 57 | +/** Usage meters fill with what is left instead of what is used. */ |
| 58 | +export const loadShowRemainingUsage = showRemainingUsage.load; |
| 59 | +export const saveShowRemainingUsage = showRemainingUsage.save; |
| 60 | +export const subscribeShowRemainingUsage = showRemainingUsage.subscribe; |
| 61 | +export const useShowRemainingUsage = showRemainingUsage.useFlag; |
| 62 | + |
| 63 | +/** Account emails stay blurred until clicked. */ |
| 64 | +export const loadMaskEmails = maskEmails.load; |
| 65 | +export const saveMaskEmails = maskEmails.save; |
| 66 | +export const subscribeMaskEmails = maskEmails.subscribe; |
| 67 | +export const useMaskEmails = maskEmails.useFlag; |
0 commit comments