feat: refactor left panel into tabbed container with Stats + Events tabs
Extract SuperlativesPanel into content-only component, create LeftPanel as the outer shell with two tabs (S for Stats, E for Events), and add EventsFeed component for displaying narration events with clickable NPC names and auto-scroll behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,9 @@ import { CharacterCompositor } from '../sprites/CharacterCompositor.js';
|
||||
import { PortraitCompositor } from '../sprites/PortraitCompositor.js';
|
||||
import { NpcInfoPanel } from '../ui/NpcInfoPanel.js';
|
||||
import { CommandPanel } from '../ui/CommandPanel.js';
|
||||
import { SuperlativesPanel } from '../ui/SuperlativesPanel.js';
|
||||
import { LeftPanel } from '../ui/LeftPanel.js';
|
||||
import { EventsFeed } from '../ui/EventsFeed.js';
|
||||
import { InteractionEmojiManager } from '../ui/InteractionEmoji.js';
|
||||
import type { WorldState, StateUpdate, EntityState, Appearance } from '@dflike/shared';
|
||||
import { TILE_SIZE, SPRITE_FRAME_WIDTH, SPRITE_FRAME_HEIGHT, SPRITE_COLS, Direction, Terrain, TILESET_TILE_SIZE, TILESET_SCALE, DAY_HOURS, TOTAL_HOURS, SUNSET_DURATION_HOURS, SUNRISE_DURATION_HOURS, NIGHT_DARKNESS } from '@dflike/shared';
|
||||
@@ -31,6 +34,9 @@ export class GameScene extends Phaser.Scene {
|
||||
private emojiManager!: InteractionEmojiManager;
|
||||
private commandPanel!: CommandPanel;
|
||||
private followCommandPanel!: CommandPanel;
|
||||
private superlativesPanel!: SuperlativesPanel;
|
||||
private leftPanel!: LeftPanel;
|
||||
private eventsFeed!: EventsFeed;
|
||||
private highlightEnabled = false;
|
||||
private highlightedSpriteId: number | null = null;
|
||||
private dayNightOverlay!: Phaser.GameObjects.Graphics;
|
||||
@@ -60,6 +66,23 @@ export class GameScene extends Phaser.Scene {
|
||||
{ key: 'TAB', label: 'Camera Mode' },
|
||||
]);
|
||||
|
||||
this.superlativesPanel = new SuperlativesPanel();
|
||||
this.eventsFeed = new EventsFeed();
|
||||
this.leftPanel = new LeftPanel(
|
||||
this.superlativesPanel.getElement(),
|
||||
this.eventsFeed.getElement(),
|
||||
);
|
||||
|
||||
document.addEventListener('left-panel-opened', ((e: CustomEvent) => {
|
||||
if (e.detail.tab === 'stats') {
|
||||
this.client.subscribeSuperlatives();
|
||||
}
|
||||
}) as EventListener);
|
||||
|
||||
document.addEventListener('left-panel-closed', () => {
|
||||
this.client.unsubscribeSuperlatives();
|
||||
});
|
||||
|
||||
// Draw tile grid
|
||||
this.drawWorld();
|
||||
|
||||
@@ -146,6 +169,52 @@ export class GameScene extends Phaser.Scene {
|
||||
};
|
||||
});
|
||||
|
||||
this.client.onSuperlativesUpdate = (data) => {
|
||||
this.superlativesPanel.update(data);
|
||||
};
|
||||
|
||||
this.client.onNarrationEvent = (event) => this.eventsFeed.addEvent(event);
|
||||
this.client.onNarrationUpdate = (data) => this.eventsFeed.updateEvent(data.id, data.narration);
|
||||
this.client.onNarrationHistory = (events) => this.eventsFeed.loadHistory(events);
|
||||
|
||||
document.addEventListener('narration-follow', ((e: CustomEvent) => {
|
||||
const { entityId } = e.detail;
|
||||
const npcIds = this.getNpcIds();
|
||||
const idx = npcIds.indexOf(entityId);
|
||||
if (idx === -1) return;
|
||||
|
||||
this.followTargetIndex = idx;
|
||||
if (this.mode !== 'follow') {
|
||||
this.mode = 'follow';
|
||||
this.showFollowPanel();
|
||||
this.followCommandPanel.show();
|
||||
this.commandPanel.hide();
|
||||
} else {
|
||||
this.updateFollowPanel();
|
||||
}
|
||||
this.updateModeText();
|
||||
this.leftPanel.collapse();
|
||||
}) as EventListener);
|
||||
|
||||
document.addEventListener('superlative-follow', ((e: CustomEvent) => {
|
||||
const { entityId } = e.detail;
|
||||
const npcIds = this.getNpcIds();
|
||||
const idx = npcIds.indexOf(entityId);
|
||||
if (idx === -1) return;
|
||||
|
||||
this.followTargetIndex = idx;
|
||||
if (this.mode !== 'follow') {
|
||||
this.mode = 'follow';
|
||||
this.showFollowPanel();
|
||||
this.followCommandPanel.show();
|
||||
this.commandPanel.hide();
|
||||
} else {
|
||||
this.updateFollowPanel();
|
||||
}
|
||||
this.updateModeText();
|
||||
this.leftPanel.collapse();
|
||||
}) as EventListener);
|
||||
|
||||
this.client.onPlayerLeft = (_data) => {
|
||||
// Entity removal will be handled by next state update
|
||||
};
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import type { NarrationEvent } from '@dflike/shared';
|
||||
|
||||
export class EventsFeed {
|
||||
private container: HTMLDivElement;
|
||||
private events: NarrationEvent[] = [];
|
||||
private eventElements: Map<number, HTMLDivElement> = new Map();
|
||||
private maxEvents = 30;
|
||||
private autoScroll = true;
|
||||
|
||||
constructor() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.style.cssText = `
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
this.container.addEventListener('scroll', () => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.container;
|
||||
this.autoScroll = scrollHeight - scrollTop - clientHeight < 20;
|
||||
});
|
||||
}
|
||||
|
||||
getElement(): HTMLDivElement {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
addEvent(event: NarrationEvent): void {
|
||||
this.events.push(event);
|
||||
const el = this.createEventElement(event);
|
||||
this.eventElements.set(event.id, el);
|
||||
this.container.appendChild(el);
|
||||
|
||||
// Trim oldest if over limit
|
||||
while (this.events.length > this.maxEvents) {
|
||||
const removed = this.events.shift()!;
|
||||
const removedEl = this.eventElements.get(removed.id);
|
||||
if (removedEl) {
|
||||
removedEl.remove();
|
||||
this.eventElements.delete(removed.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.autoScroll) {
|
||||
this.container.scrollTop = this.container.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
updateEvent(id: number, narration: string): void {
|
||||
const el = this.eventElements.get(id);
|
||||
if (!el) return;
|
||||
|
||||
// Update the narration text
|
||||
const textSpan = el.querySelector('[data-narration]') as HTMLSpanElement;
|
||||
if (textSpan) {
|
||||
textSpan.textContent = narration;
|
||||
}
|
||||
|
||||
// Update styling to LLM-narrated
|
||||
el.style.color = '#d0d0f0';
|
||||
|
||||
// Update the stored event
|
||||
const event = this.events.find((e) => e.id === id);
|
||||
if (event) {
|
||||
event.narration = narration;
|
||||
event.isLlmGenerated = true;
|
||||
}
|
||||
}
|
||||
|
||||
loadHistory(events: NarrationEvent[]): void {
|
||||
this.clear();
|
||||
for (const event of events) {
|
||||
this.events.push(event);
|
||||
const el = this.createEventElement(event);
|
||||
this.eventElements.set(event.id, el);
|
||||
this.container.appendChild(el);
|
||||
}
|
||||
|
||||
// Scroll to bottom
|
||||
this.container.scrollTop = this.container.scrollHeight;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.events = [];
|
||||
this.eventElements.clear();
|
||||
this.container.innerHTML = '';
|
||||
}
|
||||
|
||||
private createEventElement(event: NarrationEvent): HTMLDivElement {
|
||||
const el = document.createElement('div');
|
||||
el.style.cssText = `
|
||||
font-size: 11px;
|
||||
color: ${event.isLlmGenerated ? '#d0d0f0' : '#b0b0d0'};
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #2a2a4e;
|
||||
line-height: 1.5;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
`;
|
||||
|
||||
// Build narration with clickable NPC names
|
||||
const narrationText = event.narration;
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// Try to make NPC names clickable by finding them in the narration
|
||||
let remaining = narrationText;
|
||||
let builtSomething = false;
|
||||
|
||||
for (let i = 0; i < event.names.length; i++) {
|
||||
const name = event.names[i];
|
||||
const entityId = event.entityIds[i];
|
||||
const idx = remaining.indexOf(name);
|
||||
if (idx !== -1) {
|
||||
builtSomething = true;
|
||||
// Text before the name
|
||||
if (idx > 0) {
|
||||
fragment.appendChild(document.createTextNode(remaining.substring(0, idx)));
|
||||
}
|
||||
// Clickable name
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = name;
|
||||
nameSpan.style.cssText = `
|
||||
color: #58d858;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
`;
|
||||
nameSpan.addEventListener('mouseenter', () => {
|
||||
nameSpan.style.color = '#88ff88';
|
||||
nameSpan.style.textDecoration = 'underline';
|
||||
});
|
||||
nameSpan.addEventListener('mouseleave', () => {
|
||||
nameSpan.style.color = '#58d858';
|
||||
nameSpan.style.textDecoration = 'none';
|
||||
});
|
||||
nameSpan.addEventListener('click', () => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('narration-follow', {
|
||||
detail: { entityId },
|
||||
}),
|
||||
);
|
||||
});
|
||||
fragment.appendChild(nameSpan);
|
||||
remaining = remaining.substring(idx + name.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (builtSomething) {
|
||||
// Append any remaining text
|
||||
if (remaining) {
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.setAttribute('data-narration', '');
|
||||
textSpan.textContent = remaining;
|
||||
fragment.appendChild(textSpan);
|
||||
} else {
|
||||
// Mark the last text node for updateEvent
|
||||
const lastChild = fragment.lastChild as HTMLElement;
|
||||
if (lastChild && lastChild.nodeType === Node.ELEMENT_NODE) {
|
||||
lastChild.setAttribute('data-narration', '');
|
||||
}
|
||||
}
|
||||
el.appendChild(fragment);
|
||||
} else {
|
||||
// No names found in text, just show as plain text
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.setAttribute('data-narration', '');
|
||||
textSpan.textContent = narrationText;
|
||||
el.appendChild(textSpan);
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
export class LeftPanel {
|
||||
private container: HTMLDivElement;
|
||||
private panel: HTMLDivElement;
|
||||
private headerEl: HTMLDivElement;
|
||||
private contentArea: HTMLDivElement;
|
||||
private statsTab: HTMLDivElement;
|
||||
private eventsTab: HTMLDivElement;
|
||||
private expanded = false;
|
||||
private activeTab: 'stats' | 'events' | null = null;
|
||||
private superlativesContent: HTMLDivElement;
|
||||
private eventsFeedContent: HTMLDivElement;
|
||||
|
||||
constructor(superlativesContent: HTMLDivElement, eventsFeedContent: HTMLDivElement) {
|
||||
this.superlativesContent = superlativesContent;
|
||||
this.eventsFeedContent = eventsFeedContent;
|
||||
|
||||
// Outer container for positioning
|
||||
this.container = document.createElement('div');
|
||||
this.container.style.cssText = `
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
transition: transform 0.35s ease;
|
||||
`;
|
||||
|
||||
// Main panel
|
||||
this.panel = document.createElement('div');
|
||||
this.panel.style.cssText = `
|
||||
width: 340px;
|
||||
background: #1a1a2e;
|
||||
border: 3px solid #e0d0b0;
|
||||
border-left: none;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
// Inner frame (EarthBound double-border)
|
||||
const innerFrame = document.createElement('div');
|
||||
innerFrame.style.cssText = `
|
||||
border: 2px solid #8878a8;
|
||||
margin: 4px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
// Header
|
||||
this.headerEl = document.createElement('div');
|
||||
this.headerEl.style.cssText = `
|
||||
text-align: center;
|
||||
color: #e0d0b0;
|
||||
font-size: 16px;
|
||||
padding: 4px 0 8px 0;
|
||||
border-bottom: 1px solid #8878a8;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
this.headerEl.textContent = 'SUPERLATIVES';
|
||||
|
||||
// Content area (swaps between superlatives and events)
|
||||
this.contentArea = document.createElement('div');
|
||||
this.contentArea.style.cssText = `
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
innerFrame.appendChild(this.headerEl);
|
||||
innerFrame.appendChild(this.contentArea);
|
||||
this.panel.appendChild(innerFrame);
|
||||
|
||||
// Tab buttons container (stacked vertically on right edge)
|
||||
const tabContainer = document.createElement('div');
|
||||
tabContainer.style.cssText = `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
// Stats tab ("S")
|
||||
this.statsTab = this.createTab('S');
|
||||
this.statsTab.addEventListener('click', () => {
|
||||
if (this.activeTab === 'stats' && this.expanded) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand('stats');
|
||||
}
|
||||
});
|
||||
|
||||
// Events tab ("E")
|
||||
this.eventsTab = this.createTab('E');
|
||||
this.eventsTab.addEventListener('click', () => {
|
||||
if (this.activeTab === 'events' && this.expanded) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand('events');
|
||||
}
|
||||
});
|
||||
|
||||
tabContainer.appendChild(this.statsTab);
|
||||
tabContainer.appendChild(this.eventsTab);
|
||||
|
||||
this.container.appendChild(this.panel);
|
||||
this.container.appendChild(tabContainer);
|
||||
|
||||
// Start collapsed
|
||||
this.container.style.transform = 'translateY(-50%) translateX(-340px)';
|
||||
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
|
||||
private createTab(label: string): HTMLDivElement {
|
||||
const tab = document.createElement('div');
|
||||
tab.style.cssText = `
|
||||
width: 32px;
|
||||
height: 56px;
|
||||
background: #1a1a2e;
|
||||
border: 3px solid #e0d0b0;
|
||||
border-left: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.2s;
|
||||
`;
|
||||
|
||||
const tabInner = document.createElement('div');
|
||||
tabInner.style.cssText = `
|
||||
color: #e0d0b0;
|
||||
font-size: 20px;
|
||||
`;
|
||||
tabInner.textContent = label;
|
||||
tab.appendChild(tabInner);
|
||||
|
||||
tab.addEventListener('mouseenter', () => {
|
||||
if (tab.dataset.active !== 'true') {
|
||||
tab.style.background = '#2a2a4e';
|
||||
}
|
||||
});
|
||||
tab.addEventListener('mouseleave', () => {
|
||||
if (tab.dataset.active !== 'true') {
|
||||
tab.style.background = '#1a1a2e';
|
||||
}
|
||||
});
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
private updateTabs(): void {
|
||||
this.statsTab.style.background =
|
||||
this.activeTab === 'stats' && this.expanded ? '#2a2a4e' : '#1a1a2e';
|
||||
this.statsTab.dataset.active =
|
||||
this.activeTab === 'stats' && this.expanded ? 'true' : 'false';
|
||||
|
||||
this.eventsTab.style.background =
|
||||
this.activeTab === 'events' && this.expanded ? '#2a2a4e' : '#1a1a2e';
|
||||
this.eventsTab.dataset.active =
|
||||
this.activeTab === 'events' && this.expanded ? 'true' : 'false';
|
||||
}
|
||||
|
||||
private showContent(tab: 'stats' | 'events'): void {
|
||||
// Remove current content
|
||||
while (this.contentArea.firstChild) {
|
||||
this.contentArea.removeChild(this.contentArea.firstChild);
|
||||
}
|
||||
|
||||
if (tab === 'stats') {
|
||||
this.headerEl.textContent = 'SUPERLATIVES';
|
||||
this.contentArea.appendChild(this.superlativesContent);
|
||||
} else {
|
||||
this.headerEl.textContent = 'EVENTS';
|
||||
this.contentArea.appendChild(this.eventsFeedContent);
|
||||
}
|
||||
}
|
||||
|
||||
expand(tab: 'stats' | 'events'): void {
|
||||
this.activeTab = tab;
|
||||
this.expanded = true;
|
||||
this.showContent(tab);
|
||||
this.updateTabs();
|
||||
this.container.style.transform = 'translateY(-50%) translateX(0)';
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('left-panel-opened', { detail: { tab } }),
|
||||
);
|
||||
}
|
||||
|
||||
collapse(): void {
|
||||
this.expanded = false;
|
||||
this.container.style.transform = 'translateY(-50%) translateX(-340px)';
|
||||
this.updateTabs();
|
||||
document.dispatchEvent(new CustomEvent('left-panel-closed'));
|
||||
}
|
||||
|
||||
isExpanded(): boolean {
|
||||
return this.expanded;
|
||||
}
|
||||
|
||||
getActiveTab(): 'stats' | 'events' | null {
|
||||
return this.expanded ? this.activeTab : null;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.container.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { SuperlativesData } from '@dflike/shared';
|
||||
|
||||
type SuperlativeCategory = {
|
||||
key: keyof SuperlativesData;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const CATEGORIES: SuperlativeCategory[] = [
|
||||
{ key: 'mostLoved', label: 'Most Loved' },
|
||||
{ key: 'mostReviled', label: 'Most Reviled' },
|
||||
{ key: 'mostPopular', label: 'Most Popular' },
|
||||
{ key: 'mostAnnoying', label: 'Most Annoying' },
|
||||
{ key: 'mostOutgoing', label: 'Most Outgoing' },
|
||||
{ key: 'shyest', label: 'Shyest' },
|
||||
{ key: 'socialButterfly', label: 'Social Butterfly' },
|
||||
{ key: 'loneliest', label: 'Loneliest' },
|
||||
{ key: 'mostDevoted', label: 'Most Devoted' },
|
||||
{ key: 'mostPolarizing', label: 'Most Polarizing' },
|
||||
{ key: 'biggestHeartbreaker', label: 'Heartbreaker' },
|
||||
];
|
||||
|
||||
export class SuperlativesPanel {
|
||||
private contentEl: HTMLDivElement;
|
||||
private categoryElements: Map<string, { nameEl: HTMLSpanElement; valueEl: HTMLSpanElement }> = new Map();
|
||||
|
||||
constructor() {
|
||||
// Content area (no outer container, tab, or slide logic)
|
||||
this.contentEl = document.createElement('div');
|
||||
|
||||
for (const cat of CATEGORIES) {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = 'margin-bottom: 10px;';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.style.cssText = `
|
||||
color: #8878a8;
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
label.textContent = cat.label;
|
||||
|
||||
const entryRow = document.createElement('div');
|
||||
entryRow.style.cssText = `
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
`;
|
||||
|
||||
const nameEl = document.createElement('span');
|
||||
nameEl.style.cssText = `
|
||||
color: #58d858;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
`;
|
||||
nameEl.textContent = '\u2014';
|
||||
nameEl.addEventListener('mouseenter', () => {
|
||||
if (nameEl.dataset.entityId) {
|
||||
nameEl.style.color = '#88ff88';
|
||||
nameEl.style.textDecoration = 'underline';
|
||||
}
|
||||
});
|
||||
nameEl.addEventListener('mouseleave', () => {
|
||||
nameEl.style.color = '#58d858';
|
||||
nameEl.style.textDecoration = 'none';
|
||||
});
|
||||
nameEl.addEventListener('click', () => {
|
||||
const entityId = nameEl.dataset.entityId;
|
||||
if (entityId) {
|
||||
document.dispatchEvent(new CustomEvent('superlative-follow', {
|
||||
detail: { entityId: parseInt(entityId, 10) },
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
const valueEl = document.createElement('span');
|
||||
valueEl.style.cssText = `
|
||||
color: #a0a0c0;
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
entryRow.appendChild(nameEl);
|
||||
entryRow.appendChild(valueEl);
|
||||
row.appendChild(label);
|
||||
row.appendChild(entryRow);
|
||||
this.contentEl.appendChild(row);
|
||||
|
||||
this.categoryElements.set(cat.key, { nameEl, valueEl });
|
||||
}
|
||||
}
|
||||
|
||||
getElement(): HTMLDivElement {
|
||||
return this.contentEl;
|
||||
}
|
||||
|
||||
update(data: SuperlativesData): void {
|
||||
for (const cat of CATEGORIES) {
|
||||
const els = this.categoryElements.get(cat.key);
|
||||
if (!els) continue;
|
||||
const entry = data[cat.key];
|
||||
if (entry) {
|
||||
els.nameEl.textContent = entry.name;
|
||||
els.nameEl.dataset.entityId = String(entry.entityId);
|
||||
els.valueEl.textContent = `(${entry.value})`;
|
||||
} else {
|
||||
els.nameEl.textContent = '\u2014';
|
||||
delete els.nameEl.dataset.entityId;
|
||||
els.valueEl.textContent = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user