Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"platform": "^1.3.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"react-hook-form": "^7.58.1",
"react-textarea-autosize": "^8.5.3",
"react-transition-group": "^4.4.5",
"sonner": "^2.0.5",
Expand Down
62 changes: 26 additions & 36 deletions src/components/option/SettingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
LINK_COMMAND_STARTUP_METHOD,
STYLE_VARIABLE,
} from '@/const'
import type { SettingsType } from '@/types'
import type { UserSettings } from '@/types'
import { PopupPlacementSchema } from '@/types/schema'
import {
isMenuCommand,
Expand Down Expand Up @@ -112,22 +112,20 @@ const formSchema = z
.strict()

type FormValues = z.infer<typeof formSchema>
type SettingsFormType = Omit<SettingsType, 'settingVersion' | 'stars'>
type SettingsFormType = Omit<UserSettings, 'settingVersion'>

export function SettingForm({ className }: { className?: string }) {
const [settingData, setSettingData] = useState<SettingsFormType>()
const [isSaving, setIsSaving] = useState(false)
const initializedRef = useRef<boolean>(false)
const saveToRef = useRef<number>()
const iconToRef = useRef<number>()
const isLoadingRef = useRef<boolean>()
const loadingRef = useRef<HTMLDivElement>(null)
const os = isMac() ? 'mac' : 'windows'

const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
mode: 'onChange',
})
const { reset, getValues, setValue, register, watch } = form
const { reset, getValues, setValue, register, subscribe } = form

const startupMethod = useWatch({
control: form.control,
Expand Down Expand Up @@ -159,16 +157,17 @@ export function SettingForm({ className }: { className?: string }) {

// Update form with latest settings
const updateFormSettings = async () => {
isLoadingRef.current = true
const settings = await loadSettingsData()
reset(settings)
await sleep(100)
isLoadingRef.current = false
}

// Initial settings load
useEffect(() => {
const initializeSettings = async () => {
await updateFormSettings()
// Set initialized after 100ms to avoid flickering
setTimeout(() => (initializedRef.current = true), 100)
}
initializeSettings()
}, [])
Expand Down Expand Up @@ -212,9 +211,8 @@ export function SettingForm({ className }: { className?: string }) {

settings.commands = [...commands, ...linkCommands]
await Settings.set({
...current,
...settings,
settingVersion: current.settingVersion,
stars: current.stars,
})
await sleep(1000)
} catch (e) {
Expand All @@ -236,28 +234,6 @@ export function SettingForm({ className }: { className?: string }) {
setValue('popupPlacement', popupPlacement)
}

// Save after 500 ms to storage.
useEffect(() => {
let unmounted = false

// Skip saving if the settingData is not initialized.
if (!initializedRef.current) {
return
}

clearTimeout(saveToRef.current)
saveToRef.current = window.setTimeout(() => {
if (unmounted || settingData == null) return
updateSettings(settingData)
}, 1 * 500 /* ms */)

return () => {
unmounted = true
clearTimeout(saveToRef.current)
clearTimeout(iconToRef.current)
}
}, [settingData])

// Set default value for startupMethod.
useEffect(() => {
if (startupMethod === STARTUP_METHOD.KEYBOARD) {
Expand Down Expand Up @@ -317,11 +293,25 @@ export function SettingForm({ className }: { className?: string }) {
}, [linkCommandMethod])

useEffect(() => {
const subscription = watch((value) => {
setSettingData(value as SettingsFormType)
// Save after 500 ms to storage.
const subscription = subscribe({
formState: { values: true },
callback: ({ values }) => {
// Skip saving if the settingData is loaded.
if (isLoadingRef.current) return

clearTimeout(saveToRef.current)
saveToRef.current = window.setTimeout(() => {
if (values == null) return
updateSettings(values as SettingsFormType)
}, 1 * 500 /* ms */)
},
})
return () => subscription.unsubscribe()
}, [watch])
return () => {
clearTimeout(saveToRef.current)
subscription()
}
}, [subscribe])

return (
<Form {...form}>
Expand Down
16 changes: 7 additions & 9 deletions src/services/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,17 @@ export const Settings = {
// Stars
await Storage.set<Star[]>(LOCAL_STORAGE_KEY.STARS, data.stars)

// UserStats
// Remove UserStats, stars and shortcuts from data
const { commandExecutionCount, hasShownReviewRequest, stars, ...restData } =
data

const userStats: UserStats = {
commandExecutionCount: data.commandExecutionCount,
hasShownReviewRequest: data.hasShownReviewRequest,
commandExecutionCount,
hasShownReviewRequest,
}
await Storage.set<UserStats>(STORAGE_KEY.USER_STATS, userStats)

// Shortcuts
await Storage.set<UserStats>(STORAGE_KEY.USER_STATS, userStats)
await Storage.set<ShortcutSettings>(STORAGE_KEY.SHORTCUTS, data.shortcuts)

// Remove UserStats, stars and shortcuts from data
const { commandExecutionCount, hasShownReviewRequest, stars, ...restData } =
data
await Storage.set<UserSettings>(STORAGE_KEY.USER, restData)
await Storage.set(LOCAL_STORAGE_KEY.CACHES, caches)
return true
Expand Down
55 changes: 35 additions & 20 deletions src/services/storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import DefaultSettings, { DefaultCommands } from './defaultSettings'
import { Command, CaptureDataStorage } from '@/types'

const SYNC_DEBOUNCE_DELAY = 10

let syncSetTimeout: NodeJS.Timeout | null
const syncSetData = new Map<KEY, any>()

export enum STORAGE_KEY {
USER = 0,
COMMAND_COUNT = 2,
Expand Down Expand Up @@ -130,11 +135,24 @@ export const Storage = {
*/
set: async <T>(key: KEY, value: T): Promise<boolean> => {
const area = detectStorageArea(key)
await area.set({ [key]: value })
if (chrome.runtime.lastError != null) {
throw chrome.runtime.lastError

if (area === chrome.storage.sync) {
if (syncSetTimeout != null) {
clearTimeout(syncSetTimeout)
}
syncSetData.set(key, value)

syncSetTimeout = setTimeout(async () => {
const dataToSet = Object.fromEntries(syncSetData)
await area.set(dataToSet)
syncSetData.clear()
syncSetTimeout = null
}, SYNC_DEBOUNCE_DELAY)
return true
} else {
await area.set({ [key]: value })
return true
}
return true
},

/**
Expand Down Expand Up @@ -228,31 +246,28 @@ export const Storage = {
setCommands: async (
commands: Command[],
): Promise<boolean | chrome.runtime.LastError> => {
// Update commands.
const count = commands.length
const preCount = await Storage.get<number>(STORAGE_KEY.COMMAND_COUNT)

// Update commands and count.
const data = commands.reduce(
(acc, cmd, i) => {
acc[`${CMD_PREFIX}${i}`] = cmd
return acc
},
{} as { [key: string]: Command },
)
await chrome.storage.sync.set(data)
if (chrome.runtime.lastError != null) {
throw chrome.runtime.lastError
}
await chrome.storage.sync.set({
...data,
[STORAGE_KEY.COMMAND_COUNT]: commands.length,
})

// Remove surplus commands
const count = commands.length
const preCount = await Storage.get<number>(STORAGE_KEY.COMMAND_COUNT)
const removeKeys = getIndicesToRemove(preCount, count).map(
(i) => `${CMD_PREFIX}${i}`,
)
await chrome.storage.sync.remove(removeKeys)

// Update command count.
await chrome.storage.sync.set({ [STORAGE_KEY.COMMAND_COUNT]: count })
if (chrome.runtime.lastError != null) {
throw chrome.runtime.lastError
if (preCount > count) {
const removeKeys = getIndicesToRemove(preCount, count).map(
(i) => `${CMD_PREFIX}${i}`,
)
await chrome.storage.sync.remove(removeKeys)
}
return true
},
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3933,10 +3933,10 @@ react-dom@^18.3.1:
loose-envify "^1.1.0"
scheduler "^0.23.2"

react-hook-form@^7.54.2:
version "7.54.2"
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz"
integrity sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==
react-hook-form@^7.58.1:
version "7.58.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.58.1.tgz#b755acc1b42a19e4253c878b766e4c5ebd070fe8"
integrity sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==

react-is@^16.13.1:
version "16.13.1"
Expand Down