From 38641df28edb91400b9edb8c1827c5ecf5c5dc4d Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Thu, 3 Dec 2020 00:34:50 +0100 Subject: [PATCH] add classes for notification UI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UI classes are split into "general UI" part — a base class that could potentially be used for proper in-player UI if we ever get to that point — and part that's specific to our notification requirements. --- src/ext/lib/uwui/PlayerNotificationUI.js | 98 ++++++++++++++++++++++++ src/ext/lib/uwui/UI.js | 76 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 src/ext/lib/uwui/PlayerNotificationUI.js create mode 100644 src/ext/lib/uwui/UI.js diff --git a/src/ext/lib/uwui/PlayerNotificationUI.js b/src/ext/lib/uwui/PlayerNotificationUI.js new file mode 100644 index 0000000..645260f --- /dev/null +++ b/src/ext/lib/uwui/PlayerNotificationUI.js @@ -0,0 +1,98 @@ +import UI from './UI'; +import VuexWebExtensions from 'vuex-webextensions'; +import VideoNotification from '../../../csui/VideoNotification'; + +class PlayerNotification extends UI { + + constructor ( + playerElement + ) { + + super( + 'notification', + getStoreConfig(), + getUiConfig(playerElement), + getCommsConfig() + ) + } + + + //#region constructor helpers + // we will move some things out of the constructor in order to keep things clean + getStoreConfig() { + return { + plugins: [ + VuexWebExtensions({ + persistentStates: [ + 'notificationConfig' + ], + }), + ], + state: { + // should be null by default! + notificationConfig: { + text: 'sample text now with 100% more html formatting!', + icon: 'exclamation-circle', + timeout: 5000, + } + }, + mutations: { + 'uw-set-notification'(state, payload) { + state['notificationConfig'] = payload; + } + }, + actions: { + 'uw-set-notification'({commit}, payload) { + commit('uw-set-notification', payload); + } + } + }; + } + + getUiConfig(playerElement) { + return { + parentElement: playerElement, + component: VideoNotification + } + } + + getCommsConfig() { + return { + handlers = { + 'show-notification': [(message) => this.showNotification(message)], + } + } + } + //#endregion + + //#region lifecycle + replace(playerElement) { + super.replace(this.getUiConfig(playerElement)); + } + //#endregion + + /** + * Show notification on screen. + * + * @param notificationConfig notification config + * + * notificationConfig should resemble this: + * { + * timeout: number — how long we'll be displaying the notification. If empty, 10s. -1: until user dismisses it + * icon: string — what icon we're gonna show. We're using bootstrap icons. Can be empty. + * text: — notification text. Supports HTML. + * notificationActions: [ + * { + * command: function that gets executed upon clicking the button. + * label: label of the button + * icon: icon of the button + * } + * ] + * } + */ + showNotification(notificationConfig) { + this.vuexStore?.dispatch('uw-set-notification', notificationConfig); + } +} + +export default PlayerNotification; diff --git a/src/ext/lib/uwui/UI.js b/src/ext/lib/uwui/UI.js new file mode 100644 index 0000000..f3cd883 --- /dev/null +++ b/src/ext/lib/uwui/UI.js @@ -0,0 +1,76 @@ +import { createApp } from 'vue'; +import { createStore } from 'vuex'; + +class UI { + constructor( + interfaceId, + storeConfig, + uiConfig, // {component, parentElement?} + commsConfig, + ) { + this.interfaceId = interfaceId; + this.commsConfig = commsConfig; + } + + async init() { + // If comms exist, we need to destroy it + if (this.comms) { + this.comms.destroy(); + } + if (!this.settings) { + this.settings = new Settings({ + onSettingsChanged: () => this.reloadSettings(), + logger: this.logger + }); + await this.settings.init(); + } + + this.comms = new CommsClient('content-ui-port', null, this.commsConfig.handlers); + + // initialize vuejs, but only once (check handled in initVue()) + // we need to initialize this _after_ initializing comms. + this.initVue(); + } + + async initVue() { + this.vuexStore = createStore(this.storeConfig); + } + + async initUi() { + const random = Math.round(Math.random() * 69420); + const uwid = `uw-${this.interfaceId}-root-${random}` + + const rootDiv = document.createElement('div'); + rootDiv.setAttribute('style', `position: ${uiConfig.style?.position ?? 'relative'}; width: ${uiConfig.style?.width ?? '100%'}; height: ${uiConfig.style?.height ?? '100%'}; ${uiConfig.additionalStyle}`); + rootDiv.setAttribute('id', uwid); + + if (this.uiConfig.parentElement) { + this.uiConfig.parentElement.appendChild(rootDiv); + } else { + document.body.appendChild(rootDiv); + } + + this.element = rootDiv; + + createApp(this.uiConfig.component) + .use(this.vuexStore) + .mount(`${uwid}`); + } + + /** + * Replaces ui config and re-inits the UI + * @param {*} newUiConfig + */ + replace(newUiConfig) { + this.element?.remove(); + this.uiConfig = newUiConfig; + this.initUi(); + } + + destroy() { + this.comms?.destroy(); + this.element?.remove(); + } +} + +export default UI; \ No newline at end of file