mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-01-18 21:17:59 +00:00
Custom views!
This commit is contained in:
parent
29e93dcab1
commit
fa924f087d
@ -40,6 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function select(itemKey) {
|
function select(itemKey) {
|
||||||
|
if (activeKey !== itemKey) {
|
||||||
activeKey = itemKey;
|
activeKey = itemKey;
|
||||||
if (level === 0) {
|
if (level === 0) {
|
||||||
activePath = [ itemKey ];
|
activePath = [ itemKey ];
|
||||||
@ -49,6 +50,7 @@
|
|||||||
}
|
}
|
||||||
dispatch('select', { level, itemKey });
|
dispatch('select', { level, itemKey });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeAll() {
|
function closeAll() {
|
||||||
childrenOpen = {};
|
childrenOpen = {};
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let tabs = [];
|
export let tabs = [];
|
||||||
export let selectedKey = {};
|
export let selectedKey = {};
|
||||||
|
export let canAddTab = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@ -15,24 +17,52 @@
|
|||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
{#each tabs as tab (tab.key)}
|
{#each tabs as tab (tab.key)}
|
||||||
<li class="tab" class:active={tab.key === selectedKey}>
|
<li class:active={tab.key === selectedKey}>
|
||||||
<button on:click={() => select(tab.key)}>{tab.title}</button>
|
<button class="tab" on:click={() => select(tab.key)}>{tab.title}</button>
|
||||||
|
{#if tab.closable}
|
||||||
|
<button class="close" on:click={() => dispatch('closeTab', tab.key)}>
|
||||||
|
<Icon name="x" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if canAddTab}
|
||||||
|
<li class="tab add">
|
||||||
|
<button class="tab" on:click={() => dispatch('addTab')}>
|
||||||
|
<Icon name="+" />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tabs ul {
|
ul {
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
.tabs li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.tabs li button {
|
|
||||||
|
li.add {
|
||||||
|
flex: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs :global(svg) {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
li.active :global(svg) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.tab {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.7rem 1rem;
|
padding: 0.7rem 1rem;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
@ -40,13 +70,33 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
.tabs li:last-child button {
|
button.tab:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
button.tab:active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
li:last-child button.tab {
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
.tabs li.active button {
|
li.active button.tab {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #00008b;
|
background-color: #00008b;
|
||||||
border-color: #00008b;
|
border-color: #00008b;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.close {
|
||||||
|
position: absolute;
|
||||||
|
right: 7px;
|
||||||
|
top: 7px;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
button.close:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
button.close:active {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,32 +2,69 @@
|
|||||||
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';
|
import Icon from '../../../components/icon.svelte';
|
||||||
|
import { views } from '../../../stores';
|
||||||
|
import { randomString } from '../../../utils';
|
||||||
|
import { input } from '../../../actions';
|
||||||
|
|
||||||
|
export let collection;
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let activeView = 'list';
|
export let activeViewKey = 'list';
|
||||||
export let config = {
|
|
||||||
hideObjectIndicators: false,
|
|
||||||
columns: [],
|
|
||||||
};
|
|
||||||
export let firstItem = {};
|
export let firstItem = {};
|
||||||
|
|
||||||
let activeTab = activeView || 'list';
|
$: tabs = Object.entries($views).filter(v => (
|
||||||
|
v[0] === 'list' || (
|
||||||
|
v[1].host === collection.hostKey &&
|
||||||
|
v[1].database === collection.dbKey &&
|
||||||
|
v[1].collection === collection.key
|
||||||
|
)
|
||||||
|
)).sort((a, b) => sortTabKeys(a[0], b[0]))
|
||||||
|
.map(([ key, v ]) => ({ key, title: v.name, closable: key !== 'list' }));
|
||||||
|
|
||||||
$: activeView && (activeTab = activeView);
|
function sortTabKeys(a, b) {
|
||||||
$: if (!config.columns || (config.columns.length === 0)) {
|
if (a === 'list') {
|
||||||
config.columns = [ { key: '_id' } ];
|
return -1;
|
||||||
|
}
|
||||||
|
if (b === 'list') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return a.localeCompare(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createView() {
|
||||||
|
const newViewKey = randomString();
|
||||||
|
$views[newViewKey] = {
|
||||||
|
name: 'Table view',
|
||||||
|
host: collection.hostKey,
|
||||||
|
database: collection.dbKey,
|
||||||
|
collection: collection.key,
|
||||||
|
type: 'table',
|
||||||
|
columns: [ { key: '_id' } ],
|
||||||
|
};
|
||||||
|
activeViewKey = newViewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeView(viewKey) {
|
||||||
|
const keys = Object.keys($views).sort(sortTabKeys);
|
||||||
|
const oldIndex = keys.indexOf(viewKey);
|
||||||
|
const newKey = keys[oldIndex - 1];
|
||||||
|
console.log(keys, oldIndex, newKey);
|
||||||
|
activeViewKey = newKey;
|
||||||
|
delete $views[viewKey];
|
||||||
|
$views = $views;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addColumn(before) {
|
function addColumn(before) {
|
||||||
if (typeof before === 'number') {
|
if (typeof before === 'number') {
|
||||||
config.columns = [
|
$views[activeViewKey].columns = [
|
||||||
...config.columns.slice(0, before),
|
...$views[activeViewKey].columns.slice(0, before),
|
||||||
{},
|
{},
|
||||||
...config.columns.slice(before),
|
...$views[activeViewKey].columns.slice(before),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
config.columns = [ ...config.columns, {} ];
|
$views[activeViewKey].columns = [ ...$views[activeViewKey].columns, {} ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,46 +72,63 @@
|
|||||||
if ((typeof firstItem !== 'object') || (firstItem === null)) {
|
if ((typeof firstItem !== 'object') || (firstItem === null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config.columns = Object.keys(firstItem).map(key => ({ key }));
|
$views[activeViewKey].columns = Object.keys(firstItem).map(key => ({ key }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveColumn(oldIndex, delta) {
|
function moveColumn(oldIndex, delta) {
|
||||||
const column = config.columns[oldIndex];
|
const column = $views[activeViewKey].columns[oldIndex];
|
||||||
const newIndex = oldIndex + delta;
|
const newIndex = oldIndex + delta;
|
||||||
|
|
||||||
config.columns.splice(oldIndex, 1);
|
$views[activeViewKey].columns.splice(oldIndex, 1);
|
||||||
config.columns.splice(newIndex, 0, column);
|
$views[activeViewKey].columns.splice(newIndex, 0, column);
|
||||||
config.columns = config.columns;
|
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeColumn(index) {
|
function removeColumn(index) {
|
||||||
config.columns.splice(index, 1);
|
$views[activeViewKey].columns.splice(index, 1);
|
||||||
config.columns = config.columns;
|
$views[activeViewKey].columns = $views[activeViewKey].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' },
|
canAddTab={true}
|
||||||
{ key: 'table', title: 'Table view columns' },
|
on:addTab={createView}
|
||||||
]}
|
on:closeTab={e => removeView(e.detail)}
|
||||||
bind:selectedKey={activeTab}
|
bind:selectedKey={activeViewKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{#if activeTab === 'list'}
|
{#if $views[activeViewKey]}
|
||||||
|
<div class="meta">
|
||||||
|
{#key activeViewKey}
|
||||||
|
<label class="field">
|
||||||
|
<span class="label">View name</span>
|
||||||
|
<input type="text" use:input={{ autofocus: true }} bind:value={$views[activeViewKey].name} disabled={activeViewKey === 'list'} />
|
||||||
|
</label>
|
||||||
|
{/key}
|
||||||
|
<label class="field">
|
||||||
|
<span class="label">View type</span>
|
||||||
|
<select bind:value={$views[activeViewKey].type} disabled={activeViewKey === 'list'}>
|
||||||
|
<option value="list">List view</option>
|
||||||
|
<option value="table">Table view</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $views[activeViewKey].type === 'list'}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input type="checkbox" id="hideObjectIndicators" bind:checked={config.hideObjectIndicators} />
|
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[activeViewKey].hideObjectIndicators} />
|
||||||
<label for="hideObjectIndicators">
|
<label for="hideObjectIndicators">
|
||||||
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{:else if activeTab === 'table'}
|
{:else if $views[activeViewKey].type === 'table'}
|
||||||
{#each config.columns as column, columnIndex}
|
{#each $views[activeViewKey].columns as column, columnIndex}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<input type="text" bind:value={column.key} placeholder="Column keypath" />
|
<input type="text" use:input bind:value={column.key} placeholder="Column keypath" />
|
||||||
</label>
|
</label>
|
||||||
<button class="btn" type="button" on:click={() => addColumn(columnIndex)} title="Add column before this one">
|
<button class="btn" type="button" on:click={() => addColumn(columnIndex)} title="Add column before this one">
|
||||||
<Icon name="+" />
|
<Icon name="+" />
|
||||||
@ -82,7 +136,7 @@
|
|||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
||||||
<Icon name="chev-u" />
|
<Icon name="chev-u" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === config.columns.length - 1} title="Move column one position down">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[activeViewKey].columns.length - 1} title="Move column one position down">
|
||||||
<Icon name="chev-d" />
|
<Icon name="chev-d" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
||||||
@ -97,6 +151,7 @@
|
|||||||
<Icon name="zap" /> Add suggested columns
|
<Icon name="zap" /> Add suggested columns
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
@ -105,9 +160,17 @@
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template: 1fr / 1fr 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { FindItems, Hosts, RemoveItemById, UpdateHost } from '../../../../wailsjs/go/app/App';
|
import { FindItems, RemoveItemById } from '../../../../wailsjs/go/app/App';
|
||||||
import CodeExample from '../../../components/code-example.svelte';
|
import CodeExample from '../../../components/code-example.svelte';
|
||||||
import { input } from '../../../actions';
|
import { input } from '../../../actions';
|
||||||
import ObjectGrid from '../../../components/objectgrid.svelte';
|
import ObjectGrid from '../../../components/objectgrid.svelte';
|
||||||
@ -8,7 +8,7 @@
|
|||||||
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';
|
import Grid from '../../../components/grid.svelte';
|
||||||
import { applicationSettings } from '../../../stores';
|
import { applicationSettings, views } from '../../../stores';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -21,46 +21,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let form = { ...defaults };
|
let form = { ...defaults };
|
||||||
let view = 'list';
|
let activeViewKey = 'list';
|
||||||
let result = {};
|
let result = {};
|
||||||
let submittedForm = {};
|
let submittedForm = {};
|
||||||
let queryField;
|
let queryField;
|
||||||
let activePath = [];
|
let activePath = [];
|
||||||
let objectViewerData;
|
let objectViewerData;
|
||||||
let viewConfigModalOpen = false;
|
let viewConfigModalOpen = false;
|
||||||
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;
|
$: 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;
|
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
||||||
|
|
||||||
$: collection && refresh();
|
$: collection && refresh();
|
||||||
$: updateConfig(viewConfig);
|
|
||||||
|
|
||||||
async function getViewConfig() {
|
|
||||||
try {
|
|
||||||
const hosts = await Hosts();
|
|
||||||
viewConfig = hosts?.[collection.hostKey]?.databases?.[collection.dbKey]?.collections?.[collection.key]?.viewConfig || {};
|
|
||||||
console.log(hosts, viewConfig);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = [];
|
||||||
@ -72,7 +44,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
await getViewConfig();
|
|
||||||
if ($applicationSettings.autosubmitQuery) {
|
if ($applicationSettings.autosubmitQuery) {
|
||||||
await submitQuery();
|
await submitQuery();
|
||||||
}
|
}
|
||||||
@ -121,10 +92,6 @@
|
|||||||
objectViewerData = item;
|
objectViewerData = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleView() {
|
|
||||||
view = view === 'table' ? 'list' : 'table';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function performQuery(q) {
|
export function performQuery(q) {
|
||||||
form = { ...defaults, ...q };
|
form = { ...defaults, ...q };
|
||||||
submitQuery();
|
submitQuery();
|
||||||
@ -172,19 +139,19 @@
|
|||||||
<div class="result">
|
<div class="result">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#key result}
|
{#key result}
|
||||||
{#if view === 'table'}
|
{#if activeViewKey === 'table'}
|
||||||
<Grid
|
<Grid
|
||||||
key="_id"
|
key="_id"
|
||||||
columns={viewConfig.columns?.map(c => ({ key: c.key, title: c.key })) || []}
|
columns={$views[activeViewKey]?.columns?.map(c => ({ key: c.key, title: c.key })) || []}
|
||||||
showHeaders={true}
|
showHeaders={true}
|
||||||
items={result.results || []}
|
items={result.results || []}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
on:trigger={e => openJson(e.detail?.itemKey)}
|
on:trigger={e => openJson(e.detail?.itemKey)}
|
||||||
/>
|
/>
|
||||||
{:else if view === 'list'}
|
{:else if activeViewKey === 'list'}
|
||||||
<ObjectGrid
|
<ObjectGrid
|
||||||
data={result.results}
|
data={result.results}
|
||||||
hideObjectIndicators={viewConfig?.hideObjectIndicators}
|
hideObjectIndicators={$views[activeViewKey]?.hideObjectIndicators}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
on:trigger={e => openJson(e.detail?.itemKey)}
|
on:trigger={e => openJson(e.detail?.itemKey)}
|
||||||
/>
|
/>
|
||||||
@ -202,9 +169,6 @@
|
|||||||
<button class="btn" on:click={() => viewConfigModalOpen = true} title="Configure view">
|
<button class="btn" on:click={() => viewConfigModalOpen = true} title="Configure view">
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={toggleView} title="Toggle view">
|
|
||||||
<Icon name={view === 'table' ? 'list' : 'table'} />
|
|
||||||
</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>
|
||||||
@ -226,7 +190,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ObjectViewer bind:data={objectViewerData} />
|
<ObjectViewer bind:data={objectViewerData} />
|
||||||
<FindViewConfigModal bind:show={viewConfigModalOpen} activeView={view} bind:config={viewConfig} firstItem={result.results?.[0]} />
|
<FindViewConfigModal
|
||||||
|
bind:show={viewConfigModalOpen}
|
||||||
|
bind:activeViewKey
|
||||||
|
firstItem={result.results?.[0]}
|
||||||
|
{collection}
|
||||||
|
/>
|
||||||
|
|
||||||
<datalist id="limits">
|
<datalist id="limits">
|
||||||
{#each [ 1, 5, 10, 25, 50, 100, 200 ] as value}
|
{#each [ 1, 5, 10, 25, 50, 100, 200 ] as value}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { Environment } from '../wailsjs/runtime/runtime';
|
import { Environment } from '../wailsjs/runtime/runtime';
|
||||||
import { Settings, UpdateSettings } from '../wailsjs/go/app/App';
|
import { Settings, UpdateSettings, UpdateViewStore, Views } from '../wailsjs/go/app/App';
|
||||||
|
|
||||||
export const busy = (() => {
|
export const busy = (() => {
|
||||||
const { update, subscribe } = writable(0);
|
const { update, subscribe } = writable(0);
|
||||||
@ -63,3 +63,19 @@ export const environment = (() => {
|
|||||||
reload();
|
reload();
|
||||||
return { reload, subscribe };
|
return { reload, subscribe };
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
export const views = (() => {
|
||||||
|
const { set, subscribe } = writable({});
|
||||||
|
const reload = async() => {
|
||||||
|
const newViewStore = await Views();
|
||||||
|
set(newViewStore);
|
||||||
|
return newViewStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
reload();
|
||||||
|
subscribe(newViewStore => {
|
||||||
|
UpdateViewStore(JSON.stringify(newViewStore));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { reload, set, subscribe };
|
||||||
|
})();
|
||||||
|
@ -44,6 +44,13 @@ p strong {
|
|||||||
cursor: wait;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:disabled,
|
||||||
|
select:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 1;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: flex;
|
display: flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -33,3 +33,18 @@ export function controlKeyDown(event) {
|
|||||||
return event?.ctrlKey;
|
return event?.ctrlKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function randInt(min, max) {
|
||||||
|
return Math.round(Math.random() * (max - min) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomString(length = 12) {
|
||||||
|
const chars = 'qwertyuiopasdfghjklzxcvbnm1234567890';
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
Array(length).fill('').forEach(() => {
|
||||||
|
output += chars[randInt(0, chars.length - 1)];
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
6
frontend/wailsjs/go/app/App.d.ts
vendored
6
frontend/wailsjs/go/app/App.d.ts
vendored
@ -37,6 +37,8 @@ export function RemoveItemById(arg1:string,arg2:string,arg3:string,arg4:string):
|
|||||||
|
|
||||||
export function RemoveItems(arg1:string,arg2:string,arg3:string,arg4:string,arg5:boolean):Promise<number>;
|
export function RemoveItems(arg1:string,arg2:string,arg3:string,arg4:string,arg5:boolean):Promise<number>;
|
||||||
|
|
||||||
|
export function RemoveView(arg1:string):Promise<void>;
|
||||||
|
|
||||||
export function RenameCollection(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
|
export function RenameCollection(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
|
||||||
|
|
||||||
export function Settings():Promise<app.Settings>;
|
export function Settings():Promise<app.Settings>;
|
||||||
@ -48,3 +50,7 @@ export function UpdateHost(arg1:string,arg2:string):Promise<void>;
|
|||||||
export function UpdateItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<number>;
|
export function UpdateItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<number>;
|
||||||
|
|
||||||
export function UpdateSettings(arg1:string):Promise<app.Settings>;
|
export function UpdateSettings(arg1:string):Promise<app.Settings>;
|
||||||
|
|
||||||
|
export function UpdateViewStore(arg1:string):Promise<void>;
|
||||||
|
|
||||||
|
export function Views():Promise<app.ViewStore>;
|
||||||
|
@ -66,6 +66,10 @@ export function RemoveItems(arg1, arg2, arg3, arg4, arg5) {
|
|||||||
return window['go']['app']['App']['RemoveItems'](arg1, arg2, arg3, arg4, arg5);
|
return window['go']['app']['App']['RemoveItems'](arg1, arg2, arg3, arg4, arg5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RemoveView(arg1) {
|
||||||
|
return window['go']['app']['App']['RemoveView'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function RenameCollection(arg1, arg2, arg3, arg4) {
|
export function RenameCollection(arg1, arg2, arg3, arg4) {
|
||||||
return window['go']['app']['App']['RenameCollection'](arg1, arg2, arg3, arg4);
|
return window['go']['app']['App']['RenameCollection'](arg1, arg2, arg3, arg4);
|
||||||
}
|
}
|
||||||
@ -89,3 +93,11 @@ export function UpdateItems(arg1, arg2, arg3, arg4) {
|
|||||||
export function UpdateSettings(arg1) {
|
export function UpdateSettings(arg1) {
|
||||||
return window['go']['app']['App']['UpdateSettings'](arg1);
|
return window['go']['app']['App']['UpdateSettings'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UpdateViewStore(arg1) {
|
||||||
|
return window['go']['app']['App']['UpdateViewStore'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Views() {
|
||||||
|
return window['go']['app']['App']['Views']();
|
||||||
|
}
|
||||||
|
182
internal/app/collection_find_views.go
Normal file
182
internal/app/collection_find_views.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TableView ViewType = "table"
|
||||||
|
ListView ViewType = "list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewColumn struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Width int64 `json:"width"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type View struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Database string `json:"database"`
|
||||||
|
Collection string `json:"collection"`
|
||||||
|
Type ViewType `json:"type"`
|
||||||
|
HideObjectIndicators bool `json:"hideObjectIndicators"`
|
||||||
|
Columns []ViewColumn `json:"columns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var BuiltInListView = View{
|
||||||
|
Name: "List",
|
||||||
|
Type: ListView,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ViewStore map[string]View
|
||||||
|
|
||||||
|
func updateViewStore(newData ViewStore) error {
|
||||||
|
filePath, err := appDataFilePath("views.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.MarshalIndent(newData, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filePath, jsonData, os.ModePerm)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Views() (ViewStore, error) {
|
||||||
|
views := make(ViewStore, 0)
|
||||||
|
filePath, err := appDataFilePath("views.json")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return views, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
// It's ok if the file cannot be opened, for example if it is not accessible.
|
||||||
|
// Therefore no error is returned.
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return views, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(jsonData) > 0 {
|
||||||
|
err = json.Unmarshal(jsonData, &views)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return nil, errors.New("views.json file contains malformatted JSON data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views["list"] = BuiltInListView
|
||||||
|
return views, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (a *App) AddView(jsonData string) error {
|
||||||
|
// views, err := a.Views()
|
||||||
|
// if err != nil {
|
||||||
|
// runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
// Type: runtime.InfoDialog,
|
||||||
|
// Title: "Could not retrieve views",
|
||||||
|
// })
|
||||||
|
// return errors.New("could not retrieve existing view store")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var newView View
|
||||||
|
// err = json.Unmarshal([]byte(jsonData), &newView)
|
||||||
|
// if err != nil {
|
||||||
|
// runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
// Type: runtime.InfoDialog,
|
||||||
|
// Title: "Malformed JSON",
|
||||||
|
// })
|
||||||
|
// return errors.New("invalid JSON")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// id, err := uuid.NewRandom()
|
||||||
|
// if err != nil {
|
||||||
|
// runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
// Type: runtime.InfoDialog,
|
||||||
|
// Title: "Failed to generate a UUID",
|
||||||
|
// })
|
||||||
|
// return errors.New("could not generate a UUID")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// views[id.String()] = newView
|
||||||
|
// err = updateViewStore(views)
|
||||||
|
// if err != nil {
|
||||||
|
// runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
// Type: runtime.InfoDialog,
|
||||||
|
// Title: "Could not update view store",
|
||||||
|
// })
|
||||||
|
// return errors.New("could not update view store")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (a *App) UpdateViewStore(jsonData string) error {
|
||||||
|
var viewStore ViewStore
|
||||||
|
err := json.Unmarshal([]byte(jsonData), &viewStore)
|
||||||
|
if err != nil {
|
||||||
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.InfoDialog,
|
||||||
|
Title: "Malformed JSON",
|
||||||
|
})
|
||||||
|
return errors.New("invalid JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateViewStore(viewStore)
|
||||||
|
if err != nil {
|
||||||
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.InfoDialog,
|
||||||
|
Title: "Could not update view store",
|
||||||
|
})
|
||||||
|
return errors.New("could not update view store")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RemoveView(viewKey string) error {
|
||||||
|
views, err := a.Views()
|
||||||
|
if err != nil {
|
||||||
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.InfoDialog,
|
||||||
|
Title: "Could not retrieve views",
|
||||||
|
})
|
||||||
|
return errors.New("could not retrieve existing view store")
|
||||||
|
}
|
||||||
|
|
||||||
|
sure, _ := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Title: "Confirm",
|
||||||
|
Message: "Are you sure you want to remove " + views[viewKey].Name + "?",
|
||||||
|
Buttons: []string{"Yes", "No"},
|
||||||
|
DefaultButton: "Yes",
|
||||||
|
CancelButton: "No",
|
||||||
|
})
|
||||||
|
if sure != "Yes" {
|
||||||
|
return errors.New("operation aborted")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(views, viewKey)
|
||||||
|
err = updateViewStore(views)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.InfoDialog,
|
||||||
|
Title: "Could not update view store",
|
||||||
|
})
|
||||||
|
return errors.New("could not update view store")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,17 +14,6 @@ import (
|
|||||||
type Host struct {
|
type Host struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
Databases map[string]struct {
|
|
||||||
Collections map[string]struct {
|
|
||||||
ViewConfig struct {
|
|
||||||
HideObjectIndicators bool `json:"hideObjectIndicators"`
|
|
||||||
Columns []struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Width int64 `json:"width"`
|
|
||||||
} `json:"columns"`
|
|
||||||
} `json:"viewConfig"`
|
|
||||||
} `json:"collections"`
|
|
||||||
} `json:"databases"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHostsFile(newData map[string]Host) error {
|
func updateHostsFile(newData map[string]Host) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user