1
0
mirror of https://github.com/garraflavatra/rolens.git synced 2025-09-15 21:02:52 +00:00

Layout changes. Edit collection names.

This commit is contained in:
2023-01-22 21:12:56 +01:00
parent e83a02fae8
commit 4d50d43f4d
19 changed files with 265 additions and 373 deletions

View File

@ -1,148 +0,0 @@
<script>
import Modal from '../../components/modal.svelte';
import { createEventDispatcher, onMount } from 'svelte';
import Icon from '../../components/icon.svelte';
import { Hosts, RemoveHost } from '../../../wailsjs/go/app/App';
import Welcome from './welcome.svelte';
import HostDetail from './hostdetail.svelte';
export let hosts = {};
export let activeHostKey = '';
export let modalOpen = false;
const dispatch = createEventDispatcher();
let error = '';
let hostDetailModalOpen = false;
let hostDetailModalHost;
let hostDetailModalKey = '';
$: host = hosts?.[activeHostKey];
$: hostCount = Object.keys(hosts).length;
$: if (!modalOpen) {
error = '';
}
function select(hostKey) {
activeHostKey = hostKey;
dispatch('select', hostKey);
}
async function getHosts() {
try {
const h = await Hosts();
hosts = h || {};
}
catch (e) {
error = e;
}
}
function createHost() {
hostDetailModalHost = undefined;
hostDetailModalKey = '';
hostDetailModalOpen = true;
}
function editHost(hostKey) {
hostDetailModalHost = hosts[hostKey];
hostDetailModalKey = hostKey;
hostDetailModalOpen = true;
}
async function removeHost(hostKey) {
try {
await RemoveHost(hostKey);
await getHosts();
}
catch (e) {
error = e;
}
}
onMount(getHosts);
</script>
<Modal bind:show={modalOpen} title={hostCount && 'Hosts'} width="60vw" overflow={false}>
{#if hostCount}
<div class="status">
<p class:error>
{#if error}
<strong>Oops!</strong> {error}
{:else}
{hostCount} host{hostCount === 1 ? '' : 's'}
{/if}
</p>
<button class="btn" on:click={createHost}>
Create new host
</button>
</div>
<ul class="hosts">
{#each Object.entries(hosts) as [hostKey, host]}
<li>
<div class="host">
<button class="btn secondary" title="Connect to {host.name}" on:click={() => select(hostKey)}>
{host.name}
</button>
<button class="btn secondary" title="Edit {host.name}" on:click={() => editHost(hostKey)}>
<Icon name="edit" />
</button>
<button class="btn secondary" title="Remove {host.name}" on:click={() => removeHost(hostKey)}>
<Icon name="x" />
</button>
</div>
</li>
{/each}
</ul>
{:else}
<Welcome on:createHost={createHost} />
{/if}
</Modal>
<HostDetail
host={hostDetailModalHost}
hostKey={hostDetailModalKey}
on:reload={getHosts}
bind:show={hostDetailModalOpen}
/>
<style>
.hosts {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
}
.status {
display: flex;
justify-content: space-between;
align-items: center;
}
.error {
color: #c00;
}
.host {
display: grid;
grid-template: 1fr 1fr / 1fr auto;
align-items: center;
width: 100%;
height: 100%;
}
.host button {
border-radius: 0;
border-left: 1px solid #ccc;
}
.host button:nth-child(1) {
border-right: none;
grid-row: 1 / 3;
height: 100%;
border-radius: 10px 0 0 10px;
}
.host button:nth-child(2) {
border-top-right-radius: 10px;
border-bottom: none;
}
.host button:nth-child(3) {
border-bottom-right-radius: 10px;
}
</style>

View File

@ -1,49 +0,0 @@
<script>
import Icon from '../../components/icon.svelte';
import HostModal from './hostmodal.svelte';
export let hosts = {};
export let activeHostKey = '';
export let modalOpen = false;
$: host = hosts?.[activeHostKey];
</script>
<div class="addressbar">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="address" on:click={() => modalOpen = true}>
{#if host?.uri}
{@const split = host.uri.split('://').map(s => s.split('/')).flat()}
<span class="protocol">{split[0]}://</span><span class="hostname">{split[1]}</span><span class="path">{split.slice(2).join('/')}</span>
{:else}
<span class="empty">no host selected</span>
{/if}
</div>
<div class="actions">
<button class="btn" on:click={() => modalOpen = true}>
<Icon name="db" />
</button>
</div>
</div>
<HostModal bind:modalOpen bind:hosts on:select />
<style>
.addressbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.5rem 0.5rem 1rem;
height: 3rem;
border: 1px solid #ccc;
border-radius: 10px;
background-color: #fff;
}
.address .protocol,
.address .path,
.address .empty {
opacity: 0.6;
}
</style>

View File

@ -5,13 +5,14 @@
import Modal from '../../components/modal.svelte';
export let show = false;
export let host = undefined;
export let hostKey = '';
export let hosts = {};
const dispatch = createEventDispatcher();
let form = { ...(host || {}) };
let form = {};
let error = '';
$: valid = validate(form);
$: host = hosts[hostKey];
$: if (show || !show) {
init();

View File

@ -3,7 +3,7 @@
import { createEventDispatcher } from 'svelte';
import { DropCollection, DropDatabase, OpenCollection, OpenConnection, OpenDatabase } from '../../../wailsjs/go/app/App';
import Grid from '../../components/grid.svelte';
import { WindowSetTitle } from '../../../wailsjs/runtime';
import { WindowSetTitle } from '../../../wailsjs/runtime/runtime';
export let hosts = {};
export let activeHostKey = '';
@ -11,9 +11,10 @@
export let activeCollKey = '';
const dispatch = createEventDispatcher();
let dbAndCollKeys = [];
$: activeDbKey = dbAndCollKeys[0];
$: activeCollKey = dbAndCollKeys[1];
let activeGridPath = [];
$: activeHostKey = activeGridPath[0] || activeHostKey;
$: activeDbKey = activeGridPath[1];
$: activeCollKey = activeGridPath[2];
$: host = hosts[activeHostKey];
$: connection = $connections[activeHostKey];
$: database = connection?.databases[activeDbKey];
@ -74,67 +75,64 @@
await reload();
busy.end();
}
function buildMenu(hostKey, dbKey, collKey, context = [ 'new', 'edit', 'drop' ]) {
// context: n = new, d = drop
const menu = [];
if (context.includes('edit')) {
hostKey && menu.push({ label: `Edit host ${hosts[hostKey].name}`, fn: () => dispatch('editHost', hostKey) });
collKey && menu.push({ label: `Rename collection ${collKey}`, fn: () => dispatch('editCollection', collKey) });
}
if (context.includes('drop')) {
menu.length && menu.push({ separator: true });
dbKey && menu.push({ label: `Drop database ${dbKey}`, fn: () => dropDatabase(dbKey) });
collKey && menu.push({ label: `Drop collection ${collKey}`, fn: () => dropCollection(dbKey, collKey) });
}
if (context.includes('new')) {
menu.length && menu.push({ separator: true });
hostKey && menu.push({ label: 'New database…', fn: () => dispatch('newDatabase') });
dbKey && menu.push({ label: 'New collection…', fn: () => dispatch('newCollection') });
}
return menu;
}
</script>
{#if host && connection}
<Grid
striped={false}
columns={[ { key: 'id' }, { key: 'collCount', right: true } ]}
items={Object.keys(connection.databases).sort().map(dbKey => ({
<Grid
striped={false}
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
items={Object.keys(hosts).map(hostKey => ({
id: hostKey,
name: hosts[hostKey].name,
icon: 'server',
children: Object.keys(connection?.databases || {}).sort().map(dbKey => ({
id: dbKey,
name: dbKey,
icon: 'db',
collCount: Object.keys(connection.databases[dbKey].collections || {}).length || '',
count: Object.keys(connection.databases[dbKey].collections || {}).length || '',
children: Object.keys(connection.databases[dbKey].collections).sort().map(collKey => ({
id: collKey,
name: collKey,
icon: 'list',
menu: [
{ label: `Drop ${collKey}`, fn: () => dropCollection(dbKey, collKey) },
{ label: `Drop ${dbKey}`, fn: () => dropDatabase(dbKey) },
{ separator: true },
{ label: 'New database…', fn: () => dispatch('newDatabase') },
{ label: 'New collection…', fn: () => dispatch('newCollection') },
],
menu: buildMenu(hostKey, dbKey, collKey),
})) || [],
menu: [
{ label: `Drop ${dbKey}`, fn: () => dropDatabase(dbKey) },
{ separator: true },
{ label: 'New database…', fn: () => dispatch('newDatabase') },
{ label: 'New collection…', fn: () => dispatch('newCollection') },
],
}))}
actions={[
{ icon: 'reload', fn: reload },
{ icon: '+', fn: evt => {
if (activeDbKey) {
contextMenu.show(evt, [
{ label: 'New database…', fn: () => dispatch('newDatabase') },
{ label: 'New collection…', fn: () => dispatch('newCollection') },
]);
}
else {
dispatch('newDatabase');
}
} },
{ icon: '-', fn: evt => {
if (activeCollKey) {
contextMenu.show(evt, [
{ label: 'Drop database…', fn: () => dropDatabase(activeDbKey) },
{ label: 'Drop collection…', fn: () => dropCollection(activeDbKey, activeCollKey) },
]);
}
else {
dropDatabase(activeDbKey);
}
}, disabled: !activeDbKey },
]}
bind:activePath={dbAndCollKeys}
on:select={e => {
if (e.detail?.level === 0) {
openDatabase(e.detail.itemKey);
}
else if (e.detail?.level === 1) {
openCollection(e.detail.itemKey);
}
}}
/>
{/if}
menu: buildMenu(hostKey, dbKey),
})),
}))}
actions={[
{ icon: 'reload', fn: reload },
{ icon: '+', fn: evt => contextMenu.show(evt, buildMenu(activeHostKey, activeDbKey, activeCollKey, 'new')) },
{ icon: 'edit', fn: evt => contextMenu.show(evt, buildMenu(activeHostKey, activeDbKey, activeCollKey, 'edit')), disabled: !activeHostKey },
{ icon: '-', fn: evt => contextMenu.show(evt, buildMenu(activeHostKey, activeDbKey, activeCollKey, 'drop')), disabled: !activeDbKey },
]}
bind:activePath={activeGridPath}
on:select={e => {
const key = e.detail.itemKey;
switch (e.detail?.level) {
case 0: return openConnection(key);
case 1: return openDatabase(key);
case 2: return openCollection(key);
}
}}
/>

View File

@ -1,34 +1,66 @@
<script>
import { onMount, tick } from 'svelte';
import { Hosts } from '../../../wailsjs/go/app/App';
import { onMount } from 'svelte';
import { Hosts, RenameCollection } from '../../../wailsjs/go/app/App';
import { input } from '../../actions';
import Modal from '../../components/modal.svelte';
import DatabaseList from './dblist.svelte';
import HostTree from './hosttree.svelte';
import { busy, connections } from '../../stores';
import CollectionDetail from './collection/index.svelte';
import HostDetail from './hostdetail.svelte';
import Icon from '../../components/icon.svelte';
export let hosts = {};
export let activeHostKey = '';
export let activeDbKey = '';
export let activeCollKey = '';
let addressBarModalOpen = true;
let dbList;
let hostTree;
let newDb;
let newDbInput;
let newColl;
let newCollInput;
$: if (newDb) {
tick().then(() => newDbInput.focus());
let showHostDetail = false;
let hostDetailKey = '';
let collToRename = '';
let newCollKey = '';
async function getHosts() {
hosts = await Hosts();
}
function createHost() {
hostDetailKey = '';
showHostDetail = true;
}
function editHost(hostKey) {
hostDetailKey = hostKey;
showHostDetail = true;
}
async function createDatabase() {
busy.start();
$connections[activeHostKey].databases[newDb.name] = { collections: {} };
newDb = undefined;
await dbList.reload();
await hostTree.reload();
busy.end();
}
function openEditCollModal(collKey) {
newCollKey = collKey;
collToRename = collKey;
}
async function renameCollection() {
busy.start();
console.log(newCollKey);
const ok = await RenameCollection(activeHostKey, activeDbKey, collToRename, newCollKey);
if (ok) {
activeCollKey = newCollKey;
collToRename = '';
newCollKey = '';
await hostTree.reload();
}
busy.end();
}
@ -36,24 +68,24 @@
busy.start();
$connections[activeHostKey].databases[activeDbKey].collections[newColl.name] = {};
newColl = undefined;
await dbList.reload();
await hostTree.reload();
busy.end();
}
onMount(() => {
Hosts().then(h => hosts = h);
});
onMount(getHosts);
</script>
<DatabaseList
<HostTree
{hosts}
bind:activeHostKey
bind:activeCollKey
bind:activeDbKey
bind:this={dbList}
on:connected={() => addressBarModalOpen = false}
bind:this={hostTree}
on:newHost={createHost}
on:newDatabase={() => newDb = {}}
on:newCollection={() => newColl = {}}
on:editHost={e => editHost(e.detail)}
on:editCollection={e => openEditCollModal(e.detail)}
/>
<CollectionDetail
@ -63,13 +95,20 @@
collectionKey={activeCollKey}
/>
<HostDetail
bind:show={showHostDetail}
on:reload={getHosts}
hostKey={activeHostKey}
{hosts}
/>
{#if newDb}
<Modal bind:show={newDb}>
<p><strong>Create a database</strong></p>
<p>Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.</p>
<form on:submit|preventDefault={createDatabase}>
<label class="field">
<input type="text" spellcheck="false" bind:value={newDb.name} use:input placeholder="New collection name" bind:this={newDbInput} />
<input type="text" spellcheck="false" bind:value={newDb.name} use:input={{ autofocus: true }} placeholder="New collection name" />
</label>
<p class="modal-actions">
<button class="btn create" type="submit" disabled={!newDb.name?.trim()}>Create database</button>
@ -81,11 +120,11 @@
{#if newColl}
<Modal bind:show={newColl}>
<p><strong>Create a collections</strong></p>
<p><strong>Create a collection</strong></p>
<p>Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.</p>
<form on:submit|preventDefault={createCollection}>
<label class="field">
<input type="text" spellcheck="false" bind:value={newColl.name} use:input placeholder="New collection name" bind:this={newCollInput} />
<input type="text" spellcheck="false" bind:value={newColl.name} use:input={{ autofocus: true }} placeholder="New collection name" />
</label>
<p class="modal-actions">
<button class="btn create" type="submit" disabled={!newColl.name?.trim()}>Create collection</button>
@ -95,9 +134,45 @@
</Modal>
{/if}
{#if collToRename}
<Modal bind:show={collToRename} width="400px">
<form class="rename" on:submit|preventDefault={renameCollection}>
<div>Renaming collection <strong>{collToRename}</strong></div>
<Icon name="arr-d" />
<div class="inputs">
<button class="btn secondary" type="button" on:click={() => collToRename = ''}>Cancel</button>
<label class="field">
<input type="text" bind:value={newCollKey} use:input={{ autofocus: true }} spellcheck="false" />
</label>
<button class="btn" type="submit">Save</button>
</div>
</form>
</Modal>
{/if}
<style>
.modal-actions {
display: flex;
justify-content: space-between;
}
.rename {
text-align: center;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: center;
}
.rename input {
text-align: center;
}
.rename strong {
font-weight: 700;
}
.rename .inputs {
display: grid;
gap: 0.5rem;
grid-template: 1fr / auto 1fr auto;
width: 100%;
}
</style>