refactor(frontend): Formまわりの型強化 (#16260)
* refactor(frontend): Formまわりの型強化 * fix * avoid non-null assertion and add null check for safety * refactor * avoid non-null assertion and add null check for safety * Update clip.vue --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
c2a01551a7
commit
a8abb03d17
|
@ -13,7 +13,7 @@ import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import type { UploaderDialogFeatures } from '@/components/MkUploaderDialog.vue';
|
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
||||||
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -837,7 +837,7 @@ export function launchUploader(
|
||||||
options?: {
|
options?: {
|
||||||
folderId?: string | null;
|
folderId?: string | null;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
features?: UploaderDialogFeatures;
|
features?: UploaderFeatures;
|
||||||
},
|
},
|
||||||
): Promise<Misskey.entities.DriveFile[]> {
|
): Promise<Misskey.entities.DriveFile[]> {
|
||||||
return new Promise(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
|
|
|
@ -76,7 +76,8 @@ watch(() => props.clipId, async () => {
|
||||||
clip.value = await misskeyApi('clips/show', {
|
clip.value = await misskeyApi('clips/show', {
|
||||||
clipId: props.clipId,
|
clipId: props.clipId,
|
||||||
});
|
});
|
||||||
favorited.value = clip.value.isFavorited;
|
|
||||||
|
favorited.value = clip.value!.isFavorited ?? false;
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
@ -108,6 +109,8 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||||
icon: 'ti ti-pencil',
|
icon: 'ti ti-pencil',
|
||||||
text: i18n.ts.edit,
|
text: i18n.ts.edit,
|
||||||
handler: async (): Promise<void> => {
|
handler: async (): Promise<void> => {
|
||||||
|
if (clip.value == null) return;
|
||||||
|
|
||||||
const { canceled, result } = await os.form(clip.value.name, {
|
const { canceled, result } = await os.form(clip.value.name, {
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -128,6 +131,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||||
default: clip.value.isPublic,
|
default: clip.value.isPublic,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('clips/update', {
|
os.apiWithDialog('clips/update', {
|
||||||
|
@ -178,6 +182,8 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
danger: true,
|
danger: true,
|
||||||
handler: async (): Promise<void> => {
|
handler: async (): Promise<void> => {
|
||||||
|
if (clip.value == null) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.tsx.deleteAreYouSure({ x: clip.value.name }),
|
text: i18n.tsx.deleteAreYouSure({ x: clip.value.name }),
|
||||||
|
|
|
@ -64,6 +64,7 @@ async function create() {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('clips/create', result);
|
os.apiWithDialog('clips/create', result);
|
||||||
|
|
|
@ -79,7 +79,9 @@ async function createKey() {
|
||||||
default: scope.value.join('/'),
|
default: scope.value.join('/'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('i/registry/set', {
|
os.apiWithDialog('i/registry/set', {
|
||||||
scope: result.scope.split('/'),
|
scope: result.scope.split('/'),
|
||||||
key: result.key,
|
key: result.key,
|
||||||
|
|
|
@ -56,7 +56,9 @@ async function createKey() {
|
||||||
label: i18n.ts._registry.scope,
|
label: i18n.ts._registry.scope,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('i/registry/set', {
|
os.apiWithDialog('i/registry/set', {
|
||||||
scope: result.scope.split('/'),
|
scope: result.scope.split('/'),
|
||||||
key: result.key,
|
key: result.key,
|
||||||
|
|
|
@ -14,12 +14,13 @@ import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import type { FormWithDefault } from '@/utility/form.js';
|
||||||
|
|
||||||
export type Plugin = {
|
export type Plugin = {
|
||||||
installId: string;
|
installId: string;
|
||||||
name: string;
|
name: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
config?: Record<string, { default: any }>;
|
config?: FormWithDefault;
|
||||||
configData: Record<string, any>;
|
configData: Record<string, any>;
|
||||||
src: string | null;
|
src: string | null;
|
||||||
version: string;
|
version: string;
|
||||||
|
@ -240,7 +241,7 @@ async function launchPlugin(id: Plugin['installId']): Promise<void> {
|
||||||
pluginLogs.value.set(plugin.installId, []);
|
pluginLogs.value.set(plugin.installId, []);
|
||||||
|
|
||||||
function systemLog(message: string, isError = false): void {
|
function systemLog(message: string, isError = false): void {
|
||||||
pluginLogs.value.get(plugin.installId)?.push({
|
pluginLogs.value.get(plugin!.installId)?.push({
|
||||||
at: Date.now(),
|
at: Date.now(),
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
message,
|
message,
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const store = markRaw(new Pizzax('base', {
|
||||||
},
|
},
|
||||||
memo: {
|
memo: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: null,
|
default: null as string | null,
|
||||||
},
|
},
|
||||||
reactionAcceptance: {
|
reactionAcceptance: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
|
|
|
@ -29,7 +29,8 @@ export async function soundSettingsButton(soundSetting: Ref<SoundStore>): Promis
|
||||||
label: i18n.ts.sound,
|
label: i18n.ts.sound,
|
||||||
default: soundSetting.value.type ?? 'none',
|
default: soundSetting.value.type ?? 'none',
|
||||||
enum: soundsTypes.map(f => ({
|
enum: soundsTypes.map(f => ({
|
||||||
value: f ?? 'none', label: getSoundTypeName(f),
|
value: f ?? 'none' as Exclude<SoundType, null> | 'none',
|
||||||
|
label: getSoundTypeName(f),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
soundFile: {
|
soundFile: {
|
||||||
|
@ -81,16 +82,17 @@ export async function soundSettingsButton(soundSetting: Ref<SoundStore>): Promis
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
const res = buildSoundStore(result);
|
const res = buildSoundStore(result);
|
||||||
if (res) soundSetting.value = res;
|
if (res) soundSetting.value = res;
|
||||||
|
|
||||||
function buildSoundStore(result: any): SoundStore | null {
|
function buildSoundStore(r: NonNullable<typeof result>): SoundStore | null {
|
||||||
const type = (result.type === 'none' ? null : result.type) as SoundType;
|
const type = (r.type === 'none' ? null : r.type);
|
||||||
const volume = result.volume as number;
|
const volume = r.volume;
|
||||||
const fileId = result.soundFile?.id ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileId : undefined);
|
const fileId = r.soundFile?.id ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileId : undefined);
|
||||||
const fileUrl = result.soundFile?.url ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileUrl : undefined);
|
const fileUrl = r.soundFile?.url ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileUrl : undefined);
|
||||||
|
|
||||||
if (type === '_driveFile_') {
|
if (type === '_driveFile_') {
|
||||||
if (!fileUrl || !fileId) {
|
if (!fileUrl || !fileId) {
|
||||||
|
|
|
@ -5,55 +5,59 @@
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
type EnumItem = string | {
|
export type EnumItem = string | {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Hidden = boolean | ((v: any) => boolean);
|
type Hidden = boolean | ((v: any) => boolean);
|
||||||
|
|
||||||
export type FormItem = {
|
interface FormItemBase {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
hidden?: Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringFormItem extends FormItemBase {
|
||||||
type: 'string';
|
type: 'string';
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
|
||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
treatAsMfm?: boolean;
|
treatAsMfm?: boolean;
|
||||||
} | {
|
}
|
||||||
label?: string;
|
|
||||||
|
export interface NumberFormItem extends FormItemBase {
|
||||||
type: 'number';
|
type: 'number';
|
||||||
default?: number | null;
|
default?: number | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
|
||||||
step?: number;
|
step?: number;
|
||||||
} | {
|
}
|
||||||
label?: string;
|
|
||||||
|
export interface BooleanFormItem extends FormItemBase {
|
||||||
type: 'boolean';
|
type: 'boolean';
|
||||||
default?: boolean | null;
|
default?: boolean | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
hidden?: Hidden;
|
}
|
||||||
} | {
|
|
||||||
label?: string;
|
export interface EnumFormItem extends FormItemBase {
|
||||||
type: 'enum';
|
type: 'enum';
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
|
||||||
enum: EnumItem[];
|
enum: EnumItem[];
|
||||||
} | {
|
}
|
||||||
label?: string;
|
|
||||||
|
export interface RadioFormItem extends FormItemBase {
|
||||||
type: 'radio';
|
type: 'radio';
|
||||||
default?: unknown | null;
|
default?: unknown | null;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
|
||||||
options: {
|
options: {
|
||||||
label: string;
|
label: string;
|
||||||
value: unknown;
|
value: unknown;
|
||||||
}[];
|
}[];
|
||||||
} | {
|
}
|
||||||
label?: string;
|
|
||||||
|
export interface RangeFormItem extends FormItemBase {
|
||||||
type: 'range';
|
type: 'range';
|
||||||
default?: number | null;
|
default?: number | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -62,42 +66,80 @@ export type FormItem = {
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
textConverter?: (value: number) => string;
|
textConverter?: (value: number) => string;
|
||||||
hidden?: Hidden;
|
}
|
||||||
} | {
|
|
||||||
label?: string;
|
export interface ObjectFormItem extends FormItemBase {
|
||||||
type: 'object';
|
type: 'object';
|
||||||
default?: Record<string, unknown> | null;
|
default?: Record<string, unknown> | null;
|
||||||
hidden: Hidden;
|
}
|
||||||
} | {
|
|
||||||
label?: string;
|
export interface ArrayFormItem extends FormItemBase {
|
||||||
type: 'array';
|
type: 'array';
|
||||||
default?: unknown[] | null;
|
default?: unknown[] | null;
|
||||||
hidden: Hidden;
|
}
|
||||||
} | {
|
|
||||||
|
export interface ButtonFormItem extends FormItemBase {
|
||||||
type: 'button';
|
type: 'button';
|
||||||
content?: string;
|
content?: string;
|
||||||
hidden?: Hidden;
|
|
||||||
action: (ev: MouseEvent, v: any) => void;
|
action: (ev: MouseEvent, v: any) => void;
|
||||||
} | {
|
}
|
||||||
|
|
||||||
|
export interface DriveFileFormItem extends FormItemBase {
|
||||||
type: 'drive-file';
|
type: 'drive-file';
|
||||||
defaultFileId?: string | null;
|
defaultFileId?: string | null;
|
||||||
hidden?: Hidden;
|
|
||||||
validate?: (v: Misskey.entities.DriveFile) => Promise<boolean>;
|
validate?: (v: Misskey.entities.DriveFile) => Promise<boolean>;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export type FormItem =
|
||||||
|
StringFormItem |
|
||||||
|
NumberFormItem |
|
||||||
|
BooleanFormItem |
|
||||||
|
EnumFormItem |
|
||||||
|
RadioFormItem |
|
||||||
|
RangeFormItem |
|
||||||
|
ObjectFormItem |
|
||||||
|
ArrayFormItem |
|
||||||
|
ButtonFormItem |
|
||||||
|
DriveFileFormItem;
|
||||||
|
|
||||||
export type Form = Record<string, FormItem>;
|
export type Form = Record<string, FormItem>;
|
||||||
|
|
||||||
|
export type FormItemWithDefault = FormItem & {
|
||||||
|
default: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormWithDefault = Record<string, FormItemWithDefault>;
|
||||||
|
|
||||||
|
type GetRadioItemType<Item extends RadioFormItem = RadioFormItem> = Item['options'][number]['value'];
|
||||||
|
type GetEnumItemType<Item extends EnumFormItem, E = Item['enum'][number]> = E extends { value: unknown } ? E['value'] : E;
|
||||||
|
|
||||||
|
type InferDefault<T, Fallback> = T extends { default: infer D }
|
||||||
|
? D extends undefined ? Fallback : D
|
||||||
|
: Fallback;
|
||||||
|
|
||||||
|
type NonNullableIfRequired<T, Item extends FormItem> =
|
||||||
|
Item extends { required: false } ? T | null | undefined : NonNullable<T>;
|
||||||
|
|
||||||
type GetItemType<Item extends FormItem> =
|
type GetItemType<Item extends FormItem> =
|
||||||
Item['type'] extends 'string' ? string :
|
Item extends StringFormItem
|
||||||
Item['type'] extends 'number' ? number :
|
? NonNullableIfRequired<InferDefault<Item, string>, Item>
|
||||||
Item['type'] extends 'boolean' ? boolean :
|
: Item extends NumberFormItem
|
||||||
Item['type'] extends 'radio' ? unknown :
|
? NonNullableIfRequired<InferDefault<Item, number>, Item>
|
||||||
Item['type'] extends 'range' ? number :
|
: Item extends BooleanFormItem
|
||||||
Item['type'] extends 'enum' ? string :
|
? boolean
|
||||||
Item['type'] extends 'array' ? unknown[] :
|
: Item extends RadioFormItem
|
||||||
Item['type'] extends 'object' ? Record<string, unknown> :
|
? GetRadioItemType<Item>
|
||||||
Item['type'] extends 'drive-file' ? Misskey.entities.DriveFile | undefined :
|
: Item extends RangeFormItem
|
||||||
never;
|
? NonNullableIfRequired<InferDefault<RangeFormItem, number>, Item>
|
||||||
|
: Item extends EnumFormItem
|
||||||
|
? GetEnumItemType<Item>
|
||||||
|
: Item extends ArrayFormItem
|
||||||
|
? NonNullableIfRequired<InferDefault<ArrayFormItem, unknown[]>, Item>
|
||||||
|
: Item extends ObjectFormItem
|
||||||
|
? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item>
|
||||||
|
: Item extends DriveFileFormItem
|
||||||
|
? Misskey.entities.DriveFile | undefined
|
||||||
|
: never;
|
||||||
|
|
||||||
export type GetFormResultType<F extends Form> = {
|
export type GetFormResultType<F extends Form> = {
|
||||||
[P in keyof F]: GetItemType<F[P]>;
|
[P in keyof F]: GetItemType<F[P]>;
|
||||||
|
|
|
@ -101,7 +101,7 @@ export async function getNoteClipMenu(props: {
|
||||||
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: null,
|
default: null as string | null,
|
||||||
label: i18n.ts.name,
|
label: i18n.ts.name,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
|
|
@ -132,6 +132,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
||||||
const userDetailed = await misskeyApi('users/show', {
|
const userDetailed = await misskeyApi('users/show', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { canceled, result } = await os.form(i18n.ts.editMemo, {
|
const { canceled, result } = await os.form(i18n.ts.editMemo, {
|
||||||
memo: {
|
memo: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -141,6 +142,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
||||||
default: userDetailed.memo,
|
default: userDetailed.memo,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('users/update-memo', {
|
os.apiWithDialog('users/update-memo', {
|
||||||
|
|
|
@ -25,29 +25,31 @@ import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
||||||
import XCalendar from './WidgetActivity.calendar.vue';
|
import XCalendar from './WidgetActivity.calendar.vue';
|
||||||
import XChart from './WidgetActivity.chart.vue';
|
import XChart from './WidgetActivity.chart.vue';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { $i } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const name = 'activity';
|
const name = 'activity';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 0,
|
default: 0,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
|
|
||||||
const name = 'ai';
|
const name = 'ai';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ const touched = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMousemove = (ev: MouseEvent) => {
|
const onMousemove = (ev: MouseEvent) => {
|
||||||
|
if (!live2d.value || !live2d.value.contentWindow) return;
|
||||||
|
|
||||||
const iframeRect = live2d.value.getBoundingClientRect();
|
const iframeRect = live2d.value.getBoundingClientRect();
|
||||||
live2d.value.contentWindow.postMessage({
|
live2d.value.contentWindow.postMessage({
|
||||||
type: 'moveCursor',
|
type: 'moveCursor',
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ref } from 'vue';
|
||||||
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
|
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
||||||
|
@ -35,16 +35,16 @@ const name = 'aiscript';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
script: {
|
script: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
multiline: true,
|
multiline: true,
|
||||||
default: '(1 + 1)',
|
default: '(1 + 1)',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ const run = async () => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: err,
|
text: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ import type { Ref } from 'vue';
|
||||||
import { Interpreter, Parser } from '@syuilo/aiscript';
|
import { Interpreter, Parser } from '@syuilo/aiscript';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
@ -31,15 +31,15 @@ const name = 'aiscriptApp';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
script: {
|
script: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
multiline: true,
|
multiline: true,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ async function run() {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: 'AiScript Error',
|
title: 'AiScript Error',
|
||||||
text: err.message,
|
text: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.bdayFRoot">
|
<div :class="$style.bdayFRoot">
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<div v-else-if="users.length > 0" :class="$style.bdayFGrid">
|
<div v-else-if="users.length > 0" :class="$style.bdayFGrid">
|
||||||
<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar>
|
<MkAvatar v-for="user in users" :key="user.id" :user="user.followee!" link preview></MkAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.bdayFFallback">
|
<div v-else :class="$style.bdayFFallback">
|
||||||
<MkResult type="empty"/>
|
<MkResult type="empty"/>
|
||||||
|
@ -27,7 +27,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -37,10 +37,10 @@ const name = i18n.ts._widgets.birthdayFollowings;
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { Interpreter, Parser } from '@syuilo/aiscript';
|
import { Interpreter, Parser } from '@syuilo/aiscript';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
@ -25,19 +25,19 @@ const name = 'button';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
label: {
|
label: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: 'BUTTON',
|
default: 'BUTTON',
|
||||||
},
|
},
|
||||||
colored: {
|
colored: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
script: {
|
script: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
multiline: true,
|
multiline: true,
|
||||||
default: 'Mk:dialog("hello" "world")',
|
default: 'Mk:dialog("hello" "world")',
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ const run = async () => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: err,
|
text: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ const name = 'calendar';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkChatHistories from '@/components/MkChatHistories.vue';
|
import MkChatHistories from '@/components/MkChatHistories.vue';
|
||||||
|
@ -28,10 +28,10 @@ const name = 'chat';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkClickerGame from '@/components/MkClickerGame.vue';
|
import MkClickerGame from '@/components/MkClickerGame.vue';
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ const name = 'clicker';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkAnalogClock from '@/components/MkAnalogClock.vue';
|
import MkAnalogClock from '@/components/MkAnalogClock.vue';
|
||||||
import MkDigitalClock from '@/components/MkDigitalClock.vue';
|
import MkDigitalClock from '@/components/MkDigitalClock.vue';
|
||||||
|
@ -43,76 +43,92 @@ const name = 'clock';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: 'radio' as const,
|
type: 'radio',
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
options: [{
|
options: [{
|
||||||
value: 'small', label: i18n.ts.small,
|
value: 'small' as const,
|
||||||
|
label: i18n.ts.small,
|
||||||
}, {
|
}, {
|
||||||
value: 'medium', label: i18n.ts.medium,
|
value: 'medium' as const,
|
||||||
|
label: i18n.ts.medium,
|
||||||
}, {
|
}, {
|
||||||
value: 'large', label: i18n.ts.large,
|
value: 'large' as const,
|
||||||
|
label: i18n.ts.large,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
thickness: {
|
thickness: {
|
||||||
type: 'radio' as const,
|
type: 'radio',
|
||||||
default: 0.2,
|
default: 0.2,
|
||||||
options: [{
|
options: [{
|
||||||
value: 0.1, label: 'thin',
|
value: 0.1 as const,
|
||||||
|
label: 'thin',
|
||||||
}, {
|
}, {
|
||||||
value: 0.2, label: 'medium',
|
value: 0.2 as const,
|
||||||
|
label: 'medium',
|
||||||
}, {
|
}, {
|
||||||
value: 0.3, label: 'thick',
|
value: 0.3 as const,
|
||||||
|
label: 'thick',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
graduations: {
|
graduations: {
|
||||||
type: 'radio' as const,
|
type: 'radio',
|
||||||
default: 'numbers',
|
default: 'numbers',
|
||||||
options: [{
|
options: [{
|
||||||
value: 'none', label: 'None',
|
value: 'none' as const,
|
||||||
|
label: 'None',
|
||||||
}, {
|
}, {
|
||||||
value: 'dots', label: 'Dots',
|
value: 'dots' as const,
|
||||||
|
label: 'Dots',
|
||||||
}, {
|
}, {
|
||||||
value: 'numbers', label: 'Numbers',
|
value: 'numbers' as const,
|
||||||
|
label: 'Numbers',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
fadeGraduations: {
|
fadeGraduations: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
sAnimation: {
|
sAnimation: {
|
||||||
type: 'radio' as const,
|
type: 'radio',
|
||||||
default: 'elastic',
|
default: 'elastic',
|
||||||
options: [{
|
options: [{
|
||||||
value: 'none', label: 'None',
|
value: 'none' as const,
|
||||||
|
label: 'None',
|
||||||
}, {
|
}, {
|
||||||
value: 'elastic', label: 'Elastic',
|
value: 'elastic' as const,
|
||||||
|
label: 'Elastic',
|
||||||
}, {
|
}, {
|
||||||
value: 'easeOut', label: 'Ease out',
|
value: 'easeOut' as const,
|
||||||
|
label: 'Ease out',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
twentyFour: {
|
twentyFour: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
type: 'radio' as const,
|
type: 'radio',
|
||||||
default: 'none',
|
default: 'none',
|
||||||
options: [{
|
options: [{
|
||||||
value: 'none', label: 'None',
|
value: 'none' as const,
|
||||||
|
label: 'None',
|
||||||
}, {
|
}, {
|
||||||
value: 'time', label: 'Time',
|
value: 'time' as const,
|
||||||
|
label: 'Time',
|
||||||
}, {
|
}, {
|
||||||
value: 'tz', label: 'TZ',
|
value: 'tz' as const,
|
||||||
|
label: 'TZ',
|
||||||
}, {
|
}, {
|
||||||
value: 'timeAndTz', label: 'Time + TZ',
|
value: 'timeAndTz' as const,
|
||||||
|
label: 'Time + TZ',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
timezone: {
|
timezone: {
|
||||||
type: 'enum' as const,
|
type: 'enum',
|
||||||
default: null,
|
default: null,
|
||||||
enum: [...timezones.map((tz) => ({
|
enum: [...timezones.map((tz) => ({
|
||||||
label: tz.name,
|
label: tz.name,
|
||||||
|
@ -122,7 +138,7 @@ const widgetPropsDef = {
|
||||||
value: null,
|
value: null,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { timezones } from '@/utility/timezones.js';
|
import { timezones } from '@/utility/timezones.js';
|
||||||
import MkDigitalClock from '@/components/MkDigitalClock.vue';
|
import MkDigitalClock from '@/components/MkDigitalClock.vue';
|
||||||
|
|
||||||
|
@ -25,24 +25,24 @@ const name = 'digitalClock';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 1.5,
|
default: 1.5,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
},
|
||||||
showMs: {
|
showMs: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showLabel: {
|
showLabel: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
timezone: {
|
timezone: {
|
||||||
type: 'enum' as const,
|
type: 'enum',
|
||||||
default: null,
|
default: null,
|
||||||
enum: [...timezones.map((tz) => ({
|
enum: [...timezones.map((tz) => ({
|
||||||
label: tz.name,
|
label: tz.name,
|
||||||
|
@ -52,7 +52,7 @@ const widgetPropsDef = {
|
||||||
value: null,
|
value: null,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :showHeader="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation">
|
<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-federation class="mkw-federation">
|
||||||
<template #icon><i class="ti ti-whirl"></i></template>
|
<template #icon><i class="ti ti-whirl"></i></template>
|
||||||
<template #header>{{ i18n.ts._widgets.federation }}</template>
|
<template #header>{{ i18n.ts._widgets.federation }}</template>
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkMiniChart from '@/components/MkMiniChart.vue';
|
import MkMiniChart from '@/components/MkMiniChart.vue';
|
||||||
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
|
@ -42,10 +42,10 @@ const name = 'federation';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkTagCloud from '@/components/MkTagCloud.vue';
|
import MkTagCloud from '@/components/MkTagCloud.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -34,10 +34,10 @@ const name = 'instanceCloud';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_panel">
|
<div class="_panel">
|
||||||
<div :class="$style.container" :style="{ backgroundImage: instance.bannerUrl ? `url(${ instance.bannerUrl })` : null }">
|
<div :class="$style.container" :style="{ backgroundImage: instance.bannerUrl ? `url(${ instance.bannerUrl })` : undefined }">
|
||||||
<div :class="$style.iconContainer">
|
<div :class="$style.iconContainer">
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/>
|
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,14 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
const name = 'instanceInfo';
|
const name = 'instanceInfo';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { onUnmounted, reactive, ref } from 'vue';
|
import { onUnmounted, reactive, ref } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import kmg from '@/filters/kmg.js';
|
import kmg from '@/filters/kmg.js';
|
||||||
import * as sound from '@/utility/sound.js';
|
import * as sound from '@/utility/sound.js';
|
||||||
|
@ -66,14 +66,14 @@ const name = 'jobQueue';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
sound: {
|
sound: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header>{{ i18n.ts._widgets.memo }}</template>
|
<template #header>{{ i18n.ts._widgets.memo }}</template>
|
||||||
|
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<textarea v-model="text" :style="`height: ${widgetProps.height}px;`" :class="$style.textarea" :placeholder="i18n.ts.placeholder" @input="onChange"></textarea>
|
<textarea v-model="text" :style="`height: ${widgetProps.height}px;`" :class="$style.textarea" :placeholder="i18n.ts.memo" @input="onChange"></textarea>
|
||||||
<button :class="$style.save" :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
|
<button :class="$style.save" :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -28,14 +28,14 @@ const name = 'memo';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 100,
|
default: 100,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import type { notificationTypes as notificationTypes_typeReferenceOnly } from '@@/js/const.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -29,19 +30,19 @@ const name = 'notifications';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 300,
|
default: 300,
|
||||||
},
|
},
|
||||||
excludeTypes: {
|
excludeTypes: {
|
||||||
type: 'array' as const,
|
type: 'array',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
default: [],
|
default: [] as (typeof notificationTypes_typeReferenceOnly[number])[],
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
|
@ -27,10 +27,10 @@ const name = 'onlineUsers';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { onUnmounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -38,14 +38,14 @@ const name = 'photos';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
|
||||||
const name = 'postForm';
|
const name = 'postForm';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_panel">
|
<div class="_panel">
|
||||||
<div :class="$style.container" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
<div :class="$style.container" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : undefined }">
|
||||||
<div :class="$style.avatarContainer">
|
<div :class="$style.avatarContainer">
|
||||||
<MkAvatar :class="$style.avatar" :user="$i"/>
|
<MkAvatar :class="$style.avatar" :user="$i"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,14 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { $i } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
|
|
||||||
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const name = 'profile';
|
const name = 'profile';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { url as base } from '@@/js/config.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
@ -34,22 +34,22 @@ const name = 'rss';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
url: {
|
url: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
||||||
},
|
},
|
||||||
refreshIntervalSec: {
|
refreshIntervalSec: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 60,
|
default: 60,
|
||||||
},
|
},
|
||||||
maxEntries: {
|
maxEntries: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 15,
|
default: 15,
|
||||||
},
|
},
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import MarqueeText from '@/components/MkMarqueeText.vue';
|
import MarqueeText from '@/components/MkMarqueeText.vue';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { shuffle } from '@/utility/shuffle.js';
|
import { shuffle } from '@/utility/shuffle.js';
|
||||||
import { url as base } from '@@/js/config.js';
|
import { url as base } from '@@/js/config.js';
|
||||||
|
@ -42,41 +42,41 @@ const name = 'rssTicker';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
url: {
|
url: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
||||||
},
|
},
|
||||||
shuffle: {
|
shuffle: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
refreshIntervalSec: {
|
refreshIntervalSec: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 60,
|
default: 60,
|
||||||
},
|
},
|
||||||
maxEntries: {
|
maxEntries: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 15,
|
default: 15,
|
||||||
},
|
},
|
||||||
duration: {
|
duration: {
|
||||||
type: 'range' as const,
|
type: 'range',
|
||||||
default: 70,
|
default: 70,
|
||||||
step: 1,
|
step: 1,
|
||||||
min: 5,
|
min: 5,
|
||||||
max: 200,
|
max: 200,
|
||||||
},
|
},
|
||||||
reverse: {
|
reverse: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -32,15 +32,15 @@ const name = 'slideshow';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
height: {
|
height: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 300,
|
default: 300,
|
||||||
},
|
},
|
||||||
folderId: {
|
folderId: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: null,
|
default: null as string | null,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ const slideA = useTemplateRef('slideA');
|
||||||
const slideB = useTemplateRef('slideB');
|
const slideB = useTemplateRef('slideB');
|
||||||
|
|
||||||
const change = () => {
|
const change = () => {
|
||||||
if (images.value.length === 0) return;
|
if (images.value.length === 0 || slideA.value == null || slideB.value == null) return;
|
||||||
|
|
||||||
const index = Math.floor(Math.random() * images.value.length);
|
const index = Math.floor(Math.random() * images.value.length);
|
||||||
const img = `url(${ images.value[index].url })`;
|
const img = `url(${ images.value[index].url })`;
|
||||||
|
@ -73,11 +73,12 @@ const change = () => {
|
||||||
|
|
||||||
slideA.value.style.backgroundImage = img;
|
slideA.value.style.backgroundImage = img;
|
||||||
|
|
||||||
slideB.value.classList.remove('anime');
|
slideB.value!.classList.remove('anime');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetch = () => {
|
const fetch = () => {
|
||||||
|
if (slideA.value == null || slideB.value == null) return;
|
||||||
fetching.value = true;
|
fetching.value = true;
|
||||||
|
|
||||||
misskeyApi('drive/files', {
|
misskeyApi('drive/files', {
|
||||||
|
@ -87,8 +88,8 @@ const fetch = () => {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
images.value = res;
|
images.value = res;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
slideA.value.style.backgroundImage = '';
|
slideA.value!.style.backgroundImage = '';
|
||||||
slideB.value.style.backgroundImage = '';
|
slideB.value!.style.backgroundImage = '';
|
||||||
change();
|
change();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<button class="_button" @click="choose">
|
<button class="_button" @click="choose">
|
||||||
<span>{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : i18n.ts._timelines[widgetProps.src] }}</span>
|
<span>{{ headerTitle }}</span>
|
||||||
<i :class="menuOpened ? 'ti ti-chevron-up' : 'ti ti-chevron-down'" style="margin-left: 8px;"></i>
|
<i :class="menuOpened ? 'ti ti-chevron-up' : 'ti ti-chevron-down'" style="margin-left: 8px;"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,51 +25,59 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkStreamingNotesTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/>
|
<MkStreamingNotesTimeline
|
||||||
|
:key="widgetProps.src === 'list' ? `list:${widgetProps.list?.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna?.id}` : widgetProps.src"
|
||||||
|
:src="widgetProps.src"
|
||||||
|
:list="widgetProps.list ? widgetProps.list.id : undefined"
|
||||||
|
:antenna="widgetProps.antenna ? widgetProps.antenna.id : undefined"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
|
||||||
const name = 'timeline';
|
const name = 'timeline';
|
||||||
|
|
||||||
|
type TlSrc = typeof basicTimelineTypes[number] | 'list' | 'antenna';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 300,
|
default: 300,
|
||||||
},
|
},
|
||||||
src: {
|
src: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: 'home',
|
default: 'home' as TlSrc,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
antenna: {
|
antenna: {
|
||||||
type: 'object' as const,
|
type: 'object',
|
||||||
default: null,
|
default: null as Misskey.entities.Antenna | null,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
type: 'object' as const,
|
type: 'object',
|
||||||
default: null,
|
default: null as Misskey.entities.UserList | null,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -84,12 +92,22 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
|
|
||||||
const menuOpened = ref(false);
|
const menuOpened = ref(false);
|
||||||
|
|
||||||
const setSrc = (src) => {
|
const headerTitle = computed<string>(() => {
|
||||||
|
if (widgetProps.src === 'list' && widgetProps.list != null) {
|
||||||
|
return widgetProps.list.name;
|
||||||
|
} else if (widgetProps.src === 'antenna' && widgetProps.antenna != null) {
|
||||||
|
return widgetProps.antenna.name;
|
||||||
|
} else {
|
||||||
|
return i18n.ts._timelines[widgetProps.src];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setSrc = (src: TlSrc) => {
|
||||||
widgetProps.src = src;
|
widgetProps.src = src;
|
||||||
save();
|
save();
|
||||||
};
|
};
|
||||||
|
|
||||||
const choose = async (ev) => {
|
const choose = async (ev: MouseEvent) => {
|
||||||
menuOpened.value = true;
|
menuOpened.value = true;
|
||||||
const [antennas, lists] = await Promise.all([
|
const [antennas, lists] = await Promise.all([
|
||||||
misskeyApi('antennas/list'),
|
misskeyApi('antennas/list'),
|
||||||
|
|
|
@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkMiniChart from '@/components/MkMiniChart.vue';
|
import MkMiniChart from '@/components/MkMiniChart.vue';
|
||||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
|
@ -40,10 +40,10 @@ const name = 'hashtags';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -19,29 +19,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { onUnmounted, ref, watch } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
|
|
||||||
const name = 'unixClock';
|
const name = 'unixClock';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 1.5,
|
default: 1.5,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
},
|
||||||
showMs: {
|
showMs: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showLabel: {
|
showLabel: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
emit,
|
emit,
|
||||||
);
|
);
|
||||||
|
|
||||||
let intervalId;
|
let intervalId: number | null = null;
|
||||||
const ss = ref('');
|
const ss = ref('');
|
||||||
const ms = ref('');
|
const ms = ref('');
|
||||||
const showColon = ref(false);
|
const showColon = ref(false);
|
||||||
|
@ -84,7 +84,10 @@ watch(() => widgetProps.showMs, () => {
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.clearInterval(intervalId);
|
if (intervalId) {
|
||||||
|
window.clearInterval(intervalId);
|
||||||
|
intervalId = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -40,15 +40,15 @@ const name = 'userList';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
listId: {
|
listId: {
|
||||||
type: 'string' as const,
|
type: 'string',
|
||||||
default: null,
|
default: null as string | null,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
emit,
|
emit,
|
||||||
);
|
);
|
||||||
|
|
||||||
const list = ref<Misskey.entities.UserList>();
|
const list = ref<Misskey.entities.UserList | null>(null);
|
||||||
const users = ref<Misskey.entities.UserDetailed[]>([]);
|
const users = ref<Misskey.entities.UserDetailed[]>([]);
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ async function chooseList() {
|
||||||
})),
|
})),
|
||||||
default: widgetProps.listId,
|
default: widgetProps.listId,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || list == null) return;
|
||||||
|
|
||||||
widgetProps.listId = list.id;
|
widgetProps.listId = list.id;
|
||||||
save();
|
save();
|
||||||
|
@ -92,7 +92,7 @@ const fetch = () => {
|
||||||
}).then(_list => {
|
}).then(_list => {
|
||||||
list.value = _list;
|
list.value = _list;
|
||||||
misskeyApi('users/show', {
|
misskeyApi('users/show', {
|
||||||
userIds: list.value.userIds,
|
userIds: list.value.userIds ?? [],
|
||||||
}).then(_users => {
|
}).then(_users => {
|
||||||
users.value = _users;
|
users.value = _users;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
|
|
|
@ -80,7 +80,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
|
connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>,
|
||||||
meta: Misskey.entities.ServerInfoResponse
|
meta: Misskey.entities.ServerInfoResponse
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import XPie from './pie.vue';
|
import XPie from './pie.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
|
connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>,
|
||||||
meta: Misskey.entities.ServerInfoResponse
|
meta: Misskey.entities.ServerInfoResponse
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/>
|
<XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/>
|
||||||
<XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/>
|
<XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/>
|
||||||
<XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/>
|
<XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/>
|
||||||
<XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/>
|
<XDisk v-else-if="widgetProps.view === 4" :meta="meta"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -30,7 +30,7 @@ import XCpu from './cpu.vue';
|
||||||
import XMemory from './mem.vue';
|
import XMemory from './mem.vue';
|
||||||
import XDisk from './disk.vue';
|
import XDisk from './disk.vue';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -39,19 +39,19 @@ const name = 'serverMetric';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean' as const,
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
type: 'number' as const,
|
type: 'number',
|
||||||
default: 0,
|
default: 0,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
};
|
} satisfies FormWithDefault;
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import XPie from './pie.vue';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
|
connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>,
|
||||||
meta: Misskey.entities.ServerInfoResponse
|
meta: Misskey.entities.ServerInfoResponse
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ import bytes from '@/filters/bytes.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
|
connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>,
|
||||||
meta: Misskey.entities.ServerInfoResponse
|
meta: Misskey.entities.ServerInfoResponse
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reactive, watch } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
|
import type { Reactive } from 'vue';
|
||||||
import { throttle } from 'throttle-debounce';
|
import { throttle } from 'throttle-debounce';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
|
|
||||||
|
@ -28,17 +29,17 @@ export type WidgetComponentExpose = {
|
||||||
configure: () => void;
|
configure: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>(
|
export const useWidgetPropsManager = <F extends FormWithDefault>(
|
||||||
name: string,
|
name: string,
|
||||||
propsDef: F,
|
propsDef: F,
|
||||||
props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
|
props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
|
||||||
emit: WidgetComponentEmits<GetFormResultType<F>>,
|
emit: WidgetComponentEmits<GetFormResultType<F>>,
|
||||||
): {
|
): {
|
||||||
widgetProps: GetFormResultType<F>;
|
widgetProps: Reactive<GetFormResultType<F>>;
|
||||||
save: () => void;
|
save: () => void;
|
||||||
configure: () => void;
|
configure: () => void;
|
||||||
} => {
|
} => {
|
||||||
const widgetProps = reactive(props.widget ? deepClone(props.widget.data) : {});
|
const widgetProps = reactive<GetFormResultType<F>>((props.widget ? deepClone(props.widget.data) : {}) as GetFormResultType<F>);
|
||||||
|
|
||||||
const mergeProps = () => {
|
const mergeProps = () => {
|
||||||
for (const prop of Object.keys(propsDef)) {
|
for (const prop of Object.keys(propsDef)) {
|
||||||
|
@ -47,12 +48,13 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(widgetProps, () => {
|
watch(widgetProps, () => {
|
||||||
mergeProps();
|
mergeProps();
|
||||||
}, { deep: true, immediate: true });
|
}, { deep: true, immediate: true });
|
||||||
|
|
||||||
const save = throttle(3000, () => {
|
const save = throttle(3000, () => {
|
||||||
emit('updateProps', widgetProps);
|
emit('updateProps', widgetProps as GetFormResultType<F>);
|
||||||
});
|
});
|
||||||
|
|
||||||
const configure = async () => {
|
const configure = async () => {
|
||||||
|
|
Loading…
Reference in New Issue