summaryrefslogtreecommitdiff
path: root/src/client/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/widgets')
-rw-r--r--src/client/widgets/activity.calendar.vue85
-rw-r--r--src/client/widgets/activity.chart.vue107
-rw-r--r--src/client/widgets/activity.vue82
-rw-r--r--src/client/widgets/aichan.vue59
-rw-r--r--src/client/widgets/aiscript.vue163
-rw-r--r--src/client/widgets/button.vue95
-rw-r--r--src/client/widgets/calendar.vue204
-rw-r--r--src/client/widgets/clock.vue55
-rw-r--r--src/client/widgets/define.ts75
-rw-r--r--src/client/widgets/digital-clock.vue79
-rw-r--r--src/client/widgets/federation.vue145
-rw-r--r--src/client/widgets/index.ts45
-rw-r--r--src/client/widgets/job-queue.vue183
-rw-r--r--src/client/widgets/memo.vue106
-rw-r--r--src/client/widgets/notifications.vue65
-rw-r--r--src/client/widgets/online-users.vue67
-rw-r--r--src/client/widgets/photos.vue113
-rw-r--r--src/client/widgets/post-form.vue23
-rw-r--r--src/client/widgets/rss.vue89
-rw-r--r--src/client/widgets/server-metric/cpu-mem.vue174
-rw-r--r--src/client/widgets/server-metric/cpu.vue76
-rw-r--r--src/client/widgets/server-metric/disk.vue70
-rw-r--r--src/client/widgets/server-metric/index.vue82
-rw-r--r--src/client/widgets/server-metric/mem.vue85
-rw-r--r--src/client/widgets/server-metric/net.vue148
-rw-r--r--src/client/widgets/server-metric/pie.vue65
-rw-r--r--src/client/widgets/slideshow.vue167
-rw-r--r--src/client/widgets/timeline.vue116
-rw-r--r--src/client/widgets/trends.vue111
29 files changed, 0 insertions, 2934 deletions
diff --git a/src/client/widgets/activity.calendar.vue b/src/client/widgets/activity.calendar.vue
deleted file mode 100644
index ff767190f6..0000000000
--- a/src/client/widgets/activity.calendar.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<svg viewBox="0 0 21 7">
- <rect v-for="record in data" 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 data" 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="data[0].x" :y="data[0].date.weekday"
- rx="1" ry="1"
- fill="none"
- stroke-width="0.1"
- stroke="#f73520"/>
-</svg>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- props: ['data'],
- created() {
- for (const d of this.data) {
- d.total = d.notes + d.replies + d.renotes;
- }
- const peak = Math.max.apply(null, this.data.map(d => d.total));
-
- const now = new Date();
- const year = now.getFullYear();
- const month = now.getMonth();
- const day = now.getDate();
-
- let x = 20;
- this.data.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/src/client/widgets/activity.chart.vue b/src/client/widgets/activity.chart.vue
deleted file mode 100644
index ee5bc25113..0000000000
--- a/src/client/widgets/activity.chart.vue
+++ /dev/null
@@ -1,107 +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">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-
-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);
-}
-
-export default defineComponent({
- props: ['data'],
- data() {
- return {
- viewBoxX: 147,
- viewBoxY: 60,
- zoom: 1,
- pos: 0,
- pointsNote: null,
- pointsReply: null,
- pointsRenote: null,
- pointsTotal: null
- };
- },
- created() {
- for (const d of this.data) {
- d.total = d.notes + d.replies + d.renotes;
- }
-
- this.render();
- },
- methods: {
- render() {
- const peak = Math.max.apply(null, this.data.map(d => d.total));
- if (peak != 0) {
- const data = this.data.slice().reverse();
- this.pointsNote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' ');
- this.pointsReply = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' ');
- this.pointsRenote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' ');
- this.pointsTotal = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
- }
- },
- onMousedown(e) {
- const clickX = e.clientX;
- const clickY = e.clientY;
- const baseZoom = this.zoom;
- const basePos = this.pos;
-
- // 動かした時
- dragListen(me => {
- let moveLeft = me.clientX - clickX;
- let moveTop = me.clientY - clickY;
-
- this.zoom = baseZoom + (-moveTop / 20);
- this.pos = basePos + moveLeft;
- if (this.zoom < 1) this.zoom = 1;
- if (this.pos > 0) this.pos = 0;
- if (this.pos < -(((this.data.length - 1) * this.zoom) - this.viewBoxX)) this.pos = -(((this.data.length - 1) * this.zoom) - this.viewBoxX);
-
- this.render();
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-svg {
- display: block;
- padding: 16px;
- width: 100%;
- box-sizing: border-box;
- cursor: all-scroll;
-}
-</style>
diff --git a/src/client/widgets/activity.vue b/src/client/widgets/activity.vue
deleted file mode 100644
index cc8d4debd0..0000000000
--- a/src/client/widgets/activity.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader" :naked="props.transparent">
- <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template>
- <template #func><button @click="toggleView()" class="_button"><i class="fas fa-sort"></i></button></template>
-
- <div>
- <MkLoading v-if="fetching"/>
- <template v-else>
- <XCalendar v-show="props.view === 0" :data="[].concat(activity)"/>
- <XChart v-show="props.view === 1" :data="[].concat(activity)"/>
- </template>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import XCalendar from './activity.calendar.vue';
-import XChart from './activity.chart.vue';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'activity',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- transparent: {
- type: 'boolean',
- default: false,
- },
- view: {
- type: 'number',
- default: 0,
- hidden: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- XCalendar,
- XChart,
- },
- data() {
- return {
- fetching: true,
- activity: null,
- };
- },
- mounted() {
- os.api('charts/user/notes', {
- userId: this.$i.id,
- span: 'day',
- limit: 7 * 21
- }).then(activity => {
- this.activity = activity.diffs.normal.map((_, i) => ({
- total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i],
- notes: activity.diffs.normal[i],
- replies: activity.diffs.reply[i],
- renotes: activity.diffs.renote[i]
- }));
- this.fetching = false;
- });
- },
- methods: {
- toggleView() {
- if (this.props.view === 1) {
- this.props.view = 0;
- } else {
- this.props.view++;
- }
- this.save();
- }
- }
-});
-</script>
diff --git a/src/client/widgets/aichan.vue b/src/client/widgets/aichan.vue
deleted file mode 100644
index 06c49090a1..0000000000
--- a/src/client/widgets/aichan.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<template>
-<MkContainer :naked="props.transparent" :show-header="false">
- <iframe class="dedjhjmo" ref="live2d" @click="touched" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100"></iframe>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import define from './define';
-import MkContainer from '@client/components/ui/container.vue';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'ai',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: false,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- },
- data() {
- return {
- };
- },
- mounted() {
- window.addEventListener('mousemove', ev => {
- const iframeRect = this.$refs.live2d.getBoundingClientRect();
- this.$refs.live2d.contentWindow.postMessage({
- type: 'moveCursor',
- body: {
- x: ev.clientX - iframeRect.left,
- y: ev.clientY - iframeRect.top,
- }
- }, '*');
- }, { passive: true });
- },
- methods: {
- touched() {
- //if (this.live2d) this.live2d.changeExpression('gurugurume');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.dedjhjmo {
- width: 100%;
- height: 350px;
- border: none;
- pointer-events: none;
-}
-</style>
diff --git a/src/client/widgets/aiscript.vue b/src/client/widgets/aiscript.vue
deleted file mode 100644
index aaf0a0372e..0000000000
--- a/src/client/widgets/aiscript.vue
+++ /dev/null
@@ -1,163 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader">
- <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template>
-
- <div class="uylguesu _monospace">
- <textarea v-model="props.script" placeholder="(1 + 1)"></textarea>
- <button @click="run" class="_buttonPrimary">RUN</button>
- <div class="logs">
- <div v-for="log in logs" class="log" :key="log.id" :class="{ print: log.print }">{{ log.text }}</div>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import * as os from '@client/os';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
-import { createAiScriptEnv } from '@client/scripts/aiscript/api';
-
-const widget = define({
- name: 'aiscript',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- script: {
- type: 'string',
- multiline: true,
- default: '(1 + 1)',
- hidden: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer
- },
-
- data() {
- return {
- logs: [],
- };
- },
-
- methods: {
- async run() {
- this.logs = [];
- const aiscript = new AiScript(createAiScriptEnv({
- storageKey: 'widget',
- token: this.$i?.token,
- }), {
- in: (q) => {
- return new Promise(ok => {
- os.dialog({
- title: q,
- input: {}
- }).then(({ canceled, result: a }) => {
- ok(a);
- });
- });
- },
- out: (value) => {
- this.logs.push({
- id: Math.random(),
- text: value.type === 'str' ? value.value : utils.valToString(value),
- print: true
- });
- },
- log: (type, params) => {
- switch (type) {
- case 'end': this.logs.push({
- id: Math.random(),
- text: utils.valToString(params.val, true),
- print: false
- }); break;
- default: break;
- }
- }
- });
-
- let ast;
- try {
- ast = parse(this.props.script);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: 'Syntax error :('
- });
- return;
- }
- try {
- await aiscript.exec(ast);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: e
- });
- }
- },
- }
-});
-</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/src/client/widgets/button.vue b/src/client/widgets/button.vue
deleted file mode 100644
index af6718c507..0000000000
--- a/src/client/widgets/button.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<template>
-<div class="mkw-button">
- <MkButton :primary="props.colored" full @click="run">
- {{ props.label }}
- </MkButton>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import define from './define';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
-import { createAiScriptEnv } from '@client/scripts/aiscript/api';
-
-const widget = define({
- name: 'button',
- props: () => ({
- label: {
- type: 'string',
- default: 'BUTTON',
- },
- colored: {
- type: 'boolean',
- default: true,
- },
- script: {
- type: 'string',
- multiline: true,
- default: 'Mk:dialog("hello" "world")',
- },
- })
-});
-
-export default defineComponent({
- components: {
- MkButton
- },
- extends: widget,
- data() {
- return {
- };
- },
- methods: {
- async run() {
- const aiscript = new AiScript(createAiScriptEnv({
- storageKey: 'widget',
- token: this.$i?.token,
- }), {
- in: (q) => {
- return new Promise(ok => {
- os.dialog({
- title: q,
- input: {}
- }).then(({ canceled, result: a }) => {
- ok(a);
- });
- });
- },
- out: (value) => {
- // nop
- },
- log: (type, params) => {
- // nop
- }
- });
-
- let ast;
- try {
- ast = parse(this.props.script);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: 'Syntax error :('
- });
- return;
- }
- try {
- await aiscript.exec(ast);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: e
- });
- }
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-button {
-}
-</style>
diff --git a/src/client/widgets/calendar.vue b/src/client/widgets/calendar.vue
deleted file mode 100644
index fe39145f0d..0000000000
--- a/src/client/widgets/calendar.vue
+++ /dev/null
@@ -1,204 +0,0 @@
-<template>
-<div class="mkw-calendar" :class="{ _panel: !props.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 class="day">{{ $t('dayX', { day }) }}</p>
- <p class="week-day">{{ weekDay }}</p>
- </div>
- <div class="info">
- <div>
- <p>{{ $ts.today }}: <b>{{ dayP.toFixed(1) }}%</b></p>
- <div class="meter">
- <div class="val" :style="{ width: `${dayP}%` }"></div>
- </div>
- </div>
- <div>
- <p>{{ $ts.thisMonth }}: <b>{{ monthP.toFixed(1) }}%</b></p>
- <div class="meter">
- <div class="val" :style="{ width: `${monthP}%` }"></div>
- </div>
- </div>
- <div>
- <p>{{ $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">
-import { defineComponent } from 'vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'calendar',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: false,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- data() {
- return {
- now: new Date(),
- year: null,
- month: null,
- day: null,
- weekDay: null,
- yearP: null,
- dayP: null,
- monthP: null,
- isHoliday: null,
- clock: null
- };
- },
- created() {
- this.tick();
- this.clock = setInterval(this.tick, 1000);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- tick() {
- const now = new Date();
- const nd = now.getDate();
- const nm = now.getMonth();
- const ny = now.getFullYear();
-
- this.year = ny;
- this.month = nm + 1;
- this.day = nd;
- this.weekDay = [
- this.$ts._weekday.sunday,
- this.$ts._weekday.monday,
- this.$ts._weekday.tuesday,
- this.$ts._weekday.wednesday,
- this.$ts._weekday.thursday,
- this.$ts._weekday.friday,
- this.$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();
-
- this.dayP = dayNumer / dayDenom * 100;
- this.monthP = monthNumer / monthDenom * 100;
- this.yearP = yearNumer / yearDenom * 100;
-
- this.isHoliday = now.getDay() === 0 || now.getDay() === 6;
- }
- }
-});
-</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;
- }
- }
-
- > p {
- margin: 0;
- line-height: 18px;
- font-size: 0.9em;
-
- > span {
- 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/src/client/widgets/clock.vue b/src/client/widgets/clock.vue
deleted file mode 100644
index d960c3809a..0000000000
--- a/src/client/widgets/clock.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<template>
-<MkContainer :naked="props.transparent" :show-header="false">
- <div class="vubelbmv">
- <MkAnalogClock class="clock" :thickness="props.thickness"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import define from './define';
-import MkContainer from '@client/components/ui/container.vue';
-import MkAnalogClock from '@client/components/analog-clock.vue';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'clock',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: false,
- },
- thickness: {
- type: 'radio',
- default: 0.1,
- options: [{
- value: 0.1, label: 'thin'
- }, {
- value: 0.2, label: 'medium'
- }, {
- value: 0.3, label: 'thick'
- }]
- }
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- MkAnalogClock
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.vubelbmv {
- padding: 8px;
-
- > .clock {
- height: 150px;
- margin: auto;
- }
-}
-</style>
diff --git a/src/client/widgets/define.ts b/src/client/widgets/define.ts
deleted file mode 100644
index 22b7fb30a1..0000000000
--- a/src/client/widgets/define.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { defineComponent } from 'vue';
-import { throttle } from 'throttle-debounce';
-import { Form } from '@client/scripts/form';
-import * as os from '@client/os';
-
-export default function <T extends Form>(data: {
- name: string;
- props?: () => T;
-}) {
- return defineComponent({
- props: {
- widget: {
- type: Object,
- required: false
- },
- settingCallback: {
- required: false
- }
- },
-
- emits: ['updateProps'],
-
- data() {
- return {
- props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {},
- save: throttle(3000, () => {
- this.$emit('updateProps', this.props);
- }),
- };
- },
-
- computed: {
- id(): string {
- return this.widget ? this.widget.id : null;
- },
- },
-
- created() {
- this.mergeProps();
-
- this.$watch('props', () => {
- this.mergeProps();
- }, { deep: true });
-
- if (this.settingCallback) this.settingCallback(this.setting);
- },
-
- methods: {
- mergeProps() {
- if (data.props) {
- const defaultProps = data.props();
- for (const prop of Object.keys(defaultProps)) {
- if (this.props.hasOwnProperty(prop)) continue;
- this.props[prop] = defaultProps[prop].default;
- }
- }
- },
-
- async setting() {
- const form = data.props();
- for (const item of Object.keys(form)) {
- form[item].default = this.props[item];
- }
- const { canceled, result } = await os.form(data.name, form);
- if (canceled) return;
-
- for (const key of Object.keys(result)) {
- this.props[key] = result[key];
- }
-
- this.save();
- },
- }
- });
-}
diff --git a/src/client/widgets/digital-clock.vue b/src/client/widgets/digital-clock.vue
deleted file mode 100644
index 2202c9ed4b..0000000000
--- a/src/client/widgets/digital-clock.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<template>
-<div class="mkw-digitalClock _monospace" :class="{ _panel: !props.transparent }" :style="{ fontSize: `${props.fontSize}em` }">
- <span>
- <span v-text="hh"></span>
- <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
- <span v-text="mm"></span>
- <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
- <span v-text="ss"></span>
- <span :style="{ visibility: showColon ? 'visible' : 'hidden' }" v-if="props.showMs">:</span>
- <span v-text="ms" v-if="props.showMs"></span>
- </span>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'digitalClock',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: false,
- },
- fontSize: {
- type: 'number',
- default: 1.5,
- step: 0.1,
- },
- showMs: {
- type: 'boolean',
- default: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- data() {
- return {
- clock: null,
- hh: null,
- mm: null,
- ss: null,
- ms: null,
- showColon: true,
- };
- },
- created() {
- this.tick();
- this.$watch(() => this.props.showMs, () => {
- if (this.clock) clearInterval(this.clock);
- this.clock = setInterval(this.tick, this.props.showMs ? 10 : 1000);
- }, { immediate: true });
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- tick() {
- const now = new Date();
- this.hh = now.getHours().toString().padStart(2, '0');
- this.mm = now.getMinutes().toString().padStart(2, '0');
- this.ss = now.getSeconds().toString().padStart(2, '0');
- this.ms = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0');
- this.showColon = now.getSeconds() % 2 === 0;
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mkw-digitalClock {
- padding: 16px 0;
- text-align: center;
-}
-</style>
diff --git a/src/client/widgets/federation.vue b/src/client/widgets/federation.vue
deleted file mode 100644
index 8ab7f594a2..0000000000
--- a/src/client/widgets/federation.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader" :foldable="foldable" :scrollable="scrollable">
- <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template>
-
- <div class="wbrkwalb">
- <MkLoading v-if="fetching"/>
- <transition-group tag="div" name="chart" class="instances" v-else>
- <div v-for="(instance, i) in instances" :key="instance.id" class="instance">
- <img v-if="instance.iconUrl" :src="instance.iconUrl" 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">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import MkMiniChart from '@client/components/mini-chart.vue';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'federation',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer, MkMiniChart
- },
- props: {
- foldable: {
- type: Boolean,
- required: false,
- default: false
- },
- scrollable: {
- type: Boolean,
- required: false,
- default: false
- },
- },
- data() {
- return {
- instances: [],
- charts: [],
- fetching: true,
- };
- },
- mounted() {
- this.fetch();
- this.clock = setInterval(this.fetch, 1000 * 60);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- async fetch() {
- const instances = await os.api('federation/instances', {
- sort: '+lastCommunicatedAt',
- limit: 5
- });
- const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
- this.instances = instances;
- this.charts = charts;
- this.fetching = false;
- }
- }
-});
-</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/src/client/widgets/index.ts b/src/client/widgets/index.ts
deleted file mode 100644
index 51a82af080..0000000000
--- a/src/client/widgets/index.ts
+++ /dev/null
@@ -1,45 +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('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('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('MkwButton', defineAsyncComponent(() => import('./button.vue')));
- app.component('MkwAiscript', defineAsyncComponent(() => import('./aiscript.vue')));
- app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue')));
-}
-
-export const widgets = [
- 'memo',
- 'notifications',
- 'timeline',
- 'calendar',
- 'rss',
- 'trends',
- 'clock',
- 'activity',
- 'photos',
- 'digitalClock',
- 'federation',
- 'postForm',
- 'slideshow',
- 'serverMetric',
- 'onlineUsers',
- 'jobQueue',
- 'button',
- 'aiscript',
- 'aichan',
-];
diff --git a/src/client/widgets/job-queue.vue b/src/client/widgets/job-queue.vue
deleted file mode 100644
index 327d8ede6d..0000000000
--- a/src/client/widgets/job-queue.vue
+++ /dev/null
@@ -1,183 +0,0 @@
-<template>
-<div class="mkw-jobQueue _monospace" :class="{ _panel: !props.transparent }">
- <div class="inbox">
- <div class="label">Inbox queue<i v-if="inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
- <div class="values">
- <div>
- <div>Process</div>
- <div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div>
- </div>
- <div>
- <div>Active</div>
- <div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div>
- </div>
- <div>
- <div>Delayed</div>
- <div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div>
- </div>
- <div>
- <div>Waiting</div>
- <div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div>
- </div>
- </div>
- </div>
- <div class="deliver">
- <div class="label">Deliver queue<i v-if="deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
- <div class="values">
- <div>
- <div>Process</div>
- <div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div>
- </div>
- <div>
- <div>Active</div>
- <div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div>
- </div>
- <div>
- <div>Delayed</div>
- <div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div>
- </div>
- <div>
- <div>Waiting</div>
- <div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import define from './define';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import * as sound from '@client/scripts/sound';
-
-const widget = define({
- name: 'jobQueue',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: false,
- },
- sound: {
- type: 'boolean',
- default: false,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- data() {
- return {
- connection: markRaw(os.stream.useChannel('queueStats')),
- inbox: {
- activeSincePrevTick: 0,
- active: 0,
- waiting: 0,
- delayed: 0,
- },
- deliver: {
- activeSincePrevTick: 0,
- active: 0,
- waiting: 0,
- delayed: 0,
- },
- prev: {},
- sound: sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1)
- };
- },
- created() {
- for (const domain of ['inbox', 'deliver']) {
- this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
- }
-
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
-
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 1
- });
- },
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- this.connection.dispose();
- },
- methods: {
- onStats(stats) {
- for (const domain of ['inbox', 'deliver']) {
- this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
- this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
- this[domain].active = stats[domain].active;
- this[domain].waiting = stats[domain].waiting;
- this[domain].delayed = stats[domain].delayed;
-
- if (this[domain].waiting > 0 && this.props.sound && this.sound.paused) {
- this.sound.play();
- }
- }
- },
-
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- },
-
- number
- }
-});
-</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/src/client/widgets/memo.vue b/src/client/widgets/memo.vue
deleted file mode 100644
index 3f11e6409e..0000000000
--- a/src/client/widgets/memo.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader">
- <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template>
-
- <div class="otgbylcu">
- <textarea v-model="text" :placeholder="$ts.placeholder" @input="onChange"></textarea>
- <button @click="saveMemo" :disabled="!changed" class="_buttonPrimary">{{ $ts.save }}</button>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'memo',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer
- },
-
- data() {
- return {
- text: null,
- changed: false,
- timeoutId: null,
- };
- },
-
- created() {
- this.text = this.$store.state.memo;
-
- this.$watch(() => this.$store.reactiveState.memo, text => {
- this.text = text;
- });
- },
-
- methods: {
- onChange() {
- this.changed = true;
- clearTimeout(this.timeoutId);
- this.timeoutId = setTimeout(this.saveMemo, 1000);
- },
-
- saveMemo() {
- this.$store.set('memo', this.text);
- this.changed = false;
- }
- }
-});
-</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/src/client/widgets/notifications.vue b/src/client/widgets/notifications.vue
deleted file mode 100644
index 5e2648f5b9..0000000000
--- a/src/client/widgets/notifications.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<MkContainer :style="`height: ${props.height}px;`" :show-header="props.showHeader" :scrollable="true">
- <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template>
- <template #func><button @click="configure()" class="_button"><i class="fas fa-cog"></i></button></template>
-
- <div>
- <XNotifications :include-types="props.includingTypes"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import XNotifications from '@client/components/notifications.vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'notifications',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- height: {
- type: 'number',
- default: 300,
- },
- includingTypes: {
- type: 'array',
- hidden: true,
- default: null,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
-
- components: {
- MkContainer,
- XNotifications,
- },
-
- data() {
- return {
- };
- },
-
- methods: {
- configure() {
- os.popup(import('@client/components/notification-setting-window.vue'), {
- includingTypes: this.props.includingTypes,
- }, {
- done: async (res) => {
- const { includingTypes } = res;
- this.props.includingTypes = includingTypes;
- this.save();
- }
- }, 'closed');
- }
- }
-});
-</script>
diff --git a/src/client/widgets/online-users.vue b/src/client/widgets/online-users.vue
deleted file mode 100644
index 37060fca43..0000000000
--- a/src/client/widgets/online-users.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<template>
-<div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }">
- <I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text">
- <template #n><b>{{ onlineUsersCount }}</b></template>
- </I18n>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'onlineUsers',
- props: () => ({
- transparent: {
- type: 'boolean',
- default: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- data() {
- return {
- onlineUsersCount: null,
- clock: null,
- };
- },
- created() {
- this.tick();
- this.clock = setInterval(this.tick, 1000 * 15);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- tick() {
- os.api('get-online-users-count').then(res => {
- this.onlineUsersCount = res.count;
- });
- }
- }
-});
-</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/src/client/widgets/photos.vue b/src/client/widgets/photos.vue
deleted file mode 100644
index 25365d6b87..0000000000
--- a/src/client/widgets/photos.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader" :naked="props.transparent" :class="$style.root" :data-transparent="props.transparent ? true : null">
- <template #header><i class="fas fa-camera"></i>{{ $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">
-import { defineComponent, markRaw } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'photos',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- transparent: {
- type: 'boolean',
- default: false,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- },
- data() {
- return {
- images: [],
- fetching: true,
- connection: null,
- };
- },
- mounted() {
- this.connection = markRaw(os.stream.useChannel('main'));
-
- this.connection.on('driveFileCreated', this.onDriveFileCreated);
-
- os.api('drive/stream', {
- type: 'image/*',
- limit: 9
- }).then(images => {
- this.images = images;
- this.fetching = false;
- });
- },
- beforeUnmount() {
- this.connection.dispose();
- },
- methods: {
- onDriveFileCreated(file) {
- if (/^image\/.+$/.test(file.type)) {
- this.images.unshift(file);
- if (this.images.length > 9) this.images.pop();
- }
- },
-
- thumbnail(image: any): string {
- return this.$store.state.disableShowingAnimatedImages
- ? getStaticImageUrl(image.thumbnailUrl)
- : image.thumbnailUrl;
- },
- }
-});
-</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/src/client/widgets/post-form.vue b/src/client/widgets/post-form.vue
deleted file mode 100644
index 1f260c20d9..0000000000
--- a/src/client/widgets/post-form.vue
+++ /dev/null
@@ -1,23 +0,0 @@
-<template>
-<XPostForm class="_panel" :fixed="true" :autofocus="false"/>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XPostForm from '@client/components/post-form.vue';
-import define from './define';
-
-const widget = define({
- name: 'postForm',
- props: () => ({
- })
-});
-
-export default defineComponent({
- extends: widget,
-
- components: {
- XPostForm,
- },
-});
-</script>
diff --git a/src/client/widgets/rss.vue b/src/client/widgets/rss.vue
deleted file mode 100644
index 6d19a86dff..0000000000
--- a/src/client/widgets/rss.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader">
- <template #header><i class="fas fa-rss-square"></i>RSS</template>
- <template #func><button class="_button" @click="setting"><i class="fas fa-cog"></i></button></template>
-
- <div class="ekmkgxbj">
- <MkLoading v-if="fetching"/>
- <div class="feed" v-else>
- <a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
- </div>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'rss',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- url: {
- type: 'string',
- default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer
- },
- data() {
- return {
- items: [],
- fetching: true,
- clock: null,
- };
- },
- mounted() {
- this.fetch();
- this.clock = setInterval(this.fetch, 60000);
- this.$watch(() => this.props.url, this.fetch);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- fetch() {
- fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {
- }).then(res => {
- res.json().then(feed => {
- this.items = feed.items;
- this.fetching = false;
- });
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ekmkgxbj {
- > .feed {
- padding: 0;
- font-size: 0.9em;
-
- > a {
- 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/src/client/widgets/server-metric/cpu-mem.vue b/src/client/widgets/server-metric/cpu-mem.vue
deleted file mode 100644
index ad9e6a8b0f..0000000000
--- a/src/client/widgets/server-metric/cpu-mem.vue
+++ /dev/null
@@ -1,174 +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">
-import { defineComponent } from 'vue';
-import { v4 as uuid } from 'uuid';
-
-export default defineComponent({
- props: {
- connection: {
- required: true,
- },
- meta: {
- required: true,
- }
- },
- data() {
- return {
- viewBoxX: 50,
- viewBoxY: 30,
- stats: [],
- cpuGradientId: uuid(),
- cpuMaskId: uuid(),
- memGradientId: uuid(),
- memMaskId: uuid(),
- cpuPolylinePoints: '',
- memPolylinePoints: '',
- cpuPolygonPoints: '',
- memPolygonPoints: '',
- cpuHeadX: null,
- cpuHeadY: null,
- memHeadX: null,
- memHeadY: null,
- cpuP: '',
- memP: ''
- };
- },
- mounted() {
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8)
- });
- },
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- },
- methods: {
- onStats(stats) {
- this.stats.push(stats);
- if (this.stats.length > 50) this.stats.shift();
-
- const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu) * this.viewBoxY]);
- const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.active / this.meta.mem.total)) * this.viewBoxY]);
- this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
- this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-
- this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
- this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-
- this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
- this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
- this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
- this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1];
-
- this.cpuP = (stats.cpu * 100).toFixed(0);
- this.memP = (stats.mem.active / this.meta.mem.total * 100).toFixed(0);
- },
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- }
- }
-});
-</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: 5px;
- fill: currentColor;
-
- > tspan {
- opacity: 0.5;
- }
- }
- }
-}
-</style>
diff --git a/src/client/widgets/server-metric/cpu.vue b/src/client/widgets/server-metric/cpu.vue
deleted file mode 100644
index 4478ee3065..0000000000
--- a/src/client/widgets/server-metric/cpu.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-<div class="vrvdvrys">
- <XPie class="pie" :value="usage"/>
- <div>
- <p><i class="fas fa-microchip"></i>CPU</p>
- <p>{{ meta.cpu.cores }} Logical cores</p>
- <p>{{ meta.cpu.model }}</p>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XPie from './pie.vue';
-
-export default defineComponent({
- components: {
- XPie
- },
- props: {
- connection: {
- required: true,
- },
- meta: {
- required: true,
- }
- },
- data() {
- return {
- usage: 0,
- };
- },
- mounted() {
- this.connection.on('stats', this.onStats);
- },
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- },
- methods: {
- onStats(stats) {
- this.usage = stats.cpu;
- }
- }
-});
-</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/src/client/widgets/server-metric/disk.vue b/src/client/widgets/server-metric/disk.vue
deleted file mode 100644
index a3f5d0376b..0000000000
--- a/src/client/widgets/server-metric/disk.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-<template>
-<div class="zbwaqsat">
- <XPie class="pie" :value="usage"/>
- <div>
- <p><i class="fas fa-hdd"></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">
-import { defineComponent } from 'vue';
-import XPie from './pie.vue';
-import bytes from '@client/filters/bytes';
-
-export default defineComponent({
- components: {
- XPie
- },
- props: {
- meta: {
- required: true,
- }
- },
- data() {
- return {
- usage: this.meta.fs.used / this.meta.fs.total,
- total: this.meta.fs.total,
- used: this.meta.fs.used,
- available: this.meta.fs.total - this.meta.fs.used,
- };
- },
- methods: {
- bytes
- }
-});
-</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/src/client/widgets/server-metric/index.vue b/src/client/widgets/server-metric/index.vue
deleted file mode 100644
index 45cd8cebf2..0000000000
--- a/src/client/widgets/server-metric/index.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader" :naked="props.transparent">
- <template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template>
- <template #func><button @click="toggleView()" class="_button"><i class="fas fa-sort"></i></button></template>
-
- <div class="mkw-serverMetric" v-if="meta">
- <XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/>
- <XNet v-if="props.view === 1" :connection="connection" :meta="meta"/>
- <XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/>
- <XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/>
- <XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import define from '../define';
-import MkContainer from '@client/components/ui/container.vue';
-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 * as os from '@client/os';
-
-const widget = define({
- name: 'serverMetric',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- transparent: {
- type: 'boolean',
- default: false,
- },
- view: {
- type: 'number',
- default: 0,
- hidden: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- XCpuMemory,
- XNet,
- XCpu,
- XMemory,
- XDisk,
- },
- data() {
- return {
- meta: null,
- connection: null,
- };
- },
- created() {
- os.api('server-info', {}).then(res => {
- this.meta = res;
- });
- this.connection = markRaw(os.stream.useChannel('serverStats'));
- },
- unmounted() {
- this.connection.dispose();
- },
- methods: {
- toggleView() {
- if (this.props.view == 4) {
- this.props.view = 0;
- } else {
- this.props.view++;
- }
- this.save();
- },
- }
-});
-</script>
diff --git a/src/client/widgets/server-metric/mem.vue b/src/client/widgets/server-metric/mem.vue
deleted file mode 100644
index 92c0aa0c77..0000000000
--- a/src/client/widgets/server-metric/mem.vue
+++ /dev/null
@@ -1,85 +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">
-import { defineComponent } from 'vue';
-import XPie from './pie.vue';
-import bytes from '@client/filters/bytes';
-
-export default defineComponent({
- components: {
- XPie
- },
- props: {
- connection: {
- required: true,
- },
- meta: {
- required: true,
- }
- },
- data() {
- return {
- usage: 0,
- total: 0,
- used: 0,
- free: 0,
- };
- },
- mounted() {
- this.connection.on('stats', this.onStats);
- },
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- },
- methods: {
- onStats(stats) {
- this.usage = stats.mem.active / this.meta.mem.total;
- this.total = this.meta.mem.total;
- this.used = stats.mem.active;
- this.free = this.meta.mem.total - stats.mem.active;
- },
- bytes
- }
-});
-</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/src/client/widgets/server-metric/net.vue b/src/client/widgets/server-metric/net.vue
deleted file mode 100644
index 569c15b58b..0000000000
--- a/src/client/widgets/server-metric/net.vue
+++ /dev/null
@@ -1,148 +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">
-import { defineComponent } from 'vue';
-import bytes from '@client/filters/bytes';
-
-export default defineComponent({
- props: {
- connection: {
- required: true,
- },
- meta: {
- required: true,
- }
- },
- data() {
- return {
- viewBoxX: 50,
- viewBoxY: 30,
- stats: [],
- inPolylinePoints: '',
- outPolylinePoints: '',
- inPolygonPoints: '',
- outPolygonPoints: '',
- inHeadX: null,
- inHeadY: null,
- outHeadX: null,
- outHeadY: null,
- inRecent: 0,
- outRecent: 0
- };
- },
- mounted() {
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8)
- });
- },
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- },
- methods: {
- onStats(stats) {
- this.stats.push(stats);
- if (this.stats.length > 50) this.stats.shift();
-
- const inPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.rx)));
- const outPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.tx)));
-
- const inPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * this.viewBoxY]);
- const outPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * this.viewBoxY]);
- this.inPolylinePoints = inPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
- this.outPolylinePoints = outPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-
- this.inPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.inPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
- this.outPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.outPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-
- this.inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0];
- this.inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1];
- this.outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0];
- this.outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1];
-
- this.inRecent = stats.net.rx;
- this.outRecent = stats.net.tx;
- },
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- },
- bytes
- }
-});
-</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: 5px;
- fill: currentColor;
-
- > tspan {
- opacity: 0.5;
- }
- }
- }
-}
-</style>
diff --git a/src/client/widgets/server-metric/pie.vue b/src/client/widgets/server-metric/pie.vue
deleted file mode 100644
index 38dcf6fcd9..0000000000
--- a/src/client/widgets/server-metric/pie.vue
+++ /dev/null
@@ -1,65 +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">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
- props: {
- value: {
- type: Number,
- required: true
- }
- },
- data() {
- return {
- r: 0.45
- };
- },
- computed: {
- color(): string {
- return `hsl(${180 - (this.value * 180)}, 80%, 70%)`;
- },
- strokeDashoffset(): number {
- return (1 - this.value) * (Math.PI * (this.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/src/client/widgets/slideshow.vue b/src/client/widgets/slideshow.vue
deleted file mode 100644
index 2f079e0d42..0000000000
--- a/src/client/widgets/slideshow.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<template>
-<div class="kvausudm _panel">
- <div @click="choose">
- <p v-if="props.folderId == null">
- <template v-if="isCustomizeMode">{{ $t('folder-customize-mode') }}</template>
- <template v-else>{{ $ts.folder }}</template>
- </p>
- <p v-if="props.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">
-import { defineComponent } from 'vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'slideshow',
- props: () => ({
- height: {
- type: 'number',
- default: 300,
- },
- folderId: {
- type: 'string',
- default: null,
- hidden: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- data() {
- return {
- images: [],
- fetching: true,
- clock: null
- };
- },
- mounted() {
- this.$nextTick(() => {
- this.applySize();
- });
-
- if (this.props.folderId != null) {
- this.fetch();
- }
-
- this.clock = setInterval(this.change, 10000);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- applySize() {
- let h;
-
- if (this.props.size == 1) {
- h = 250;
- } else {
- h = 170;
- }
-
- this.$el.style.height = `${h}px`;
- },
- resize() {
- if (this.props.size == 1) {
- this.props.size = 0;
- } else {
- this.props.size++;
- }
- this.save();
-
- this.applySize();
- },
- change() {
- if (this.images.length == 0) return;
-
- const index = Math.floor(Math.random() * this.images.length);
- const img = `url(${ this.images[index].url })`;
-
- (this.$refs.slideB as any).style.backgroundImage = img;
-
- this.$refs.slideB.classList.add('anime');
- setTimeout(() => {
- // 既にこのウィジェットがunmountされていたら要素がない
- if ((this.$refs.slideA as any) == null) return;
-
- (this.$refs.slideA as any).style.backgroundImage = img;
-
- this.$refs.slideB.classList.remove('anime');
- }, 1000);
- },
- fetch() {
- this.fetching = true;
-
- os.api('drive/files', {
- folderId: this.props.folderId,
- type: 'image/*',
- limit: 100
- }).then(images => {
- this.images = images;
- this.fetching = false;
- (this.$refs.slideA as any).style.backgroundImage = '';
- (this.$refs.slideB as any).style.backgroundImage = '';
- this.change();
- });
- },
- choose() {
- os.selectDriveFolder(false).then(folder => {
- if (folder == null) {
- return;
- }
- this.props.folderId = folder.id;
- this.save();
- this.fetch();
- });
- }
- }
-});
-</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/src/client/widgets/timeline.vue b/src/client/widgets/timeline.vue
deleted file mode 100644
index bd951d8565..0000000000
--- a/src/client/widgets/timeline.vue
+++ /dev/null
@@ -1,116 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader" :style="`height: ${props.height}px;`" :scrollable="true">
- <template #header>
- <button @click="choose" class="_button">
- <i v-if="props.src === 'home'" class="fas fa-home"></i>
- <i v-else-if="props.src === 'local'" class="fas fa-comments"></i>
- <i v-else-if="props.src === 'social'" class="fas fa-share-alt"></i>
- <i v-else-if="props.src === 'global'" class="fas fa-globe"></i>
- <i v-else-if="props.src === 'list'" class="fas fa-list-ul"></i>
- <i v-else-if="props.src === 'antenna'" class="fas fa-satellite"></i>
- <span style="margin-left: 8px;">{{ props.src === 'list' ? props.list.name : props.src === 'antenna' ? props.antenna.name : $t('_timelines.' + props.src) }}</span>
- <i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i>
- </button>
- </template>
-
- <div>
- <XTimeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list ? props.list.id : null" :antenna="props.antenna ? props.antenna.id : null"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import XTimeline from '@client/components/timeline.vue';
-import define from './define';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'timeline',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- height: {
- type: 'number',
- default: 300,
- },
- src: {
- type: 'string',
- default: 'home',
- hidden: true,
- },
- list: {
- type: 'object',
- default: null,
- hidden: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer,
- XTimeline,
- },
-
- data() {
- return {
- menuOpened: false,
- };
- },
-
- methods: {
- async choose(ev) {
- this.menuOpened = 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: 'fas fa-satellite',
- action: () => {
- this.props.antenna = antenna;
- this.setSrc('antenna');
- }
- }));
- const listItems = lists.map(list => ({
- text: list.name,
- icon: 'fas fa-list-ul',
- action: () => {
- this.props.list = list;
- this.setSrc('list');
- }
- }));
- os.popupMenu([{
- text: this.$ts._timelines.home,
- icon: 'fas fa-home',
- action: () => { this.setSrc('home') }
- }, {
- text: this.$ts._timelines.local,
- icon: 'fas fa-comments',
- action: () => { this.setSrc('local') }
- }, {
- text: this.$ts._timelines.social,
- icon: 'fas fa-share-alt',
- action: () => { this.setSrc('social') }
- }, {
- text: this.$ts._timelines.global,
- icon: 'fas fa-globe',
- action: () => { this.setSrc('global') }
- }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => {
- this.menuOpened = false;
- });
- },
-
- setSrc(src) {
- this.props.src = src;
- this.save();
- },
- }
-});
-</script>
diff --git a/src/client/widgets/trends.vue b/src/client/widgets/trends.vue
deleted file mode 100644
index 8511bc718f..0000000000
--- a/src/client/widgets/trends.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-<template>
-<MkContainer :show-header="props.showHeader">
- <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template>
-
- <div class="wbrkwala">
- <MkLoading v-if="fetching"/>
- <transition-group tag="div" name="chart" class="tags" v-else>
- <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">
-import { defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import define from './define';
-import MkMiniChart from '@client/components/mini-chart.vue';
-import * as os from '@client/os';
-
-const widget = define({
- name: 'hashtags',
- props: () => ({
- showHeader: {
- type: 'boolean',
- default: true,
- },
- })
-});
-
-export default defineComponent({
- extends: widget,
- components: {
- MkContainer, MkMiniChart
- },
- data() {
- return {
- stats: [],
- fetching: true,
- };
- },
- mounted() {
- this.fetch();
- this.clock = setInterval(this.fetch, 1000 * 60);
- },
- beforeUnmount() {
- clearInterval(this.clock);
- },
- methods: {
- fetch() {
- os.api('hashtags/trend').then(stats => {
- this.stats = stats;
- this.fetching = false;
- });
- }
- }
-});
-</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>