mirror of
https://github.com/garraflavatra/rolens.git
synced 2024-12-01 13:20:54 +00:00
Grid: correct indentation and column widths
This commit is contained in:
parent
69e7502222
commit
f51abb45cd
168
frontend/src/components/grid-items.svelte
Normal file
168
frontend/src/components/grid-items.svelte
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<script>
|
||||||
|
import { contextMenu } from '../stores';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
|
export let items = [];
|
||||||
|
export let columns = [];
|
||||||
|
export let key = '';
|
||||||
|
export let activeKey = '';
|
||||||
|
export let activeChildKey = '';
|
||||||
|
export let level = 0;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
let childrenOpen = {};
|
||||||
|
|
||||||
|
$: _items = objectToArray(items).map(item => {
|
||||||
|
item.children = objectToArray(item.children);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
function objectToArray(obj) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
else if (typeof obj === 'object') {
|
||||||
|
return Object.entries(obj).map(([ k, item ]) => ({ ...item, [key]: k }));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(itemKey) {
|
||||||
|
activeKey = itemKey;
|
||||||
|
activeChildKey = '';
|
||||||
|
dispatch('select', itemKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAll() {
|
||||||
|
childrenOpen = {};
|
||||||
|
dispatch('closeAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectChild(itemKey, childKey) {
|
||||||
|
select(itemKey);
|
||||||
|
activeChildKey = childKey;
|
||||||
|
dispatch('selectChild', childKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleChildren(itemKey, shift) {
|
||||||
|
childrenOpen[itemKey] = !childrenOpen[itemKey];
|
||||||
|
if (shift) {
|
||||||
|
closeAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doubleClick(itemKey) {
|
||||||
|
toggleChildren(itemKey, false);
|
||||||
|
dispatch('trigger', itemKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showContextMenu(evt, item) {
|
||||||
|
select(item[key]);
|
||||||
|
contextMenu.show(evt, item.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return '[...]';
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return '{...}';
|
||||||
|
}
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
if (String(value).length <= 1000) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return String(value).slice(0, 1000) + '…';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each _items as item (item[key])}
|
||||||
|
<tr
|
||||||
|
on:click={() => select(item[key])}
|
||||||
|
on:dblclick={() => doubleClick(item[key])}
|
||||||
|
on:contextmenu|preventDefault={evt => showContextMenu(evt, item)}
|
||||||
|
class:selected={activeKey === item[key] && !activeChildKey}
|
||||||
|
>
|
||||||
|
<td class="has-toggle">
|
||||||
|
{#if item.children?.length}
|
||||||
|
<button
|
||||||
|
class="toggle"
|
||||||
|
on:click={evt => toggleChildren(item[key], evt.shiftKey)}
|
||||||
|
style:transform="translateX({level * 10}px)"
|
||||||
|
>
|
||||||
|
<Icon name={childrenOpen[item[key]] ? 'chev-d' : 'chev-r'} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{#each columns as column, columnIndex}
|
||||||
|
{@const value = item[column.key]}
|
||||||
|
<td class:right={column.right} title={value}>
|
||||||
|
<div class="value" style:margin-left="{level * 10}px">
|
||||||
|
{formatValue(value)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{#if item.children && childrenOpen[item[key]]}
|
||||||
|
<svelte:self
|
||||||
|
{columns}
|
||||||
|
{key}
|
||||||
|
bind:activeKey={activeChildKey}
|
||||||
|
showHeaders={false}
|
||||||
|
items={item.children}
|
||||||
|
level={level + 1}
|
||||||
|
on:select={e => selectChild(item[key], e.detail)}
|
||||||
|
on:closeAll={closeAll}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td {
|
||||||
|
padding: 0.3rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
td.has-toggle {
|
||||||
|
width: calc(20px + 0.3rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
td .value {
|
||||||
|
height: 2.1ex;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr.selected td {
|
||||||
|
background-color: #00008b;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.toggle {
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
button.toggle :global(svg) {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { contextMenu } from '../stores';
|
import GridItems from './grid-items.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let columns = [];
|
export let columns = [];
|
||||||
@ -11,79 +10,6 @@
|
|||||||
export let activeChildKey = '';
|
export let activeChildKey = '';
|
||||||
export let showHeaders = true;
|
export let showHeaders = true;
|
||||||
export let level = 0;
|
export let level = 0;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let childrenOpen = {};
|
|
||||||
|
|
||||||
$: _items = objectToArray(items).map(item => {
|
|
||||||
item.children = objectToArray(item.children);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
function objectToArray(obj) {
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
else if (typeof obj === 'object') {
|
|
||||||
return Object.entries(obj).map(([ k, item ]) => ({ ...item, [key]: k }));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function select(itemKey) {
|
|
||||||
activeKey = itemKey;
|
|
||||||
activeChildKey = '';
|
|
||||||
dispatch('select', itemKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectChild(itemKey, childKey) {
|
|
||||||
select(itemKey);
|
|
||||||
activeChildKey = childKey;
|
|
||||||
dispatch('selectChild', childKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleChildren(itemKey, shift) {
|
|
||||||
childrenOpen[itemKey] = !childrenOpen[itemKey];
|
|
||||||
if (shift) {
|
|
||||||
closeAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doubleClick(itemKey) {
|
|
||||||
toggleChildren(itemKey, false);
|
|
||||||
dispatch('trigger', itemKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showContextMenu(evt, item) {
|
|
||||||
select(item[key]);
|
|
||||||
contextMenu.show(evt, item.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatValue(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return '[...]';
|
|
||||||
}
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return '{...}';
|
|
||||||
}
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
if (String(value).length <= 1000) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return String(value).slice(0, 1000) + '…';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeAll() {
|
|
||||||
childrenOpen = {};
|
|
||||||
dispatch('closeAll');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class:grid={level === 0} class:subgrid={level > 0}>
|
<div class:grid={level === 0} class:subgrid={level > 0}>
|
||||||
@ -99,13 +25,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<colgroup>
|
|
||||||
<col style:width="calc(15px + 0.6rem)" />
|
|
||||||
{#each columns as column}
|
|
||||||
<col />
|
|
||||||
{/each}
|
|
||||||
</colgroup>
|
|
||||||
|
|
||||||
{#if showHeaders && columns.some(col => col.title)}
|
{#if showHeaders && columns.some(col => col.title)}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -118,47 +37,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each _items as item (item[key])}
|
<GridItems {items} {columns} {key} bind:activeKey bind:activeChildKey on:select on:selectChild on:trigger />
|
||||||
<tr
|
|
||||||
on:click={() => select(item[key])}
|
|
||||||
on:dblclick={() => doubleClick(item[key])}
|
|
||||||
on:contextmenu|preventDefault={evt => showContextMenu(evt, item)}
|
|
||||||
class:selected={activeKey === item[key] && !activeChildKey}
|
|
||||||
>
|
|
||||||
<td class="has-toggle">
|
|
||||||
{#if item.children?.length}
|
|
||||||
<button class="toggle" on:click={evt => toggleChildren(item[key], evt.shiftKey)}>
|
|
||||||
<Icon name={childrenOpen[item[key]] ? 'chev-d' : 'chev-r'} />
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{#each columns as column}
|
|
||||||
{@const value = item[column.key]}
|
|
||||||
<td class:right={column.right} title={value}>
|
|
||||||
<div class="value">{formatValue(value)}</div>
|
|
||||||
</td>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{#if item.children && childrenOpen[item[key]]}
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td colspan={columns.length + 1} class="subgrid-parent">
|
|
||||||
<svelte:self
|
|
||||||
{columns}
|
|
||||||
{key}
|
|
||||||
bind:activeKey={activeChildKey}
|
|
||||||
showHeaders={false}
|
|
||||||
items={item.children}
|
|
||||||
level={level + 1}
|
|
||||||
on:select={e => selectChild(item[key], e.detail)}
|
|
||||||
on:closeAll={closeAll}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -201,43 +80,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
th {
|
||||||
padding: 0.3rem;
|
padding: 0.3rem;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
td.has-toggle {
|
|
||||||
width: calc(20px + 0.3rem);
|
|
||||||
}
|
|
||||||
td.subgrid-parent {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td .value {
|
|
||||||
height: 2.1ex;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr.selected td {
|
|
||||||
background-color: #00008b;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toggle {
|
|
||||||
color: inherit;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
button.toggle :global(svg) {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user