mirror of
https://github.com/garraflavatra/rolens.git
synced 2024-12-01 13:20:54 +00:00
A lot of improvements
This commit is contained in:
parent
9811235be7
commit
6703950734
@ -2,6 +2,7 @@
|
|||||||
import { contextMenu } from '../stores';
|
import { contextMenu } from '../stores';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
|
import { resolveKeypath } from '../utils';
|
||||||
|
|
||||||
export let items = [];
|
export let items = [];
|
||||||
export let columns = [];
|
export let columns = [];
|
||||||
@ -30,7 +31,7 @@
|
|||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
else if (typeof obj === 'object') {
|
else if ((typeof obj === 'object') && (obj !== null)) {
|
||||||
return Object.entries(obj).map(([ k, item ]) => ({ ...item, [key]: k }));
|
return Object.entries(obj).map(([ k, item ]) => ({ ...item, [key]: k }));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -81,10 +82,10 @@
|
|||||||
if ((value === undefined) || (value === null)) {
|
if ((value === undefined) || (value === null)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (new Date(value).toString() !== 'Invalid Date') {
|
// if (new Date(value).toString() !== 'Invalid Date') {
|
||||||
return new Date(value);
|
// return new Date(value);
|
||||||
}
|
// }
|
||||||
if (typeof value === 'object') {
|
if ((typeof value === 'object') && (value !== null)) {
|
||||||
return hideObjectIndicators ? '' : '{...}';
|
return hideObjectIndicators ? '' : '{...}';
|
||||||
}
|
}
|
||||||
if (String(value).length <= 1000) {
|
if (String(value).length <= 1000) {
|
||||||
@ -115,7 +116,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{#each columns as column, columnIndex}
|
{#each columns as column, columnIndex}
|
||||||
{@const value = item[column.key]}
|
{@const value = column.key?.includes('.') ? resolveKeypath(item, column.key) : item[column.key]}
|
||||||
<td class:right={column.right} title={value}>
|
<td class:right={column.right} title={value}>
|
||||||
<div class="value" style:margin-left="{level * 10}px">
|
<div class="value" style:margin-left="{level * 10}px">
|
||||||
{formatValue(value)}
|
{formatValue(value)}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
export let activePath = [];
|
export let activePath = [];
|
||||||
export let striped = true;
|
export let striped = true;
|
||||||
export let hideObjectIndicators = false;
|
export let hideObjectIndicators = false;
|
||||||
|
export let showHeaders = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@ -24,6 +25,17 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
{#if showHeaders && columns.some(col => col.title)}
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="has-toggle"></th>
|
||||||
|
{#each columns as column}
|
||||||
|
<th scope="col">{column.title || ''}</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<GridItems
|
<GridItems
|
||||||
{items}
|
{items}
|
||||||
@ -60,4 +72,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
border-bottom: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
8
frontend/src/components/icon.svelte
vendored
8
frontend/src/components/icon.svelte
vendored
@ -11,6 +11,12 @@
|
|||||||
<polyline points="9 18 15 12 9 6"></polyline>
|
<polyline points="9 18 15 12 9 6"></polyline>
|
||||||
{:else if name === 'chev-d'}
|
{:else if name === 'chev-d'}
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
{:else if name === 'chev-u'}
|
||||||
|
<path d="m18 15-6-6-6 6"/>
|
||||||
|
{:else if name === 'chevs-l'}
|
||||||
|
<path d="m11 17-5-5 5-5M18 17l-5-5 5-5"/>
|
||||||
|
{:else if name === 'chevs-r'}
|
||||||
|
<path d="m13 17 5-5-5-5M6 17l5-5-5-5"/>
|
||||||
{:else if name === 'db'}
|
{:else if name === 'db'}
|
||||||
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
||||||
{:else if name === 'x'}
|
{:else if name === 'x'}
|
||||||
@ -33,5 +39,7 @@
|
|||||||
<path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"/>
|
<path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"/>
|
||||||
{:else if name === 'cog'}
|
{:else if name === 'cog'}
|
||||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||||
|
{:else if name === 'zap'}
|
||||||
|
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||||
{/if}
|
{/if}
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -42,10 +42,10 @@
|
|||||||
}
|
}
|
||||||
return 'integer';
|
return 'integer';
|
||||||
}
|
}
|
||||||
else if (new Date(value).toString() !== 'Invalid Date') {
|
// else if (new Date(value).toString() !== 'Invalid Date') {
|
||||||
return 'date';
|
// return 'date';
|
||||||
}
|
// }
|
||||||
else if (typeof value === 'object') {
|
else if ((typeof value === 'object') && (value !== null)) {
|
||||||
const keys = Object.keys(value);
|
const keys = Object.keys(value);
|
||||||
return `object (${keys.length} item${keys.length === 1 ? '' : 's'})`;
|
return `object (${keys.length} item${keys.length === 1 ? '' : 's'})`;
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,19 @@
|
|||||||
|
|
||||||
let copySucceeded = false;
|
let copySucceeded = false;
|
||||||
let timeout;
|
let timeout;
|
||||||
|
let _data;
|
||||||
|
|
||||||
|
$: if (data) {
|
||||||
|
_data = JSON.parse(JSON.stringify(data));
|
||||||
|
for (const key of Object.keys(_data)) {
|
||||||
|
if (typeof _data[key] === 'undefined') {
|
||||||
|
delete _data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function copy() {
|
async function copy() {
|
||||||
await navigator.clipboard.writeText(JSON.stringify(data));
|
await navigator.clipboard.writeText(JSON.stringify(_data));
|
||||||
copySucceeded = true;
|
copySucceeded = true;
|
||||||
timeout = setTimeout(() => copySucceeded = false, 1500);
|
timeout = setTimeout(() => copySucceeded = false, 1500);
|
||||||
}
|
}
|
||||||
@ -19,7 +29,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data}
|
{#if data}
|
||||||
<Modal bind:show={data} title="Object viewer" contentPadding={false}>
|
<Modal bind:show={data} title="Object viewer">
|
||||||
<div class="objectviewer">
|
<div class="objectviewer">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="btn" on:click={copy}>
|
<button class="btn" on:click={copy}>
|
||||||
@ -27,7 +37,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="code">
|
<div class="code">
|
||||||
<ObjectTree {data} />
|
<ObjectTree data={_data} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -37,14 +47,10 @@
|
|||||||
.objectviewer {
|
.objectviewer {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.code {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.buttons {
|
.buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: 1rem;
|
|
||||||
}
|
}
|
||||||
.buttons button {
|
.buttons button {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import TabBar from '../../../components/tabbar.svelte';
|
import TabBar from '../../../components/tabbar.svelte';
|
||||||
import Modal from '../../../components/modal.svelte';
|
import Modal from '../../../components/modal.svelte';
|
||||||
|
import Icon from '../../../components/icon.svelte';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let activeView = 'list';
|
export let activeView = 'list';
|
||||||
@ -8,15 +9,55 @@
|
|||||||
hideObjectIndicators: false,
|
hideObjectIndicators: false,
|
||||||
columns: [],
|
columns: [],
|
||||||
};
|
};
|
||||||
|
export let firstItem = {};
|
||||||
|
|
||||||
let activeTab = activeView || 'list';
|
let activeTab = activeView || 'list';
|
||||||
|
|
||||||
|
$: activeView && (activeTab = activeView);
|
||||||
|
$: if (!config.columns || (config.columns.length === 0)) {
|
||||||
|
config.columns = [ { key: '_id' } ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addColumn(before) {
|
||||||
|
if (typeof before === 'number') {
|
||||||
|
config.columns = [
|
||||||
|
...config.columns.slice(0, before),
|
||||||
|
{},
|
||||||
|
...config.columns.slice(before),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config.columns = [ ...config.columns, {} ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSuggestedColumns() {
|
||||||
|
if ((typeof firstItem !== 'object') || (firstItem === null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config.columns = Object.keys(firstItem).map(key => ({ key }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveColumn(oldIndex, delta) {
|
||||||
|
const column = config.columns[oldIndex];
|
||||||
|
const newIndex = oldIndex + delta;
|
||||||
|
|
||||||
|
config.columns.splice(oldIndex, 1);
|
||||||
|
config.columns.splice(newIndex, 0, column);
|
||||||
|
config.columns = config.columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeColumn(index) {
|
||||||
|
config.columns.splice(index, 1);
|
||||||
|
config.columns = config.columns;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="View configuration" bind:show contentPadding={false}>
|
<Modal title="View configuration" bind:show contentPadding={false}>
|
||||||
<TabBar
|
<TabBar
|
||||||
tabs={[
|
tabs={[
|
||||||
{ key: 'list', title: 'List view' },
|
{ key: 'list', title: 'List view' },
|
||||||
{ key: 'table', title: 'Table view' },
|
{ key: 'table', title: 'Table view columns' },
|
||||||
]}
|
]}
|
||||||
bind:selectedKey={activeTab}
|
bind:selectedKey={activeTab}
|
||||||
/>
|
/>
|
||||||
@ -30,11 +71,31 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{:else if activeTab === 'table'}
|
{:else if activeTab === 'table'}
|
||||||
<input
|
{#each config.columns as column, columnIndex}
|
||||||
type="text"
|
<div class="column">
|
||||||
value={config.columns?.map(c => c.key).join(', ') || ''}
|
<label class="field">
|
||||||
on:input={e => config.columns = e.currentTarget.value?.split(',').map(k => ({ key: k.trim() })) || ''}
|
<input type="text" bind:value={column.key} placeholder="Column keypath" />
|
||||||
/>
|
</label>
|
||||||
|
<button class="btn" type="button" on:click={() => addColumn(columnIndex)} title="Add column before this one">
|
||||||
|
<Icon name="+" />
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
||||||
|
<Icon name="chev-u" />
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === config.columns.length - 1} title="Move column one position down">
|
||||||
|
<Icon name="chev-d" />
|
||||||
|
</button>
|
||||||
|
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
||||||
|
<Icon name="x" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<button class="btn" on:click={addColumn}>
|
||||||
|
<Icon name="+" /> Add column
|
||||||
|
</button>
|
||||||
|
<button class="btn" on:click={addSuggestedColumns} disabled={!firstItem}>
|
||||||
|
<Icon name="zap" /> Add suggested columns
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -48,7 +109,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
.flex + .flex {
|
|
||||||
margin-top: 1rem;
|
.column {
|
||||||
|
display: grid;
|
||||||
|
grid-template: 1fr / 1fr repeat(4, auto);
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import ObjectViewer from '../../../components/objectviewer.svelte';
|
import ObjectViewer from '../../../components/objectviewer.svelte';
|
||||||
import FindViewConfigModal from './find-viewconfig.svelte';
|
import FindViewConfigModal from './find-viewconfig.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import Grid from '../../../components/grid.svelte';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -28,6 +29,8 @@
|
|||||||
let viewConfigModalOpen = false;
|
let viewConfigModalOpen = false;
|
||||||
let viewConfig = {};
|
let viewConfig = {};
|
||||||
$: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
$: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
||||||
|
$: lastPage = (submittedForm.limit && result?.results?.length) ? Math.max(0, Math.ceil((result.total - submittedForm.limit) / submittedForm.limit)) : 0;
|
||||||
|
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
||||||
|
|
||||||
$: collection && refresh();
|
$: collection && refresh();
|
||||||
$: updateConfig(viewConfig);
|
$: updateConfig(viewConfig);
|
||||||
@ -43,6 +46,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateConfig(viewConfig) {
|
||||||
|
try {
|
||||||
|
const hosts = await Hosts();
|
||||||
|
hosts[collection.hostKey].databases = hosts[collection.hostKey].databases || {};
|
||||||
|
hosts[collection.hostKey].databases[collection.dbKey] = hosts[collection.hostKey].databases[collection.dbKey] || {};
|
||||||
|
hosts[collection.hostKey].databases[collection.dbKey].collections = hosts[collection.hostKey].databases[collection.dbKey].collections || {};
|
||||||
|
hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key] = hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key] || {};
|
||||||
|
hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key].viewConfig = viewConfig;
|
||||||
|
await UpdateHost(collection.hostKey, JSON.stringify(hosts[collection.hostKey]));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitQuery() {
|
async function submitQuery() {
|
||||||
activePath = [];
|
activePath = [];
|
||||||
result = await FindItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
result = await FindItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
||||||
@ -57,22 +75,6 @@
|
|||||||
await submitQuery();
|
await submitQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateConfig(viewConfig) {
|
|
||||||
try {
|
|
||||||
const hosts = await Hosts();
|
|
||||||
hosts[collection.hostKey].databases = hosts[collection.hostKey].databases || {};
|
|
||||||
hosts[collection.hostKey].databases[collection.dbKey] = hosts[collection.hostKey].databases[collection.dbKey] || {};
|
|
||||||
hosts[collection.hostKey].databases[collection.dbKey].collections = hosts[collection.hostKey].databases[collection.dbKey].collections || {};
|
|
||||||
hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key] = hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key] || {};
|
|
||||||
hosts[collection.hostKey].databases[collection.dbKey].collections[collection.key].viewConfig = viewConfig;
|
|
||||||
await UpdateHost(collection.hostKey, JSON.stringify(hosts[collection.hostKey]));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
console.log(viewConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
function prev() {
|
function prev() {
|
||||||
form.skip -= form.limit;
|
form.skip -= form.limit;
|
||||||
if (form.skip < 0) {
|
if (form.skip < 0) {
|
||||||
@ -86,6 +88,16 @@
|
|||||||
submitQuery();
|
submitQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function first() {
|
||||||
|
form.skip = 0;
|
||||||
|
submitQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
function last() {
|
||||||
|
form.skip = lastPage * submittedForm.limit;
|
||||||
|
submitQuery();
|
||||||
|
}
|
||||||
|
|
||||||
async function removeActive() {
|
async function removeActive() {
|
||||||
if (!activePath[0]) {
|
if (!activePath[0]) {
|
||||||
return;
|
return;
|
||||||
@ -140,12 +152,12 @@
|
|||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Skip</span>
|
<span class="label">Skip</span>
|
||||||
<input type="number" min="0" bind:value={form.skip} use:input placeholder={defaults.skip} />
|
<input type="number" min="0" bind:value={form.skip} use:input placeholder={defaults.skip} list="skipstops" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Limit</span>
|
<span class="label">Limit</span>
|
||||||
<input type="number" min="0" bind:value={form.limit} use:input placeholder={defaults.limit} />
|
<input type="number" min="0" bind:value={form.limit} use:input placeholder={defaults.limit} list="limits" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button type="submit" class="btn">Run</button>
|
<button type="submit" class="btn">Run</button>
|
||||||
@ -157,12 +169,23 @@
|
|||||||
<div class="result">
|
<div class="result">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#key result}
|
{#key result}
|
||||||
<ObjectGrid
|
{#if view === 'table'}
|
||||||
data={result.results}
|
<Grid
|
||||||
hideObjectIndicators={viewConfig?.hideObjectIndicators}
|
key="_id"
|
||||||
bind:activePath
|
columns={viewConfig.columns?.map(c => ({ key: c.key, title: c.key })) || []}
|
||||||
on:trigger={e => openJson(e.detail?.itemKey)}
|
showHeaders={true}
|
||||||
/>
|
items={result.results || []}
|
||||||
|
bind:activePath
|
||||||
|
on:trigger={e => openJson(e.detail?.itemKey)}
|
||||||
|
/>
|
||||||
|
{:else if view === 'list'}
|
||||||
|
<ObjectGrid
|
||||||
|
data={result.results}
|
||||||
|
hideObjectIndicators={viewConfig?.hideObjectIndicators}
|
||||||
|
bind:activePath
|
||||||
|
on:trigger={e => openJson(e.detail?.itemKey)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -177,24 +200,44 @@
|
|||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={toggleView} title="Toggle view">
|
<button class="btn" on:click={toggleView} title="Toggle view">
|
||||||
<Icon name={view} />
|
<Icon name={view === 'table' ? 'list' : 'table'} />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" on:click={removeActive} disabled={!activePath?.length} title="Drop selected item">
|
<button class="btn danger" on:click={removeActive} disabled={!activePath?.length} title="Drop selected item">
|
||||||
<Icon name="-" />
|
<Icon name="-" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={prev} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results?.length} title="Previous {form.limit} items">
|
<button class="btn" on:click={first} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)} title="First page">
|
||||||
|
<Icon name="chevs-l" />
|
||||||
|
</button>
|
||||||
|
<button class="btn" on:click={prev} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)} title="Previous {submittedForm.limit} items">
|
||||||
<Icon name="chev-l" />
|
<Icon name="chev-l" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={next} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results?.length} title="Next {form.limit} items">
|
<button class="btn" on:click={next} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results || !lastPage || (activePage >= lastPage)} title="Next {submittedForm.limit} items">
|
||||||
<Icon name="chev-r" />
|
<Icon name="chev-r" />
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn" on:click={last} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results || !lastPage || (activePage >= lastPage)} title="Last page">
|
||||||
|
<Icon name="chevs-r" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ObjectViewer bind:data={objectViewerData} />
|
<ObjectViewer bind:data={objectViewerData} />
|
||||||
<FindViewConfigModal bind:show={viewConfigModalOpen} activeView={view} bind:config={viewConfig} />
|
<FindViewConfigModal bind:show={viewConfigModalOpen} activeView={view} bind:config={viewConfig} firstItem={result.results?.[0]} />
|
||||||
|
|
||||||
|
<datalist id="limits">
|
||||||
|
{#each [ 1, 5, 10, 25, 50, 100, 200 ] as value}
|
||||||
|
<option {value} />
|
||||||
|
{/each}
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
{#if submittedForm?.limit}
|
||||||
|
<datalist id="skipstops">
|
||||||
|
{#each Array(lastPage).fill('').map((_, i) => i * submittedForm.limit) as value}
|
||||||
|
<option {value} />
|
||||||
|
{/each}
|
||||||
|
</datalist>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.find {
|
.find {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import ObjectViewer from '../../../components/objectviewer.svelte';
|
import ObjectViewer from '../../../components/objectviewer.svelte';
|
||||||
import ObjectGrid from '../../../components/objectgrid.svelte';
|
import ObjectGrid from '../../../components/objectgrid.svelte';
|
||||||
import { DropIndex, GetIndexes } from '../../../../wailsjs/go/app/App';
|
import { DropIndex, GetIndexes } from '../../../../wailsjs/go/app/App';
|
||||||
|
import Icon from '../../../components/icon.svelte';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -9,6 +10,8 @@
|
|||||||
let activePath = [];
|
let activePath = [];
|
||||||
let objectViewerData = '';
|
let objectViewerData = '';
|
||||||
|
|
||||||
|
$: collection && getIndexes();
|
||||||
|
|
||||||
async function getIndexes() {
|
async function getIndexes() {
|
||||||
const result = await GetIndexes(collection.hostKey, collection.dbKey, collection.key);
|
const result = await GetIndexes(collection.hostKey, collection.dbKey, collection.key);
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -35,11 +38,15 @@
|
|||||||
|
|
||||||
<div class="indexes">
|
<div class="indexes">
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" on:click={getIndexes}>Get indexes</button>
|
<button class="btn" on:click={getIndexes}>
|
||||||
<button class="btn danger" on:click={drop} disabled={!indexes?.length || !activePath[0]}>
|
<Icon name="reload" /> Reload
|
||||||
Drop selected
|
</button>
|
||||||
|
<button class="btn">
|
||||||
|
<Icon name="+" /> Create index…
|
||||||
|
</button>
|
||||||
|
<button class="btn danger" on:click={drop} disabled={!indexes?.length || !activePath[0]}>
|
||||||
|
<Icon name="x" /> Drop selected
|
||||||
</button>
|
</button>
|
||||||
<button class="btn">Create…</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { input } from '../../../actions';
|
import { input } from '../../../actions';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { InsertItems } from '../../../../wailsjs/go/app/App';
|
import { InsertItems } from '../../../../wailsjs/go/app/App';
|
||||||
|
import Icon from '../../../components/icon.svelte';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -44,7 +45,9 @@
|
|||||||
{#if insertedIds}
|
{#if insertedIds}
|
||||||
<button class="btn" type="button" on:click={showDocs}>View inserted docs</button>
|
<button class="btn" type="button" on:click={showDocs}>View inserted docs</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button type="submit" class="btn" disabled={!json}>Insert</button>
|
<button type="submit" class="btn" disabled={!json}>
|
||||||
|
<Icon name="+" /> Insert
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { input } from '../../../actions';
|
import { input } from '../../../actions';
|
||||||
import { RemoveItems } from '../../../../wailsjs/go/app/App';
|
import { RemoveItems } from '../../../../wailsjs/go/app/App';
|
||||||
import CodeExample from '../../../components/code-example.svelte';
|
import CodeExample from '../../../components/code-example.svelte';
|
||||||
|
import Icon from '../../../components/icon.svelte';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -45,7 +46,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn">Remove</button>
|
<button type="submit" class="btn danger">
|
||||||
|
<Icon name="-" /> Remove
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -114,7 +114,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<button class="btn" type="submit">Update</button>
|
<button class="btn" type="submit">
|
||||||
|
<Icon name="check" /> Update
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
|
@ -21,7 +21,7 @@ time, mark, audio, video {
|
|||||||
border: 0;
|
border: 0;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
vertical-align: baseline;
|
/* vertical-align: baseline; */
|
||||||
}
|
}
|
||||||
/* HTML5 display-role reset for older browsers */
|
/* HTML5 display-role reset for older browsers */
|
||||||
article, aside, details, figcaption, figure,
|
article, aside, details, figcaption, figure,
|
||||||
|
@ -63,6 +63,11 @@ p strong {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.field svg {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
.field > :first-child {
|
.field > :first-child {
|
||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
border-bottom-left-radius: 10px;
|
border-bottom-left-radius: 10px;
|
||||||
|
21
frontend/src/utils.js
Normal file
21
frontend/src/utils.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export function resolveKeypath(object, path) {
|
||||||
|
// Get a value from an object with a JSON path, from Webdesq core
|
||||||
|
|
||||||
|
const parts = path.split('.').flatMap(part => {
|
||||||
|
const indexMatch = part.match(/\[\d+\]/g);
|
||||||
|
if (indexMatch) {
|
||||||
|
// Convert strings to numbers
|
||||||
|
const indexes = indexMatch.map(index => Number(index.slice(1, -1)));
|
||||||
|
const base = part.slice(0, part.indexOf(indexMatch[0]));
|
||||||
|
return base.length ? [ base, ...indexes ] : indexes;
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = object;
|
||||||
|
while (result && parts.length) {
|
||||||
|
result = result[parts.shift()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user