diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2025-07-06 19:36:11 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-06 19:36:11 +0900 |
| commit | a8abb03d1785791ab40e57ab49c87640914532c9 (patch) | |
| tree | f80ea7a393a278e29f9642e86be8b341fcb4b95b /packages/frontend/src/widgets | |
| parent | Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff) | |
| download | misskey-a8abb03d1785791ab40e57ab49c87640914532c9.tar.gz misskey-a8abb03d1785791ab40e57ab49c87640914532c9.tar.bz2 misskey-a8abb03d1785791ab40e57ab49c87640914532c9.zip | |
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>
Diffstat (limited to 'packages/frontend/src/widgets')
34 files changed, 233 insertions, 186 deletions
diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue index db03d1406c..9625abb4d1 100644 --- a/packages/frontend/src/widgets/WidgetActivity.vue +++ b/packages/frontend/src/widgets/WidgetActivity.vue @@ -25,29 +25,31 @@ import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js'; import XCalendar from './WidgetActivity.calendar.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 MkContainer from '@/components/MkContainer.vue'; -import { $i } from '@/i.js'; +import { ensureSignin } from '@/i.js'; import { i18n } from '@/i18n.js'; +const $i = ensureSignin(); + const name = 'activity'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, view: { - type: 'number' as const, + type: 'number', default: 0, hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue index 2bc7facc88..3951de1d84 100644 --- a/packages/frontend/src/widgets/WidgetAichan.vue +++ b/packages/frontend/src/widgets/WidgetAichan.vue @@ -13,16 +13,16 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, onUnmounted, useTemplateRef } from 'vue'; import { useWidgetPropsManager } 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 widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -42,6 +42,8 @@ const touched = () => { }; const onMousemove = (ev: MouseEvent) => { + if (!live2d.value || !live2d.value.contentWindow) return; + const iframeRect = live2d.value.getBoundingClientRect(); live2d.value.contentWindow.postMessage({ type: 'moveCursor', diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index da71dcad29..a2d964718e 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -23,7 +23,7 @@ import { ref } from 'vue'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; import { useWidgetPropsManager } 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 MkContainer from '@/components/MkContainer.vue'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; @@ -35,16 +35,16 @@ const name = 'aiscript'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, script: { - type: 'string' as const, + type: 'string', multiline: true, default: '(1 + 1)', hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -106,7 +106,7 @@ const run = async () => { } catch (err) { os.alert({ type: 'error', - text: err, + text: err instanceof Error ? err.message : String(err), }); } }; diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index 429b0e0ffb..fdd4eaae06 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -18,7 +18,7 @@ import type { Ref } from 'vue'; import { Interpreter, Parser } from '@syuilo/aiscript'; import { useWidgetPropsManager } 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 { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; import { $i } from '@/i.js'; @@ -31,15 +31,15 @@ const name = 'aiscriptApp'; const widgetPropsDef = { script: { - type: 'string' as const, + type: 'string', multiline: true, default: '', }, showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -92,7 +92,7 @@ async function run() { os.alert({ type: 'error', title: 'AiScript Error', - text: err.message, + text: err instanceof Error ? err.message : String(err), }); } } diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue index 4790f143cb..d1991cd70a 100644 --- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue +++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.bdayFRoot"> <MkLoading v-if="fetching"/> <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 v-else :class="$style.bdayFFallback"> <MkResult type="empty"/> @@ -27,7 +27,7 @@ import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -37,10 +37,10 @@ const name = i18n.ts._widgets.birthdayFollowings; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue index 4afe735a22..e88a960f87 100644 --- a/packages/frontend/src/widgets/WidgetButton.vue +++ b/packages/frontend/src/widgets/WidgetButton.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { Interpreter, Parser } from '@syuilo/aiscript'; import { useWidgetPropsManager } 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 { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; import { $i } from '@/i.js'; @@ -25,19 +25,19 @@ const name = 'button'; const widgetPropsDef = { label: { - type: 'string' as const, + type: 'string', default: 'BUTTON', }, colored: { - type: 'boolean' as const, + type: 'boolean', default: true, }, script: { - type: 'string' as const, + type: 'string', multiline: true, default: 'Mk:dialog("hello" "world")', }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -81,7 +81,7 @@ const run = async () => { } catch (err) { os.alert({ type: 'error', - text: err, + text: err instanceof Error ? err.message : String(err), }); } }; diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index 54f78469b2..12c0a66c5c 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import { useWidgetPropsManager } 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 { useInterval } from '@@/js/use-interval.js'; @@ -49,10 +49,10 @@ const name = 'calendar'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetChat.vue b/packages/frontend/src/widgets/WidgetChat.vue index 43b2a6e522..8fee7f00f6 100644 --- a/packages/frontend/src/widgets/WidgetChat.vue +++ b/packages/frontend/src/widgets/WidgetChat.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { } from 'vue'; import { useWidgetPropsManager } 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 { i18n } from '@/i18n.js'; import MkChatHistories from '@/components/MkChatHistories.vue'; @@ -28,10 +28,10 @@ const name = 'chat'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue index 87ffd3d732..282a1a6d93 100644 --- a/packages/frontend/src/widgets/WidgetClicker.vue +++ b/packages/frontend/src/widgets/WidgetClicker.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { useWidgetPropsManager } 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 MkClickerGame from '@/components/MkClickerGame.vue'; @@ -22,10 +22,10 @@ const name = 'clicker'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue index 826ecf6e02..7aa69a39b5 100644 --- a/packages/frontend/src/widgets/WidgetClock.vue +++ b/packages/frontend/src/widgets/WidgetClock.vue @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed } from 'vue'; import { useWidgetPropsManager } 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 MkAnalogClock from '@/components/MkAnalogClock.vue'; import MkDigitalClock from '@/components/MkDigitalClock.vue'; @@ -43,76 +43,92 @@ const name = 'clock'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, size: { - type: 'radio' as const, + type: 'radio', default: 'medium', 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: { - type: 'radio' as const, + type: 'radio', default: 0.2, 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: { - type: 'radio' as const, + type: 'radio', default: 'numbers', 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: { - type: 'boolean' as const, + type: 'boolean', default: true, }, sAnimation: { - type: 'radio' as const, + type: 'radio', default: 'elastic', 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: { - type: 'boolean' as const, + type: 'boolean', default: false, }, label: { - type: 'radio' as const, + type: 'radio', default: 'none', 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: { - type: 'enum' as const, + type: 'enum', default: null, enum: [...timezones.map((tz) => ({ label: tz.name, @@ -122,7 +138,7 @@ const widgetPropsDef = { value: null, }], }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue index d79ec79d4f..b8cbc6429c 100644 --- a/packages/frontend/src/widgets/WidgetDigitalClock.vue +++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed } from 'vue'; import { useWidgetPropsManager } 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 MkDigitalClock from '@/components/MkDigitalClock.vue'; @@ -25,24 +25,24 @@ const name = 'digitalClock'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, fontSize: { - type: 'number' as const, + type: 'number', default: 1.5, step: 0.1, }, showMs: { - type: 'boolean' as const, + type: 'boolean', default: true, }, showLabel: { - type: 'boolean' as const, + type: 'boolean', default: true, }, timezone: { - type: 'enum' as const, + type: 'enum', default: null, enum: [...timezones.map((tz) => ({ label: tz.name, @@ -52,7 +52,7 @@ const widgetPropsDef = { value: null, }], }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index ca6f27bd09..3e880af03b 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <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 #header>{{ i18n.ts._widgets.federation }}</template> @@ -30,7 +30,7 @@ import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 MkMiniChart from '@/components/MkMiniChart.vue'; import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; @@ -42,10 +42,10 @@ const name = 'federation'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue index 0c9f98f9e3..8053dd43cf 100644 --- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue +++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue @@ -23,7 +23,7 @@ import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 MkTagCloud from '@/components/MkTagCloud.vue'; import * as os from '@/os.js'; @@ -34,10 +34,10 @@ const name = 'instanceCloud'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index 8d721298d5..722e6fadb2 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <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"> <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/> </div> @@ -22,14 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { useWidgetPropsManager } 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 { instance } from '@/instance.js'; const name = 'instanceInfo'; const widgetPropsDef = { -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 84fd4669cd..fba7d82062 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onUnmounted, reactive, ref } from 'vue'; import { useWidgetPropsManager } 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 kmg from '@/filters/kmg.js'; import * as sound from '@/utility/sound.js'; @@ -66,14 +66,14 @@ const name = 'jobQueue'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, sound: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue index 3df5c5bfd7..2beca8c43a 100644 --- a/packages/frontend/src/widgets/WidgetMemo.vue +++ b/packages/frontend/src/widgets/WidgetMemo.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header>{{ i18n.ts._widgets.memo }}</template> <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> </div> </MkContainer> @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, watch } from 'vue'; import { useWidgetPropsManager } 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 { store } from '@/store.js'; import { i18n } from '@/i18n.js'; @@ -28,14 +28,14 @@ const name = 'memo'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, height: { - type: 'number' as const, + type: 'number', default: 100, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue index a1af5d084c..8fb7968238 100644 --- a/packages/frontend/src/widgets/WidgetNotifications.vue +++ b/packages/frontend/src/widgets/WidgetNotifications.vue @@ -17,9 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; +import type { notificationTypes as notificationTypes_typeReferenceOnly } from '@@/js/const.js'; import { useWidgetPropsManager } 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 MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue'; import * as os from '@/os.js'; @@ -29,19 +30,19 @@ const name = 'notifications'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, height: { - type: 'number' as const, + type: 'number', default: 300, }, excludeTypes: { - type: 'array' as const, + type: 'array', hidden: true, - default: [], + default: [] as (typeof notificationTypes_typeReferenceOnly[number])[], }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index ce1871420a..9fd8c013d1 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; -import type { GetFormResultType } from '@/utility/form.js'; -import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; +import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; +import { misskeyApiGet } from '@/utility/misskey-api.js'; import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import number from '@/filters/number.js'; @@ -27,10 +27,10 @@ const name = 'onlineUsers'; const widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 6bbc52b2c2..e89a642b99 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -26,7 +26,7 @@ import { onUnmounted, ref } from 'vue'; import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } 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 { getStaticImageUrl } from '@/utility/media-proxy.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -38,14 +38,14 @@ const name = 'photos'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue index 3170eab305..9f464cac95 100644 --- a/packages/frontend/src/widgets/WidgetPostForm.vue +++ b/packages/frontend/src/widgets/WidgetPostForm.vue @@ -11,13 +11,13 @@ SPDX-License-Identifier: AGPL-3.0-only import { } from 'vue'; import { useWidgetPropsManager } 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'; const name = 'postForm'; const widgetPropsDef = { -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue index 3fe8378a39..be149dae35 100644 --- a/packages/frontend/src/widgets/WidgetProfile.vue +++ b/packages/frontend/src/widgets/WidgetProfile.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <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"> <MkAvatar :class="$style.avatar" :user="$i"/> </div> @@ -24,14 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; -import type { GetFormResultType } from '@/utility/form.js'; -import { $i } from '@/i.js'; +import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; +import { ensureSignin } from '@/i.js'; import { userPage } from '@/filters/user.js'; +const $i = ensureSignin(); + const name = 'profile'; const widgetPropsDef = { -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 2594262df1..e5499aa0da 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -26,7 +26,7 @@ import { url as base } from '@@/js/config.js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 { i18n } from '@/i18n.js'; @@ -34,22 +34,22 @@ const name = 'rss'; const widgetPropsDef = { url: { - type: 'string' as const, + type: 'string', default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, refreshIntervalSec: { - type: 'number' as const, + type: 'number', default: 60, }, maxEntries: { - type: 'number' as const, + type: 'number', default: 15, }, showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 7fe7c6111a..9d4feb784c 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -32,7 +32,7 @@ import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; 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 { shuffle } from '@/utility/shuffle.js'; import { url as base } from '@@/js/config.js'; @@ -42,41 +42,41 @@ const name = 'rssTicker'; const widgetPropsDef = { url: { - type: 'string' as const, + type: 'string', default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, shuffle: { - type: 'boolean' as const, + type: 'boolean', default: true, }, refreshIntervalSec: { - type: 'number' as const, + type: 'number', default: 60, }, maxEntries: { - type: 'number' as const, + type: 'number', default: 15, }, duration: { - type: 'range' as const, + type: 'range', default: 70, step: 1, min: 5, max: 200, }, reverse: { - type: 'boolean' as const, + type: 'boolean', default: false, }, showHeader: { - type: 'boolean' as const, + type: 'boolean', default: false, }, transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue index 3fe8cfa7e6..8e5dc9e8d3 100644 --- a/packages/frontend/src/widgets/WidgetSlideshow.vue +++ b/packages/frontend/src/widgets/WidgetSlideshow.vue @@ -22,7 +22,7 @@ import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -32,15 +32,15 @@ const name = 'slideshow'; const widgetPropsDef = { height: { - type: 'number' as const, + type: 'number', default: 300, }, folderId: { - type: 'string' as const, - default: null, + type: 'string', + default: null as string | null, hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -59,7 +59,7 @@ const slideA = useTemplateRef('slideA'); const slideB = useTemplateRef('slideB'); 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 img = `url(${ images.value[index].url })`; @@ -73,11 +73,12 @@ const change = () => { slideA.value.style.backgroundImage = img; - slideB.value.classList.remove('anime'); + slideB.value!.classList.remove('anime'); }, 1000); }; const fetch = () => { + if (slideA.value == null || slideB.value == null) return; fetching.value = true; misskeyApi('drive/files', { @@ -87,8 +88,8 @@ const fetch = () => { }).then(res => { images.value = res; fetching.value = false; - slideA.value.style.backgroundImage = ''; - slideB.value.style.backgroundImage = ''; + slideA.value!.style.backgroundImage = ''; + slideB.value!.style.backgroundImage = ''; change(); }); }; diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 9cbeb9cf2e..6c775fd98c 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template #header> <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> </button> </template> @@ -25,51 +25,59 @@ SPDX-License-Identifier: AGPL-3.0-only <p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p> </div> <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> </MkContainer> </template> <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 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 { misskeyApi } from '@/utility/misskey-api.js'; import MkContainer from '@/components/MkContainer.vue'; import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; 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'; const name = 'timeline'; +type TlSrc = typeof basicTimelineTypes[number] | 'list' | 'antenna'; + const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, height: { - type: 'number' as const, + type: 'number', default: 300, }, src: { - type: 'string' as const, - default: 'home', + type: 'string', + default: 'home' as TlSrc, hidden: true, }, antenna: { - type: 'object' as const, - default: null, + type: 'object', + default: null as Misskey.entities.Antenna | null, hidden: true, }, list: { - type: 'object' as const, - default: null, + type: 'object', + default: null as Misskey.entities.UserList | null, hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -84,12 +92,22 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, 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; save(); }; -const choose = async (ev) => { +const choose = async (ev: MouseEvent) => { menuOpened.value = true; const [antennas, lists] = await Promise.all([ misskeyApi('antennas/list'), diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index db09031c33..dcb900b0c9 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager } 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 MkMiniChart from '@/components/MkMiniChart.vue'; import { misskeyApiGet } from '@/utility/misskey-api.js'; @@ -40,10 +40,10 @@ const name = 'hashtags'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue index f51ef12a2a..226a4c73aa 100644 --- a/packages/frontend/src/widgets/WidgetUnixClock.vue +++ b/packages/frontend/src/widgets/WidgetUnixClock.vue @@ -19,29 +19,29 @@ SPDX-License-Identifier: AGPL-3.0-only import { onUnmounted, ref, watch } from 'vue'; import { useWidgetPropsManager } 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 widgetPropsDef = { transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, fontSize: { - type: 'number' as const, + type: 'number', default: 1.5, step: 0.1, }, showMs: { - type: 'boolean' as const, + type: 'boolean', default: true, }, showLabel: { - type: 'boolean' as const, + type: 'boolean', default: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -54,7 +54,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, emit, ); -let intervalId; +let intervalId: number | null = null; const ss = ref(''); const ms = ref(''); const showColon = ref(false); @@ -84,7 +84,10 @@ watch(() => widgetProps.showMs, () => { }, { immediate: true }); onUnmounted(() => { - window.clearInterval(intervalId); + if (intervalId) { + window.clearInterval(intervalId); + intervalId = null; + } }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue index eb86732817..d87ea5ade2 100644 --- a/packages/frontend/src/widgets/WidgetUserList.vue +++ b/packages/frontend/src/widgets/WidgetUserList.vue @@ -28,7 +28,7 @@ import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } 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 * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -40,15 +40,15 @@ const name = 'userList'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, listId: { - type: 'string' as const, - default: null, + type: 'string', + default: null as string | null, hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; @@ -61,7 +61,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, emit, ); -const list = ref<Misskey.entities.UserList>(); +const list = ref<Misskey.entities.UserList | null>(null); const users = ref<Misskey.entities.UserDetailed[]>([]); const fetching = ref(true); @@ -74,7 +74,7 @@ async function chooseList() { })), default: widgetProps.listId, }); - if (canceled) return; + if (canceled || list == null) return; widgetProps.listId = list.id; save(); @@ -92,7 +92,7 @@ const fetch = () => { }).then(_list => { list.value = _list; misskeyApi('users/show', { - userIds: list.value.userIds, + userIds: list.value.userIds ?? [], }).then(_users => { users.value = _users; fetching.value = false; diff --git a/packages/frontend/src/widgets/server-metric/cpu-mem.vue b/packages/frontend/src/widgets/server-metric/cpu-mem.vue index d8a876f936..fe86b73f99 100644 --- a/packages/frontend/src/widgets/server-metric/cpu-mem.vue +++ b/packages/frontend/src/widgets/server-metric/cpu-mem.vue @@ -80,7 +80,7 @@ import * as Misskey from 'misskey-js'; import { genId } from '@/utility/id.js'; const props = defineProps<{ - connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>, + connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>, meta: Misskey.entities.ServerInfoResponse }>(); diff --git a/packages/frontend/src/widgets/server-metric/cpu.vue b/packages/frontend/src/widgets/server-metric/cpu.vue index ba98a926ff..99f502d94c 100644 --- a/packages/frontend/src/widgets/server-metric/cpu.vue +++ b/packages/frontend/src/widgets/server-metric/cpu.vue @@ -20,7 +20,7 @@ import * as Misskey from 'misskey-js'; import XPie from './pie.vue'; const props = defineProps<{ - connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>, + connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>, meta: Misskey.entities.ServerInfoResponse }>(); diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue index 9026fefb20..f52b6fd12e 100644 --- a/packages/frontend/src/widgets/server-metric/index.vue +++ b/packages/frontend/src/widgets/server-metric/index.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only <XNet v-else-if="widgetProps.view === 1" :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"/> - <XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/> + <XDisk v-else-if="widgetProps.view === 4" :meta="meta"/> </div> </MkContainer> </template> @@ -30,7 +30,7 @@ import XCpu from './cpu.vue'; import XMemory from './mem.vue'; import XDisk from './disk.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 { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; @@ -39,19 +39,19 @@ const name = 'serverMetric'; const widgetPropsDef = { showHeader: { - type: 'boolean' as const, + type: 'boolean', default: true, }, transparent: { - type: 'boolean' as const, + type: 'boolean', default: false, }, view: { - type: 'number' as const, + type: 'number', default: 0, hidden: true, }, -}; +} satisfies FormWithDefault; type WidgetProps = GetFormResultType<typeof widgetPropsDef>; diff --git a/packages/frontend/src/widgets/server-metric/mem.vue b/packages/frontend/src/widgets/server-metric/mem.vue index ff4c6a44b4..089281b2ef 100644 --- a/packages/frontend/src/widgets/server-metric/mem.vue +++ b/packages/frontend/src/widgets/server-metric/mem.vue @@ -22,7 +22,7 @@ import XPie from './pie.vue'; import bytes from '@/filters/bytes.js'; const props = defineProps<{ - connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>, + connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>, meta: Misskey.entities.ServerInfoResponse }>(); diff --git a/packages/frontend/src/widgets/server-metric/net.vue b/packages/frontend/src/widgets/server-metric/net.vue index 5e08a47100..38acd633a6 100644 --- a/packages/frontend/src/widgets/server-metric/net.vue +++ b/packages/frontend/src/widgets/server-metric/net.vue @@ -55,7 +55,7 @@ import bytes from '@/filters/bytes.js'; import { genId } from '@/utility/id.js'; const props = defineProps<{ - connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>, + connection: Misskey.IChannelConnection<Misskey.Channels['serverStats']>, meta: Misskey.entities.ServerInfoResponse }>(); diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts index de4c369cbb..c5ca7ac26c 100644 --- a/packages/frontend/src/widgets/widget.ts +++ b/packages/frontend/src/widgets/widget.ts @@ -4,8 +4,9 @@ */ import { reactive, watch } from 'vue'; +import type { Reactive } from 'vue'; 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 { deepClone } from '@/utility/clone.js'; @@ -28,17 +29,17 @@ export type WidgetComponentExpose = { configure: () => void; }; -export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>( +export const useWidgetPropsManager = <F extends FormWithDefault>( name: string, propsDef: F, props: Readonly<WidgetComponentProps<GetFormResultType<F>>>, emit: WidgetComponentEmits<GetFormResultType<F>>, ): { - widgetProps: GetFormResultType<F>; + widgetProps: Reactive<GetFormResultType<F>>; save: () => 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 = () => { for (const prop of Object.keys(propsDef)) { @@ -47,12 +48,13 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default: } } }; + watch(widgetProps, () => { mergeProps(); }, { deep: true, immediate: true }); const save = throttle(3000, () => { - emit('updateProps', widgetProps); + emit('updateProps', widgetProps as GetFormResultType<F>); }); const configure = async () => { |