summaryrefslogtreecommitdiff
path: root/packages/client/src/widgets
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
commit9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch)
treece5959571a981b9c4047da3c7b3fd080aa44222c /packages/client/src/widgets
parentwip: retention for dashboard (diff)
downloadsharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz
sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2
sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip
rename: client -> frontend
Diffstat (limited to 'packages/client/src/widgets')
-rw-r--r--packages/client/src/widgets/activity.calendar.vue81
-rw-r--r--packages/client/src/widgets/activity.chart.vue92
-rw-r--r--packages/client/src/widgets/activity.vue90
-rw-r--r--packages/client/src/widgets/aichan.vue74
-rw-r--r--packages/client/src/widgets/aiscript.vue175
-rw-r--r--packages/client/src/widgets/button.vue103
-rw-r--r--packages/client/src/widgets/calendar.vue213
-rw-r--r--packages/client/src/widgets/clock.vue203
-rw-r--r--packages/client/src/widgets/digital-clock.vue92
-rw-r--r--packages/client/src/widgets/federation.vue147
-rw-r--r--packages/client/src/widgets/index.ts53
-rw-r--r--packages/client/src/widgets/instance-cloud.vue81
-rw-r--r--packages/client/src/widgets/job-queue.vue197
-rw-r--r--packages/client/src/widgets/memo.vue111
-rw-r--r--packages/client/src/widgets/notifications.vue70
-rw-r--r--packages/client/src/widgets/online-users.vue78
-rw-r--r--packages/client/src/widgets/photos.vue123
-rw-r--r--packages/client/src/widgets/post-form.vue35
-rw-r--r--packages/client/src/widgets/rss-ticker.vue152
-rw-r--r--packages/client/src/widgets/rss.vue96
-rw-r--r--packages/client/src/widgets/server-metric/cpu-mem.vue167
-rw-r--r--packages/client/src/widgets/server-metric/cpu.vue65
-rw-r--r--packages/client/src/widgets/server-metric/disk.vue57
-rw-r--r--packages/client/src/widgets/server-metric/index.vue87
-rw-r--r--packages/client/src/widgets/server-metric/mem.vue73
-rw-r--r--packages/client/src/widgets/server-metric/net.vue140
-rw-r--r--packages/client/src/widgets/server-metric/pie.vue52
-rw-r--r--packages/client/src/widgets/slideshow.vue159
-rw-r--r--packages/client/src/widgets/timeline.vue129
-rw-r--r--packages/client/src/widgets/trends.vue120
-rw-r--r--packages/client/src/widgets/unix-clock.vue116
-rw-r--r--packages/client/src/widgets/user-list.vue136
-rw-r--r--packages/client/src/widgets/widget.ts73
33 files changed, 0 insertions, 3640 deletions
diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue
deleted file mode 100644
index 84f6af1c13..0000000000
--- a/packages/client/src/widgets/activity.calendar.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<template>
-<svg viewBox="0 0 21 7">
- <rect v-for="record in activity" class="day"
- width="1" height="1"
- :x="record.x" :y="record.date.weekday"
- rx="1" ry="1"
- fill="transparent">
- <title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title>
- </rect>
- <rect v-for="record in activity" class="day"
- :width="record.v" :height="record.v"
- :x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)"
- rx="1" ry="1"
- :fill="record.color"
- style="pointer-events: none;"/>
- <rect class="today"
- width="1" height="1"
- :x="activity[0].x" :y="activity[0].date.weekday"
- rx="1" ry="1"
- fill="none"
- stroke-width="0.1"
- stroke="#f73520"/>
-</svg>
-</template>
-
-<script lang="ts" setup>
-const props = defineProps<{
- activity: any[]
-}>();
-
-for (const d of props.activity) {
- d.total = d.notes + d.replies + d.renotes;
-}
-const peak = Math.max(...props.activity.map(d => d.total));
-
-const now = new Date();
-const year = now.getFullYear();
-const month = now.getMonth();
-const day = now.getDate();
-
-let x = 20;
-props.activity.slice().forEach((d, i) => {
- d.x = x;
-
- const date = new Date(year, month, day - i);
- d.date = {
- year: date.getFullYear(),
- month: date.getMonth(),
- day: date.getDate(),
- weekday: date.getDay(),
- };
-
- d.v = peak === 0 ? 0 : d.total / (peak / 2);
- if (d.v > 1) d.v = 1;
- const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
- const cs = d.v * 100;
- const cl = 15 + ((1 - d.v) * 80);
- d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
-
- if (d.date.weekday === 0) x--;
-});
-</script>
-
-<style lang="scss" scoped>
-svg {
- display: block;
- padding: 16px;
- width: 100%;
- box-sizing: border-box;
-
- > rect {
- transform-origin: center;
-
- &.day {
- &:hover {
- fill: rgba(#000, 0.05);
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/activity.chart.vue b/packages/client/src/widgets/activity.chart.vue
deleted file mode 100644
index b61e419f94..0000000000
--- a/packages/client/src/widgets/activity.chart.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<template>
-<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" @mousedown.prevent="onMousedown">
- <polyline
- :points="pointsNote"
- fill="none"
- stroke-width="1"
- stroke="#41ddde"/>
- <polyline
- :points="pointsReply"
- fill="none"
- stroke-width="1"
- stroke="#f7796c"/>
- <polyline
- :points="pointsRenote"
- fill="none"
- stroke-width="1"
- stroke="#a1de41"/>
- <polyline
- :points="pointsTotal"
- fill="none"
- stroke-width="1"
- stroke="#555"
- stroke-dasharray="2 2"/>
-</svg>
-</template>
-
-<script lang="ts" setup>
-const props = defineProps<{
- activity: any[]
-}>();
-
-let viewBoxX: number = $ref(147);
-let viewBoxY: number = $ref(60);
-let zoom: number = $ref(1);
-let pos: number = $ref(0);
-let pointsNote: any = $ref(null);
-let pointsReply: any = $ref(null);
-let pointsRenote: any = $ref(null);
-let pointsTotal: any = $ref(null);
-
-function dragListen(fn) {
- window.addEventListener('mousemove', fn);
- window.addEventListener('mouseleave', dragClear.bind(null, fn));
- window.addEventListener('mouseup', dragClear.bind(null, fn));
-}
-
-function dragClear(fn) {
- window.removeEventListener('mousemove', fn);
- window.removeEventListener('mouseleave', dragClear);
- window.removeEventListener('mouseup', dragClear);
-}
-
-function onMousedown(ev) {
- const clickX = ev.clientX;
- const clickY = ev.clientY;
- const baseZoom = zoom;
- const basePos = pos;
-
- // 動かした時
- dragListen(me => {
- let moveLeft = me.clientX - clickX;
- let moveTop = me.clientY - clickY;
-
- zoom = Math.max(1, baseZoom + (-moveTop / 20));
- pos = Math.min(0, basePos + moveLeft);
- if (pos < -(((props.activity.length - 1) * zoom) - viewBoxX)) pos = -(((props.activity.length - 1) * zoom) - viewBoxX);
-
- render();
- });
-}
-
-function render() {
- const peak = Math.max(...props.activity.map(d => d.total));
- if (peak !== 0) {
- const activity = props.activity.slice().reverse();
- pointsNote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.notes / peak)) * viewBoxY}`).join(' ');
- pointsReply = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.replies / peak)) * viewBoxY}`).join(' ');
- pointsRenote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.renotes / peak)) * viewBoxY}`).join(' ');
- pointsTotal = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.total / peak)) * viewBoxY}`).join(' ');
- }
-}
-</script>
-
-<style lang="scss" scoped>
-svg {
- display: block;
- padding: 16px;
- width: 100%;
- box-sizing: border-box;
- cursor: all-scroll;
-}
-</style>
diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue
deleted file mode 100644
index 238a05ca09..0000000000
--- a/packages/client/src/widgets/activity.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" class="mkw-activity">
- <template #header><i class="ti ti-chart-line"></i>{{ i18n.ts._widgets.activity }}</template>
- <template #func><button class="_button" @click="toggleView()"><i class="ti ti-selector"></i></button></template>
-
- <div>
- <MkLoading v-if="fetching"/>
- <template v-else>
- <XCalendar v-show="widgetProps.view === 0" :activity="[].concat(activity)"/>
- <XChart v-show="widgetProps.view === 1" :activity="[].concat(activity)"/>
- </template>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import XCalendar from './activity.calendar.vue';
-import XChart from './activity.chart.vue';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { $i } from '@/account';
-import { i18n } from '@/i18n';
-
-const name = 'activity';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- view: {
- type: 'number' as const,
- default: 0,
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const activity = ref(null);
-const fetching = ref(true);
-
-const toggleView = () => {
- if (widgetProps.view === 1) {
- widgetProps.view = 0;
- } else {
- widgetProps.view++;
- }
- save();
-};
-
-os.apiGet('charts/user/notes', {
- userId: $i.id,
- span: 'day',
- limit: 7 * 21,
-}).then(res => {
- activity.value = res.diffs.normal.map((_, i) => ({
- total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i],
- notes: res.diffs.normal[i],
- replies: res.diffs.reply[i],
- renotes: res.diffs.renote[i],
- }));
- fetching.value = false;
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue
deleted file mode 100644
index 828490fd9c..0000000000
--- a/packages/client/src/widgets/aichan.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-aichan">
- <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-
-const name = 'ai';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const live2d = ref<HTMLIFrameElement>();
-
-const touched = () => {
- //if (this.live2d) this.live2d.changeExpression('gurugurume');
-};
-
-const onMousemove = (ev: MouseEvent) => {
- const iframeRect = live2d.value.getBoundingClientRect();
- live2d.value.contentWindow.postMessage({
- type: 'moveCursor',
- body: {
- x: ev.clientX - iframeRect.left,
- y: ev.clientY - iframeRect.top,
- },
- }, '*');
-};
-
-onMounted(() => {
- window.addEventListener('mousemove', onMousemove, { passive: true });
-});
-
-onUnmounted(() => {
- window.removeEventListener('mousemove', onMousemove);
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.dedjhjmo {
- width: 100%;
- height: 350px;
- border: none;
- pointer-events: none;
-}
-</style>
diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue
deleted file mode 100644
index 4009edb8b8..0000000000
--- a/packages/client/src/widgets/aiscript.vue
+++ /dev/null
@@ -1,175 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscript">
- <template #header><i class="ti ti-terminal-2"></i>{{ i18n.ts._widgets.aiscript }}</template>
-
- <div class="uylguesu _monospace">
- <textarea v-model="widgetProps.script" placeholder="(1 + 1)"></textarea>
- <button class="_buttonPrimary" @click="run">RUN</button>
- <div class="logs">
- <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { createAiScriptEnv } from '@/scripts/aiscript/api';
-import { $i } from '@/account';
-import { i18n } from '@/i18n';
-
-const name = 'aiscript';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- script: {
- type: 'string' as const,
- multiline: true,
- default: '(1 + 1)',
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const logs = ref<{
- id: string;
- text: string;
- print: boolean;
-}[]>([]);
-
-const run = async () => {
- logs.value = [];
- const aiscript = new AiScript(createAiScriptEnv({
- storageKey: 'widget',
- token: $i?.token,
- }), {
- in: (q) => {
- return new Promise(ok => {
- os.inputText({
- title: q,
- }).then(({ canceled, result: a }) => {
- ok(a);
- });
- });
- },
- out: (value) => {
- logs.value.push({
- id: Math.random().toString(),
- text: value.type === 'str' ? value.value : utils.valToString(value),
- print: true,
- });
- },
- log: (type, params) => {
- switch (type) {
- case 'end': logs.value.push({
- id: Math.random().toString(),
- text: utils.valToString(params.val, true),
- print: false,
- }); break;
- default: break;
- }
- },
- });
-
- let ast;
- try {
- ast = parse(widgetProps.script);
- } catch (err) {
- os.alert({
- type: 'error',
- text: 'Syntax error :(',
- });
- return;
- }
- try {
- await aiscript.exec(ast);
- } catch (err) {
- os.alert({
- type: 'error',
- text: err,
- });
- }
-};
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.uylguesu {
- text-align: right;
-
- > textarea {
- display: block;
- width: 100%;
- max-width: 100%;
- min-width: 100%;
- padding: 16px;
- color: var(--fg);
- background: transparent;
- border: none;
- border-bottom: solid 0.5px var(--divider);
- border-radius: 0;
- box-sizing: border-box;
- font: inherit;
-
- &:focus-visible {
- outline: none;
- }
- }
-
- > button {
- display: inline-block;
- margin: 8px;
- padding: 0 10px;
- height: 28px;
- outline: none;
- border-radius: 4px;
-
- &:disabled {
- opacity: 0.7;
- cursor: default;
- }
- }
-
- > .logs {
- border-top: solid 0.5px var(--divider);
- text-align: left;
- padding: 16px;
-
- &:empty {
- display: none;
- }
-
- > .log {
- &:not(.print) {
- opacity: 0.7;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue
deleted file mode 100644
index f0148d7f4e..0000000000
--- a/packages/client/src/widgets/button.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<template>
-<div class="mkw-button">
- <MkButton :primary="widgetProps.colored" full @click="run">
- {{ widgetProps.label }}
- </MkButton>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import { createAiScriptEnv } from '@/scripts/aiscript/api';
-import { $i } from '@/account';
-import MkButton from '@/components/MkButton.vue';
-
-const name = 'button';
-
-const widgetPropsDef = {
- label: {
- type: 'string' as const,
- default: 'BUTTON',
- },
- colored: {
- type: 'boolean' as const,
- default: true,
- },
- script: {
- type: 'string' as const,
- multiline: true,
- default: 'Mk:dialog("hello" "world")',
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const run = async () => {
- const aiscript = new AiScript(createAiScriptEnv({
- storageKey: 'widget',
- token: $i?.token,
- }), {
- in: (q) => {
- return new Promise(ok => {
- os.inputText({
- title: q,
- }).then(({ canceled, result: a }) => {
- ok(a);
- });
- });
- },
- out: (value) => {
- // nop
- },
- log: (type, params) => {
- // nop
- },
- });
-
- let ast;
- try {
- ast = parse(widgetProps.script);
- } catch (err) {
- os.alert({
- type: 'error',
- text: 'Syntax error :(',
- });
- return;
- }
- try {
- await aiscript.exec(ast);
- } catch (err) {
- os.alert({
- type: 'error',
- text: err,
- });
- }
-};
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-button {
-}
-</style>
diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue
deleted file mode 100644
index 99bd36e2fc..0000000000
--- a/packages/client/src/widgets/calendar.vue
+++ /dev/null
@@ -1,213 +0,0 @@
-<template>
-<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }">
- <div class="calendar" :class="{ isHoliday }">
- <p class="month-and-year">
- <span class="year">{{ $t('yearX', { year }) }}</span>
- <span class="month">{{ $t('monthX', { month }) }}</span>
- </p>
- <p v-if="month === 1 && day === 1" class="day">🎉{{ $t('dayX', { day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
- <p v-else class="day">{{ $t('dayX', { day }) }}</p>
- <p class="week-day">{{ weekDay }}</p>
- </div>
- <div class="info">
- <div>
- <p>{{ i18n.ts.today }}: <b>{{ dayP.toFixed(1) }}%</b></p>
- <div class="meter">
- <div class="val" :style="{ width: `${dayP}%` }"></div>
- </div>
- </div>
- <div>
- <p>{{ i18n.ts.thisMonth }}: <b>{{ monthP.toFixed(1) }}%</b></p>
- <div class="meter">
- <div class="val" :style="{ width: `${monthP}%` }"></div>
- </div>
- </div>
- <div>
- <p>{{ i18n.ts.thisYear }}: <b>{{ yearP.toFixed(1) }}%</b></p>
- <div class="meter">
- <div class="val" :style="{ width: `${yearP}%` }"></div>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import { i18n } from '@/i18n';
-import { useInterval } from '@/scripts/use-interval';
-
-const name = 'calendar';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const year = ref(0);
-const month = ref(0);
-const day = ref(0);
-const weekDay = ref('');
-const yearP = ref(0);
-const monthP = ref(0);
-const dayP = ref(0);
-const isHoliday = ref(false);
-const tick = () => {
- const now = new Date();
- const nd = now.getDate();
- const nm = now.getMonth();
- const ny = now.getFullYear();
-
- year.value = ny;
- month.value = nm + 1;
- day.value = nd;
- weekDay.value = [
- i18n.ts._weekday.sunday,
- i18n.ts._weekday.monday,
- i18n.ts._weekday.tuesday,
- i18n.ts._weekday.wednesday,
- i18n.ts._weekday.thursday,
- i18n.ts._weekday.friday,
- i18n.ts._weekday.saturday,
- ][now.getDay()];
-
- const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime();
- const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
- const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime();
- const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime();
- const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime();
- const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime();
-
- dayP.value = dayNumer / dayDenom * 100;
- monthP.value = monthNumer / monthDenom * 100;
- yearP.value = yearNumer / yearDenom * 100;
-
- isHoliday.value = now.getDay() === 0 || now.getDay() === 6;
-};
-
-useInterval(tick, 1000, {
- immediate: true,
- afterMounted: false,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-calendar {
- padding: 16px 0;
-
- &:after {
- content: "";
- display: block;
- clear: both;
- }
-
- > .calendar {
- float: left;
- width: 60%;
- text-align: center;
-
- &.isHoliday {
- > .day {
- color: #ef95a0;
- }
- }
-
- > .month-and-year, > .week-day {
- margin: 0;
- line-height: 18px;
- font-size: 0.9em;
-
- > .year, > .month {
- margin: 0 4px;
- }
- }
-
- > .day {
- margin: 10px 0;
- line-height: 32px;
- font-size: 1.75em;
- }
- }
-
- > .info {
- display: block;
- float: left;
- width: 40%;
- padding: 0 16px 0 0;
- box-sizing: border-box;
-
- > div {
- margin-bottom: 8px;
-
- &:last-child {
- margin-bottom: 4px;
- }
-
- > p {
- margin: 0 0 2px 0;
- font-size: 0.75em;
- line-height: 18px;
- opacity: 0.8;
-
- > b {
- margin-left: 2px;
- }
- }
-
- > .meter {
- width: 100%;
- overflow: hidden;
- background: var(--X11);
- border-radius: 8px;
-
- > .val {
- height: 4px;
- transition: width .3s cubic-bezier(0.23, 1, 0.32, 1);
- }
- }
-
- &:nth-child(1) {
- > .meter > .val {
- background: #f7796c;
- }
- }
-
- &:nth-child(2) {
- > .meter > .val {
- background: #a1de41;
- }
- }
-
- &:nth-child(3) {
- > .meter > .val {
- background: #41ddde;
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue
deleted file mode 100644
index dc99b6631e..0000000000
--- a/packages/client/src/widgets/clock.vue
+++ /dev/null
@@ -1,203 +0,0 @@
-<template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock">
- <div class="vubelbmv" :class="widgetProps.size">
- <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label a abbrev">{{ tzAbbrev }}</div>
- <MkAnalogClock
- class="clock"
- :thickness="widgetProps.thickness"
- :offset="tzOffset"
- :graduations="widgetProps.graduations"
- :fade-graduations="widgetProps.fadeGraduations"
- :twentyfour="widgetProps.twentyFour"
- :s-animation="widgetProps.sAnimation"
- />
- <MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" class="_monospace label c time" :show-s="false" :offset="tzOffset"/>
- <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label d offset">{{ tzOffsetLabel }}</div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import MkAnalogClock from '@/components/MkAnalogClock.vue';
-import MkDigitalClock from '@/components/MkDigitalClock.vue';
-import { timezones } from '@/scripts/timezones';
-import { i18n } from '@/i18n';
-
-const name = 'clock';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- size: {
- type: 'radio' as const,
- default: 'medium',
- options: [{
- value: 'small', label: i18n.ts.small,
- }, {
- value: 'medium', label: i18n.ts.medium,
- }, {
- value: 'large', label: i18n.ts.large,
- }],
- },
- thickness: {
- type: 'radio' as const,
- default: 0.2,
- options: [{
- value: 0.1, label: 'thin',
- }, {
- value: 0.2, label: 'medium',
- }, {
- value: 0.3, label: 'thick',
- }],
- },
- graduations: {
- type: 'radio' as const,
- default: 'numbers',
- options: [{
- value: 'none', label: 'None',
- }, {
- value: 'dots', label: 'Dots',
- }, {
- value: 'numbers', label: 'Numbers',
- }],
- },
- fadeGraduations: {
- type: 'boolean' as const,
- default: true,
- },
- sAnimation: {
- type: 'radio' as const,
- default: 'elastic',
- options: [{
- value: 'none', label: 'None',
- }, {
- value: 'elastic', label: 'Elastic',
- }, {
- value: 'easeOut', label: 'Ease out',
- }],
- },
- twentyFour: {
- type: 'boolean' as const,
- default: false,
- },
- label: {
- type: 'radio' as const,
- default: 'none',
- options: [{
- value: 'none', label: 'None',
- }, {
- value: 'time', label: 'Time',
- }, {
- value: 'tz', label: 'TZ',
- }, {
- value: 'timeAndTz', label: 'Time + TZ',
- }],
- },
- timezone: {
- type: 'enum' as const,
- default: null,
- enum: [...timezones.map((tz) => ({
- label: tz.name,
- value: tz.name.toLowerCase(),
- })), {
- label: '(auto)',
- value: null,
- }],
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const tzAbbrev = $computed(() => (widgetProps.timezone === null
- ? timezones.find((tz) => tz.name.toLowerCase() === Intl.DateTimeFormat().resolvedOptions().timeZone.toLowerCase())?.abbrev
- : timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.abbrev) ?? '?');
-
-const tzOffset = $computed(() => widgetProps.timezone === null
- ? 0 - new Date().getTimezoneOffset()
- : timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.offset ?? 0);
-
-const tzOffsetLabel = $computed(() => (tzOffset >= 0 ? '+' : '-') + Math.floor(tzOffset / 60).toString().padStart(2, '0') + ':' + (tzOffset % 60).toString().padStart(2, '0'));
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.vubelbmv {
- position: relative;
-
- > .label {
- position: absolute;
- opacity: 0.7;
-
- &.a {
- top: 14px;
- left: 14px;
- }
-
- &.b {
- top: 14px;
- right: 14px;
- }
-
- &.c {
- bottom: 14px;
- left: 14px;
- }
-
- &.d {
- bottom: 14px;
- right: 14px;
- }
- }
-
- > .clock {
- margin: auto;
- }
-
- &.small {
- padding: 12px;
-
- > .clock {
- height: 100px;
- }
- }
-
- &.medium {
- padding: 14px;
-
- > .clock {
- height: 150px;
- }
- }
-
- &.large {
- padding: 16px;
-
- > .clock {
- height: 200px;
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue
deleted file mode 100644
index d2bfd523f3..0000000000
--- a/packages/client/src/widgets/digital-clock.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<template>
-<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
- <div v-if="widgetProps.showLabel" class="label">{{ tzAbbrev }}</div>
- <div class="time">
- <MkDigitalClock :show-ms="widgetProps.showMs" :offset="tzOffset"/>
- </div>
- <div v-if="widgetProps.showLabel" class="label">{{ tzOffsetLabel }}</div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import { timezones } from '@/scripts/timezones';
-import MkDigitalClock from '@/components/MkDigitalClock.vue';
-
-const name = 'digitalClock';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- fontSize: {
- type: 'number' as const,
- default: 1.5,
- step: 0.1,
- },
- showMs: {
- type: 'boolean' as const,
- default: true,
- },
- showLabel: {
- type: 'boolean' as const,
- default: true,
- },
- timezone: {
- type: 'enum' as const,
- default: null,
- enum: [...timezones.map((tz) => ({
- label: tz.name,
- value: tz.name.toLowerCase(),
- })), {
- label: '(auto)',
- value: null,
- }],
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const tzAbbrev = $computed(() => (widgetProps.timezone === null
- ? timezones.find((tz) => tz.name.toLowerCase() === Intl.DateTimeFormat().resolvedOptions().timeZone.toLowerCase())?.abbrev
- : timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.abbrev) ?? '?');
-
-const tzOffset = $computed(() => widgetProps.timezone === null
- ? 0 - new Date().getTimezoneOffset()
- : timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.offset ?? 0);
-
-const tzOffsetLabel = $computed(() => (tzOffset >= 0 ? '+' : '-') + Math.floor(tzOffset / 60).toString().padStart(2, '0') + ':' + (tzOffset % 60).toString().padStart(2, '0'));
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-digitalClock {
- padding: 16px 0;
- text-align: center;
-
- > .label {
- font-size: 65%;
- opacity: 0.7;
- }
-}
-</style>
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
deleted file mode 100644
index 3374783b0c..0000000000
--- a/packages/client/src/widgets/federation.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" class="mkw-federation">
- <template #header><i class="ti ti-whirl"></i>{{ i18n.ts._widgets.federation }}</template>
-
- <div class="wbrkwalb">
- <MkLoading v-if="fetching"/>
- <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances">
- <div v-for="(instance, i) in instances" :key="instance.id" class="instance">
- <img :src="getInstanceIcon(instance)" alt=""/>
- <div class="body">
- <a class="a" :href="'https://' + instance.host" target="_blank" :title="instance.host">{{ instance.host }}</a>
- <p>{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</p>
- </div>
- <MkMiniChart class="chart" :src="charts[i].requests.received"/>
- </div>
- </transition-group>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { i18n } from '@/i18n';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
-
-const name = 'federation';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const instances = ref([]);
-const charts = ref([]);
-const fetching = ref(true);
-
-const fetch = async () => {
- const fetchedInstances = await os.api('federation/instances', {
- sort: '+lastCommunicatedAt',
- limit: 5,
- });
- const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
- instances.value = fetchedInstances;
- charts.value = fetchedCharts;
- fetching.value = false;
-};
-
-useInterval(fetch, 1000 * 60, {
- immediate: true,
- afterMounted: true,
-});
-
-function getInstanceIcon(instance): string {
- return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
-}
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.wbrkwalb {
- $bodyTitleHieght: 18px;
- $bodyInfoHieght: 16px;
-
- height: (62px + 1px) + (62px + 1px) + (62px + 1px) + (62px + 1px) + 62px;
- overflow: hidden;
-
- > .instances {
- .chart-move {
- transition: transform 1s ease;
- }
-
- > .instance {
- display: flex;
- align-items: center;
- padding: 14px 16px;
- border-bottom: solid 0.5px var(--divider);
-
- > img {
- display: block;
- width: ($bodyTitleHieght + $bodyInfoHieght);
- height: ($bodyTitleHieght + $bodyInfoHieght);
- object-fit: cover;
- border-radius: 4px;
- margin-right: 8px;
- }
-
- > .body {
- flex: 1;
- overflow: hidden;
- font-size: 0.9em;
- color: var(--fg);
- padding-right: 8px;
-
- > .a {
- display: block;
- width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: $bodyTitleHieght;
- }
-
- > p {
- margin: 0;
- font-size: 75%;
- opacity: 0.7;
- line-height: $bodyInfoHieght;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
-
- > .chart {
- height: 30px;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/index.ts b/packages/client/src/widgets/index.ts
deleted file mode 100644
index 39826f13c8..0000000000
--- a/packages/client/src/widgets/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { App, defineAsyncComponent } from 'vue';
-
-export default function(app: App) {
- app.component('MkwMemo', defineAsyncComponent(() => import('./memo.vue')));
- app.component('MkwNotifications', defineAsyncComponent(() => import('./notifications.vue')));
- app.component('MkwTimeline', defineAsyncComponent(() => import('./timeline.vue')));
- app.component('MkwCalendar', defineAsyncComponent(() => import('./calendar.vue')));
- app.component('MkwRss', defineAsyncComponent(() => import('./rss.vue')));
- app.component('MkwRssTicker', defineAsyncComponent(() => import('./rss-ticker.vue')));
- app.component('MkwTrends', defineAsyncComponent(() => import('./trends.vue')));
- app.component('MkwClock', defineAsyncComponent(() => import('./clock.vue')));
- app.component('MkwActivity', defineAsyncComponent(() => import('./activity.vue')));
- app.component('MkwPhotos', defineAsyncComponent(() => import('./photos.vue')));
- app.component('MkwDigitalClock', defineAsyncComponent(() => import('./digital-clock.vue')));
- app.component('MkwUnixClock', defineAsyncComponent(() => import('./unix-clock.vue')));
- app.component('MkwFederation', defineAsyncComponent(() => import('./federation.vue')));
- app.component('MkwPostForm', defineAsyncComponent(() => import('./post-form.vue')));
- app.component('MkwSlideshow', defineAsyncComponent(() => import('./slideshow.vue')));
- app.component('MkwServerMetric', defineAsyncComponent(() => import('./server-metric/index.vue')));
- app.component('MkwOnlineUsers', defineAsyncComponent(() => import('./online-users.vue')));
- app.component('MkwJobQueue', defineAsyncComponent(() => import('./job-queue.vue')));
- app.component('MkwInstanceCloud', defineAsyncComponent(() => import('./instance-cloud.vue')));
- app.component('MkwButton', defineAsyncComponent(() => import('./button.vue')));
- app.component('MkwAiscript', defineAsyncComponent(() => import('./aiscript.vue')));
- app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue')));
- app.component('MkwUserList', defineAsyncComponent(() => import('./user-list.vue')));
-}
-
-export const widgets = [
- 'memo',
- 'notifications',
- 'timeline',
- 'calendar',
- 'rss',
- 'rssTicker',
- 'trends',
- 'clock',
- 'activity',
- 'photos',
- 'digitalClock',
- 'unixClock',
- 'federation',
- 'instanceCloud',
- 'postForm',
- 'slideshow',
- 'serverMetric',
- 'onlineUsers',
- 'jobQueue',
- 'button',
- 'aiscript',
- 'aichan',
- 'userList',
-];
diff --git a/packages/client/src/widgets/instance-cloud.vue b/packages/client/src/widgets/instance-cloud.vue
deleted file mode 100644
index 4965616995..0000000000
--- a/packages/client/src/widgets/instance-cloud.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-instance-cloud">
- <div class="">
- <MkTagCloud v-if="activeInstances">
- <li v-for="instance in activeInstances" :key="instance.id">
- <a @click.prevent="onInstanceClick(instance)">
- <img style="width: 32px;" :src="getInstanceIcon(instance)">
- </a>
- </li>
- </MkTagCloud>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import MkTagCloud from '@/components/MkTagCloud.vue';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
-
-const name = 'instanceCloud';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-let cloud = $ref<InstanceType<typeof MkTagCloud> | null>();
-let activeInstances = $shallowRef(null);
-
-function onInstanceClick(i) {
- os.pageWindow(`/instance-info/${i.host}`);
-}
-
-useInterval(() => {
- os.api('federation/instances', {
- sort: '+lastCommunicatedAt',
- limit: 25,
- }).then(res => {
- activeInstances = res;
- if (cloud) cloud.update();
- });
-}, 1000 * 60 * 3, {
- immediate: true,
- afterMounted: true,
-});
-
-function getInstanceIcon(instance): string {
- return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
-}
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue
deleted file mode 100644
index 9f19c51825..0000000000
--- a/packages/client/src/widgets/job-queue.vue
+++ /dev/null
@@ -1,197 +0,0 @@
-<template>
-<div class="mkw-jobQueue _monospace" :class="{ _panel: !widgetProps.transparent }">
- <div class="inbox">
- <div class="label">Inbox queue<i v-if="current.inbox.waiting > 0" class="ti ti-alert-triangle icon"></i></div>
- <div class="values">
- <div>
- <div>Process</div>
- <div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div>
- </div>
- <div>
- <div>Active</div>
- <div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div>
- </div>
- <div>
- <div>Delayed</div>
- <div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div>
- </div>
- <div>
- <div>Waiting</div>
- <div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div>
- </div>
- </div>
- </div>
- <div class="deliver">
- <div class="label">Deliver queue<i v-if="current.deliver.waiting > 0" class="ti ti-alert-triangle icon"></i></div>
- <div class="values">
- <div>
- <div>Process</div>
- <div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div>
- </div>
- <div>
- <div>Active</div>
- <div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div>
- </div>
- <div>
- <div>Delayed</div>
- <div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div>
- </div>
- <div>
- <div>Waiting</div>
- <div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import { stream } from '@/stream';
-import number from '@/filters/number';
-import * as sound from '@/scripts/sound';
-import * as os from '@/os';
-import { deepClone } from '@/scripts/clone';
-
-const name = 'jobQueue';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- sound: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const connection = stream.useChannel('queueStats');
-const current = reactive({
- inbox: {
- activeSincePrevTick: 0,
- active: 0,
- waiting: 0,
- delayed: 0,
- },
- deliver: {
- activeSincePrevTick: 0,
- active: 0,
- waiting: 0,
- delayed: 0,
- },
-});
-const prev = reactive({} as typeof current);
-const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
-
-for (const domain of ['inbox', 'deliver']) {
- prev[domain] = deepClone(current[domain]);
-}
-
-const onStats = (stats) => {
- for (const domain of ['inbox', 'deliver']) {
- prev[domain] = deepClone(current[domain]);
- current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
- current[domain].active = stats[domain].active;
- current[domain].waiting = stats[domain].waiting;
- current[domain].delayed = stats[domain].delayed;
-
- if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) {
- jammedSound.play();
- }
- }
-};
-
-const onStatsLog = (statsLog) => {
- for (const stats of [...statsLog].reverse()) {
- onStats(stats);
- }
-};
-
-connection.on('stats', onStats);
-connection.on('statsLog', onStatsLog);
-
-connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 1,
-});
-
-onUnmounted(() => {
- connection.off('stats', onStats);
- connection.off('statsLog', onStatsLog);
- connection.dispose();
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-@keyframes warnBlink {
- 0% { opacity: 1; }
- 50% { opacity: 0; }
-}
-
-.mkw-jobQueue {
- font-size: 0.9em;
-
- > div {
- padding: 16px;
-
- &:not(:first-child) {
- border-top: solid 0.5px var(--divider);
- }
-
- > .label {
- display: flex;
-
- > .icon {
- color: var(--warn);
- margin-left: auto;
- animation: warnBlink 1s infinite;
- }
- }
-
- > .values {
- display: flex;
-
- > div {
- flex: 1;
-
- > div:first-child {
- opacity: 0.7;
- }
-
- > div:last-child {
- &.inc {
- color: var(--warn);
- }
-
- &.dec {
- color: var(--success);
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue
deleted file mode 100644
index 1cc0e10bba..0000000000
--- a/packages/client/src/widgets/memo.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-memo">
- <template #header><i class="ti ti-note"></i>{{ i18n.ts._widgets.memo }}</template>
-
- <div class="otgbylcu">
- <textarea v-model="text" :placeholder="i18n.ts.placeholder" @input="onChange"></textarea>
- <button :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { defaultStore } from '@/store';
-import { i18n } from '@/i18n';
-
-const name = 'memo';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const text = ref<string | null>(defaultStore.state.memo);
-const changed = ref(false);
-let timeoutId;
-
-const saveMemo = () => {
- defaultStore.set('memo', text.value);
- changed.value = false;
-};
-
-const onChange = () => {
- changed.value = true;
- window.clearTimeout(timeoutId);
- timeoutId = window.setTimeout(saveMemo, 1000);
-};
-
-watch(() => defaultStore.reactiveState.memo, newText => {
- text.value = newText.value;
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.otgbylcu {
- padding-bottom: 28px + 16px;
-
- > textarea {
- display: block;
- width: 100%;
- max-width: 100%;
- min-width: 100%;
- padding: 16px;
- color: var(--fg);
- background: transparent;
- border: none;
- border-bottom: solid 0.5px var(--divider);
- border-radius: 0;
- box-sizing: border-box;
- font: inherit;
- font-size: 0.9em;
-
- &:focus-visible {
- outline: none;
- }
- }
-
- > button {
- display: block;
- position: absolute;
- bottom: 8px;
- right: 8px;
- margin: 0;
- padding: 0 10px;
- height: 28px;
- outline: none;
- border-radius: 4px;
-
- &:disabled {
- opacity: 0.7;
- cursor: default;
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue
deleted file mode 100644
index e697209444..0000000000
--- a/packages/client/src/widgets/notifications.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-<template>
-<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" class="mkw-notifications">
- <template #header><i class="ti ti-bell"></i>{{ i18n.ts.notifications }}</template>
- <template #func><button class="_button" @click="configureNotification()"><i class="ti ti-settings"></i></button></template>
-
- <div>
- <XNotifications :include-types="widgetProps.includingTypes"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import XNotifications from '@/components/MkNotifications.vue';
-import * as os from '@/os';
-import { i18n } from '@/i18n';
-
-const name = 'notifications';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- height: {
- type: 'number' as const,
- default: 300,
- },
- includingTypes: {
- type: 'array' as const,
- hidden: true,
- default: null,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const configureNotification = () => {
- os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
- includingTypes: widgetProps.includingTypes,
- }, {
- done: async (res) => {
- const { includingTypes } = res;
- widgetProps.includingTypes = includingTypes;
- save();
- },
- }, 'closed');
-};
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue
deleted file mode 100644
index e9ab79b111..0000000000
--- a/packages/client/src/widgets/online-users.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-<div class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }">
- <I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" text-tag="span" class="text">
- <template #n><b>{{ onlineUsersCount }}</b></template>
- </I18n>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { i18n } from '@/i18n';
-
-const name = 'onlineUsers';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const onlineUsersCount = ref(0);
-
-const tick = () => {
- os.api('get-online-users-count').then(res => {
- onlineUsersCount.value = res.count;
- });
-};
-
-useInterval(tick, 1000 * 15, {
- immediate: true,
- afterMounted: true,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-onlineUsers {
- text-align: center;
-
- &.pad {
- padding: 16px 0;
- }
-
- > .text {
- ::v-deep(b) {
- color: #41b781;
- }
-
- ::v-deep(span) {
- opacity: 0.7;
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue
deleted file mode 100644
index 4ad5324053..0000000000
--- a/packages/client/src/widgets/photos.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" class="mkw-photos">
- <template #header><i class="ti ti-camera"></i>{{ i18n.ts._widgets.photos }}</template>
-
- <div class="">
- <MkLoading v-if="fetching"/>
- <div v-else :class="$style.stream">
- <div
- v-for="(image, i) in images" :key="i"
- :class="$style.img"
- :style="`background-image: url(${thumbnail(image)})`"
- ></div>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import { stream } from '@/stream';
-import { getStaticImageUrl } from '@/scripts/get-static-image-url';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { defaultStore } from '@/store';
-import { i18n } from '@/i18n';
-
-const name = 'photos';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const connection = stream.useChannel('main');
-const images = ref([]);
-const fetching = ref(true);
-
-const onDriveFileCreated = (file) => {
- if (/^image\/.+$/.test(file.type)) {
- images.value.unshift(file);
- if (images.value.length > 9) images.value.pop();
- }
-};
-
-const thumbnail = (image: any): string => {
- return defaultStore.state.disableShowingAnimatedImages
- ? getStaticImageUrl(image.thumbnailUrl)
- : image.thumbnailUrl;
-};
-
-os.api('drive/stream', {
- type: 'image/*',
- limit: 9,
-}).then(res => {
- images.value = res;
- fetching.value = false;
-});
-
-connection.on('driveFileCreated', onDriveFileCreated);
-onUnmounted(() => {
- connection.dispose();
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" module>
-.root[data-transparent] {
- .stream {
- padding: 0;
- }
-
- .img {
- border: solid 4px transparent;
- border-radius: 8px;
- }
-}
-
-.stream {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- padding: 8px;
-
- .img {
- flex: 1 1 33%;
- width: 33%;
- height: 80px;
- box-sizing: border-box;
- background-position: center center;
- background-size: cover;
- background-clip: content-box;
- border: solid 2px transparent;
- border-radius: 4px;
- }
-}
-</style>
diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue
deleted file mode 100644
index f1708775ba..0000000000
--- a/packages/client/src/widgets/post-form.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<template>
-<XPostForm class="_panel mkw-postForm" :fixed="true" :autofocus="false"/>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import XPostForm from '@/components/MkPostForm.vue';
-
-const name = 'postForm';
-
-const widgetPropsDef = {
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue
deleted file mode 100644
index 44c21d1836..0000000000
--- a/packages/client/src/widgets/rss-ticker.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
-<MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-ticker">
- <template #header><i class="ti ti-rss"></i>RSS</template>
- <template #func><button class="_button" @click="configure"><i class="ti ti-settings"></i></button></template>
-
- <div class="ekmkgxbk">
- <MkLoading v-if="fetching"/>
- <div v-else class="feed">
- <transition name="change" mode="default">
- <MarqueeText :key="key" :duration="widgetProps.duration" :reverse="widgetProps.reverse">
- <span v-for="item in items" class="item">
- <a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span>
- </span>
- </MarqueeText>
- </transition>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import MarqueeText from '@/components/MkMarquee.vue';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { useInterval } from '@/scripts/use-interval';
-import { shuffle } from '@/scripts/shuffle';
-
-const name = 'rssTicker';
-
-const widgetPropsDef = {
- url: {
- type: 'string' as const,
- default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
- },
- shuffle: {
- type: 'boolean' as const,
- default: true,
- },
- refreshIntervalSec: {
- type: 'number' as const,
- default: 60,
- },
- duration: {
- type: 'range' as const,
- default: 70,
- step: 1,
- min: 5,
- max: 200,
- },
- reverse: {
- type: 'boolean' as const,
- default: false,
- },
- showHeader: {
- type: 'boolean' as const,
- default: false,
- },
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const items = ref([]);
-const fetching = ref(true);
-let key = $ref(0);
-
-const tick = () => {
- window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
- res.json().then(feed => {
- if (widgetProps.shuffle) {
- shuffle(feed.items);
- }
- items.value = feed.items;
- fetching.value = false;
- key++;
- });
- });
-};
-
-watch(() => widgetProps.url, tick);
-
-useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
- immediate: true,
- afterMounted: true,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.change-enter-active, .change-leave-active {
- position: absolute;
- top: 0;
- transition: all 1s ease;
-}
-.change-enter-from {
- opacity: 0;
- transform: translateY(-100%);
-}
-.change-leave-to {
- opacity: 0;
- transform: translateY(100%);
-}
-
-.ekmkgxbk {
- > .feed {
- --height: 42px;
- padding: 0;
- font-size: 0.9em;
- line-height: var(--height);
- height: var(--height);
- contain: strict;
-
- ::v-deep(.item) {
- display: inline-flex;
- align-items: center;
- vertical-align: bottom;
- color: var(--fg);
-
- > .divider {
- display: inline-block;
- width: 0.5px;
- height: 16px;
- margin: 0 1em;
- background: var(--divider);
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
deleted file mode 100644
index c0338c8e47..0000000000
--- a/packages/client/src/widgets/rss.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-rss">
- <template #header><i class="ti ti-rss"></i>RSS</template>
- <template #func><button class="_button" @click="configure"><i class="ti ti-settings"></i></button></template>
-
- <div class="ekmkgxbj">
- <MkLoading v-if="fetching"/>
- <div v-else class="feed">
- <a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import { useInterval } from '@/scripts/use-interval';
-
-const name = 'rss';
-
-const widgetPropsDef = {
- url: {
- type: 'string' as const,
- default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
- },
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const items = ref([]);
-const fetching = ref(true);
-
-const tick = () => {
- window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
- res.json().then(feed => {
- items.value = feed.items;
- fetching.value = false;
- });
- });
-};
-
-watch(() => widgetProps.url, tick);
-
-useInterval(tick, 60000, {
- immediate: true,
- afterMounted: true,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.ekmkgxbj {
- > .feed {
- padding: 0;
- font-size: 0.9em;
-
- > .item {
- display: block;
- padding: 8px 16px;
- color: var(--fg);
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-
- &:nth-child(even) {
- background: rgba(#000, 0.05);
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue
deleted file mode 100644
index 80a8e427e1..0000000000
--- a/packages/client/src/widgets/server-metric/cpu-mem.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<template>
-<div class="lcfyofjk">
- <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
- <defs>
- <linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
- <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
- <stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
- </linearGradient>
- <mask :id="cpuMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
- <polygon
- :points="cpuPolygonPoints"
- fill="#fff"
- fill-opacity="0.5"
- />
- <polyline
- :points="cpuPolylinePoints"
- fill="none"
- stroke="#fff"
- stroke-width="1"
- />
- <circle
- :cx="cpuHeadX"
- :cy="cpuHeadY"
- r="1.5"
- fill="#fff"
- />
- </mask>
- </defs>
- <rect
- x="-2" y="-2"
- :width="viewBoxX + 4" :height="viewBoxY + 4"
- :style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"
- />
- <text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
- </svg>
- <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
- <defs>
- <linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
- <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
- <stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
- </linearGradient>
- <mask :id="memMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
- <polygon
- :points="memPolygonPoints"
- fill="#fff"
- fill-opacity="0.5"
- />
- <polyline
- :points="memPolylinePoints"
- fill="none"
- stroke="#fff"
- stroke-width="1"
- />
- <circle
- :cx="memHeadX"
- :cy="memHeadY"
- r="1.5"
- fill="#fff"
- />
- </mask>
- </defs>
- <rect
- x="-2" y="-2"
- :width="viewBoxX + 4" :height="viewBoxY + 4"
- :style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"
- />
- <text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
- </svg>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
-import { v4 as uuid } from 'uuid';
-
-const props = defineProps<{
- connection: any,
- meta: any
-}>();
-
-let viewBoxX: number = $ref(50);
-let viewBoxY: number = $ref(30);
-let stats: any[] = $ref([]);
-const cpuGradientId = uuid();
-const cpuMaskId = uuid();
-const memGradientId = uuid();
-const memMaskId = uuid();
-let cpuPolylinePoints: string = $ref('');
-let memPolylinePoints: string = $ref('');
-let cpuPolygonPoints: string = $ref('');
-let memPolygonPoints: string = $ref('');
-let cpuHeadX: any = $ref(null);
-let cpuHeadY: any = $ref(null);
-let memHeadX: any = $ref(null);
-let memHeadY: any = $ref(null);
-let cpuP: string = $ref('');
-let memP: string = $ref('');
-
-onMounted(() => {
- props.connection.on('stats', onStats);
- props.connection.on('statsLog', onStatsLog);
- props.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- });
-});
-
-onBeforeUnmount(() => {
- props.connection.off('stats', onStats);
- props.connection.off('statsLog', onStatsLog);
-});
-
-function onStats(connStats) {
- stats.push(connStats);
- if (stats.length > 50) stats.shift();
-
- let cpuPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - s.cpu) * viewBoxY]);
- let memPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY]);
- cpuPolylinePoints = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
- memPolylinePoints = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-
- cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
- memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
-
- cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0];
- cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1];
- memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0];
- memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1];
-
- cpuP = (connStats.cpu * 100).toFixed(0);
- memP = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0);
-}
-
-function onStatsLog(statsLog) {
- for (const revStats of [...statsLog].reverse()) {
- onStats(revStats);
- }
-}
-</script>
-
-<style lang="scss" scoped>
-.lcfyofjk {
- display: flex;
-
- > svg {
- display: block;
- padding: 10px;
- width: 50%;
-
- &:first-child {
- padding-right: 5px;
- }
-
- &:last-child {
- padding-left: 5px;
- }
-
- > text {
- font-size: 4.5px;
- fill: currentColor;
-
- > tspan {
- opacity: 0.5;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue
deleted file mode 100644
index e7b2226d1f..0000000000
--- a/packages/client/src/widgets/server-metric/cpu.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div class="vrvdvrys">
- <XPie class="pie" :value="usage"/>
- <div>
- <p><i class="ti ti-cpu"></i>CPU</p>
- <p>{{ meta.cpu.cores }} Logical cores</p>
- <p>{{ meta.cpu.model }}</p>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
-import XPie from './pie.vue';
-
-const props = defineProps<{
- connection: any,
- meta: any
-}>();
-
-let usage: number = $ref(0);
-
-function onStats(stats) {
- usage = stats.cpu;
-}
-
-onMounted(() => {
- props.connection.on('stats', onStats);
-});
-
-onBeforeUnmount(() => {
- props.connection.off('stats', onStats);
-});
-</script>
-
-<style lang="scss" scoped>
-.vrvdvrys {
- display: flex;
- padding: 16px;
-
- > .pie {
- height: 82px;
- flex-shrink: 0;
- margin-right: 16px;
- }
-
- > div {
- flex: 1;
-
- > p {
- margin: 0;
- font-size: 0.8em;
-
- &:first-child {
- font-weight: bold;
- margin-bottom: 4px;
-
- > i {
- margin-right: 4px;
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue
deleted file mode 100644
index 3d22d05383..0000000000
--- a/packages/client/src/widgets/server-metric/disk.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
-<div class="zbwaqsat">
- <XPie class="pie" :value="usage"/>
- <div>
- <p><i class="ti ti-database"></i>Disk</p>
- <p>Total: {{ bytes(total, 1) }}</p>
- <p>Free: {{ bytes(available, 1) }}</p>
- <p>Used: {{ bytes(used, 1) }}</p>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import XPie from './pie.vue';
-import bytes from '@/filters/bytes';
-
-const props = defineProps<{
- meta: any; // TODO
-}>();
-
-const usage = $computed(() => props.meta.fs.used / props.meta.fs.total);
-const total = $computed(() => props.meta.fs.total);
-const used = $computed(() => props.meta.fs.used);
-const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
-</script>
-
-<style lang="scss" scoped>
-.zbwaqsat {
- display: flex;
- padding: 16px;
-
- > .pie {
- height: 82px;
- flex-shrink: 0;
- margin-right: 16px;
- }
-
- > div {
- flex: 1;
-
- > p {
- margin: 0;
- font-size: 0.8em;
-
- &:first-child {
- font-weight: bold;
- margin-bottom: 4px;
-
- > i {
- margin-right: 4px;
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue
deleted file mode 100644
index bc3fca6fc1..0000000000
--- a/packages/client/src/widgets/server-metric/index.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
- <template #header><i class="ti ti-server"></i>{{ i18n.ts._widgets.serverMetric }}</template>
- <template #func><button class="_button" @click="toggleView()"><i class="ti ti-selector"></i></button></template>
-
- <div v-if="meta" class="mkw-serverMetric">
- <XCpuMemory v-if="widgetProps.view === 0" :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"/>
- <XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/>
- <XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget';
-import XCpuMemory from './cpu-mem.vue';
-import XNet from './net.vue';
-import XCpu from './cpu.vue';
-import XMemory from './mem.vue';
-import XDisk from './disk.vue';
-import MkContainer from '@/components/MkContainer.vue';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import { stream } from '@/stream';
-import { i18n } from '@/i18n';
-
-const name = 'serverMetric';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- view: {
- type: 'number' as const,
- default: 0,
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const meta = ref(null);
-
-os.api('server-info', {}).then(res => {
- meta.value = res;
-});
-
-const toggleView = () => {
- if (widgetProps.view === 4) {
- widgetProps.view = 0;
- } else {
- widgetProps.view++;
- }
- save();
-};
-
-const connection = stream.useChannel('serverStats');
-onUnmounted(() => {
- connection.dispose();
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue
deleted file mode 100644
index 6018eb4265..0000000000
--- a/packages/client/src/widgets/server-metric/mem.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
-<div class="zlxnikvl">
- <XPie class="pie" :value="usage"/>
- <div>
- <p><i class="fas fa-memory"></i>RAM</p>
- <p>Total: {{ bytes(total, 1) }}</p>
- <p>Used: {{ bytes(used, 1) }}</p>
- <p>Free: {{ bytes(free, 1) }}</p>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
-import XPie from './pie.vue';
-import bytes from '@/filters/bytes';
-
-const props = defineProps<{
- connection: any,
- meta: any
-}>();
-
-let usage: number = $ref(0);
-let total: number = $ref(0);
-let used: number = $ref(0);
-let free: number = $ref(0);
-
-function onStats(stats) {
- usage = stats.mem.active / props.meta.mem.total;
- total = props.meta.mem.total;
- used = stats.mem.active;
- free = total - used;
-}
-
-onMounted(() => {
- props.connection.on('stats', onStats);
-});
-
-onBeforeUnmount(() => {
- props.connection.off('stats', onStats);
-});
-</script>
-
-<style lang="scss" scoped>
-.zlxnikvl {
- display: flex;
- padding: 16px;
-
- > .pie {
- height: 82px;
- flex-shrink: 0;
- margin-right: 16px;
- }
-
- > div {
- flex: 1;
-
- > p {
- margin: 0;
- font-size: 0.8em;
-
- &:first-child {
- font-weight: bold;
- margin-bottom: 4px;
-
- > i {
- margin-right: 4px;
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue
deleted file mode 100644
index ab8b0fe471..0000000000
--- a/packages/client/src/widgets/server-metric/net.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-<template>
-<div class="oxxrhrto">
- <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
- <polygon
- :points="inPolygonPoints"
- fill="#94a029"
- fill-opacity="0.5"
- />
- <polyline
- :points="inPolylinePoints"
- fill="none"
- stroke="#94a029"
- stroke-width="1"
- />
- <circle
- :cx="inHeadX"
- :cy="inHeadY"
- r="1.5"
- fill="#94a029"
- />
- <text x="1" y="5">NET rx <tspan>{{ bytes(inRecent) }}</tspan></text>
- </svg>
- <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
- <polygon
- :points="outPolygonPoints"
- fill="#ff9156"
- fill-opacity="0.5"
- />
- <polyline
- :points="outPolylinePoints"
- fill="none"
- stroke="#ff9156"
- stroke-width="1"
- />
- <circle
- :cx="outHeadX"
- :cy="outHeadY"
- r="1.5"
- fill="#ff9156"
- />
- <text x="1" y="5">NET tx <tspan>{{ bytes(outRecent) }}</tspan></text>
- </svg>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
-import bytes from '@/filters/bytes';
-
-const props = defineProps<{
- connection: any,
- meta: any
-}>();
-
-let viewBoxX: number = $ref(50);
-let viewBoxY: number = $ref(30);
-let stats: any[] = $ref([]);
-let inPolylinePoints: string = $ref('');
-let outPolylinePoints: string = $ref('');
-let inPolygonPoints: string = $ref('');
-let outPolygonPoints: string = $ref('');
-let inHeadX: any = $ref(null);
-let inHeadY: any = $ref(null);
-let outHeadX: any = $ref(null);
-let outHeadY: any = $ref(null);
-let inRecent: number = $ref(0);
-let outRecent: number = $ref(0);
-
-onMounted(() => {
- props.connection.on('stats', onStats);
- props.connection.on('statsLog', onStatsLog);
- props.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- });
-});
-
-onBeforeUnmount(() => {
- props.connection.off('stats', onStats);
- props.connection.off('statsLog', onStatsLog);
-});
-
-function onStats(connStats) {
- stats.push(connStats);
- if (stats.length > 50) stats.shift();
-
- const inPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.rx)));
- const outPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.tx)));
-
- let inPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY]);
- let outPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY]);
- inPolylinePoints = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
- outPolylinePoints = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-
- inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
- outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
-
- inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
- inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
- outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
- outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
-
- inRecent = connStats.net.rx;
- outRecent = connStats.net.tx;
-}
-
-function onStatsLog(statsLog) {
- for (const revStats of [...statsLog].reverse()) {
- onStats(revStats);
- }
-}
-</script>
-
-<style lang="scss" scoped>
-.oxxrhrto {
- display: flex;
-
- > svg {
- display: block;
- padding: 10px;
- width: 50%;
-
- &:first-child {
- padding-right: 5px;
- }
-
- &:last-child {
- padding-left: 5px;
- }
-
- > text {
- font-size: 4.5px;
- fill: currentColor;
-
- > tspan {
- opacity: 0.5;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/server-metric/pie.vue b/packages/client/src/widgets/server-metric/pie.vue
deleted file mode 100644
index 868dbc0484..0000000000
--- a/packages/client/src/widgets/server-metric/pie.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<template>
-<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none">
- <circle
- :r="r"
- cx="50%" cy="50%"
- fill="none"
- stroke-width="0.1"
- stroke="rgba(0, 0, 0, 0.05)"
- />
- <circle
- :r="r"
- cx="50%" cy="50%"
- :stroke-dasharray="Math.PI * (r * 2)"
- :stroke-dashoffset="strokeDashoffset"
- fill="none"
- stroke-width="0.1"
- :stroke="color"
- />
- <text x="50%" y="50%" dy="0.05" text-anchor="middle">{{ (value * 100).toFixed(0) }}%</text>
-</svg>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-
-const props = defineProps<{
- value: number;
-}>();
-
-const r = 0.45;
-
-const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`);
-const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2)));
-</script>
-
-<style lang="scss" scoped>
-.hsalcinq {
- display: block;
- height: 100%;
-
- > circle {
- transform-origin: center;
- transform: rotate(-90deg);
- transition: stroke-dashoffset 0.5s ease;
- }
-
- > text {
- font-size: 0.15px;
- fill: currentColor;
- }
-}
-</style>
diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue
deleted file mode 100644
index e317b8ab94..0000000000
--- a/packages/client/src/widgets/slideshow.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-<template>
-<div class="kvausudm _panel mkw-slideshow" :style="{ height: widgetProps.height + 'px' }">
- <div @click="choose">
- <p v-if="widgetProps.folderId == null">
- {{ i18n.ts.folder }}
- </p>
- <p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p>
- <div ref="slideA" class="slide a"></div>
- <div ref="slideB" class="slide b"></div>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { i18n } from '@/i18n';
-
-const name = 'slideshow';
-
-const widgetPropsDef = {
- height: {
- type: 'number' as const,
- default: 300,
- },
- folderId: {
- type: 'string' as const,
- default: null,
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const images = ref([]);
-const fetching = ref(true);
-const slideA = ref<HTMLElement>();
-const slideB = ref<HTMLElement>();
-
-const change = () => {
- if (images.value.length === 0) return;
-
- const index = Math.floor(Math.random() * images.value.length);
- const img = `url(${ images.value[index].url })`;
-
- slideB.value.style.backgroundImage = img;
-
- slideB.value.classList.add('anime');
- window.setTimeout(() => {
- // 既にこのウィジェットがunmountされていたら要素がない
- if (slideA.value == null) return;
-
- slideA.value.style.backgroundImage = img;
-
- slideB.value.classList.remove('anime');
- }, 1000);
-};
-
-const fetch = () => {
- fetching.value = true;
-
- os.api('drive/files', {
- folderId: widgetProps.folderId,
- type: 'image/*',
- limit: 100,
- }).then(res => {
- images.value = res;
- fetching.value = false;
- slideA.value.style.backgroundImage = '';
- slideB.value.style.backgroundImage = '';
- change();
- });
-};
-
-const choose = () => {
- os.selectDriveFolder(false).then(folder => {
- if (folder == null) {
- return;
- }
- widgetProps.folderId = folder.id;
- save();
- fetch();
- });
-};
-
-useInterval(change, 10000, {
- immediate: false,
- afterMounted: true,
-});
-
-onMounted(() => {
- if (widgetProps.folderId != null) {
- fetch();
- }
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.kvausudm {
- position: relative;
-
- > div {
- width: 100%;
- height: 100%;
- cursor: pointer;
-
- > p {
- display: block;
- margin: 1em;
- text-align: center;
- color: #888;
- }
-
- > * {
- pointer-events: none;
- }
-
- > .slide {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-size: cover;
- background-position: center;
-
- &.b {
- opacity: 0;
- }
-
- &.anime {
- transition: opacity 1s;
- opacity: 1;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue
deleted file mode 100644
index e48444d33f..0000000000
--- a/packages/client/src/widgets/timeline.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" class="mkw-timeline">
- <template #header>
- <button class="_button" @click="choose">
- <i v-if="widgetProps.src === 'home'" class="ti ti-home"></i>
- <i v-else-if="widgetProps.src === 'local'" class="ti ti-messages"></i>
- <i v-else-if="widgetProps.src === 'social'" class="ti ti-share"></i>
- <i v-else-if="widgetProps.src === 'global'" class="ti ti-world"></i>
- <i v-else-if="widgetProps.src === 'list'" class="ti ti-list"></i>
- <i v-else-if="widgetProps.src === 'antenna'" class="ti ti-antenna"></i>
- <span style="margin-left: 8px;">{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : $t('_timelines.' + widgetProps.src) }}</span>
- <i :class="menuOpened ? 'ti ti-chevron-up' : 'ti ti-chevron-down'" style="margin-left: 8px;"></i>
- </button>
- </template>
-
- <div>
- <XTimeline :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"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import * as os from '@/os';
-import MkContainer from '@/components/MkContainer.vue';
-import XTimeline from '@/components/MkTimeline.vue';
-import { $i } from '@/account';
-import { i18n } from '@/i18n';
-
-const name = 'timeline';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- height: {
- type: 'number' as const,
- default: 300,
- },
- src: {
- type: 'string' as const,
- default: 'home',
- hidden: true,
- },
- antenna: {
- type: 'object' as const,
- default: null,
- hidden: true,
- },
- list: {
- type: 'object' as const,
- default: null,
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const menuOpened = ref(false);
-
-const setSrc = (src) => {
- widgetProps.src = src;
- save();
-};
-
-const choose = async (ev) => {
- menuOpened.value = true;
- const [antennas, lists] = await Promise.all([
- os.api('antennas/list'),
- os.api('users/lists/list'),
- ]);
- const antennaItems = antennas.map(antenna => ({
- text: antenna.name,
- icon: 'ti ti-antenna',
- action: () => {
- widgetProps.antenna = antenna;
- setSrc('antenna');
- },
- }));
- const listItems = lists.map(list => ({
- text: list.name,
- icon: 'ti ti-list',
- action: () => {
- widgetProps.list = list;
- setSrc('list');
- },
- }));
- os.popupMenu([{
- text: i18n.ts._timelines.home,
- icon: 'ti ti-home',
- action: () => { setSrc('home'); },
- }, {
- text: i18n.ts._timelines.local,
- icon: 'ti ti-messages',
- action: () => { setSrc('local'); },
- }, {
- text: i18n.ts._timelines.social,
- icon: 'ti ti-share',
- action: () => { setSrc('social'); },
- }, {
- text: i18n.ts._timelines.global,
- icon: 'ti ti-world',
- action: () => { setSrc('global'); },
- }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => {
- menuOpened.value = false;
- });
-};
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
deleted file mode 100644
index 02eec0431e..0000000000
--- a/packages/client/src/widgets/trends.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-trends">
- <template #header><i class="ti ti-hash"></i>{{ i18n.ts._widgets.trends }}</template>
-
- <div class="wbrkwala">
- <MkLoading v-if="fetching"/>
- <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="tags">
- <div v-for="stat in stats" :key="stat.tag">
- <div class="tag">
- <MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
- <p>{{ $t('nUsersMentioned', { n: stat.usersCount }) }}</p>
- </div>
- <MkMiniChart class="chart" :src="stat.chart"/>
- </div>
- </transition-group>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { i18n } from '@/i18n';
-
-const name = 'hashtags';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-const stats = ref([]);
-const fetching = ref(true);
-
-const fetch = () => {
- os.api('hashtags/trend').then(res => {
- stats.value = res;
- fetching.value = false;
- });
-};
-
-useInterval(fetch, 1000 * 60, {
- immediate: true,
- afterMounted: true,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.wbrkwala {
- height: (62px + 1px) + (62px + 1px) + (62px + 1px) + (62px + 1px) + 62px;
- overflow: hidden;
-
- > .tags {
- .chart-move {
- transition: transform 1s ease;
- }
-
- > div {
- display: flex;
- align-items: center;
- padding: 14px 16px;
- border-bottom: solid 0.5px var(--divider);
-
- > .tag {
- flex: 1;
- overflow: hidden;
- font-size: 0.9em;
- color: var(--fg);
-
- > .a {
- display: block;
- width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 18px;
- }
-
- > p {
- margin: 0;
- font-size: 75%;
- opacity: 0.7;
- line-height: 16px;
- }
- }
-
- > .chart {
- height: 30px;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/unix-clock.vue b/packages/client/src/widgets/unix-clock.vue
deleted file mode 100644
index cf85ac782c..0000000000
--- a/packages/client/src/widgets/unix-clock.vue
+++ /dev/null
@@ -1,116 +0,0 @@
-<template>
-<div class="mkw-unixClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
- <div v-if="widgetProps.showLabel" class="label">UNIX Epoch</div>
- <div class="time">
- <span v-text="ss"></span>
- <span v-if="widgetProps.showMs" class="colon" :class="{ showColon }">:</span>
- <span v-if="widgetProps.showMs" v-text="ms"></span>
- </div>
- <div v-if="widgetProps.showLabel" class="label">UTC</div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-
-const name = 'unixClock';
-
-const widgetPropsDef = {
- transparent: {
- type: 'boolean' as const,
- default: false,
- },
- fontSize: {
- type: 'number' as const,
- default: 1.5,
- step: 0.1,
- },
- showMs: {
- type: 'boolean' as const,
- default: true,
- },
- showLabel: {
- type: 'boolean' as const,
- default: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-let intervalId;
-const ss = ref('');
-const ms = ref('');
-const showColon = ref(false);
-let prevSec: string | null = null;
-
-watch(showColon, (v) => {
- if (v) {
- window.setTimeout(() => {
- showColon.value = false;
- }, 30);
- }
-});
-
-const tick = () => {
- const now = new Date();
- ss.value = Math.floor(now.getTime() / 1000).toString();
- ms.value = Math.floor(now.getTime() % 1000 / 10).toString().padStart(2, '0');
- if (ss.value !== prevSec) showColon.value = true;
- prevSec = ss.value;
-};
-
-tick();
-
-watch(() => widgetProps.showMs, () => {
- if (intervalId) window.clearInterval(intervalId);
- intervalId = window.setInterval(tick, widgetProps.showMs ? 10 : 1000);
-}, { immediate: true });
-
-onUnmounted(() => {
- window.clearInterval(intervalId);
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-unixClock {
- padding: 16px 0;
- text-align: center;
-
- > .label {
- font-size: 65%;
- opacity: 0.7;
- }
-
- > .time {
- > .colon {
- opacity: 0;
- transition: opacity 1s ease;
-
- &.showColon {
- opacity: 1;
- transition: opacity 0s;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/user-list.vue b/packages/client/src/widgets/user-list.vue
deleted file mode 100644
index 9ffbf0d8e3..0000000000
--- a/packages/client/src/widgets/user-list.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-userList">
- <template #header><i class="ti ti-users"></i>{{ list ? list.name : i18n.ts._widgets.userList }}</template>
- <template #func><button class="_button" @click="configure()"><i class="ti ti-settings"></i></button></template>
-
- <div :class="$style.root">
- <div v-if="widgetProps.listId == null" class="init">
- <MkButton primary @click="chooseList">{{ i18n.ts._widgets._userList.chooseList }}</MkButton>
- </div>
- <MkLoading v-else-if="fetching"/>
- <div v-else class="users">
- <MkA v-for="user in users" :key="user.id" class="user">
- <MkAvatar :user="user" class="avatar" :show-indicator="true"/>
- </MkA>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/MkContainer.vue';
-import * as os from '@/os';
-import { useInterval } from '@/scripts/use-interval';
-import { i18n } from '@/i18n';
-import MkButton from '@/components/MkButton.vue';
-
-const name = 'userList';
-
-const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
- listId: {
- type: 'string' as const,
- default: null,
- hidden: true,
- },
-};
-
-type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
-
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
-const { widgetProps, configure, save } = useWidgetPropsManager(name,
- widgetPropsDef,
- props,
- emit,
-);
-
-let list = $ref();
-let users = $ref([]);
-let fetching = $ref(true);
-
-async function chooseList() {
- const lists = await os.api('users/lists/list');
- const { canceled, result: list } = await os.select({
- title: i18n.ts.selectList,
- items: lists.map(x => ({
- value: x, text: x.name,
- })),
- default: widgetProps.listId,
- });
- if (canceled) return;
-
- widgetProps.listId = list.id;
- save();
- fetch();
-}
-
-const fetch = () => {
- if (widgetProps.listId == null) {
- fetching = false;
- return;
- }
-
- os.api('users/lists/show', {
- listId: widgetProps.listId,
- }).then(_list => {
- list = _list;
- os.api('users/show', {
- userIds: list.userIds,
- }).then(_users => {
- users = _users;
- fetching = false;
- });
- });
-};
-
-useInterval(fetch, 1000 * 60, {
- immediate: true,
- afterMounted: true,
-});
-
-defineExpose<WidgetComponentExpose>({
- name,
- configure,
- id: props.widget ? props.widget.id : null,
-});
-</script>
-
-<style lang="scss" module>
-.root {
- &:global {
- > .init {
- padding: 16px;
- }
-
- > .users {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(30px, 40px));
- grid-gap: 12px;
- place-content: center;
- padding: 16px;
-
- > .user {
- width: 100%;
- height: 100%;
- aspect-ratio: 1;
-
- > .avatar {
- width: 100%;
- height: 100%;
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts
deleted file mode 100644
index 8bd56a5966..0000000000
--- a/packages/client/src/widgets/widget.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { reactive, watch } from 'vue';
-import { throttle } from 'throttle-debounce';
-import { Form, GetFormResultType } from '@/scripts/form';
-import * as os from '@/os';
-import { deepClone } from '@/scripts/clone';
-
-export type Widget<P extends Record<string, unknown>> = {
- id: string;
- data: Partial<P>;
-};
-
-export type WidgetComponentProps<P extends Record<string, unknown>> = {
- widget?: Widget<P>;
-};
-
-export type WidgetComponentEmits<P extends Record<string, unknown>> = {
- (ev: 'updateProps', props: P);
-};
-
-export type WidgetComponentExpose = {
- name: string;
- id: string | null;
- configure: () => void;
-};
-
-export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>(
- name: string,
- propsDef: F,
- props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
- emit: WidgetComponentEmits<GetFormResultType<F>>,
-): {
- widgetProps: GetFormResultType<F>;
- save: () => void;
- configure: () => void;
-} => {
- const widgetProps = reactive(props.widget ? deepClone(props.widget.data) : {});
-
- const mergeProps = () => {
- for (const prop of Object.keys(propsDef)) {
- if (typeof widgetProps[prop] === 'undefined') {
- widgetProps[prop] = propsDef[prop].default;
- }
- }
- };
- watch(widgetProps, () => {
- mergeProps();
- }, { deep: true, immediate: true });
-
- const save = throttle(3000, () => {
- emit('updateProps', widgetProps);
- });
-
- const configure = async () => {
- const form = deepClone(propsDef);
- for (const item of Object.keys(form)) {
- form[item].default = widgetProps[item];
- }
- const { canceled, result } = await os.form(name, form);
- if (canceled) return;
-
- for (const key of Object.keys(result)) {
- widgetProps[key] = result[key];
- }
-
- save();
- };
-
- return {
- widgetProps,
- save,
- configure,
- };
-};