/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ import {WidgetPrototype} from "./widget/widget-prototype"; import {Globals} from "./domain/globals"; import {UhrRenderer} from "./renderer"; import {EMPTY_LAYOUT, Layout} from "./domain/layout"; import * as Cookies from "js-cookie"; export class Uhr { private timer: number = null; private currentMinute: number = null; constructor(private widgetInstance: WidgetPrototype) { const userTime = this.widgetInstance.options.time; if (this.widgetInstance.options.time === undefined) { this.widgetInstance.options.time = new Date(); } this.parseHash(); this.setupHTML(); this.wireFunctionality(); if (userTime !== undefined) { this.setTime(userTime); } } destroy(): void { if (!!this.timer) { window.clearInterval(this.timer); this.timer = null; } this.widgetInstance.element .removeAttr('style') .removeAttr('class') .empty(); $(`#uhr-configlink${this.widgetInstance.uuid}`).remove(); $(`#uhr-controlpanel${this.widgetInstance.uuid}`).remove(); } start(): void { if (!this.isOn()) { this.timer = window.setInterval(() => { this.widgetInstance.options.time = new Date(); this.update(); }, 1000); this.update(); this.setCookie('uhr-status', 'on'); } } stop(): void { if (this.isOn()) { window.clearInterval(this.timer); this.timer = null; this.update(); this.setCookie('uhr-status', 'off'); } } toggle(): void { if (this.isOn()) { this.stop(); } else { this.start(); } } setLanguage(key: string): void { if (key !== this.widgetInstance.options.language) { this.widgetInstance.options.language = key; const renderer = new UhrRenderer(this.getCurrentLayout(), this.widgetInstance.element.find('.letterarea')); renderer.render(() => { this.currentMinute = -1; this.update(); }); this.setCookie('uhr-language', key); this.update(); } } setTheme(styleClass: string): void { if (styleClass !== this.widgetInstance.options.theme) { this.widgetInstance.element.removeClass(this.widgetInstance.options.theme).addClass(styleClass); $(`#uhr-onoffswitch${this.widgetInstance.uuid}`).removeClass(this.widgetInstance.options.theme).addClass(styleClass); this.widgetInstance.options.theme = styleClass; this.setCookie('uhr-theme', styleClass); } } setTime(time: Date): void { this.currentMinute = null; if (time === null) { this.widgetInstance.options.time = new Date(); } else { if (this.timer !== null) { window.clearInterval(this.timer); } this.widgetInstance.options.time = time; } this.update(); } setMode(mode: string): void { this.widgetInstance.options.mode = mode; this.currentMinute = null; this.update(); this.setCookie('uhr-mode', mode); } setWidth(width: string): void { const e = this.widgetInstance.element; e.css('width', width); const realWidth = e.width(); e.width(realWidth); e.height(realWidth); e.css('font-size', (realWidth / 40) + 'px'); } private setupHTML(): void { const e: JQuery<HTMLElement> = this.widgetInstance.element; // Base clock area e.addClass('uhr') .empty() .append('<span class="item dot dot1"></span>') .append('<span class="item dot dot2"></span>') .append('<span class="item dot dot3"></span>') .append('<span class="item dot dot4"></span>') .append('<div class="letterarea"></div>') .append('<div class="reflection"></div>'); this.setWidth(this.widgetInstance.options.width); if (this.widgetInstance.options.controls) { const controlpanel = $(`<div class="uhr-controlpanel" id="uhr-controlpanel${this.widgetInstance.uuid}"></div>`); const content = $('<div class="content"></div>'); controlpanel.append(content); // on/off switch const toggleSwitch = $(`<div class="onoffswitch" id="uhr-onoffswitch${this.widgetInstance.uuid}"></div>`); toggleSwitch.append(`<input type="checkbox" class="onoffswitch-checkbox" id="uhr-onoffswitch-checkbox${this.widgetInstance.uuid}" checked="checked" />`); toggleSwitch.append(`<label class="onoffswitch-label" for="uhr-onoffswitch-checkbox${this.widgetInstance.uuid}"><div class="onoffswitch-inner"></div><div class="onoffswitch-switch"></div></label>`); content.append(toggleSwitch); // time mode switch const modeSwitch = $(`<div class="onoffswitch" id="uhr-modeswitch${this.widgetInstance.uuid}"></div>`); modeSwitch.append(`<input type="checkbox" class="onoffswitch-checkbox" id="uhr-modeswitch-checkbox${this.widgetInstance.uuid}" checked="checked" />`); modeSwitch.append(`<label class="onoffswitch-label" for="uhr-modeswitch-checkbox${this.widgetInstance.uuid}"><div class="modeswitch-inner"></div><div class="onoffswitch-switch"></div></label>`); content.append(modeSwitch); // language chooser if (Globals.hasMultipleLayouts()) { const languageChooser = $(`<select id="uhr-languagechooser${this.widgetInstance.uuid}"></select>`); Globals.getLayouts().forEach(layout => { languageChooser.append(`<option value="${layout.code}">${layout.prettyName}</option>`); }); content.append(languageChooser); } // theme chooser if (Globals.hasMultipleThemes()) { const themeChooser = $(`<select id="uhr-themechooser${this.widgetInstance.uuid}"></select>`); Globals.getThemes().forEach(theme => { themeChooser.append(`<option value="${theme.styleClass}">${theme.name}</option>`); }); content.append(themeChooser); } const closebutton = $(`<a class="uhr-closecontrolpanel" id="uhr-closecontrolpanel${this.widgetInstance.uuid}"></a>`); //FIXME deprecated?! closebutton.on('click', () => $(`#uhr-controlpanel${this.widgetInstance.uuid}`).hide('fast')); content.append(closebutton); e.after(controlpanel); controlpanel.hide(); const configlink = $(`<a class="uhr-configlink" id="uhr-configlink${this.widgetInstance.uuid}"></a>`); // FIXME deprecated!? configlink.on('click', () => this.toggleConfigScreen()); e.after(configlink); } }; private wireFunctionality(): void { // on/off switch const toggleSwitch = $(`#uhr-onoffswitch-checkbox${this.widgetInstance.uuid}`); // FIXME deprecated!? toggleSwitch.on('click', () => this.toggle()); let status = this.getCookie('uhr-status'); if (status === undefined || this.widgetInstance.options.force) { status = this.widgetInstance.options.status; } toggleSwitch.prop('checked', status === 'on'); if (status === 'on') { this.start(); } else { this.stop(); } // time mode switch const modeSwitch = $(`#uhr-modeswitch-checkbox${this.widgetInstance.uuid}`); // FIXME deprecated!? modeSwitch.on('click', () => { if (this.widgetInstance.options.mode === 'seconds') { this.setMode('normal'); } else { this.setMode('seconds'); } }); let mode = this.getCookie('uhr-mode'); if (mode === undefined || this.widgetInstance.options.force) { mode = this.widgetInstance.options.mode; } modeSwitch.prop('checked', mode !== 'seconds'); if (mode === 'seconds') { this.setMode('seconds'); } else { this.setMode('normal'); } // language chooser const languageChooser = $(`#uhr-languagechooser${this.widgetInstance.uuid}`); // FIXME deprecated!? languageChooser.on('change', () => { const languageKey = $(`#uhr-languagechooser${this.widgetInstance.uuid}`).val() as string; this.setLanguage(languageKey); }); let selectedLayout = this.getCookie('uhr-language'); if (selectedLayout === undefined || this.widgetInstance.options.force) { selectedLayout = this.widgetInstance.options.language; } let found = Globals.getLayouts().some(item => selectedLayout === item.code); if (!found) { let fallbackLanguage; if (Globals.hasLayouts()) { fallbackLanguage = Globals.getFirstLayout().code; } else { fallbackLanguage = ''; } console.warn(`Language '${selectedLayout}' not found! Using fallback '${fallbackLanguage}'.`); selectedLayout = fallbackLanguage; } languageChooser.val(selectedLayout); this.widgetInstance.options.language = ""; this.setLanguage(selectedLayout); // theme chooser const themeChooser = $(`#uhr-themechooser${this.widgetInstance.uuid}`); // FIXME deprecated!? themeChooser.on('change', () => { const themeKey = $(`#uhr-themechooser${this.widgetInstance.uuid}`).val() as string; this.setTheme(themeKey); }); let selectedTheme = this.getCookie('uhr-theme'); if (selectedTheme === undefined || this.widgetInstance.options.force) { selectedTheme = this.widgetInstance.options.theme; } found = Globals.getThemes().some(item => selectedTheme === item.styleClass); if (!found) { const fallbackTheme = Globals.getFirstTheme().styleClass; console.warn(`Theme '${selectedTheme}' not found! Using fallback '${fallbackTheme}'.`); selectedTheme = fallbackTheme; } themeChooser.val(selectedTheme); this.widgetInstance.options.theme = ""; this.setTheme(selectedTheme); if (this.widgetInstance.options.autoresize) { // FIXME deprecated!? $(window).on('resize', () => { const $e = this.widgetInstance.element; const $parent = $e.parent(); const $window = $(window); const parentWidth = $parent.width(); const parentHeight = $parent.height(); const windowWidth = $window.width(); const windowHeight = $window.height(); const size = `${Math.min(parentWidth, parentHeight, windowWidth, windowHeight)}px`; this.setWidth(size); }); } } private isOn(): boolean { return this.timer !== null; } private getCookie(cookieName: string): string { return Cookies.get(cookieName + this.widgetInstance.uuid); } private setCookie(cookieName: string, cookieValue: string): void { let options; if (this.widgetInstance.options.cookiePath !== undefined) { options = {expires: 365, path: this.widgetInstance.options.cookiePath}; } else { options = {expires: 365}; } Cookies.set(cookieName + this.widgetInstance.uuid, cookieValue, options); } private update(): void { if (this.isOn()) { const time = this.widgetInstance.options.time; if (!this.getCurrentLayout().hasOwnProperty('seconds') && this.widgetInstance.options.mode !== 'seconds') { if (time.getMinutes() === this.currentMinute) { return; } this.currentMinute = time.getMinutes(); } this.show(time); } else { this.clear(); this.currentMinute = -1; } } private show(time: Date): void { const second = this.getSecond(time); const dotMinute = this.getDotMinute(time); const hour = this.getHour(time); const coarseMinute = this.getCoarseMinute(time); this.clear(); if (this.widgetInstance.options.mode === 'seconds') { this.highlight(`second${second}`); } else { this.highlight('on'); for (let i = 1; i <= dotMinute; i++) { this.highlight(`dot${i}`); } this.highlight(`minute${coarseMinute}`); this.highlight(`hour${hour}`); } } private clear(): void { this.widgetInstance.element.find('.item').removeClass('active'); } private highlight(itemClass: string): void { this.widgetInstance.element.find(`.item.${itemClass}`).addClass('active'); } private getSecond(time: Date): number { if (typeof this.getCurrentLayout().getSeconds === 'function') { return this.getCurrentLayout().getSeconds(time); } return time.getSeconds(); }; private getDotMinute(date: Date): number { if (typeof this.getCurrentLayout().getDotMinute === 'function') { return this.getCurrentLayout().getDotMinute(date); } return date.getMinutes() % 5; }; private getCoarseMinute(date: Date): number { if (typeof this.getCurrentLayout().getCoarseMinute === 'function') { return this.getCurrentLayout().getCoarseMinute(date); } return date.getMinutes(); }; private getHour(date: Date): number { if (typeof this.getCurrentLayout().getHour === 'function') { return this.getCurrentLayout().getHour(date); } const hour = date.getHours(); if (date.getMinutes() >= 25) { return (hour + 1) % 24; } return hour; }; private toggleConfigScreen() { $(`#uhr-controlpanel${this.widgetInstance.uuid}`).toggle('fast'); }; private parseHash(): void { let hash: string = window.location.hash; if (hash !== undefined && hash.charAt(0) === '#') { hash = hash.substring(1); hash = decodeURIComponent(hash); const params: string[] = hash.split('&'); params.forEach(element => { const pair: string[] = element.split('='); const key = pair[0]; const value = pair[1]; switch (key) { case 'l': case 'language': this.widgetInstance.options.language = value; this.widgetInstance.options.force = true; break; case 't': case 'theme': this.widgetInstance.options.theme = value; this.widgetInstance.options.force = true; break; case 'm': case 'mode': this.widgetInstance.options.mode = value; this.widgetInstance.options.force = true; break; case 's': case 'status': this.widgetInstance.options.status = value; this.widgetInstance.options.force = true; break; } }); } } private getCurrentLayout(): Layout { const matchingLanguages: Layout[] = Globals.getLayouts().filter(element => element.code === this.widgetInstance.options.language, this); if (matchingLanguages.length > 0) { return matchingLanguages[0]; } // fallback: return empty object return EMPTY_LAYOUT; }; }