Skip to content

Commit fa904c9

Browse files
author
Eaglenait
committed
allow to import strings to display to the ui from the strings keys file
1 parent 782d583 commit fa904c9

File tree

4 files changed

+133
-5
lines changed

4 files changed

+133
-5
lines changed

src/app/components/header-bar.component.html

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,27 @@ <h4>Import UI Code</h4>
239239
<textarea
240240
id="import-source"
241241
class="import-textarea"
242-
rows="14"
242+
rows="10"
243243
[value]="importSource()"
244244
(input)="onImportSourceChange($event)"
245245
placeholder="const widget = modlib.ParseUI(...);"
246246
></textarea>
247+
248+
<div class="import-strings-section">
249+
<label for="import-strings">
250+
Paste the strings JSON file (optional):
251+
<span class="import-strings-hint">This will populate text elements with localized strings</span>
252+
</label>
253+
<textarea
254+
id="import-strings"
255+
class="import-textarea import-textarea--strings"
256+
rows="6"
257+
[value]="importStrings()"
258+
(input)="onImportStringsChange($event)"
259+
placeholder='{"ElementName": "Display Text", ...}'
260+
></textarea>
261+
</div>
262+
247263
<p class="import-help">By default, existing elements are replaced. Enable append to keep the current layout. Your project is auto-saved before import.</p>
248264
<div class="import-mode-toggle">
249265
<label class="mode-switch">

src/app/components/header-bar.component.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type BannerMessage = { type: 'success' | 'error'; text: string };
1313
styleUrl: './header-bar.component.scss'
1414
})
1515
export class HeaderBarComponent implements AfterViewInit {
16-
readonly version = 'V1.5.2';
16+
readonly version = 'V1.5.3';
1717
readonly title = 'UI Builder';
1818
readonly defaultBackgroundImage: CanvasBackgroundAsset;
1919

@@ -31,6 +31,7 @@ export class HeaderBarComponent implements AfterViewInit {
3131
readonly showContainerLabels = computed(() => this.uiBuilder.showContainerLabels());
3232
readonly importModalOpen = signal(false);
3333
readonly importSource = signal('');
34+
readonly importStrings = signal('');
3435
readonly importError = signal<string | null>(null);
3536
readonly importMode = signal<'replace' | 'append'>('replace');
3637
readonly bannerMessage = signal<BannerMessage | null>(null);
@@ -177,6 +178,7 @@ export class HeaderBarComponent implements AfterViewInit {
177178

178179
openImportModal() {
179180
this.importSource.set('');
181+
this.importStrings.set('');
180182
this.importError.set(null);
181183
this.importMode.set('replace');
182184
this.importModalOpen.set(true);
@@ -185,6 +187,7 @@ export class HeaderBarComponent implements AfterViewInit {
185187
closeImportModal() {
186188
this.importModalOpen.set(false);
187189
this.importSource.set('');
190+
this.importStrings.set('');
188191
this.importError.set(null);
189192
this.importMode.set('replace');
190193
}
@@ -197,6 +200,14 @@ export class HeaderBarComponent implements AfterViewInit {
197200
}
198201
}
199202

203+
onImportStringsChange(event: Event) {
204+
const value = (event.target as HTMLTextAreaElement | null)?.value ?? '';
205+
this.importStrings.set(value);
206+
if (this.importError()) {
207+
this.importError.set(null);
208+
}
209+
}
210+
200211
setImportMode(event: Event) {
201212
const target = event.target as HTMLInputElement | null;
202213
if (!target) {
@@ -214,9 +225,10 @@ export class HeaderBarComponent implements AfterViewInit {
214225
}
215226

216227
const mode = this.importMode();
228+
const stringsJson = this.importStrings().trim();
217229

218230
try {
219-
const result = this.uiBuilder.importFromTypescript(source, { mode });
231+
const result = this.uiBuilder.importFromTypescript(source, { mode, stringsJson: stringsJson || undefined });
220232
if (!result.success) {
221233
this.importError.set(result.error ?? 'Import failed.');
222234
this.showBannerMessage(result.error ?? 'Import failed.', 'error', false);
@@ -232,7 +244,10 @@ export class HeaderBarComponent implements AfterViewInit {
232244
? 'No new elements detected.'
233245
: 'UI cleared. No elements detected in snippet.'
234246
: `${verb} ${count} root element${suffix} from script.`;
235-
this.showBannerMessage(details, 'success');
247+
248+
// Add info about strings if they were imported
249+
const stringsInfo = stringsJson ? ' Text strings applied.' : '';
250+
this.showBannerMessage(details + stringsInfo, 'success');
236251
} catch (error) {
237252
console.error('Failed to import UI:', error);
238253
this.importError.set('Unexpected error while importing. Please verify the snippet and try again.');

src/app/services/ui-builder.service.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1745,7 +1745,7 @@ export class UiBuilderService {
17451745

17461746
importFromTypescript(
17471747
source: string,
1748-
options?: { mode?: 'replace' | 'append' }
1748+
options?: { mode?: 'replace' | 'append'; stringsJson?: string }
17491749
): { success: boolean; importedCount: number; error?: string } {
17501750
const text = typeof source === 'string' ? source : '';
17511751
if (!text.trim()) {
@@ -1776,6 +1776,11 @@ export class UiBuilderService {
17761776
this._elements.set(restored);
17771777
}
17781778

1779+
// Apply strings if provided
1780+
if (options?.stringsJson) {
1781+
this.importStringsJson(options.stringsJson);
1782+
}
1783+
17791784
this.clearSelection();
17801785

17811786
return { success: true, importedCount: restored.length };
@@ -1785,6 +1790,72 @@ export class UiBuilderService {
17851790
}
17861791
}
17871792

1793+
importStringsJson(stringsJson: string): { success: boolean; appliedCount: number; error?: string } {
1794+
const text = typeof stringsJson === 'string' ? stringsJson.trim() : '';
1795+
if (!text) {
1796+
return { success: true, appliedCount: 0 };
1797+
}
1798+
1799+
try {
1800+
const strings = JSON.parse(text);
1801+
if (!strings || typeof strings !== 'object' || Array.isArray(strings)) {
1802+
return { success: false, appliedCount: 0, error: 'Invalid strings JSON format.' };
1803+
}
1804+
1805+
let appliedCount = 0;
1806+
1807+
// Apply strings to matching elements by name or ID
1808+
this._elements.update(elements => {
1809+
return this.applyStringsToElements(elements, strings, (count) => {
1810+
appliedCount += count;
1811+
});
1812+
});
1813+
1814+
return { success: true, appliedCount };
1815+
} catch (error) {
1816+
const message = error instanceof Error ? error.message : 'Failed to parse strings JSON.';
1817+
return { success: false, appliedCount: 0, error: message };
1818+
}
1819+
}
1820+
1821+
private applyStringsToElements(
1822+
elements: UIElement[],
1823+
strings: Record<string, string>,
1824+
onApplied: (count: number) => void
1825+
): UIElement[] {
1826+
return elements.map(element => {
1827+
let updated = element;
1828+
1829+
// Check if this element has a matching string by name or ID
1830+
if (element.type === 'Text') {
1831+
const key = element.name || element.id;
1832+
if (key in strings) {
1833+
const stringValue = strings[key];
1834+
if (typeof stringValue === 'string' && stringValue.trim().length > 0) {
1835+
updated = {
1836+
...element,
1837+
textLabel: stringValue,
1838+
};
1839+
onApplied(1);
1840+
}
1841+
}
1842+
}
1843+
1844+
// Recursively apply to children
1845+
if (element.children?.length) {
1846+
const updatedChildren = this.applyStringsToElements(element.children, strings, onApplied);
1847+
if (updatedChildren !== element.children) {
1848+
updated = {
1849+
...updated,
1850+
children: updatedChildren,
1851+
};
1852+
}
1853+
}
1854+
1855+
return updated;
1856+
});
1857+
}
1858+
17881859
// Clear all elements
17891860
clear(): void {
17901861
this._elements.set([]);

src/styles.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,38 @@ html, body {
111111
min-height: 220px;
112112
}
113113

114+
.import-textarea--strings {
115+
min-height: 140px;
116+
}
117+
114118
.import-textarea:focus {
115119
outline: none;
116120
border-color: #4a9eff;
117121
box-shadow: 0 0 0 1px rgba(74, 158, 255, 0.3);
118122
}
119123

124+
.import-strings-section {
125+
display: flex;
126+
flex-direction: column;
127+
gap: 8px;
128+
padding-top: 8px;
129+
margin-top: 12px;
130+
border-top: 1px solid #2a2a2a;
131+
}
132+
133+
.import-strings-section label {
134+
display: flex;
135+
flex-direction: column;
136+
gap: 4px;
137+
}
138+
139+
.import-strings-hint {
140+
font-size: 11px;
141+
color: #6b7280;
142+
font-weight: 400;
143+
letter-spacing: 0.2px;
144+
}
145+
120146
.import-help {
121147
font-size: 12px;
122148
color: #9ca3af;

0 commit comments

Comments
 (0)