From 5f358885a3e67c31f681830496c9d6325e59044c Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Wed, 15 Feb 2023 17:00:53 +0100 Subject: [PATCH] Save queries --- frontend/src/components/grid-items.svelte | 11 +- frontend/src/components/grid.svelte | 2 + frontend/src/components/hint.svelte | 7 +- frontend/src/components/icon.svelte | 6 + frontend/src/lib/stores/queries.js | 45 ++++++ .../collection/components/querychooser.svelte | 134 ++++++++++++++++++ .../connection/collection/find.svelte | 53 ++++++- frontend/wailsjs/go/app/App.d.ts | 10 +- frontend/wailsjs/go/app/App.js | 16 +++ frontend/wailsjs/go/models.ts | 28 ++-- internal/app/collection_find.go | 23 +-- internal/app/collection_find_queries.go | 121 ++++++++++++++++ 12 files changed, 419 insertions(+), 37 deletions(-) create mode 100644 frontend/src/lib/stores/queries.js create mode 100644 frontend/src/organisms/connection/collection/components/querychooser.svelte create mode 100644 internal/app/collection_find_queries.go diff --git a/frontend/src/components/grid-items.svelte b/frontend/src/components/grid-items.svelte index bac58f8..bfe1c7c 100644 --- a/frontend/src/components/grid-items.svelte +++ b/frontend/src/components/grid-items.svelte @@ -96,9 +96,12 @@ contextMenu.show(evt, item.menu); } - function removeItem(index) { - items.splice(index, 1); - items = items; + function removeItem(index, itemKey) { + if (Array.isArray(items)) { + items.splice(index, 1); + items = items; + } + dispatch('removeItem', itemKey); } function formatValue(value) { @@ -167,7 +170,7 @@ {#if canRemoveItems} - diff --git a/frontend/src/components/grid.svelte b/frontend/src/components/grid.svelte index 71c53a1..a582675 100644 --- a/frontend/src/components/grid.svelte +++ b/frontend/src/components/grid.svelte @@ -62,6 +62,7 @@ bind:inputsValid on:select on:trigger + on:removeItem /> @@ -87,6 +88,7 @@ border-collapse: collapse; width: 100%; background-color: #fff; + table-layout: fixed; } table thead { diff --git a/frontend/src/components/hint.svelte b/frontend/src/components/hint.svelte index ffeebf5..9371ef7 100644 --- a/frontend/src/components/hint.svelte +++ b/frontend/src/components/hint.svelte @@ -9,7 +9,7 @@ diff --git a/frontend/src/components/icon.svelte b/frontend/src/components/icon.svelte index 85b869e..2511e6b 100644 --- a/frontend/src/components/icon.svelte +++ b/frontend/src/components/icon.svelte @@ -81,5 +81,11 @@ {:else if name === 'info'} + {:else if name === 'play'} + + {:else if name === 'upload'} + + {:else if name === 'save'} + {/if} diff --git a/frontend/src/lib/stores/queries.js b/frontend/src/lib/stores/queries.js new file mode 100644 index 0000000..6bc983c --- /dev/null +++ b/frontend/src/lib/stores/queries.js @@ -0,0 +1,45 @@ +import { get, writable } from 'svelte/store'; +import { RemoveQuery, SavedQueries, SaveQuery, UpdateQueries } from '../../../wailsjs/go/app/App'; + +const { set, subscribe } = writable({}); +let skipUpdate = true; + +async function reload() { + const newViewStore = await SavedQueries(); + set(newViewStore); + return newViewStore; +} + +function forCollection(hostKey, dbKey, collKey) { + const allViews = get({ subscribe }); + const entries = Object.entries(allViews).filter(v => ( + v[1].hostKey === hostKey && + v[1].dbKey === dbKey && + v[1].collKey === collKey + )); + + return Object.fromEntries(entries); +} + +async function create(query) { + const newId = await SaveQuery(JSON.stringify(query)); + await reload(); + return newId; +} + +async function remove(id) { + await RemoveQuery(id); + await reload(); +} + +reload(); +subscribe(newViewStore => { + if (skipUpdate) { + skipUpdate = false; + return; + } + UpdateQueries(JSON.stringify(newViewStore)); +}); + +const queries = { reload, create, remove, set, subscribe, forCollection }; +export default queries; diff --git a/frontend/src/organisms/connection/collection/components/querychooser.svelte b/frontend/src/organisms/connection/collection/components/querychooser.svelte new file mode 100644 index 0000000..88c2558 --- /dev/null +++ b/frontend/src/organisms/connection/collection/components/querychooser.svelte @@ -0,0 +1,134 @@ + + + +
+ {#if queryToSave} + + + {/if} + +
+ +
+ + {#if queryToSave} + + + {#if Object.keys($queries).includes(queryToSave.name)} + + You are about to overwrite a saved query. Rename it + if you do not want to overwrite. + + {/if} + {:else} + + {/if} +
+
+ + diff --git a/frontend/src/organisms/connection/collection/find.svelte b/frontend/src/organisms/connection/collection/find.svelte index c8ba1f7..1436af5 100644 --- a/frontend/src/organisms/connection/collection/find.svelte +++ b/frontend/src/organisms/connection/collection/find.svelte @@ -9,6 +9,8 @@ import Icon from '../../../components/icon.svelte'; import ObjectGrid from '../../../components/objectgrid.svelte'; import views from '../../../lib/stores/views'; + import QueryChooser from './components/querychooser.svelte'; + import queries from '../../../lib/stores/queries'; // import ObjectViewer from '../../../components/objectviewer.svelte'; export let collection; @@ -28,6 +30,9 @@ let queryField; let activePath = []; let objectViewerData; + let queryToSave; + let showQueryChooser = false; + $: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key); $: 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; @@ -50,6 +55,23 @@ } } + function loadQuery() { + queryToSave = undefined; + showQueryChooser = true; + } + + function saveQuery() { + queryToSave = form; + showQueryChooser = true; + } + + function queryChosen(event) { + if ($queries[event?.detail]) { + form = { ...$queries[event?.detail] }; + submitQuery(); + } + } + function prev() { form.skip -= form.limit; if (form.skip < 0) { @@ -131,13 +153,22 @@ Limit + - +
+ + + +
- -
{#key result} @@ -198,6 +229,12 @@
+ + @@ -218,19 +255,23 @@ .find { display: grid; gap: 0.5rem; - grid-template: auto auto 1fr / 1fr; + grid-template: auto 1fr / 1fr; } .form-row { display: grid; gap: 0.5rem; + margin-bottom: 0.5rem; } .form-row.one { grid-template: 1fr / 3fr 2fr; - margin-bottom: 0.5rem; } .form-row.two { - grid-template: 1fr / 5fr 1fr 1fr 1fr; + grid-template: 1fr / 5fr 1fr 1fr; + } + .form-row.three { + margin-bottom: 0rem; + grid-template: 1fr / 1fr repeat(3, auto); } .result { diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index c037ad0..19d5b9b 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -17,7 +17,7 @@ export function DropIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promi export function Environment():Promise; -export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise; export function GetIndexes(arg1:string,arg2:string,arg3:string):Promise>; @@ -43,10 +43,16 @@ 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; +export function RemoveQuery(arg1:string):Promise; + export function RemoveView(arg1:string):Promise; export function RenameCollection(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function SaveQuery(arg1:string):Promise; + +export function SavedQueries():Promise; + export function Settings():Promise; export function TruncateCollection(arg1:string,arg2:string,arg3:string):Promise; @@ -55,6 +61,8 @@ export function UpdateHost(arg1:string,arg2:string):Promise; export function UpdateItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function UpdateQueries(arg1:string):Promise; + export function UpdateSettings(arg1:string):Promise; export function UpdateViewStore(arg1:string):Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index 3bcd97c..8ba3108 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -78,6 +78,10 @@ export function RemoveItems(arg1, arg2, arg3, arg4, arg5) { return window['go']['app']['App']['RemoveItems'](arg1, arg2, arg3, arg4, arg5); } +export function RemoveQuery(arg1) { + return window['go']['app']['App']['RemoveQuery'](arg1); +} + export function RemoveView(arg1) { return window['go']['app']['App']['RemoveView'](arg1); } @@ -86,6 +90,14 @@ export function RenameCollection(arg1, arg2, arg3, arg4) { return window['go']['app']['App']['RenameCollection'](arg1, arg2, arg3, arg4); } +export function SaveQuery(arg1) { + return window['go']['app']['App']['SaveQuery'](arg1); +} + +export function SavedQueries() { + return window['go']['app']['App']['SavedQueries'](); +} + export function Settings() { return window['go']['app']['App']['Settings'](); } @@ -102,6 +114,10 @@ export function UpdateItems(arg1, arg2, arg3, arg4) { return window['go']['app']['App']['UpdateItems'](arg1, arg2, arg3, arg4); } +export function UpdateQueries(arg1) { + return window['go']['app']['App']['UpdateQueries'](arg1); +} + export function UpdateSettings(arg1) { return window['go']['app']['App']['UpdateSettings'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 98d34b4..12a250e 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -26,6 +26,20 @@ export namespace app { this.logDirectory = source["logDirectory"]; } } + export class QueryResult { + total: number; + results: string[]; + + static createFrom(source: any = {}) { + return new QueryResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.total = source["total"]; + this.results = source["results"]; + } + } export class Settings { defaultLimit: number; defaultSort: string; @@ -44,20 +58,6 @@ export namespace app { this.defaultExportDirectory = source["defaultExportDirectory"]; } } - export class findResult { - total: number; - results: string[]; - - static createFrom(source: any = {}) { - return new findResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.total = source["total"]; - this.results = source["results"]; - } - } } diff --git a/internal/app/collection_find.go b/internal/app/collection_find.go index 19c80c7..805b7a2 100644 --- a/internal/app/collection_find.go +++ b/internal/app/collection_find.go @@ -4,25 +4,26 @@ import ( "encoding/json" "github.com/wailsapp/wails/v2/pkg/runtime" - "go.mongodb.org/mongo-driver/bson" mongoOptions "go.mongodb.org/mongo-driver/mongo/options" ) -type findResult struct { +type Query struct { + Fields string `json:"fields"` + Limit int64 `json:"limit"` + Query string `json:"query"` + Skip int64 `json:"skip"` + Sort string `json:"sort"` +} + +type QueryResult struct { Total int64 `json:"total"` Results []string `json:"results"` } -func (a *App) FindItems(hostKey, dbKey, collKey string, formJson string) findResult { - var out findResult - var form struct { - Fields string `json:"fields"` - Limit int64 `json:"limit"` - Query string `json:"query"` - Skip int64 `json:"skip"` - Sort string `json:"sort"` - } +func (a *App) FindItems(hostKey, dbKey, collKey string, formJson string) QueryResult { + var out QueryResult + var form Query err := json.Unmarshal([]byte(formJson), &form) if err != nil { diff --git a/internal/app/collection_find_queries.go b/internal/app/collection_find_queries.go new file mode 100644 index 0000000..6526e7c --- /dev/null +++ b/internal/app/collection_find_queries.go @@ -0,0 +1,121 @@ +package app + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +type SavedQuery struct { + Query + Name string `json:"name"` + Remarks string `json:"remarks"` + HostKey string `json:"hostKey"` + DbKey string `json:"dbKey"` + CollKey string `json:"collKey"` +} + +func updateQueryFile(a *App, newData map[string]SavedQuery) error { + filePath := path.Join(a.Env.DataDirectory, "queries.json") + jsonData, err := json.MarshalIndent(newData, "", "\t") + if err != nil { + return err + } + + err = ioutil.WriteFile(filePath, jsonData, os.ModePerm) + return err +} + +func (a *App) SavedQueries() map[string]SavedQuery { + filePath := path.Join(a.Env.DataDirectory, "queries.json") + 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. + runtime.LogInfo(a.ctx, "Could not open queries.json") + runtime.LogInfo(a.ctx, err.Error()) + return make(map[string]SavedQuery, 0) + } + + if len(jsonData) == 0 { + return make(map[string]SavedQuery, 0) + } else { + var queries map[string]SavedQuery + err = json.Unmarshal(jsonData, &queries) + + if err != nil { + runtime.LogInfo(a.ctx, "queries.json file contains malformatted JSON data") + runtime.LogInfo(a.ctx, err.Error()) + return nil + } + + return queries + } +} + +func (a *App) SaveQuery(jsonData string) string { + var query SavedQuery + err := json.Unmarshal([]byte(jsonData), &query) + if err != nil { + runtime.LogError(a.ctx, "Add query: malformed form") + runtime.LogError(a.ctx, err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.InfoDialog, + Title: "Malformed JSON", + }) + return "" + } + + queries := a.SavedQueries() + queries[query.Name] = query + err = updateQueryFile(a, queries) + if err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.InfoDialog, + Title: "Could not update query list", + }) + return "" + } + + return query.Name +} + +func (a *App) RemoveQuery(queryName string) { + queries := a.SavedQueries() + delete(queries, queryName) + if err := updateQueryFile(a, queries); err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.InfoDialog, + Title: "Could not update query list", + }) + } +} + +func (a *App) UpdateQueries(jsonData string) bool { + var queries map[string]SavedQuery + err := json.Unmarshal([]byte(jsonData), &queries) + if err != nil { + runtime.LogError(a.ctx, "Update queries: malformed form") + runtime.LogError(a.ctx, err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.InfoDialog, + Title: "Malformed JSON", + }) + return false + } + + err = updateQueryFile(a, queries) + if err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.InfoDialog, + Title: "Could not save queries", + }) + return false + } + + return true +}