Skip to content

Commit

Permalink
Merge pull request #291 from Vonage/iss-290-init-core-revision
Browse files Browse the repository at this point in the history
feat: issue #290: core services init and config resolver
  • Loading branch information
gullerya committed Sep 7, 2020
2 parents 1b94d37 + 2a19a6a commit 82496e9
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 136 deletions.
5 changes: 2 additions & 3 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import '@storybook/addon-console';
import { addParameters, setCustomElements } from '@storybook/web-components';
import customElements from '../custom-elements.json';
import context from '@vonage/vvd-context';
import { contextReady } from '@vonage/vvd-context';

context
.init()
contextReady
.then(() => console.info('init Vivid context done (preview frame)'));

async function run() {
Expand Down
5 changes: 2 additions & 3 deletions .storybook/vivid-theme.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { create } from '@storybook/theming/create';
import context from '@vonage/vvd-context';
import { contextReady } from '@vonage/vvd-context';

context
.init()
contextReady
.then(() => console.info('init Vivid context done (main page)'));

export default create({
Expand Down
37 changes: 21 additions & 16 deletions common/context/src/vvd-context.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { init as coreInit } from '@vonage/vvd-core';
import { coreReady } from '@vonage/vvd-core';
import { style } from './vvd-context.css';

/**
* Vivid context initialiser
* this API is a 'customer facing' one - meant to be used by a consuming application to initialise the Vivid context, services and resource for the application as a whole
*
* @param services map of services to initialise; keys are a service names, values are an options to pass to those services upon initialisation
* @returns a compound Promise of all initialisation Promises of the participating parties (services, resources etc)
*/
async function init(services: Record<string, unknown>): Promise<void[]> {
injectGlobalStyle();
return await coreInit(services);
let
initResolver: (value?: unknown) => void | PromiseLike<void>,
initRejector: (reason?: unknown) => void | PromiseLike<void>;

export const contextReady = new Promise((resolve, reject) => {
initResolver = resolve;
initRejector = reject;
});

init();

async function init(): Promise<void> {
try {
injectGlobalStyle();
await coreReady;
initResolver();
} catch (e) {
initRejector(e);
}
}

function injectGlobalStyle() {
const globalStyleSheet = document.createElement('style');
globalStyleSheet.type = 'text/css';
globalStyleSheet.innerHTML = style.cssText;
document.head.appendChild(globalStyleSheet);
}

export default Object.freeze({
init
});
}
9 changes: 4 additions & 5 deletions common/context/stories/context-basic.stories.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import context from '@vonage/vvd-context/vvd-context';
import { contextReady } from '@vonage/vvd-context/vvd-context';
import '@vonage/vwc-scheme-select';
import '@vonage/vwc-top-app-bar';
import { html } from 'lit-element';

export default {
title: 'Cells/Context',
title: 'Cells/Context',
};

export const basic = () => html`
Expand Down Expand Up @@ -42,6 +42,5 @@ export const basic = () => html`
</main>
`;

context
.init()
.then(() => console.log('Vivid context initialised for the demo story'));
contextReady
.then(() => console.log('Vivid context initialised for the context demo story'));
28 changes: 28 additions & 0 deletions common/core/src/config-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { SchemeOption } from '@vonage/vvd-scheme';

const
VVD_CONTEXT_ATTRIBUTE = 'data-vvd-context',
defaultConfig = {} as Record<string, unknown>;

let tmpConfig: Record<string, unknown> | null = null;

tmpConfig = updateByHtmlAttribute();

if (!tmpConfig) {
tmpConfig = defaultConfig;
}

export interface Configuration {
scheme?: SchemeOption
}
const effectiveConfig: Configuration = tmpConfig;
export default effectiveConfig;

function updateByHtmlAttribute(): Record<string, unknown> | null {
let result = null;
const htmlContextAttribute = document.documentElement.getAttribute(VVD_CONTEXT_ATTRIBUTE);
if (htmlContextAttribute) {
result = {};
}
return result;
}
60 changes: 19 additions & 41 deletions common/core/src/vvd-core.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,25 @@
import configuration, { Configuration } from './config-resolver.js';
import fonts from '@vonage/vvd-fonts/vvd-fonts.js';
import scheme from '@vonage/vvd-scheme';
import schemeService from '@vonage/vvd-scheme';

interface ServiceEntry {
defaultInitParam?: unknown,
initialiser(initParam: unknown): Promise<void>
}
let
initResolver: (value?: unknown) => void | PromiseLike<void>,
initRejector: (reason?: unknown) => void | PromiseLike<void>;

const servicesRegistry: Record<string, ServiceEntry> = {
'fonts': {
defaultInitParam: null,
initialiser: fonts.init
},
'scheme': {
defaultInitParam: null,
initialiser: scheme.init
}
};
export const coreReady = new Promise((resolve, reject) => {
initResolver = resolve;
initRejector = reject;
});

export function validateInitParameters(services: Record<string, unknown>): void {
const knownServices: string[] = Object.keys(servicesRegistry);
const unknownService = Object.keys(services).find(serviceKey => !knownServices.includes(serviceKey));
if (unknownService) {
throw new Error(`unknown service key '${unknownService}' specified for init API`);
}
}
console.debug('effective config', JSON.stringify(configuration));
init(configuration);

/**
* Internal initialiser
* this API is an internal initialiser of the core services
*
* @param services a map of the services requested to be initialised, where the key is the name of the service and the value is the parameter/s to be passed to the service initialisation function
* @returns compound Promise of all inidividual service initialisation Promises
*/
export async function init(services?: Record<string, unknown>): Promise<void[]> {
if (services) {
validateInitParameters(services);
} else {
services = servicesRegistry;
}

const serviceInitPromises = Object
.entries(services)
.map(([serviceKey, initParams]) => servicesRegistry[serviceKey].initialiser(initParams));

return await Promise.all(serviceInitPromises);
async function init({ scheme }: Configuration): Promise<void> {
Promise
.all([
fonts.init(),
schemeService.set(scheme)
])
.then(initResolver)
.catch(initRejector);
}
101 changes: 49 additions & 52 deletions common/scheme/src/vvd-scheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { CSSResult } from 'lit-element';
import { pipe } from 'ramda';
import { onSchemeChange } from './scheme-change-listener';
import {
pcs,
getPreferedColorScheme,
prefersColorSchemeSupported,
pcs,
getPreferedColorScheme,
prefersColorSchemeSupported,
} from './os-sync.utils';

export type PredefinedScheme = 'light' | 'dark';
export type SchemeOption = 'syncWithOSSettings' | PredefinedScheme;

type ModuleType =
| typeof import('./scheme.dark.css')
| typeof import('./scheme.light.css'); // This is the import type!
| typeof import('./scheme.dark.css')
| typeof import('./scheme.light.css'); // This is the import type!

const getSchemeCssText = pipe(getSchemeModule, getStyleSheet, getCssText);

Expand All @@ -21,86 +21,83 @@ export const getSelectedScheme = (): PredefinedScheme => _selectedScheme;

let _selectedSchemeOption: SchemeOption;
export const getSelectedSchemeOption = (): SchemeOption =>
_selectedSchemeOption;
_selectedSchemeOption;

const style = mountStyle();

function mountStyle() {
const style = document.createElement('style');
style.type = 'text/css';
document.head.appendChild(style);
return style;
const style = document.createElement('style');
style.type = 'text/css';
document.head.appendChild(style);
return style;
}

function schemeDefault(): SchemeOption {
// if no scheme chosen try 'prefers-color-scheme' and if not supported just return 'light
return prefersColorSchemeSupported() ? 'syncWithOSSettings' : 'light';
// if no scheme chosen try 'prefers-color-scheme' and if not supported just return 'light
return prefersColorSchemeSupported() ? 'syncWithOSSettings' : 'light';
}

function getSchemeModule(schemeOption: SchemeOption): Promise<ModuleType> {
switch (schemeOption) {
case 'dark':
return import('./scheme.dark.css');
case 'light':
default:
return import('./scheme.light.css');
}
switch (schemeOption) {
case 'dark':
return import('./scheme.dark.css');
case 'light':
default:
return import('./scheme.light.css');
}
}

async function getStyleSheet(ModulePromise: Promise<ModuleType>) {
return (await ModulePromise).style;
return (await ModulePromise).style;
}

async function getCssText(
resultPromise: Promise<CSSResult>
resultPromise: Promise<CSSResult>
): Promise<CSSResult['cssText']> {
const { cssText } = await resultPromise;
return cssText;
const { cssText } = await resultPromise;
return cssText;
}

function updateStyleCssText(newCssText: CSSResult['cssText']) {
style.innerHTML = newCssText || '';
style.innerHTML = newCssText || '';
}

async function syncWithOSSettings() {
updateStyleCssText(
await getSchemeCssText(getPreferedColorScheme() as SchemeOption)
);
updateStyleCssText(
await getSchemeCssText(getPreferedColorScheme() as SchemeOption)
);
}

// TODO refactor this to an IIFE and remove any exposed API methods
// TODO identities need to be defined prior to initialization by a config
async function init(scheme?: SchemeOption) {
// listen to selection change event
onSchemeChange(async (scheme: SchemeOption) => {
set(scheme);
});
return await set(scheme);
function init(): void {
onSchemeChange(async (scheme: SchemeOption) => {
set(scheme);
});
}

async function set(scheme: SchemeOption = schemeDefault()) {
_selectedSchemeOption = scheme;
let nextScheme: PredefinedScheme;

if (scheme == 'syncWithOSSettings') {
pcs.addEventListener('change', syncWithOSSettings);
nextScheme = getPreferedColorScheme() as PredefinedScheme;
} else {
pcs.removeEventListener('change', syncWithOSSettings);
nextScheme = scheme;
}
if (_selectedScheme === nextScheme) {
return;
}
_selectedScheme = nextScheme;
updateStyleCssText(await getSchemeCssText(nextScheme));
_selectedSchemeOption = scheme;
let nextScheme: PredefinedScheme;

if (scheme == 'syncWithOSSettings') {
pcs.addEventListener('change', syncWithOSSettings);
nextScheme = getPreferedColorScheme() as PredefinedScheme;
} else {
pcs.removeEventListener('change', syncWithOSSettings);
nextScheme = scheme;
}
if (_selectedScheme === nextScheme) {
return;
}
_selectedScheme = nextScheme;
updateStyleCssText(await getSchemeCssText(nextScheme));
}

export default Object.freeze({
init,
set,
set
});

init();

//TODO add the following tests:
//!scheme init with/without arguments
//!scheme change event
Expand Down
4 changes: 1 addition & 3 deletions components/button/src/vwc-button.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { init as coreInit } from '@vonage/vvd-core';
import '@vonage/vvd-core';
import { customElement, property } from 'lit-element';
import { Button as MWCButton } from '@material/mwc-button';
import { style as vwcButtonStyle } from './vwc-button.css';
Expand All @@ -14,8 +14,6 @@ declare global {
}
}

coreInit();

/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-ignore
MWCButton.styles = [styleCoupling, mwcButtonStyle, vwcButtonStyle];
Expand Down
4 changes: 1 addition & 3 deletions components/fab/src/vwc-fab.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { init as coreInit } from '@vonage/vvd-core';
import '@vonage/vvd-core';
import { customElement, property, html, TemplateResult } from 'lit-element';
import { Fab as MWCFab } from '@material/mwc-fab';
import { style as mwcFabStyle } from '@material/mwc-fab/mwc-fab-css.js';
Expand All @@ -12,8 +12,6 @@ declare global {
}
}

coreInit();

/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-ignore
MWCFab.styles = [mwcFabStyle, vwcFabStyle, styleCoupling];
Expand Down
12 changes: 5 additions & 7 deletions components/top-app-bar-fixed/src/vwc-top-app-bar-fixed.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { init as coreInit } from '@vonage/vvd-core';
import '@vonage/vvd-core';
import { customElement } from 'lit-element';
import { TopAppBarFixed as MWCTopAppBarFixed } from '@material/mwc-top-app-bar-fixed';

declare global {
interface HTMLElementTagNameMap {
'vwc-top-app-bar-fixed': VWCTopAppBarFixed;
}
interface HTMLElementTagNameMap {
'vwc-top-app-bar-fixed': VWCTopAppBarFixed;
}
}

coreInit();

/**
* This component is an extension of [<mwc-top-app-bar-fixed>](https://github.com/material-components/material-components-web-components/tree/master/packages/top-app-bar-fixed)
*/
@customElement('vwc-top-app-bar-fixed')
export class VWCTopAppBarFixed extends MWCTopAppBarFixed {}
export class VWCTopAppBarFixed extends MWCTopAppBarFixed { }

0 comments on commit 82496e9

Please sign in to comment.