Merge branch 'develop' into pr/ThatOneCalculator/8764

This commit is contained in:
tamaina 2022-08-09 02:12:01 +00:00
commit 9326445aac
34 changed files with 1080 additions and 108 deletions

View file

@ -1,12 +1,30 @@
<template>
<svg class="mbcofsoe" viewBox="0 0 10 10" preserveAspectRatio="none">
<circle v-for="(angle, i) in graduations"
:key="i"
:cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
:cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
:r="i % 5 == 0 ? 0.125 : 0.05"
:fill="i % 5 == 0 ? majorGraduationColor : minorGraduationColor"
/>
<template v-if="props.graduations === 'dots'">
<circle
v-for="(angle, i) in graduationsMajor"
:cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
:cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
:r="0.125"
:fill="(props.twentyfour ? h : h % 12) === i ? nowColor : majorGraduationColor"
:opacity="!props.fadeGraduations || (props.twentyfour ? h : h % 12) === i ? 1 : Math.max(0, 1 - (angleDiff(hAngle, angle) / Math.PI) - numbersOpacityFactor)"
/>
</template>
<template v-else-if="props.graduations === 'numbers'">
<text
v-for="(angle, i) in texts"
:x="5 + (Math.sin(angle) * (5 - textsPadding))"
:y="5 - (Math.cos(angle) * (5 - textsPadding))"
text-anchor="middle"
dominant-baseline="middle"
:font-size="(props.twentyfour ? h : h % 12) === i ? 1 : 0.7"
:font-weight="(props.twentyfour ? h : h % 12) === i ? 'bold' : 'normal'"
:fill="(props.twentyfour ? h : h % 12) === i ? nowColor : 'currentColor'"
:opacity="!props.fadeGraduations || (props.twentyfour ? h : h % 12) === i ? 1 : Math.max(0, 1 - (angleDiff(hAngle, angle) / Math.PI) - numbersOpacityFactor)"
>
{{ i === 0 ? (props.twentyfour ? '24' : '12') : i }}
</text>
</template>
<line
:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
@ -41,63 +59,116 @@
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { ref, computed, onMounted, onBeforeUnmount, shallowRef } from 'vue';
import tinycolor from 'tinycolor2';
import { globalEvents } from '@/events.js';
withDefaults(defineProps<{
thickness: number;
// https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
const angleDiff = (a: number, b: number) => {
const x = Math.abs(a - b);
return Math.abs((x + Math.PI) % (Math.PI * 2) - Math.PI);
};
const graduationsPadding = 0.5;
const textsPadding = 0.6;
const handsPadding = 1;
const handsTailLength = 0.7;
const hHandLengthRatio = 0.75;
const mHandLengthRatio = 1;
const sHandLengthRatio = 1;
const numbersOpacityFactor = 0.35;
const props = withDefaults(defineProps<{
thickness?: number;
offset?: number;
twentyfour?: boolean;
graduations?: 'none' | 'dots' | 'numbers';
fadeGraduations?: boolean;
}>(), {
numbers: false,
thickness: 0.1,
offset: 0 - new Date().getTimezoneOffset(),
twentyfour: false,
graduations: 'dots',
fadeGraduations: true,
});
const now = ref(new Date());
const enabled = ref(true);
const graduationsPadding = ref(0.5);
const handsPadding = ref(1);
const handsTailLength = ref(0.7);
const hHandLengthRatio = ref(0.75);
const mHandLengthRatio = ref(1);
const sHandLengthRatio = ref(1);
const computedStyle = getComputedStyle(document.documentElement);
const dark = computed(() => tinycolor(computedStyle.getPropertyValue('--bg')).isDark());
const majorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)');
const minorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)');
const sHandColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)');
const mHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--fg')).toHexString());
const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--accent')).toHexString());
const s = computed(() => now.value.getSeconds());
const m = computed(() => now.value.getMinutes());
const h = computed(() => now.value.getHours());
const hAngle = computed(() => Math.PI * (h.value % 12 + (m.value + s.value / 60) / 60) / 6);
const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30);
const sAngle = computed(() => Math.PI * s.value / 30);
const graduations = computed(() => {
const graduationsMajor = computed(() => {
const angles: number[] = [];
for (let i = 0; i < 60; i++) {
const angle = Math.PI * i / 30;
const times = props.twentyfour ? 24 : 12;
for (let i = 0; i < times; i++) {
const angle = Math.PI * i / (times / 2);
angles.push(angle);
}
return angles;
});
const texts = computed(() => {
const angles: number[] = [];
const times = props.twentyfour ? 24 : 12;
for (let i = 0; i < times; i++) {
const angle = Math.PI * i / (times / 2);
angles.push(angle);
}
return angles;
});
let enabled = true;
let majorGraduationColor = $ref<string>();
//let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>();
let mHandColor = $ref<string>();
let hHandColor = $ref<string>();
let nowColor = $ref<string>();
let h = $ref<number>(0);
let m = $ref<number>(0);
let s = $ref<number>(0);
let hAngle = $ref<number>(0);
let mAngle = $ref<number>(0);
let sAngle = $ref<number>(0);
function tick() {
now.value = new Date();
const now = new Date();
now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle = Math.PI * (m + s / 60) / 30;
sAngle = Math.PI * s / 30;
}
tick();
function calcColors() {
const computedStyle = getComputedStyle(document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
hHandColor = accent;
nowColor = accent;
}
calcColors();
onMounted(() => {
const update = () => {
if (enabled.value) {
if (enabled) {
tick();
window.setTimeout(update, 1000);
}
};
update();
globalEvents.on('themeChanged', calcColors);
});
onBeforeUnmount(() => {
enabled.value = false;
enabled = false;
globalEvents.off('themeChanged', calcColors);
});
</script>

View file

@ -0,0 +1,77 @@
<template>
<span class="zjobosdg">
<span v-text="hh"></span>
<span class="colon" :class="{ showColon }">:</span>
<span v-text="mm"></span>
<span v-if="showS" class="colon" :class="{ showColon }">:</span>
<span v-if="showS" v-text="ss"></span>
<span v-if="showMs" class="colon" :class="{ showColon }">:</span>
<span v-if="showMs" v-text="ms"></span>
</span>
</template>
<script lang="ts" setup>
import { onUnmounted, ref, watch } from 'vue';
const props = withDefaults(defineProps<{
showS?: boolean;
showMs?: boolean;
offset?: number;
}>(), {
showS: true,
showMs: false,
offset: 0 - new Date().getTimezoneOffset(),
});
let intervalId;
const hh = ref('');
const mm = ref('');
const ss = ref('');
const ms = ref('');
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();
now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
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');
if (now.getSeconds() !== prevSec) showColon.value = true;
prevSec = now.getSeconds();
};
tick();
watch(() => props.showMs, () => {
if (intervalId) window.clearInterval(intervalId);
intervalId = window.setInterval(tick, props.showMs ? 10 : 1000);
}, { immediate: true });
onUnmounted(() => {
window.clearInterval(intervalId);
});
</script>
<style lang="scss" scoped>
.zjobosdg {
> .colon {
opacity: 0;
transition: opacity 1s ease;
&.showColon {
opacity: 1;
transition: opacity 0s;
}
}
}
</style>

View file

@ -20,7 +20,7 @@ const props = withDefaults(defineProps<{
const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
const absolute = _time.toLocaleString();
let now = $ref(new Date());
let now = $shallowRef(new Date());
const relative = $computed(() => {
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
return (

View file

@ -105,6 +105,10 @@ export const routes = [{
path: '/sounds',
name: 'sounds',
component: page(() => import('./pages/settings/sounds.vue')),
}, {
path: '/plugin/install',
name: 'plugin',
component: page(() => import('./pages/settings/plugin.install.vue')),
}, {
path: '/plugin',
name: 'plugin',
@ -149,6 +153,18 @@ export const routes = [{
path: '/preferences-backups',
name: 'preferences-backups',
component: page(() => import('./pages/settings/preferences-backups.vue')),
}, {
path: '/custom-css',
name: 'general',
component: page(() => import('./pages/settings/custom-css.vue')),
}, {
path: '/account-info',
name: 'other',
component: page(() => import('./pages/settings/account-info.vue')),
}, {
path: '/delete-account',
name: 'other',
component: page(() => import('./pages/settings/delete-account.vue')),
}, {
path: '/other',
name: 'other',
@ -451,7 +467,7 @@ mainRouter.addListener('push', ctx => {
if (scrollPos !== 0) {
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
window.scroll({ top: scrollPos, behavior: 'instant' });
}, 1000);
}, 100);
}
});
@ -469,7 +485,7 @@ window.addEventListener('popstate', (event) => {
window.scroll({ top: scrollPos, behavior: 'instant' });
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
window.scroll({ top: scrollPos, behavior: 'instant' });
}, 1000);
}, 100);
});
export function useRouter(): Router {

View file

@ -0,0 +1,49 @@
export const timezones = [{
name: 'UTC',
abbrev: 'UTC',
offset: 0,
}, {
name: 'Europe/Berlin',
abbrev: 'CET',
offset: 60,
}, {
name: 'Asia/Tokyo',
abbrev: 'JST',
offset: 540,
}, {
name: 'Asia/Seoul',
abbrev: 'KST',
offset: 540,
}, {
name: 'Asia/Shanghai',
abbrev: 'CST',
offset: 480,
}, {
name: 'Australia/Sydney',
abbrev: 'AEST',
offset: 600,
}, {
name: 'Australia/Darwin',
abbrev: 'ACST',
offset: 570,
}, {
name: 'Australia/Perth',
abbrev: 'AWST',
offset: 480,
}, {
name: 'America/New_York',
abbrev: 'EST',
offset: -300,
}, {
name: 'America/Mexico_City',
abbrev: 'CST',
offset: -360,
}, {
name: 'America/Phoenix',
abbrev: 'MST',
offset: -420,
}, {
name: 'America/Los_Angeles',
abbrev: 'PST',
offset: -480,
}];

View file

@ -1,17 +1,30 @@
<template>
<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock">
<div class="vubelbmv">
<MkAnalogClock class="clock" :thickness="widgetProps.thickness"/>
<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"
/>
<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 { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
import { GetFormResultType } from '@/scripts/form';
import MkContainer from '@/components/ui/container.vue';
import MkAnalogClock from '@/components/analog-clock.vue';
import MkDigitalClock from '@/components/digital-clock.vue';
import { timezones } from '@/scripts/timezones';
import { i18n } from '@/i18n';
const name = 'clock';
@ -20,15 +33,69 @@ const widgetPropsDef = {
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.1,
default: 0.2,
options: [{
value: 0.1, label: 'thin'
value: 0.1, label: 'thin',
}, {
value: 0.2, label: 'medium'
value: 0.2, label: 'medium',
}, {
value: 0.3, label: 'thick'
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,
},
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,
}],
},
};
@ -47,6 +114,16 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
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,
@ -56,11 +133,59 @@ defineExpose<WidgetComponentExpose>({
<style lang="scss" scoped>
.vubelbmv {
padding: 8px;
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 {
height: 150px;
margin: auto;
}
&.small {
padding: 12px;
> .clock {
height: 100px;
}
}
&.medium {
padding: 14px;
> .clock {
height: 150px;
}
}
&.large {
padding: 16px;
> .clock {
height: 200px;
}
}
}
</style>

View file

@ -1,21 +1,19 @@
<template>
<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.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 v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
<span v-if="widgetProps.showMs" v-text="ms"></span>
</span>
<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 { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
import { GetFormResultType } from '@/scripts/form';
import { timezones } from '@/scripts/timezones';
import MkDigitalClock from '@/components/digital-clock.vue';
const name = 'digitalClock';
@ -33,6 +31,21 @@ const widgetPropsDef = {
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>;
@ -49,31 +62,15 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);
let intervalId;
const hh = ref('');
const mm = ref('');
const ss = ref('');
const ms = ref('');
const showColon = ref(true);
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;
};
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) ?? '?');
tick();
const tzOffset = $computed(() => widgetProps.timezone === null
? 0 - new Date().getTimezoneOffset()
: timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.offset ?? 0);
watch(() => widgetProps.showMs, () => {
if (intervalId) window.clearInterval(intervalId);
intervalId = window.setInterval(tick, widgetProps.showMs ? 10 : 1000);
}, { immediate: true });
onUnmounted(() => {
window.clearInterval(intervalId);
});
const tzOffsetLabel = $computed(() => (tzOffset >= 0 ? '+' : '-') + Math.floor(tzOffset / 60).toString().padStart(2, '0') + ':' + (tzOffset % 60).toString().padStart(2, '0'));
defineExpose<WidgetComponentExpose>({
name,
@ -86,5 +83,10 @@ defineExpose<WidgetComponentExpose>({
.mkw-digitalClock {
padding: 16px 0;
text-align: center;
> .label {
font-size: 65%;
opacity: 0.7;
}
}
</style>

View file

@ -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',

View file

@ -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>;
// vueimporttype
//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>