summaryrefslogtreecommitdiff
path: root/packages/frontend/src/composables/use-form.ts
blob: 38e9b40e200f3be1b9aa12edc13183341c499a97 (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
/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { computed, reactive, watch } from 'vue';
import type { Reactive } from 'vue';
import { deepEqual } from '@/utility/deep-equal';

function copy<T>(v: T): T {
	return JSON.parse(JSON.stringify(v));
}

function unwrapReactive<T>(v: Reactive<T>): T {
	return JSON.parse(JSON.stringify(v));
}

export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) {
	const currentState = reactive<T>(copy(initialState));
	const previousState = reactive<T>(copy(initialState));

	const modifiedStates = reactive<Record<keyof T, boolean>>((() => {
		const obj: Record<keyof T, boolean> = {} as Record<keyof T, boolean>;
		for (const key in initialState) {
			obj[key] = false;
		}
		return obj;
	})());
	const modified = computed(() => Object.values(modifiedStates).some(v => v));
	const modifiedCount = computed(() => Object.values(modifiedStates).filter(v => v).length);

	watch([currentState, previousState], () => {
		for (const key in modifiedStates) {
			modifiedStates[key] = !deepEqual(currentState[key], previousState[key]);
		}
	}, { deep: true });

	async function _save() {
		await save(unwrapReactive(currentState));
		for (const key in currentState) {
			previousState[key] = copy(currentState[key]);
		}
	}

	function discard() {
		for (const key in currentState) {
			currentState[key] = copy(previousState[key]);
		}
	}

	return {
		state: currentState,
		savedState: previousState,
		modifiedStates,
		modified,
		modifiedCount,
		save: _save,
		discard,
	};
}