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(settings): load nsdoc to local storage #502

Merged
merged 20 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
22 changes: 22 additions & 0 deletions src/Divider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {css, customElement, html, LitElement, TemplateResult} from "lit-element";
Flurb marked this conversation as resolved.
Show resolved Hide resolved

@customElement('openscd-divider')
export class DividerElement extends LitElement {
render(): TemplateResult {
return html `
<div role="separator"></div>
`
}

static styles = css`
div {
height: 0px;
margin: 10px 0px 10px 0px;
border-top: none;
border-right: none;
border-left: none;
border-image: initial;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
`
}
171 changes: 139 additions & 32 deletions src/Setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,67 @@ import { Switch } from '@material/mwc-switch';

import { ifImplemented, LitElementConstructor, Mixin } from './foundation.js';
import { Language, languages, loader } from './translations/loader.js';
import { WizardDialog } from './wizard-dialog.js';

export type Settings = {
import './Divider.js';
JakobVogelsang marked this conversation as resolved.
Show resolved Hide resolved

export type SettingsRecord = {
language: Language;
theme: 'light' | 'dark';
mode: 'safe' | 'pro';
showieds: 'on' | 'off';
};
export const defaults: Settings = {
language: 'en',
theme: 'light',
mode: 'safe',
showieds: 'off',
'IEC 61850-7-2': string | undefined;
'IEC 61850-7-3': string | undefined;
'IEC 61850-7-4': string | undefined;
'IEC 61850-8-1': string | undefined;
};

export function Settings() {
JakobVogelsang marked this conversation as resolved.
Show resolved Hide resolved
return {
/** Current [[`CompasSettings`]] in `localStorage`, default to [[`defaults`]]. */
get settings(): SettingsRecord {
return {
language: this.getSetting('language'),
theme: this.getSetting('theme'),
mode: this.getSetting('mode'),
showieds: this.getSetting('showieds'),
'IEC 61850-7-2': this.getSetting('IEC 61850-7-2'),
'IEC 61850-7-3': this.getSetting('IEC 61850-7-3'),
'IEC 61850-7-4': this.getSetting('IEC 61850-7-4'),
'IEC 61850-8-1': this.getSetting('IEC 61850-8-1')
};
},

get defaultSettings(): SettingsRecord {
return {
language: 'en',
theme: 'light',
mode: 'safe',
showieds: 'off',
'IEC 61850-7-2': undefined,
'IEC 61850-7-3': undefined,
'IEC 61850-7-4': undefined,
'IEC 61850-8-1': undefined
}
},

/** Update the `value` of `setting`, storing to `localStorage`. */
setSetting<T extends keyof SettingsRecord>(setting: T, value: SettingsRecord[T]): void {
localStorage.setItem(setting, <string>(<unknown>value));
},

/** Update the `value` of `setting`, storing to `localStorage`. */
removeSetting<T extends keyof SettingsRecord>(setting: T): void {
localStorage.removeItem(setting);
},

getSetting<T extends keyof SettingsRecord>(setting: T): SettingsRecord[T] {
return (
<SettingsRecord[T] | null>localStorage.getItem(setting) ?? this.defaultSettings[setting]
);
}
}
}

/** Mixin that saves [[`Settings`]] to `localStorage`, reflecting them in the
* `settings` property, setting them through `setSetting(setting, value)`. */
export type SettingElement = Mixin<typeof Setting>;
Expand All @@ -36,13 +82,8 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
class SettingElement extends Base {
/** Current [[`Settings`]] in `localStorage`, default to [[`defaults`]]. */
@property()
get settings(): Settings {
return {
language: this.getSetting('language'),
theme: this.getSetting('theme'),
mode: this.getSetting('mode'),
showieds: this.getSetting('showieds'),
};
get settings(): SettingsRecord {
return Settings().settings;
}

@query('#settings')
Expand All @@ -56,19 +97,8 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
@query('#showieds')
showiedsUI!: Switch;

private getSetting<T extends keyof Settings>(setting: T): Settings[T] {
return (
<Settings[T] | null>localStorage.getItem(setting) ?? defaults[setting]
);
}
/** Update the `value` of `setting`, storing to `localStorage`. */
setSetting<T extends keyof Settings>(setting: T, value: Settings[T]): void {
localStorage.setItem(setting, <string>(<unknown>value));
this.shadowRoot
?.querySelector<WizardDialog>('wizard-dialog')
?.requestUpdate();
this.requestUpdate();
}
@query('#nsdoc-file')
private nsdocFileUI!: HTMLInputElement;

private onClosing(ae: CustomEvent<{ action: string } | null>): void {
if (ae.detail?.action === 'reset') {
Expand All @@ -77,10 +107,10 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
);
this.requestUpdate('settings');
} else if (ae.detail?.action === 'save') {
this.setSetting('language', <Language>this.languageUI.value);
this.setSetting('theme', this.darkThemeUI.checked ? 'dark' : 'light');
this.setSetting('mode', this.modeUI.checked ? 'pro' : 'safe');
this.setSetting('showieds', this.showiedsUI.checked ? 'on' : 'off');
Settings().setSetting('language', <Language>this.languageUI.value);
Settings().setSetting('theme', this.darkThemeUI.checked ? 'dark' : 'light');
Settings().setSetting('mode', this.modeUI.checked ? 'pro' : 'safe');
Settings().setSetting('showieds', this.showiedsUI.checked ? 'on' : 'off');
this.requestUpdate('settings');
}
}
Expand All @@ -90,6 +120,72 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
if (changedProperties.has('settings')) use(this.settings.language);
}

private renderFileSelect(): TemplateResult {
return html `
<input id="nsdoc-file" accept=".nsdoc" type="file" hidden required multiple
@change=${(evt: Event) => this.loadNsdocFile(evt)}}>
<mwc-button label="${translate('settings.selectFileButton')}"
id="selectFileButton"
@click=${() => {
const input = <HTMLInputElement | null>this.shadowRoot!.querySelector("#nsdoc-file");
input?.click();
}}>
</mwc-button>
`;
}

private async loadNsdocFile(evt: Event): Promise<void> {
const files = Array.from(
(<HTMLInputElement | null>evt.target)?.files ?? []
);

if (files.length == 0) return;
files.forEach(async file => {
const text = await file.text();
const id = this.parseToXmlObject(text).querySelector('NSDoc')?.getAttribute('id');
if (!id) return;
Flurb marked this conversation as resolved.
Show resolved Hide resolved

Settings().setSetting(id as keyof SettingsRecord, text);
})

this.nsdocFileUI.value = '';
this.requestUpdate();
}

/**
* Render one .nsdoc item in the Settings wizard
* @param key - The key of the nsdoc file in the settings.
* @returns a .nsdoc item for the Settings wizard
*/
private renderNsdocItem<T extends keyof SettingsRecord>(key: T): TemplateResult {
const nsdSetting = this.settings[key];
let nsdVersion: string | undefined | null;
let nsdRevision: string | undefined | null;

if (nsdSetting) {
const nsdoc = this.parseToXmlObject(nsdSetting)!.querySelector('NSDoc');
nsdVersion = nsdoc?.getAttribute('version');
nsdRevision = nsdoc?.getAttribute('revision');
}

return html`<mwc-list-item id=${key} graphic="avatar" hasMeta twoline .disabled=${!nsdSetting}>
<span>${key}</span>
${nsdSetting ? html`<span slot="secondary">${nsdVersion}${nsdRevision}</span>` :
Flurb marked this conversation as resolved.
Show resolved Hide resolved
html``}
${nsdSetting ? html`<mwc-icon slot="graphic" style="color:green;">done</mwc-icon>` :
html`<mwc-icon slot="graphic" style="color:red;">close</mwc-icon>`}
${nsdSetting ? html`<mwc-icon id="deleteNsdocItem" slot="meta" @click=${() => {
Settings().removeSetting(key);
this.requestUpdate();
}}>delete</mwc-icon>` :
html``}
</mwc-list-item>`;
}

private parseToXmlObject(text: string): XMLDocument {
return new DOMParser().parseFromString(text, 'application/xml');
}

constructor(...params: any[]) {
super(...params);

Expand Down Expand Up @@ -140,6 +236,17 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
></mwc-switch>
</mwc-formfield>
</form>
<openscd-divider></openscd-divider>
<section>
<h3>${translate('settings.loadNsdTranslations')}</h3>
${this.renderFileSelect()}
</section>
<mwc-list id="nsdocList">
${this.renderNsdocItem('IEC 61850-7-2')}
${this.renderNsdocItem('IEC 61850-7-3')}
${this.renderNsdocItem('IEC 61850-7-4')}
${this.renderNsdocItem('IEC 61850-8-1')}
</mwc-list>
<mwc-button slot="secondaryAction" dialogAction="close">
${translate('cancel')}
</mwc-button>
Expand Down
8 changes: 4 additions & 4 deletions src/themes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { html, TemplateResult } from 'lit-element';
import { Settings } from './Setting.js';
import { SettingsRecord } from './Setting.js';

export function getTheme(theme: Settings['theme']): TemplateResult {
export function getTheme(theme: SettingsRecord['theme']): TemplateResult {
document.body.style.cssText = bodyStyles[theme];
return html`
${themes[theme]}
Expand Down Expand Up @@ -61,12 +61,12 @@ export function getTheme(theme: Settings['theme']): TemplateResult {
`;
}

const bodyStyles: Record<Settings['theme'], string> = {
const bodyStyles: Record<SettingsRecord['theme'], string> = {
dark: 'background: #073642',
light: 'background: #eee8d5',
};

const themes: Record<Settings['theme'], TemplateResult> = {
const themes: Record<SettingsRecord['theme'], TemplateResult> = {
light: html`
<style>
* {
Expand Down
2 changes: 2 additions & 0 deletions src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const de: Translations = {
dark: 'Dunkles Design',
mode: 'Profimodus',
showieds: 'Zeige IEDs im Substation-Editor',
selectFileButton: '???',
Flurb marked this conversation as resolved.
Show resolved Hide resolved
loadNsdTranslations: '???'
JakobVogelsang marked this conversation as resolved.
Show resolved Hide resolved
Flurb marked this conversation as resolved.
Show resolved Hide resolved
},
menu: {
new: 'Neues projekt',
Expand Down
2 changes: 2 additions & 0 deletions src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const en = {
dark: 'Dark theme',
mode: 'Pro mode',
showieds: 'Show IEDs in substation editor',
selectFileButton: 'Select file',
loadNsdTranslations: 'Uploading NSDoc files'
},
menu: {
new: 'New project',
Expand Down
4 changes: 2 additions & 2 deletions src/zeroline-pane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IconButtonToggle } from '@material/mwc-icon-button-toggle';

import './zeroline/substation-editor.js';
import './zeroline/ied-editor.js';
import { Settings } from './Setting.js';
import { SettingsRecord } from './Setting.js';
import { communicationMappingWizard } from './wizards/commmap-wizards.js';
import { gooseIcon, smvIcon } from './icons.js';
import { isPublic, newWizardEvent } from './foundation.js';
Expand All @@ -29,7 +29,7 @@ function shouldShowIEDs(): boolean {
return localStorage.getItem('showieds') === 'on';
}

function setShowIEDs(value: Settings['showieds']) {
function setShowIEDs(value: SettingsRecord['showieds']) {
localStorage.setItem('showieds', value);
}

Expand Down
Loading