Skip to content

Commit b1e61cd

Browse files
authored
Merge pull request #15 from openscd/feature/add-lnodetype-update-option
feat: add lnodetype update option
2 parents 6165643 + 55ed637 commit b1e61cd

File tree

7 files changed

+389
-109
lines changed

7 files changed

+389
-109
lines changed

components/settings-dialog.spec.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { expect, fixture, html } from '@open-wc/testing';
2+
import { SettingsDialog } from './settings-dialog.js';
3+
4+
customElements.define('settings-dialog', SettingsDialog);
5+
6+
describe('SettingsDialog', () => {
7+
let element: SettingsDialog;
8+
9+
const getRadio = (value: 'update' | 'swap') =>
10+
element.shadowRoot!.querySelector(`md-radio[value="${value}"]`) as any;
11+
12+
beforeEach(async () => {
13+
localStorage.clear();
14+
element = await fixture(html`<settings-dialog></settings-dialog>`);
15+
element.show();
16+
await element.updateComplete;
17+
});
18+
19+
afterEach(() => {
20+
localStorage.clear();
21+
element.close();
22+
});
23+
24+
it('should default to "update" setting', async () => {
25+
expect((element as any).updateSetting).to.equal('update');
26+
27+
await element.updateComplete;
28+
29+
expect(getRadio('update').checked).to.equal(true);
30+
expect(getRadio('swap').checked).to.equal(false);
31+
});
32+
33+
it('should load settings from localStorage', async () => {
34+
localStorage.setItem('template-update-setting', 'swap');
35+
element.connectedCallback();
36+
expect((element as any).updateSetting).to.equal('swap');
37+
38+
await element.updateComplete;
39+
40+
expect(getRadio('update').checked).to.equal(false);
41+
expect(getRadio('swap').checked).to.equal(true);
42+
});
43+
44+
it('should fallback to "update" for invalid localStorage values', () => {
45+
localStorage.setItem('template-update-setting', 'invalid');
46+
element.connectedCallback();
47+
expect((element as any).updateSetting).to.equal('update');
48+
});
49+
50+
it('should save settings on confirm', async () => {
51+
const swapRadio = getRadio('swap');
52+
swapRadio.click();
53+
await element.updateComplete;
54+
55+
const confirmButton = element.shadowRoot!.querySelector(
56+
'md-text-button:last-child'
57+
) as any;
58+
confirmButton.click();
59+
60+
expect(localStorage.getItem('template-update-setting')).to.equal('swap');
61+
});
62+
63+
it('should not save changes on cancel', async () => {
64+
localStorage.setItem('template-update-setting', 'update');
65+
66+
const swapRadio = getRadio('swap');
67+
swapRadio.click();
68+
await element.updateComplete;
69+
70+
const cancelButton = element.shadowRoot!.querySelector(
71+
'md-text-button:first-child'
72+
) as any;
73+
cancelButton.click();
74+
75+
expect(localStorage.getItem('template-update-setting')).to.equal('update');
76+
});
77+
78+
it('should update setting when radio button changes', async () => {
79+
const swapRadio = getRadio('swap');
80+
81+
swapRadio.checked = true;
82+
swapRadio.dispatchEvent(new Event('change'));
83+
await element.updateComplete;
84+
85+
expect((element as any).updateSetting).to.equal('swap');
86+
expect(getRadio('swap').checked).to.equal(true);
87+
expect(getRadio('update').checked).to.equal(false);
88+
});
89+
90+
it('should reload settings when dialog is shown', async () => {
91+
localStorage.setItem('template-update-setting', 'swap');
92+
93+
element.close();
94+
element.show();
95+
await element.updateComplete;
96+
97+
expect((element as any).updateSetting).to.equal('swap');
98+
expect(getRadio('swap').checked).to.equal(true);
99+
});
100+
});

components/settings-dialog.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
3+
import { LitElement, html, css } from 'lit';
4+
import { query, state } from 'lit/decorators.js';
5+
import { MdDialog } from '@scopedelement/material-web/dialog/dialog.js';
6+
import { MdTextButton } from '@scopedelement/material-web/button/text-button.js';
7+
import { MdRadio } from '@scopedelement/material-web/radio/radio.js';
8+
import { TEMPLATE_UPDATE_SETTING_STORAGE_KEY } from '../foundation/constants.js';
9+
10+
// eslint-disable-next-line no-shadow
11+
export enum UpdateSetting {
12+
Swap = 'swap',
13+
Update = 'update',
14+
}
15+
16+
export class SettingsDialog extends ScopedElementsMixin(LitElement) {
17+
static scopedElements = {
18+
'md-dialog': MdDialog,
19+
'md-text-button': MdTextButton,
20+
'md-radio': MdRadio,
21+
};
22+
23+
@query('md-dialog')
24+
dialog!: MdDialog;
25+
26+
@state()
27+
private updateSetting: UpdateSetting = UpdateSetting.Update;
28+
29+
connectedCallback() {
30+
super.connectedCallback();
31+
this.loadSettings();
32+
}
33+
34+
private loadSettings() {
35+
const stored = localStorage.getItem(
36+
TEMPLATE_UPDATE_SETTING_STORAGE_KEY
37+
) as UpdateSetting;
38+
if (stored && Object.values(UpdateSetting).includes(stored)) {
39+
this.updateSetting = stored;
40+
} else {
41+
this.updateSetting = UpdateSetting.Update;
42+
}
43+
}
44+
45+
private saveSettings() {
46+
localStorage.setItem(
47+
TEMPLATE_UPDATE_SETTING_STORAGE_KEY,
48+
this.updateSetting
49+
);
50+
}
51+
52+
get open() {
53+
return this.dialog?.open ?? false;
54+
}
55+
56+
show() {
57+
this.loadSettings();
58+
this.dialog?.show();
59+
}
60+
61+
close() {
62+
this.dialog?.close();
63+
}
64+
65+
private handleRadioChange(event: Event) {
66+
const target = event.target as MdRadio;
67+
if (target.checked) {
68+
this.updateSetting = target.value as UpdateSetting;
69+
}
70+
}
71+
72+
private handleConfirm() {
73+
this.saveSettings();
74+
this.close();
75+
}
76+
77+
private handleCancel() {
78+
this.loadSettings();
79+
this.close();
80+
}
81+
82+
render() {
83+
return html`
84+
<md-dialog @closed=${() => this.dialog?.close()}>
85+
<div slot="headline">LNodeType update behaviour</div>
86+
<div slot="content">
87+
<div class="radio-group">
88+
<label class="radio-item">
89+
<md-radio
90+
name="update-setting"
91+
value=${UpdateSetting.Update}
92+
.checked=${this.updateSetting === UpdateSetting.Update}
93+
@change=${this.handleRadioChange}
94+
></md-radio>
95+
<span class="radio-label">Update logical node type </span>
96+
</label>
97+
<label class="radio-item">
98+
<md-radio
99+
name="update-setting"
100+
value=${UpdateSetting.Swap}
101+
.checked=${this.updateSetting === UpdateSetting.Swap}
102+
@change=${this.handleRadioChange}
103+
></md-radio>
104+
<span class="radio-label">Swap logical node type </span>
105+
</label>
106+
</div>
107+
</div>
108+
<div slot="actions">
109+
<md-text-button @click=${this.handleCancel} type="button">
110+
Cancel
111+
</md-text-button>
112+
<md-text-button @click=${this.handleConfirm} type="button">
113+
Save
114+
</md-text-button>
115+
</div>
116+
</md-dialog>
117+
`;
118+
}
119+
120+
static styles = css`
121+
md-dialog {
122+
--md-dialog-container-max-width: 400px;
123+
}
124+
125+
.radio-group {
126+
display: flex;
127+
flex-direction: column;
128+
gap: 12px;
129+
}
130+
131+
.radio-item {
132+
display: flex;
133+
align-items: flex-start;
134+
gap: 12px;
135+
cursor: pointer;
136+
padding: 8px;
137+
}
138+
139+
.radio-item:hover {
140+
background-color: rgba(0, 0, 0, 0.04);
141+
}
142+
143+
.radio-label {
144+
display: flex;
145+
flex-direction: column;
146+
gap: 4px;
147+
flex: 1;
148+
}
149+
150+
md-text-button {
151+
text-transform: uppercase;
152+
}
153+
`;
154+
}

foundation/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ export const cdClasses = [
3333
'VSS',
3434
'WYE',
3535
] as const;
36+
37+
export const TEMPLATE_UPDATE_SETTING_STORAGE_KEY = 'template-update-setting';

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@scopedelement/material-web": "^3.8.0",
2323
"@openenergytools/open-scd-core": "0.0.5",
2424
"@open-wc/scoped-elements": "^3.0.5",
25-
"@openenergytools/scl-lib": "1.6.1",
25+
"@openenergytools/scl-lib": "1.8.0",
2626
"lit": "^3.0.0"
2727
},
2828
"devDependencies": {

0 commit comments

Comments
 (0)