Migrate ClockControl to vue

This commit is contained in:
James Lyne 2022-06-23 21:28:36 +01:00
parent 4584410ae2
commit c51d2ef554
6 changed files with 208 additions and 277 deletions

View File

@ -17,7 +17,7 @@
<template> <template>
<div id="ui"> <div id="ui">
<div id="ui__top-center" class="ui__section"> <div id="ui__top-center" class="ui__section">
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl> <ClockControl v-if="clockControlEnabled"></ClockControl>
</div> </div>
<div id="ui__top-left" class="ui__section section--vertical"> <div id="ui__top-left" class="ui__section section--vertical">

View File

@ -14,43 +14,205 @@
- limitations under the License. - limitations under the License.
--> -->
<template>
<div :class="{'clock': true, 'ui__element': true, 'ui__panel': digital}">
<div class="clock__sun" :style="sunStyle">
<SvgIcon :name="sunIcon"></SvgIcon>
</div>
<div class="clock__moon" :style="moonStyle">
<SvgIcon :name="moonIcon"></SvgIcon>
</div>
<div v-if="showTime" :class="{'clock__time': true, 'day': minecraftTime?.day, 'night': minecraftTime?.night}">
{{ formattedTime }}
</div>
</div>
</template>
<script lang="ts"> <script lang="ts">
import {computed, defineComponent, onMounted, onUnmounted} from "@vue/runtime-core"; import {computed, defineComponent} from "@vue/runtime-core";
import {useStore} from "@/store"; import {useStore} from "@/store";
import {ClockControl, ClockControlOptions} from "@/leaflet/control/ClockControl"; import SvgIcon from "@/components/SvgIcon.vue";
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap"; import {getMinecraftTime} from "@/util";
import {watch} from "vue";
import "@/assets/icons/clock_moon.svg";
import "@/assets/icons/clock_moon_rain.svg";
import "@/assets/icons/clock_moon_storm.svg";
import "@/assets/icons/clock_sun.svg";
import "@/assets/icons/clock_sun_rain.svg";
import "@/assets/icons/clock_sun_storm.svg";
export default defineComponent({ export default defineComponent({
props: { components: {SvgIcon},
leaflet: { setup() {
type: Object as () => LiveAtlasLeafletMap,
required: true,
}
},
setup(props) {
const store = useStore(), const store = useStore(),
componentSettings = computed(() => store.state.components.clockControl); componentSettings = computed(() => store.state.components.clockControl),
let control = new ClockControl(componentSettings.value as ClockControlOptions) as ClockControl; digital = computed(() => componentSettings.value!.showTimeOfDay && !componentSettings.value!.showWeather && componentSettings.value!.showDigitalClock),
showTime = computed(() => componentSettings.value!.showDigitalClock),
watch(componentSettings, (newSettings) => { worldState = computed(() => store.state.currentWorldState),
props.leaflet.removeControl(control); minecraftTime = computed(() => getMinecraftTime(worldState.value.timeOfDay)),
if(!newSettings) { formattedTime = computed(() => {
return; if (minecraftTime.value) {
return [
minecraftTime.value.hours.toString().padStart(2, '0'),
minecraftTime.value.minutes.toString().padStart(2, '0')
].join(':');
} else {
return '';
}
}),
sunAngle = computed(() => {
const timeOfDay = worldState.value.timeOfDay;
if (timeOfDay > 23100 || timeOfDay < 12900) {
//day mode
let movedTime = timeOfDay + 900;
movedTime = (movedTime >= 24000) ? movedTime - 24000 : movedTime;
//Now we have 0 -> 13800 for the day period
//Divide by 13800*2=27600 instead of 24000 to compress day
return ((movedTime) / 27600 * 2 * Math.PI);
} else {
//night mode
const movedTime = timeOfDay - 12900;
//Now we have 0 -> 10200 for the night period
//Divide by 10200*2=20400 instead of 24000 to expand night
return Math.PI + ((movedTime) / 20400 * 2 * Math.PI);
}
}),
moonAngle = computed(() => sunAngle.value + Math.PI),
sunStyle = computed(() => {
if (worldState.value.timeOfDay >= 0) {
return {'transform': 'translate(' + Math.round(-50 * Math.cos(sunAngle.value)) + 'px, ' + Math.round(-50 * Math.sin(sunAngle.value)) + 'px)'};
} else {
return {'transform': 'translate(-150px, -150px)'};
}
}),
moonStyle = computed(() => {
if (worldState.value.timeOfDay >= 0) {
return {'transform': 'translate(' + Math.round(-50 * Math.cos(moonAngle.value)) + 'px, ' + Math.round(-50 * Math.sin(moonAngle.value)) + 'px)'};
} else {
return {'transform': 'translate(-150px, -150px)'};
}
}),
sunIcon = computed(() => {
if (componentSettings.value!.showWeather) {
if (worldState.value.thundering) {
return 'clock_sun_storm';
} else if (worldState.value.raining) {
return 'clock_sun_rain';
}
} }
control = new ClockControl(newSettings as ClockControlOptions); return 'clock_sun';
props.leaflet.addControl(control); }),
}, {deep: true});
onMounted(() => props.leaflet.addControl(control)); moonIcon = computed(() => {
onUnmounted(() => props.leaflet.removeControl(control)); if (componentSettings.value!.showWeather) {
}, if (worldState.value.thundering) {
return 'clock_moon_storm';
} else if (worldState.value.raining) {
return 'clock_moon_rain';
}
}
render() { return 'clock_moon';
return null; });
return {
digital,
showTime,
minecraftTime,
formattedTime,
sunStyle,
moonStyle,
sunIcon,
moonIcon
}
} }
}) })
</script> </script>
<style lang="scss" scoped>
@import '../../../scss/placeholders';
.clock {
@extend %panel;
position: relative;
width: 15rem;
height: 6rem;
z-index: 50;
font-family: monospace;
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 2rem;
overflow: hidden;
.clock__time {
text-align: center;
font-size: 2rem;
line-height: 2rem;
margin-top: auto;
background-color: var(--background-base);
z-index: 1;
padding: 0.1rem 0.1rem 0;
border-radius: 0.3rem;
&.night {
color: var(--text-night);
}
&.day {
color: var(--text-day);
}
&.night, &.day {
transition: color 8s 8s linear;
}
}
.clock__sun,
.clock__moon {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
width: 15rem;
height: 12rem;
}
}
&.clock--digital {
justify-content: center;
height: var(--ui-button-size);
width: auto;
.clock__sun,
.clock__moon {
display: none;
}
.clock__time {
margin: 0;
font-size: 3rem;
}
}
@media (max-width: 480px), (max-height: 480px) {
transform: scale(calc((1/6)*5));
transform-origin: top center
}
}
</style>

8
src/index.d.ts vendored
View File

@ -18,6 +18,7 @@ import {State} from "@/store";
import {DynmapUrlConfig} from "@/dynmap"; import {DynmapUrlConfig} from "@/dynmap";
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
import { import {
ControlOptions,
Coords, Coords,
DoneCallback, FitBoundsOptions, DoneCallback, FitBoundsOptions,
InternalTiles, LatLng, InternalTiles, LatLng,
@ -26,7 +27,6 @@ import {
PolylineOptions PolylineOptions
} from "leaflet"; } from "leaflet";
import {CoordinatesControlOptions} from "@/leaflet/control/CoordinatesControl"; import {CoordinatesControlOptions} from "@/leaflet/control/CoordinatesControl";
import {ClockControlOptions} from "@/leaflet/control/ClockControl";
import {LogoControlOptions} from "@/leaflet/control/LogoControl"; import {LogoControlOptions} from "@/leaflet/control/LogoControl";
import {globalMessages, serverMessages} from "../messages"; import {globalMessages, serverMessages} from "../messages";
import {LiveAtlasMarkerType} from "@/util/markers"; import {LiveAtlasMarkerType} from "@/util/markers";
@ -291,6 +291,12 @@ interface LiveAtlasComponentConfig {
login: boolean; login: boolean;
} }
export interface ClockControlOptions extends ControlOptions {
showTimeOfDay: boolean;
showDigitalClock: boolean;
showWeather: boolean;
}
interface LiveAtlasPartialComponentConfig { interface LiveAtlasPartialComponentConfig {
markers?: { markers?: {
showLabels: boolean; showLabels: boolean;

View File

@ -1,175 +0,0 @@
/*
* Copyright 2022 James Lyne
*
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
* These portions are Copyright 2020 Dynmap Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ControlOptions, DomUtil, Util, Control} from 'leaflet';
import {getMinecraftTime} from '@/util';
import {watch} from "@vue/runtime-core";
import {useStore} from "@/store";
import "@/assets/icons/clock_moon.svg";
import "@/assets/icons/clock_moon_rain.svg";
import "@/assets/icons/clock_moon_storm.svg";
import "@/assets/icons/clock_sun.svg";
import "@/assets/icons/clock_sun_rain.svg";
import "@/assets/icons/clock_sun_storm.svg";
import {LiveAtlasWorldState} from "@/index";
export interface ClockControlOptions extends ControlOptions {
showTimeOfDay: boolean;
showDigitalClock: boolean;
showWeather: boolean;
}
/**
* Leaflet map control providing a clock which can display the current in-game time of day and weather
*/
export class ClockControl extends Control {
declare options: ClockControlOptions;
declare _container?: HTMLElement;
private _sun?: HTMLElement;
private _moon?: HTMLElement;
private _clock?: HTMLElement;
private _currentMoonIcon?: string;
private _currentSunIcon?: string;
private _unwatchHandler?: Function;
constructor(options: ClockControlOptions) {
super(Object.assign(options, {position: 'topcenter'}));
Util.setOptions(this, options);
}
onAdd() {
const digital = !this.options.showTimeOfDay && !this.options.showWeather && this.options.showDigitalClock,
worldState = useStore().state.currentWorldState;
this._container = DomUtil.create('div', 'clock' + (digital ? ' clock--digital' : ''));
this._sun = DomUtil.create('div', 'clock__sun', this._container);
this._moon = DomUtil.create('div', 'clock__moon', this._container);
this._sun.style.transform = 'translate(-150px, -150px)';
this._moon.style.transform = 'translate(-150px, -150px)';
this._sun!.innerHTML = `
<svg class="svg-icon" aria-hidden="true">
<use xlink:href="#icon--clock_sun" />
</svg>`;
this._moon!.innerHTML = `
<svg class="svg-icon" aria-hidden="true">
<use xlink:href="#icon--clock_moon" />
</svg>`;
if (this.options.showDigitalClock) {
this._clock = DomUtil.create('div', 'clock__time', this._container)
}
this._unwatchHandler = watch(worldState, (newValue) => {
this._update(newValue);
}, { deep: true });
return this._container;
}
onRemove() {
if(this._unwatchHandler) {
this._unwatchHandler();
}
}
_update(worldState: LiveAtlasWorldState) {
const timeOfDay = worldState.timeOfDay;
let sunAngle;
if (timeOfDay > 23100 || timeOfDay < 12900) {
//day mode
let movedTime = timeOfDay + 900;
movedTime = (movedTime >= 24000) ? movedTime - 24000 : movedTime;
//Now we have 0 -> 13800 for the day period
//Divide by 13800*2=27600 instead of 24000 to compress day
sunAngle = ((movedTime) / 27600 * 2 * Math.PI);
} else {
//night mode
const movedTime = timeOfDay - 12900;
//Now we have 0 -> 10200 for the night period
//Divide by 10200*2=20400 instead of 24000 to expand night
sunAngle = Math.PI + ((movedTime) / 20400 * 2 * Math.PI);
}
const moonAngle = sunAngle + Math.PI;
if (timeOfDay >= 0) {
this._sun!.style.transform = 'translate(' + Math.round(-50 * Math.cos(sunAngle)) + 'px, ' + Math.round(-50 * Math.sin(sunAngle)) + 'px)';
this._moon!.style.transform = 'translate(' + Math.round(-50 * Math.cos(moonAngle)) + 'px, ' + Math.round(-50 * Math.sin(moonAngle)) + 'px)';
} else {
this._sun!.style.transform = 'translate(-150px, -150px)';
this._moon!.style.transform = 'translate(-150px, -150px)';
}
const minecraftTime = getMinecraftTime(timeOfDay);
if (this.options.showDigitalClock) {
if (timeOfDay >= 0) {
this._clock!.classList.remove(minecraftTime.night ? 'day' : 'night');
this._clock!.classList.add(minecraftTime.day ? 'day' : 'night');
this._clock!.textContent = [
minecraftTime.hours.toString().padStart(2, '0'),
minecraftTime.minutes.toString().padStart(2, '0')
].join(':');
} else {
this._clock!.classList.remove(minecraftTime.night ? 'day' : 'night');
this._clock!.textContent = '';
}
}
if (this.options.showWeather) {
if (worldState.thundering) {
this._setSunIcon('clock_sun_storm');
this._setMoonIcon('clock_moon_storm');
} else if (worldState.raining) {
this._setSunIcon('clock_sun_rain');
this._setMoonIcon('clock_moon_rain');
} else {
this._setSunIcon('clock_sun');
this._setMoonIcon('clock_moon');
}
}
}
_setSunIcon(icon: string) {
if(this._sun && this._currentSunIcon !== icon) {
this._sun!.innerHTML = `
<svg class="svg-icon" aria-hidden="true">
<use xlink:href="#icon--${icon}" />
</svg>`;
this._currentSunIcon = icon;
}
}
_setMoonIcon(icon: string) {
if(this._moon && this._currentMoonIcon !== icon) {
this._moon!.innerHTML = `
<svg class="svg-icon" aria-hidden="true">
<use xlink:href="#icon--${icon}" />
</svg>`;
this._currentMoonIcon = icon;
}
}
}

View File

@ -282,78 +282,6 @@ img {
image-rendering: pixelated; image-rendering: pixelated;
} }
.clock {
@extend %panel;
position: relative;
width: 15rem;
height: 6rem;
z-index: 50;
font-family: monospace;
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 2rem;
overflow: hidden;
.clock__time {
text-align: center;
font-size: 2rem;
line-height: 2rem;
margin-top: auto;
background-color: var(--background-base);
z-index: 1;
padding: 0.1rem 0.1rem 0;
border-radius: 0.3rem;
&.night {
color: var(--text-night);
}
&.day {
color: var(--text-day);
}
&.night, &.day {
transition: color 8s 8s linear;
}
}
.clock__sun,
.clock__moon {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
width: 15rem;
height: 12rem;
}
}
&.clock--digital {
justify-content: center;
height: var(--ui-button-size);
width: auto;
.clock__sun,
.clock__moon {
display: none;
}
.clock__time {
margin: 0;
font-size: 3rem;
}
}
@media (max-width: 480px), (max-height: 480px) {
transform: scale(calc((1/6)*5));
transform-origin: top center
}
}
.form { .form {
.form__group { .form__group {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;

View File

@ -34,12 +34,22 @@ export const titleColoursRegex = /§[0-9a-f]/ig;
export const netherWorldNameRegex = /[_\s]?nether([\s_]|$)/i; export const netherWorldNameRegex = /[_\s]?nether([\s_]|$)/i;
export const endWorldNameRegex = /(^|[_\s])end([\s_]|$)/i; export const endWorldNameRegex = /(^|[_\s])end([\s_]|$)/i;
export interface MinecraftTime {
serverTime: number;
days: number;
hours: number;
minutes: number;
seconds: number;
day: boolean;
night: boolean;
}
/** /**
* Calculates 24 hour time of day and the current day from the given server time * Calculates 24 hour time of day and the current day from the given server time
* @param {number} serverTime Server time in ticks * @param {number} serverTime Server time in ticks
* @returns The equivalent 24 hour time, current day and whether it is currently day or night * @returns The equivalent 24 hour time, current day and whether it is currently day or night
*/ */
export const getMinecraftTime = (serverTime: number) => { export const getMinecraftTime = (serverTime: number): MinecraftTime => {
const day = serverTime >= 0 && serverTime < 13700; const day = serverTime >= 0 && serverTime < 13700;
return { return {