mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	enhance(client): improve clock widgets
This commit is contained in:
		
							parent
							
								
									bdaa35d11f
								
							
						
					
					
						commit
						2cd70b80a2
					
				
					 4 changed files with 203 additions and 31 deletions
				
			
		| 
						 | 
				
			
			@ -1,13 +1,29 @@
 | 
			
		|||
<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"
 | 
			
		||||
	<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.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-family="Verdana"
 | 
			
		||||
			font-size="0.75"
 | 
			
		||||
			fill="currentColor"
 | 
			
		||||
		>
 | 
			
		||||
			{{ i === 0 ? (props.twentyfour ? '24' : '12') : i }}
 | 
			
		||||
		</text>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<line
 | 
			
		||||
		:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
 | 
			
		||||
		:y1="5 + (Math.cos(sAngle) * (sHandLengthRatio * handsTailLength))"
 | 
			
		||||
| 
						 | 
				
			
			@ -44,22 +60,50 @@
 | 
			
		|||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
 | 
			
		||||
import tinycolor from 'tinycolor2';
 | 
			
		||||
 | 
			
		||||
withDefaults(defineProps<{
 | 
			
		||||
	thickness: number;
 | 
			
		||||
const graduationsPadding = 0.5;
 | 
			
		||||
const textsPadding = 0.5;
 | 
			
		||||
const handsPadding = 1;
 | 
			
		||||
const handsTailLength = 0.7;
 | 
			
		||||
const hHandLengthRatio = 0.75;
 | 
			
		||||
const mHandLengthRatio = 1;
 | 
			
		||||
const sHandLengthRatio = 1;
 | 
			
		||||
const graduations = (() => {
 | 
			
		||||
	const angles: number[] = [];
 | 
			
		||||
	for (let i = 0; i < 60; i++) {
 | 
			
		||||
		const angle = Math.PI * i / 30;
 | 
			
		||||
		angles.push(angle);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return angles;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	numbers?: boolean;
 | 
			
		||||
	thickness?: number;
 | 
			
		||||
	offset?: number;
 | 
			
		||||
	twentyfour?: boolean;
 | 
			
		||||
}>(), {
 | 
			
		||||
	numbers: false,
 | 
			
		||||
	thickness: 0.1,
 | 
			
		||||
	offset: 0 - new Date().getTimezoneOffset(),
 | 
			
		||||
	twentyfour: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
now.value.setMinutes(now.value.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
 | 
			
		||||
 | 
			
		||||
const enabled = ref(true);
 | 
			
		||||
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)');
 | 
			
		||||
| 
						 | 
				
			
			@ -69,21 +113,13 @@ const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--ac
 | 
			
		|||
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 hAngle = computed(() => Math.PI * (h.value % (props.twentyfour ? 24 : 12) + (m.value + s.value / 60) / 60) / (props.twentyfour ? 12 : 6));
 | 
			
		||||
const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30);
 | 
			
		||||
const sAngle = computed(() => Math.PI * s.value / 30);
 | 
			
		||||
const graduations = computed(() => {
 | 
			
		||||
	const angles: number[] = [];
 | 
			
		||||
	for (let i = 0; i < 60; i++) {
 | 
			
		||||
		const angle = Math.PI * i / 30;
 | 
			
		||||
		angles.push(angle);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return angles;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function tick() {
 | 
			
		||||
	now.value = new Date();
 | 
			
		||||
	now.value.setMinutes(now.value.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										49
									
								
								packages/client/src/scripts/timezones.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/client/src/scripts/timezones.ts
									
										
									
									
									
										Normal 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,
 | 
			
		||||
}];
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +1,20 @@
 | 
			
		|||
<template>
 | 
			
		||||
<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock">
 | 
			
		||||
	<div class="vubelbmv">
 | 
			
		||||
		<MkAnalogClock class="clock" :thickness="widgetProps.thickness"/>
 | 
			
		||||
		<div v-if="widgetProps.showLabel" class="label abbrev">{{ tzAbbrev }}</div>
 | 
			
		||||
		<MkAnalogClock class="clock" :thickness="widgetProps.thickness" :offset="tzOffset" :numbers="widgetProps.numbers" :twentyfour="widgetProps.twentyFour"/>
 | 
			
		||||
		<div v-if="widgetProps.showLabel" class="label 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 { timezones } from '@/scripts/timezones';
 | 
			
		||||
 | 
			
		||||
const name = 'clock';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,11 +27,34 @@ const widgetPropsDef = {
 | 
			
		|||
		type: 'radio' as const,
 | 
			
		||||
		default: 0.1,
 | 
			
		||||
		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',
 | 
			
		||||
		}],
 | 
			
		||||
	},
 | 
			
		||||
	numbers: {
 | 
			
		||||
		type: 'boolean' as const,
 | 
			
		||||
		default: false,
 | 
			
		||||
	},
 | 
			
		||||
	twentyFour: {
 | 
			
		||||
		type: 'boolean' as const,
 | 
			
		||||
		default: false,
 | 
			
		||||
	},
 | 
			
		||||
	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,
 | 
			
		||||
		}],
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +73,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,
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +93,23 @@ defineExpose<WidgetComponentExpose>({
 | 
			
		|||
<style lang="scss" scoped>
 | 
			
		||||
.vubelbmv {
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	position: relative;
 | 
			
		||||
 | 
			
		||||
	> .label {
 | 
			
		||||
		opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
		&.abbrev {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 14px;
 | 
			
		||||
			left: 14px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&.offset {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			bottom: 14px;
 | 
			
		||||
			right: 14px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .clock {
 | 
			
		||||
		height: 150px;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
 | 
			
		||||
	<div v-if="widgetProps.showLabel" class="label">{{ tzAbbrev }}</div>
 | 
			
		||||
	<div class="time">
 | 
			
		||||
		<span v-text="hh"></span>
 | 
			
		||||
		<span class="colon" :class="{ showColon }">:</span>
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,7 @@
 | 
			
		|||
		<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">{{ tzOffsetLabel }}</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +18,7 @@
 | 
			
		|||
import { onUnmounted, ref, watch } from 'vue';
 | 
			
		||||
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 | 
			
		||||
import { GetFormResultType } from '@/scripts/form';
 | 
			
		||||
import { timezones } from '@/scripts/timezones';
 | 
			
		||||
 | 
			
		||||
const name = 'digitalClock';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +36,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,6 +67,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'));
 | 
			
		||||
 | 
			
		||||
let intervalId;
 | 
			
		||||
const hh = ref('');
 | 
			
		||||
const mm = ref('');
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +95,7 @@ watch(showColon, (v) => {
 | 
			
		|||
 | 
			
		||||
const tick = () => {
 | 
			
		||||
	const now = new Date();
 | 
			
		||||
	now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + tzOffset));
 | 
			
		||||
	hh.value = now.getHours().toString().padStart(2, '0');
 | 
			
		||||
	mm.value = now.getMinutes().toString().padStart(2, '0');
 | 
			
		||||
	ss.value = now.getSeconds().toString().padStart(2, '0');
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +127,11 @@ defineExpose<WidgetComponentExpose>({
 | 
			
		|||
	padding: 16px 0;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
 | 
			
		||||
	> .label {
 | 
			
		||||
		font-size: 65%;
 | 
			
		||||
		opacity: 0.7;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .time {
 | 
			
		||||
		> .colon {
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue