mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-19 14:14:05 +00:00
Layout changes. Edit collection names.
This commit is contained in:
87
frontend/src/organisms/connection/hostdetail.svelte
Normal file
87
frontend/src/organisms/connection/hostdetail.svelte
Normal file
@ -0,0 +1,87 @@
|
||||
<script>
|
||||
import { input } from '../../actions';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { AddHost, UpdateHost } from '../../../wailsjs/go/app/App';
|
||||
import Modal from '../../components/modal.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let hostKey = '';
|
||||
export let hosts = {};
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let form = {};
|
||||
let error = '';
|
||||
$: valid = validate(form);
|
||||
$: host = hosts[hostKey];
|
||||
|
||||
$: if (show || !show) {
|
||||
init();
|
||||
}
|
||||
|
||||
function init() {
|
||||
form = { ...(host || {}) };
|
||||
}
|
||||
|
||||
function validate(form) {
|
||||
return form.name && form.uri && true;
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (host && hostKey) {
|
||||
await UpdateHost(hostKey, JSON.stringify(form));
|
||||
}
|
||||
else {
|
||||
await AddHost(JSON.stringify(form));
|
||||
}
|
||||
show = false;
|
||||
dispatch('reload');
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show title={host ? `Edit ${host.name}` : 'Create a new host'}>
|
||||
<form on:submit|preventDefault={submit}>
|
||||
<label class="field">
|
||||
<span class="label">Label</span>
|
||||
<input type="text" placeholder="mywebsite.com MongoDB" bind:value={form.name} use:input={{ autofocus: true }} />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="label">Connection string</span>
|
||||
<input type="text" placeholder="mongodb://..." bind:value={form.uri} spellcheck="false" use:input />
|
||||
</label>
|
||||
|
||||
<div class="result">
|
||||
<div>
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="btn" disabled={!valid} type="submit">
|
||||
{host ? 'Save' : 'Create'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@ -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);
|
||||
}
|
||||
}}
|
||||
/>
|
@ -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>
|
||||
|
60
frontend/src/organisms/connection/welcome.svelte
Normal file
60
frontend/src/organisms/connection/welcome.svelte
Normal file
@ -0,0 +1,60 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function createHost() {
|
||||
dispatch('createHost');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="welcome">
|
||||
<div class="brand">
|
||||
<img src="/logo.png" alt="" class="logo" />
|
||||
<div class="text">
|
||||
<div class="name">Welcome to Rolens!</div>
|
||||
<div class="subtitle">A modest MongoDB client</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" on:click={createHost}>Create your first host</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.welcome {
|
||||
/* transform: translateY(-80px); */
|
||||
margin-top: -90px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
}
|
||||
.brand .logo {
|
||||
height: 200px;
|
||||
}
|
||||
.brand .text {
|
||||
align-self: flex-end;
|
||||
margin: 0 0 4rem 1rem;
|
||||
}
|
||||
.brand .text .name {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.brand .text .subtitle {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user