From bdaa35d11fa1ab08a21c90e6e71adfa3d767cf7e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 4 Aug 2022 22:20:00 +0900
Subject: [PATCH] feat(client): improve widget

---
 CHANGELOG.md                                  |   1 +
 locales/ja-JP.yml                             |   1 +
 packages/client/src/widgets/digital-clock.vue |  39 ++++--
 packages/client/src/widgets/index.ts          |   2 +
 packages/client/src/widgets/unix-clock.vue    | 116 ++++++++++++++++++
 5 files changed, 151 insertions(+), 8 deletions(-)
 create mode 100644 packages/client/src/widgets/unix-clock.vue

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 225e2fbae6..5111cce436 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ You should also include the user name that made the change.
 
 ### Improvements
 - Client: Add vi-VN language support
+- Client: Add unix time widget @syuilo
 
 ### Bugfixes
 - Server: リモートユーザーを正しくブロックできるように修正する @xianonn
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index f8895bd99c..b10cce9231 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1312,6 +1312,7 @@ _widgets:
   activity: "アクティビティ"
   photos: "フォト"
   digitalClock: "デジタル時計"
+  unixClock: "UNIX時計"
   federation: "連合"
   instanceCloud: "インスタンスクラウド"
   postForm: "投稿フォーム"
diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue
index a17ed040c9..743c5657b4 100644
--- a/packages/client/src/widgets/digital-clock.vue
+++ b/packages/client/src/widgets/digital-clock.vue
@@ -1,21 +1,21 @@
 <template>
 <div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
-	<span>
+	<div class="time">
 		<span v-text="hh"></span>
-		<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
+		<span class="colon" :class="{ showColon }">:</span>
 		<span v-text="mm"></span>
-		<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
+		<span class="colon" :class="{ showColon }">:</span>
 		<span v-text="ss"></span>
-		<span v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
+		<span v-if="widgetProps.showMs" class="colon" :class="{ showColon }">:</span>
 		<span v-if="widgetProps.showMs" v-text="ms"></span>
-	</span>
+	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { onUnmounted, ref, watch } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
 
 const name = 'digitalClock';
 
@@ -54,14 +54,25 @@ const hh = ref('');
 const mm = ref('');
 const ss = ref('');
 const ms = ref('');
-const showColon = ref(true);
+const showColon = ref(false);
+let prevSec: number | null = null;
+
+watch(showColon, (v) => {
+	if (v) {
+		window.setTimeout(() => {
+			showColon.value = false;
+		}, 30);
+	}
+});
+
 const tick = () => {
 	const now = new Date();
 	hh.value = now.getHours().toString().padStart(2, '0');
 	mm.value = now.getMinutes().toString().padStart(2, '0');
 	ss.value = now.getSeconds().toString().padStart(2, '0');
 	ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0');
-	showColon.value = now.getSeconds() % 2 === 0;
+	if (now.getSeconds() !== prevSec) showColon.value = true;
+	prevSec = now.getSeconds();
 };
 
 tick();
@@ -86,5 +97,17 @@ defineExpose<WidgetComponentExpose>({
 .mkw-digitalClock {
 	padding: 16px 0;
 	text-align: center;
+
+	> .time {
+		> .colon {
+			opacity: 0;
+			transition: opacity 1s ease;
+
+			&.showColon {
+				opacity: 1;
+				transition: opacity 0s;
+			}
+		}
+	}
 }
 </style>
diff --git a/packages/client/src/widgets/index.ts b/packages/client/src/widgets/index.ts
index baf6acd23d..66bec7c83f 100644
--- a/packages/client/src/widgets/index.ts
+++ b/packages/client/src/widgets/index.ts
@@ -12,6 +12,7 @@ export default function(app: App) {
 	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')));
@@ -36,6 +37,7 @@ export const widgets = [
 	'activity',
 	'photos',
 	'digitalClock',
+	'unixClock',
 	'federation',
 	'instanceCloud',
 	'postForm',
diff --git a/packages/client/src/widgets/unix-clock.vue b/packages/client/src/widgets/unix-clock.vue
new file mode 100644
index 0000000000..c9e2b4b92a
--- /dev/null
+++ b/packages/client/src/widgets/unix-clock.vue
@@ -0,0 +1,116 @@
+<template>
+<div class="mkw-unixClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
+	<div v-if="widgetProps.showLabel" class="label">UNIX time</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>