mirror of
https://github.com/jcreek/OpenNetworkDiagram.git
synced 2026-07-12 18:43:44 +00:00
feat(#5): replace icon key inputs with searchable modal icon picker
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
import cytoscape from 'cytoscape';
|
import cytoscape from 'cytoscape';
|
||||||
import dagre from 'cytoscape-dagre';
|
import dagre from 'cytoscape-dagre';
|
||||||
|
|
||||||
import { listIconDefinitions } from '$lib/config/iconRegistry';
|
import { listIconDefinitions, resolveIconPath } from '$lib/config/iconRegistry';
|
||||||
import {
|
import {
|
||||||
cloneNetworkData,
|
cloneNetworkData,
|
||||||
createEmptyDevice,
|
createEmptyDevice,
|
||||||
@@ -95,6 +95,7 @@
|
|||||||
let mapControlsCollapsed = false;
|
let mapControlsCollapsed = false;
|
||||||
|
|
||||||
const iconDefinitions = listIconDefinitions();
|
const iconDefinitions = listIconDefinitions();
|
||||||
|
const iconResultLimit = 100;
|
||||||
let tooltip = {
|
let tooltip = {
|
||||||
visible: false,
|
visible: false,
|
||||||
text: '',
|
text: '',
|
||||||
@@ -112,13 +113,22 @@
|
|||||||
};
|
};
|
||||||
const tooltipViewportPadding = 8;
|
const tooltipViewportPadding = 8;
|
||||||
|
|
||||||
|
function normalizeIconSearchValue(value: string): string {
|
||||||
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
$: normalizedIconSearch = normalizeIconSearchValue(iconSearch);
|
||||||
|
$: iconSearchTokens = normalizedIconSearch.split(' ').filter(Boolean);
|
||||||
$: filteredIconDefinitions = iconDefinitions.filter((icon) => {
|
$: filteredIconDefinitions = iconDefinitions.filter((icon) => {
|
||||||
const needle = iconSearch.trim().toLowerCase();
|
if (iconSearchTokens.length === 0) {
|
||||||
if (!needle) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return icon.label.toLowerCase().includes(needle) || icon.key.toLowerCase().includes(needle);
|
const searchable = normalizeIconSearchValue(`${icon.label} ${icon.key}`);
|
||||||
|
return (
|
||||||
|
iconSearchTokens.every((token) => searchable.includes(token))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
$: visibleIconDefinitions = filteredIconDefinitions.slice(0, iconResultLimit);
|
||||||
|
|
||||||
$: selectedMachine =
|
$: selectedMachine =
|
||||||
selectedTarget?.type === 'machine' ? networkData.machines[selectedTarget.index] : null;
|
selectedTarget?.type === 'machine' ? networkData.machines[selectedTarget.index] : null;
|
||||||
@@ -145,6 +155,22 @@
|
|||||||
Object.keys(machineVmIndex)
|
Object.keys(machineVmIndex)
|
||||||
.filter((hostId) => (machineVmIndex[hostId]?.vmCount ?? 0) > 0)
|
.filter((hostId) => (machineVmIndex[hostId]?.vmCount ?? 0) > 0)
|
||||||
.every((hostId) => isHostCollapsed(hostId));
|
.every((hostId) => isHostCollapsed(hostId));
|
||||||
|
$: selectedTargetIconKey =
|
||||||
|
selectedTarget?.type === 'machine'
|
||||||
|
? selectedMachine?.iconKey ?? ''
|
||||||
|
: selectedTarget?.type === 'device'
|
||||||
|
? selectedDevice?.iconKey ?? ''
|
||||||
|
: selectedTarget?.type === 'vm'
|
||||||
|
? selectedVm?.iconKey ?? ''
|
||||||
|
: '';
|
||||||
|
$: selectedTargetIconPath = resolveIconPath(selectedTargetIconKey || undefined);
|
||||||
|
$: addModalIconKey =
|
||||||
|
addModalKind === 'machine'
|
||||||
|
? newMachineDraft.iconKey ?? ''
|
||||||
|
: addModalKind === 'device'
|
||||||
|
? newDeviceDraft.iconKey ?? ''
|
||||||
|
: '';
|
||||||
|
$: addModalIconPath = resolveIconPath(addModalIconKey || undefined);
|
||||||
|
|
||||||
function equalsIgnoreCase(a: string, b: string): boolean {
|
function equalsIgnoreCase(a: string, b: string): boolean {
|
||||||
return a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
|
return a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
|
||||||
@@ -351,20 +377,20 @@
|
|||||||
|
|
||||||
if (selectedTarget.type === 'machine') {
|
if (selectedTarget.type === 'machine') {
|
||||||
if (!networkData.machines[selectedTarget.index]) {
|
if (!networkData.machines[selectedTarget.index]) {
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTarget.type === 'device') {
|
if (selectedTarget.type === 'device') {
|
||||||
if (!networkData.devices[selectedTarget.index]) {
|
if (!networkData.devices[selectedTarget.index]) {
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!networkData.machines[selectedTarget.machineIndex]?.software.vms[selectedTarget.vmIndex]) {
|
if (!networkData.machines[selectedTarget.machineIndex]?.software.vms[selectedTarget.vmIndex]) {
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,8 +459,8 @@
|
|||||||
'background-image': 'data(iconUrl)',
|
'background-image': 'data(iconUrl)',
|
||||||
'background-fit': 'contain',
|
'background-fit': 'contain',
|
||||||
'background-clip': 'none',
|
'background-clip': 'none',
|
||||||
'background-width': '56%',
|
'background-width': 'auto',
|
||||||
'background-height': '56%',
|
'background-height': 'auto',
|
||||||
'background-position-y': '30%'
|
'background-position-y': '30%'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1014,6 +1040,56 @@
|
|||||||
applyDraft(next, { autosave: options?.autosave ?? true });
|
applyDraft(next, { autosave: options?.autosave ?? true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSelectedTargetIconKey(nextKey: string) {
|
||||||
|
const normalized = nextKey.trim();
|
||||||
|
const target = selectedTarget;
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.type === 'machine') {
|
||||||
|
const index = target.index;
|
||||||
|
mutateDraft(
|
||||||
|
(draft) => {
|
||||||
|
draft.machines[index].iconKey = normalized || undefined;
|
||||||
|
},
|
||||||
|
{ autosave: true }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.type === 'device') {
|
||||||
|
const index = target.index;
|
||||||
|
mutateDraft(
|
||||||
|
(draft) => {
|
||||||
|
draft.devices[index].iconKey = normalized || undefined;
|
||||||
|
},
|
||||||
|
{ autosave: true }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const machineIndex = target.machineIndex;
|
||||||
|
const vmIndex = target.vmIndex;
|
||||||
|
mutateDraft(
|
||||||
|
(draft) => {
|
||||||
|
draft.machines[machineIndex].software.vms[vmIndex].iconKey = normalized || undefined;
|
||||||
|
},
|
||||||
|
{ autosave: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAddModalIconKey(nextKey: string) {
|
||||||
|
const normalized = nextKey.trim();
|
||||||
|
if (addModalKind === 'machine') {
|
||||||
|
newMachineDraft.iconKey = normalized || undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (addModalKind === 'device') {
|
||||||
|
newDeviceDraft.iconKey = normalized || undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function nextUniqueName(base: string, existing: string[]): string {
|
function nextUniqueName(base: string, existing: string[]): string {
|
||||||
const normalized = new Set(existing.map((name) => name.toLowerCase()));
|
const normalized = new Set(existing.map((name) => name.toLowerCase()));
|
||||||
if (!normalized.has(base.toLowerCase())) {
|
if (!normalized.has(base.toLowerCase())) {
|
||||||
@@ -1195,7 +1271,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyDraft(deleteOwner(networkData, 'machine', machine.machineName));
|
applyDraft(deleteOwner(networkData, 'machine', machine.machineName));
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
deleteTarget = null;
|
deleteTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,7 +1281,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyDraft(deleteOwner(networkData, 'device', device.name));
|
applyDraft(deleteOwner(networkData, 'device', device.name));
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
deleteTarget = null;
|
deleteTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,7 +1289,7 @@
|
|||||||
mutateDraft((draft) => {
|
mutateDraft((draft) => {
|
||||||
draft.machines[machineIndex]?.software.vms.splice(vmIndex, 1);
|
draft.machines[machineIndex]?.software.vms.splice(vmIndex, 1);
|
||||||
});
|
});
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
deleteTarget = null;
|
deleteTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,7 +1324,7 @@
|
|||||||
});
|
});
|
||||||
selectedTarget = { type: 'device', index: networkData.devices.length - 1 };
|
selectedTarget = { type: 'device', index: networkData.devices.length - 1 };
|
||||||
}
|
}
|
||||||
addModalKind = null;
|
closeAddEntityModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onThemeToggleChange(event: Event) {
|
function onThemeToggleChange(event: Event) {
|
||||||
@@ -1267,6 +1343,20 @@
|
|||||||
mapControlsCollapsed = !mapControlsCollapsed;
|
mapControlsCollapsed = !mapControlsCollapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearIconSearch() {
|
||||||
|
iconSearch = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSelectedTargetModal() {
|
||||||
|
selectedTarget = null;
|
||||||
|
clearIconSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAddEntityModal() {
|
||||||
|
addModalKind = null;
|
||||||
|
clearIconSearch();
|
||||||
|
}
|
||||||
|
|
||||||
function saveStateLabel(): string {
|
function saveStateLabel(): string {
|
||||||
if (saveState === 'saved') {
|
if (saveState === 'saved') {
|
||||||
return writable ? 'Saved' : 'Read-only';
|
return writable ? 'Saved' : 'Read-only';
|
||||||
@@ -1424,7 +1514,7 @@
|
|||||||
|
|
||||||
cy.on('tap', (event) => {
|
cy.on('tap', (event) => {
|
||||||
if (event.target === cy) {
|
if (event.target === cy) {
|
||||||
selectedTarget = null;
|
closeSelectedTargetModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1628,13 +1718,45 @@
|
|||||||
: selectedVm?.name ?? 'Virtual Machine'
|
: selectedVm?.name ?? 'Virtual Machine'
|
||||||
}
|
}
|
||||||
maxWidth="880px"
|
maxWidth="880px"
|
||||||
on:close={() => (selectedTarget = null)}
|
on:close={closeSelectedTargetModal}
|
||||||
>
|
>
|
||||||
<div class="icon-search">
|
<div class="icon-search">
|
||||||
<label>
|
<label>
|
||||||
Icon Search
|
Icon Search
|
||||||
<input type="search" bind:value={iconSearch} placeholder="Filter icon keys..." />
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={iconSearch}
|
||||||
|
placeholder="Search icons..."
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
{#if selectedTargetIconPath}
|
||||||
|
<div class="icon-preview-card">
|
||||||
|
<img src={selectedTargetIconPath} alt="Selected icon preview" loading="lazy" />
|
||||||
|
<code>{selectedTargetIconKey}</code>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="icon-results-meta">
|
||||||
|
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
|
||||||
|
matching icons
|
||||||
|
</p>
|
||||||
|
<div class="icon-results-grid">
|
||||||
|
{#each visibleIconDefinitions as icon (icon.key)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="icon-result-option"
|
||||||
|
on:click={() => setSelectedTargetIconKey(icon.key)}
|
||||||
|
title={icon.key}
|
||||||
|
>
|
||||||
|
<img src={icon.path} alt={icon.label} loading="lazy" />
|
||||||
|
<span>{icon.label}</span>
|
||||||
|
<code>{icon.key}</code>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if filteredIconDefinitions.length === 0}
|
||||||
|
<p class="icon-results-empty">No icons match your search.</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if selectedTarget?.type === 'machine' && selectedMachine}
|
{#if selectedTarget?.type === 'machine' && selectedMachine}
|
||||||
<div class="inline-actions">
|
<div class="inline-actions">
|
||||||
@@ -1702,23 +1824,6 @@
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Icon
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
list="icon-options"
|
|
||||||
value={selectedMachine.iconKey ?? ''}
|
|
||||||
on:change={(event) =>
|
|
||||||
mutateDraft(
|
|
||||||
(draft) => {
|
|
||||||
const nextValue = (event.currentTarget as HTMLInputElement).value.trim();
|
|
||||||
draft.machines[selectedTarget.index].iconKey = nextValue || undefined;
|
|
||||||
},
|
|
||||||
{ autosave: true }
|
|
||||||
)}
|
|
||||||
placeholder="server"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="edit-section">
|
<section class="edit-section">
|
||||||
@@ -1829,7 +1934,6 @@
|
|||||||
Icon
|
Icon
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
list="icon-options"
|
|
||||||
bind:value={vm.iconKey}
|
bind:value={vm.iconKey}
|
||||||
on:change={() => mutateDraft(() => undefined, { autosave: true })}
|
on:change={() => mutateDraft(() => undefined, { autosave: true })}
|
||||||
/>
|
/>
|
||||||
@@ -2007,22 +2111,6 @@
|
|||||||
)}
|
)}
|
||||||
></textarea>
|
></textarea>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Icon
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
list="icon-options"
|
|
||||||
value={selectedDevice.iconKey ?? ''}
|
|
||||||
on:change={(event) =>
|
|
||||||
mutateDraft(
|
|
||||||
(draft) => {
|
|
||||||
const nextValue = (event.currentTarget as HTMLInputElement).value.trim();
|
|
||||||
draft.devices[selectedTarget.index].iconKey = nextValue || undefined;
|
|
||||||
},
|
|
||||||
{ autosave: true }
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="edit-section">
|
<section class="edit-section">
|
||||||
@@ -2179,24 +2267,6 @@
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Icon
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
list="icon-options"
|
|
||||||
value={selectedVm.iconKey ?? ''}
|
|
||||||
on:change={(event) =>
|
|
||||||
mutateDraft(
|
|
||||||
(draft) => {
|
|
||||||
const nextValue = (event.currentTarget as HTMLInputElement).value.trim();
|
|
||||||
draft.machines[selectedTarget.machineIndex].software.vms[
|
|
||||||
selectedTarget.vmIndex
|
|
||||||
].iconKey = nextValue || undefined;
|
|
||||||
},
|
|
||||||
{ autosave: true }
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</section>
|
</section>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button
|
<button
|
||||||
@@ -2219,13 +2289,45 @@
|
|||||||
isOpen={addModalKind !== null}
|
isOpen={addModalKind !== null}
|
||||||
title={addModalKind === 'machine' ? 'Add Machine' : 'Add Device'}
|
title={addModalKind === 'machine' ? 'Add Machine' : 'Add Device'}
|
||||||
maxWidth="760px"
|
maxWidth="760px"
|
||||||
on:close={() => (addModalKind = null)}
|
on:close={closeAddEntityModal}
|
||||||
>
|
>
|
||||||
<div class="icon-search">
|
<div class="icon-search">
|
||||||
<label>
|
<label>
|
||||||
Icon Search
|
Icon Search
|
||||||
<input type="search" bind:value={iconSearch} placeholder="Filter icon keys..." />
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={iconSearch}
|
||||||
|
placeholder="Search icons..."
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
{#if addModalIconPath}
|
||||||
|
<div class="icon-preview-card">
|
||||||
|
<img src={addModalIconPath} alt="Selected icon preview" loading="lazy" />
|
||||||
|
<code>{addModalIconKey}</code>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="icon-results-meta">
|
||||||
|
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
|
||||||
|
matching icons
|
||||||
|
</p>
|
||||||
|
<div class="icon-results-grid">
|
||||||
|
{#each visibleIconDefinitions as icon (icon.key)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="icon-result-option"
|
||||||
|
on:click={() => setAddModalIconKey(icon.key)}
|
||||||
|
title={icon.key}
|
||||||
|
>
|
||||||
|
<img src={icon.path} alt={icon.label} loading="lazy" />
|
||||||
|
<span>{icon.label}</span>
|
||||||
|
<code>{icon.key}</code>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if filteredIconDefinitions.length === 0}
|
||||||
|
<p class="icon-results-empty">No icons match your search.</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if addModalKind === 'machine'}
|
{#if addModalKind === 'machine'}
|
||||||
<section class="edit-section">
|
<section class="edit-section">
|
||||||
@@ -2245,10 +2347,6 @@
|
|||||||
Operating System
|
Operating System
|
||||||
<input type="text" bind:value={newMachineDraft.operatingSystem} />
|
<input type="text" bind:value={newMachineDraft.operatingSystem} />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Icon
|
|
||||||
<input type="text" list="icon-options" bind:value={newMachineDraft.iconKey} />
|
|
||||||
</label>
|
|
||||||
</section>
|
</section>
|
||||||
{:else if addModalKind === 'device'}
|
{:else if addModalKind === 'device'}
|
||||||
<section class="edit-section">
|
<section class="edit-section">
|
||||||
@@ -2268,10 +2366,6 @@
|
|||||||
Notes
|
Notes
|
||||||
<textarea bind:value={newDeviceDraft.notes}></textarea>
|
<textarea bind:value={newDeviceDraft.notes}></textarea>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Icon
|
|
||||||
<input type="text" list="icon-options" bind:value={newDeviceDraft.iconKey} />
|
|
||||||
</label>
|
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -2320,12 +2414,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<datalist id="icon-options">
|
|
||||||
{#each filteredIconDefinitions as icon (icon.key)}
|
|
||||||
<option value={icon.key}>{icon.label}</option>
|
|
||||||
{/each}
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.diagram-shell {
|
.diagram-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -2699,6 +2787,8 @@
|
|||||||
|
|
||||||
.icon-search {
|
.icon-search {
|
||||||
margin-bottom: 0.7rem;
|
margin-bottom: 0.7rem;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-search label {
|
.icon-search label {
|
||||||
@@ -2718,6 +2808,94 @@
|
|||||||
font-size: 0.86rem;
|
font-size: 0.86rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-preview-card {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: color-mix(in oklab, var(--panel-bg) 92%, black 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-preview-card img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-preview-card code {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--muted-text);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-results-meta {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-results-empty {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-results-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||||
|
gap: 0.45rem;
|
||||||
|
max-height: 280px;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-result-option {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'icon label'
|
||||||
|
'icon key';
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2rem 0.5rem;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
background: var(--panel-bg);
|
||||||
|
color: var(--panel-contrast);
|
||||||
|
padding: 0.4rem 0.45rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-result-option:hover {
|
||||||
|
background: color-mix(in oklab, var(--panel-bg) 84%, #3b82f6 16%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-result-option img {
|
||||||
|
grid-area: icon;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-result-option span {
|
||||||
|
grid-area: label;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-result-option code {
|
||||||
|
grid-area: key;
|
||||||
|
font-size: 0.66rem;
|
||||||
|
color: var(--muted-text);
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.diagram-shell {
|
.diagram-shell {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user