summaryrefslogtreecommitdiff
path: root/packages/frontend/src/widgets/widget.ts
blob: 90adea538fe6a742aa6398a75fe9877dc5f9b76b (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
/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { defineAsyncComponent, reactive, watch } from 'vue';
import { throttle } from 'throttle-debounce';
import { getDefaultFormValues } from '@/utility/form.js';
import type { Reactive } from 'vue';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
import { deepClone } from '@/utility/clone.js';
import { i18n } from '@/i18n';

export type Widget<P extends Record<string, unknown>> = {
	id: string;
	data: Partial<P>;
};

export type WidgetComponentProps<P extends Record<string, unknown>> = {
	widget?: Widget<P>;
};

export type WidgetComponentEmits<P extends Record<string, unknown>> = {
	(ev: 'updateProps', props: P);
};

export type WidgetComponentExpose = {
	name: string;
	id: string | null;
	configure: () => void;
};

export const useWidgetPropsManager = <F extends FormWithDefault>(
	name: string,
	propsDef: F,
	props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
	emit: WidgetComponentEmits<GetFormResultType<F>>,
): {
	widgetProps: Reactive<GetFormResultType<F>>;
	save: () => void;
	configure: () => void;
} => {
	const widgetProps = reactive((() => {
		const np = getDefaultFormValues(propsDef);
		if (props.widget?.data != null) {
			for (const key of Object.keys(props.widget.data) as (keyof F)[]) {
				np[key] = props.widget.data[key] as GetFormResultType<F>[typeof key];
			}
		}
		return np;
	})());

	watch(() => props.widget?.data, (to) => {
		if (to != null) {
			for (const key of Object.keys(propsDef)) {
				widgetProps[key] = to[key];
			}
		}
	}, { deep: true });

	const save = throttle(3000, () => {
		emit('updateProps', widgetProps as GetFormResultType<F>);
	});

	const configure = async () => {
		const form = deepClone(propsDef);
		for (const item of Object.keys(form)) {
			form[item].default = widgetProps[item];
		}

		const res = await new Promise<{
			canceled: false;
			result: GetFormResultType<F>;
		} | {
			canceled: true;
		}>((resolve) => {
			const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWidgetSettingsDialog.vue')), {
				widgetName: name,
				form: form,
				currentSettings: widgetProps,
			}, {
				saved: (newProps: GetFormResultType<F>) => {
					resolve({ canceled: false, result: newProps });
				},
				canceled: () => {
					resolve({ canceled: true });
				},
				closed: () => {
					dispose();
				},
			});
		});

		if (res.canceled) {
			return;
		}

		for (const key of Object.keys(res.result)) {
			widgetProps[key] = res.result[key];
		}

		save();
	};

	return {
		widgetProps,
		save,
		configure,
	};
};