Skip to content

Commit 0c1074b

Browse files
authored
feat: add elements to virtual ied (openscd#1714)
* feat: add accesspoint to virtual IED * feat: add LDevice to server * feat: add LN to LDevice element * fix: improve amount validation * test: update snaps and add tests for ap dialog * add unit tests * update snapshot * feat: add prefix field to ln dialog * fix: prefix has max length of 8 * feat: add custom tooltip * refactor: simplify show method by using reset * fix: LDevice inst is required * fix: move tooltip to own component
1 parent 003161f commit 0c1074b

22 files changed

+1771
-18
lines changed

packages/openscd/src/translations/de.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export const de: Translations = {
230230
toggleChildElements: 'Kindelemente umschalten',
231231
settings: 'Services für IED or AccessPoint',
232232
createIed: 'Virtuelles IED erstellen',
233+
addAccessPoint: 'AccessPoint hinzufügen',
233234
wizard: {
234235
daTitle: 'DA Informationen anzeigen',
235236
doTitle: 'DO Informationen anzeigen',
@@ -254,6 +255,34 @@ export const de: Translations = {
254255
nameFormatError: 'IED Name darf keine Leerzeichen enthalten',
255256
nameUniqueError: 'IED Name ist bereits vergeben',
256257
},
258+
addAccessPointDialog: {
259+
title: 'AccessPoint hinzufügen',
260+
nameHelper: 'AccessPoint Name',
261+
descHelper: 'AccessPoint Beschreibung',
262+
apName: 'AccessPoint Name',
263+
createServerAt: 'ServerAt hinzufügen',
264+
selectAccessPoint: 'AccessPoint auswählen',
265+
serverAtDesc: 'ServerAt Beschreibung',
266+
nameFormatError: 'AccessPoint Name darf keine Leerzeichen enthalten',
267+
nameUniqueError: 'AccessPoint Name ist bereits vergeben',
268+
nameTooLongError: 'AccessPoint Name ist zu lang',
269+
},
270+
addLDeviceDialog: {
271+
title: 'LDevice hinzufügen',
272+
inst: 'LDevice inst',
273+
desc: 'LDevice Beschreibung',
274+
instRequiredError: 'LDevice inst ist erforderlich',
275+
instFormatError: 'LDevice inst darf keine Leerzeichen enthalten',
276+
instUniqueError: 'LDevice inst ist bereits vergeben',
277+
instTooLongError: 'LDevice inst ist zu lang',
278+
},
279+
addLnDialog: {
280+
title: 'LN hinzufügen',
281+
amount: 'Menge',
282+
prefix: 'Prefix',
283+
filter: 'Logical Node Types filtern',
284+
noResults: 'Keine Logical Node Types gefunden',
285+
},
257286
},
258287
ied: {
259288
wizard: {

packages/openscd/src/translations/en.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export const en = {
227227
toggleChildElements: 'Toggle child elements',
228228
settings: 'Show Services the IED/AccessPoint provides',
229229
createIed: 'Create Virtual IED',
230+
addAccessPoint: 'Add AccessPoint',
230231
wizard: {
231232
daTitle: 'Show DA Info',
232233
doTitle: 'Show DO Info',
@@ -247,10 +248,38 @@ export const en = {
247248
daValue: 'Data attribute value',
248249
},
249250
createDialog: {
250-
iedName: 'IED Name',
251+
iedName: 'IED name',
251252
nameFormatError: 'IED name cannot contain spaces',
252253
nameUniqueError: 'IED name already exists',
253254
},
255+
addAccessPointDialog: {
256+
title: 'Add AccessPoint',
257+
nameHelper: 'AccessPoint name',
258+
descHelper: 'AccessPoint description',
259+
apName: 'AccessPoint name',
260+
createServerAt: 'Add ServerAt',
261+
selectAccessPoint: 'Select AccessPoint',
262+
serverAtDesc: 'ServerAt description',
263+
nameFormatError: 'AccessPoint name cannot contain spaces',
264+
nameUniqueError: 'AccessPoint name already exists',
265+
nameTooLongError: 'AccessPoint name is too long',
266+
},
267+
addLDeviceDialog: {
268+
title: 'Add LDevice',
269+
inst: 'LDevice inst',
270+
desc: 'LDevice description',
271+
instRequiredError: 'LDevice inst is required',
272+
instFormatError: 'Invalid inst',
273+
instUniqueError: 'LDevice inst already exists',
274+
instTooLongError: 'LDevice inst is too long',
275+
},
276+
addLnDialog: {
277+
title: 'Add LN',
278+
amount: 'Amount',
279+
prefix: 'Prefix',
280+
filter: 'Filter Logical Node Types',
281+
noResults: 'No Logical Node Types found',
282+
},
254283
},
255284
ied: {
256285
wizard: {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
css,
3+
customElement,
4+
html,
5+
LitElement,
6+
property,
7+
TemplateResult,
8+
} from 'lit-element';
9+
10+
/** A tooltip element that follows the mouse cursor and displays a text box. */
11+
@customElement('oscd-tooltip-4c6027dd')
12+
export class OscdTooltip extends LitElement {
13+
@property({ type: String })
14+
text = '';
15+
16+
@property({ type: Boolean, reflect: true })
17+
visible = false;
18+
19+
@property({ type: Number })
20+
x = 0;
21+
22+
@property({ type: Number })
23+
y = 0;
24+
25+
@property({ type: Number })
26+
offset = 12;
27+
28+
private pendingFrame = 0;
29+
30+
show(text: string, clientX: number, clientY: number): void {
31+
this.text = text;
32+
this.visible = true;
33+
this.updatePosition(clientX, clientY);
34+
}
35+
36+
hide(): void {
37+
this.visible = false;
38+
this.text = '';
39+
if (this.pendingFrame) {
40+
cancelAnimationFrame(this.pendingFrame);
41+
this.pendingFrame = 0;
42+
}
43+
}
44+
45+
updatePosition(clientX: number, clientY: number): void {
46+
this.x = clientX + this.offset;
47+
this.y = clientY + this.offset;
48+
49+
if (this.pendingFrame) return;
50+
51+
this.pendingFrame = requestAnimationFrame(() => {
52+
this.pendingFrame = 0;
53+
if (!this.visible) return;
54+
55+
const tipRect = this.getBoundingClientRect();
56+
let x = this.x;
57+
let y = this.y;
58+
59+
const innerW = window.innerWidth;
60+
const innerH = window.innerHeight;
61+
62+
if (x + tipRect.width + this.offset > innerW) {
63+
x = this.x - this.offset - tipRect.width - this.offset;
64+
}
65+
if (x < this.offset) x = this.offset;
66+
67+
if (y + tipRect.height + this.offset > innerH) {
68+
y = this.y - this.offset - tipRect.height - this.offset;
69+
}
70+
if (y < this.offset) y = this.offset;
71+
72+
this.style.transform = `translate3d(${Math.round(x)}px, ${Math.round(
73+
y
74+
)}px, 0)`;
75+
});
76+
}
77+
78+
render(): TemplateResult {
79+
return html`<slot>${this.text}</slot>`;
80+
}
81+
82+
static styles = css`
83+
:host {
84+
position: fixed;
85+
pointer-events: none;
86+
background: rgba(20, 20, 20, 0.95);
87+
color: rgba(240, 240, 240, 0.98);
88+
padding: 6px 8px;
89+
border-radius: 4px;
90+
font-size: 0.85em;
91+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
92+
z-index: 6000;
93+
max-width: 60vw;
94+
border: 1px solid rgba(255, 255, 255, 0.04);
95+
left: 0;
96+
top: 0;
97+
transform: translate3d(0, 0, 0);
98+
will-change: transform;
99+
opacity: 1;
100+
transition: opacity 0.15s ease-in-out;
101+
}
102+
103+
:host(:not([visible])) {
104+
opacity: 0;
105+
pointer-events: none;
106+
}
107+
108+
:host([hidden]) {
109+
display: none;
110+
}
111+
`;
112+
}

0 commit comments

Comments
 (0)