Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add feature configuration UI #258

Merged
merged 4 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/@types/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { TI18nLang } from './core/i18n';
import type { TFeatureFlagMenu } from './components/menu';
import type { TComponentDefinitionElementsPainter } from './components/painter';
import type { TComponentDefinitionElementsSinger } from './components/singer';

/** Type definition of an app configuration preset's component configuration. */
export type TAppComponentConfig =
Expand All @@ -12,11 +14,11 @@ export type TAppComponentConfig =
}
| {
id: 'painter';
elements?: string[] | true;
elements?: TComponentDefinitionElementsPainter[] | true;
}
| {
id: 'singer';
elements?: string[] | true;
elements?: TComponentDefinitionElementsSinger[] | true;
};

/** Type defintion of an app configuration preset. */
Expand Down
4 changes: 1 addition & 3 deletions src/@types/components/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ export interface IComponentDefinition {
/** Assets used. */
assets?: string[];
/** Syntax elements exposed. */
elements?: {
[identifier: string]: IElementSpecification;
};
elements?: Record<string, IElementSpecification>;
}

import { IElementSpecification } from '@sugarlabs/musicblocks-v4-lib';
Expand Down
28 changes: 28 additions & 0 deletions src/@types/components/painter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IComponentDefinition } from '.';
import type { TAsset } from '../core/assets';
import type { IElementSpecification } from '@sugarlabs/musicblocks-v4-lib';

export type TComponentDefinitionElementsPainter =
| 'move-forward'
| 'move-backward'
| 'turn-left'
| 'turn-right'
| 'set-xy'
| 'set-heading'
| 'draw-arc'
| 'set-color'
| 'set-thickness'
| 'pen-up'
| 'pen-down'
| 'set-background'
| 'clear';

export interface IComponentDefinitionPainter extends IComponentDefinition {
elements: Record<TComponentDefinitionElementsPainter, IElementSpecification>;
}

export type TInjectedPainter = {
flags: undefined;
i18n: undefined;
assets: Record<'image.icon.mouse', TAsset>;
};
7 changes: 0 additions & 7 deletions src/@types/components/painter.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/@types/components/singer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { IComponentDefinition } from '.';
import type { IElementSpecification } from '@sugarlabs/musicblocks-v4-lib';

export type TComponentDefinitionElementsSinger =
| 'test-synth'
| 'play-note'
| 'reset-notes-played'
| 'play-generic';

export interface IComponentDefinitionSinger extends IComponentDefinition {
elements: Record<TComponentDefinitionElementsSinger, IElementSpecification>;
}

export type TInjectedSinger = {
flags: undefined;
i18n: undefined;
assets: undefined;
};
5 changes: 0 additions & 5 deletions src/@types/components/singer.ts

This file was deleted.

145 changes: 111 additions & 34 deletions src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,51 @@ function updateImportMap(
return loadMap;
}

// =================================================================================================
async function _importComponents(config?: IAppConfig): Promise<{
components: Partial<Record<TComponentId, IComponent>>;
componentDefinitions: Partial<Record<TComponentId, IComponentDefinition>>;
}> {
/** Map of component identifier and corresponding component module. */
let components: Partial<Record<TComponentId, IComponent>>;
/** List of 2-tuples of component identifier and component definition. */
let componentDefinitions: Partial<Record<TComponentId, IComponentDefinition>>;

(async () => {
// load configuration preset file
const config = await loadConfig(import.meta.env.VITE_CONFIG_PRESET);
{
const componentIds = (
config !== undefined
? Object.entries(componentMap)
.filter(([id]) =>
config.components.map(({ id }) => id).includes(id as TComponentId),
)
.map(([id]) => id)
: Object.keys(componentMap)
) as TComponentId[];

const callback =
config !== undefined
? (componentId: TComponentId) =>
updateImportMap('import', 'components', componentId)
: () => 1;

components = await importComponents(componentIds, callback);

componentDefinitions = Object.fromEntries(
(Object.entries(components) as [TComponentId, IComponent][]).map(([id, component]) => [
id,
component.definition,
]) as [TComponentId, IComponentDefinition][],
);
}

return {
components,
componentDefinitions,
};
}

// -------------------------------------------------------------------------------------------------

async function init(config: IAppConfig) {
/*
* Import and load i18n strings for the configured language asynchronously.
*/
Expand All @@ -85,20 +124,9 @@ function updateImportMap(
*/

{
components = await importComponents(
(import.meta.env.PROD
? Object.entries(componentMap)
.filter(([id]) =>
config.components.map(({ id }) => id).includes(id as TComponentId),
)
.map(([id]) => id)
: Object.keys(componentMap)) as TComponentId[],
(componentId: TComponentId) => updateImportMap('import', 'components', componentId),
);

componentDefinitionEntries = (
Object.entries(components) as [TComponentId, IComponent][]
).map(([id, component]) => [id, component.definition]) as [
const _components = await _importComponents(config);
components = _components.components;
componentDefinitionEntries = Object.entries(_components.componentDefinitions) as [
TComponentId,
IComponentDefinition,
][];
Expand Down Expand Up @@ -150,18 +178,11 @@ function updateImportMap(

// Inject feature flags.
componentDefinitionEntries.forEach(
([componentId, { flags }]) =>
(components[componentId]!.injected.flags = import.meta.env.PROD
? // @ts-ignore
config.components.find(({ id }) => id === componentId)?.flags
: Object.keys(flags).length !== 0
? Object.fromEntries(
Object.keys(
componentDefinitionEntries.find(([id]) => id === componentId)![1]
.flags,
).map((flag) => [flag, false]),
)
: undefined),
([componentId]) =>
(components[componentId]!.injected.flags = config.components.find(
({ id }) => id === componentId,
// @ts-ignore
)?.flags),
);
}

Expand Down Expand Up @@ -199,10 +220,8 @@ function updateImportMap(
componentsOrdered.map((componentId) => {
return {
id: componentId,
filter: import.meta.env.PROD
? // @ts-ignore
config.components.find(({ id }) => id === componentId)?.elements
: true,
// @ts-ignore
filter: config.components.find(({ id }) => id === componentId)?.elements,
};
}),
);
Expand All @@ -221,4 +240,62 @@ function updateImportMap(
if (import.meta.env.PROD) {
loadServiceWorker();
}
}

// =================================================================================================

(async function () {
// load configuration preset file
const config = await loadConfig(import.meta.env.VITE_CONFIG_PRESET);

/**
* if PRODUCTION mode, proceed initializing with configuration preset.
*/

if (import.meta.env.PROD) {
await init(config);
return;
}

/**
* if DEVELOPMENT mode, and configuration in session storage,
* proceed initializing with configuration from session storage.
*/

{
const config = window.sessionStorage.getItem('appConfig');

if (config !== null) {
await init(JSON.parse(config) as IAppConfig);
return;
}
}

/**
* if DEVELOPMENT mode, and configuration not in session storage,
* open configurator page.
* @todo currently needs refresh to go to main app page
*/

{
const { mountConfigPage, updateConfigPage } = await import('@/core/view');

requestAnimationFrame(() => {
(async function () {
window.sessionStorage.setItem('appConfig', JSON.stringify(config));

await mountConfigPage(
{ ...config },
(
await _importComponents()
).componentDefinitions,
(config: IAppConfig) =>
requestAnimationFrame(() => {
window.sessionStorage.setItem('appConfig', JSON.stringify(config));
updateConfigPage(config);
}),
);
})();
});
}
})();
5 changes: 2 additions & 3 deletions src/components/painter/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IComponentDefinition } from '@/@types/components';
import type { TInjectedPainter } from '@/@types/components/painter';
import type { IComponentDefinitionPainter, TInjectedPainter } from '@/@types/components/painter';
import type { IComponentMenu } from '@/@types/components/menu';

import { getComponent } from '@/core/config';
Expand Down Expand Up @@ -28,7 +27,7 @@ import { exportDrawing, startRecording, stopRecording } from './core/sketchP5';

// == definition ===================================================================================

export const definition: IComponentDefinition = {
export const definition: IComponentDefinitionPainter = {
dependencies: {
optional: ['menu'],
required: [],
Expand Down
5 changes: 2 additions & 3 deletions src/components/singer/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IComponentDefinition } from '@/@types/components';
import type { TInjectedSinger } from '@/@types/components/singer';
import type { IComponentDefinitionSinger, TInjectedSinger } from '@/@types/components/singer';

import { setup as setupComponent } from './singer';
import {
Expand All @@ -11,7 +10,7 @@ import {

// == definition ===================================================================================

export const definition: IComponentDefinition = {
export const definition: IComponentDefinitionSinger = {
dependencies: {
optional: ['menu'],
required: [],
Expand Down
Loading