1
0
mirror of https://github.com/garraflavatra/rolens.git synced 2025-06-28 05:25:11 +00:00

69 Commits

Author SHA1 Message Date
4001614d49 Merge 01405b49ef into 21089009b4 2024-08-04 12:46:20 +02:00
21089009b4 Update Actions dependencies 2024-08-04 12:43:55 +02:00
1551a5073b Upgrade actions/upload-artifact to v4 2024-08-04 12:38:14 +02:00
e54a39bb3e Update CI platforms 2024-08-04 12:37:35 +02:00
949d609167 Update vulnerable dependencies 2024-08-04 12:23:55 +02:00
3ac85342bd Add favicons, compress logo 2024-06-25 15:10:35 +02:00
c14894fb31 Make sure WebP files are copied over to doc site 2024-06-25 15:05:17 +02:00
27ebbbc898 Replace PNG screenshots with WebP ones 2024-06-25 15:03:22 +02:00
9ce6a58afc It is 2024 (quite a while, actually) 2024-06-25 10:49:47 +02:00
ca5cdc203a Replaced hasMongoDump with hasMongoShell 2024-06-25 10:47:03 +02:00
01405b49ef Bump vite from 3.2.7 to 3.2.10 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 3.2.7 to 3.2.10.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v3.2.10/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v3.2.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 18:40:17 +00:00
132df17ff7 Updated readme on shell scripts 2023-12-23 14:45:17 +01:00
8a1f170121 Prepare v0.3.0 2023-12-23 14:20:12 +01:00
b04d0b324a Add modal transitions 2023-12-23 14:17:39 +01:00
4e68bf324c Correctly open settings & about dialogs 2023-12-23 14:10:48 +01:00
3a325fe064 Run CI using Node.js v18 2023-12-23 13:20:51 +01:00
9f9ba2de6a Updated ci_bundle script 2023-12-23 13:08:56 +01:00
42993b28e9 Generate DMG images for macOS instead of placing .app in tarball 2023-12-23 13:02:38 +01:00
6af1f4a028 Upgrade Go to 1.21 2023-12-23 12:28:27 +01:00
fca08479e8 Add startup animation 2023-12-23 12:05:46 +01:00
d90d5bcf63 Updated tab bar animation 2023-12-23 11:54:10 +01:00
421bde13f5 Upgrade Wails to 2.7.1 2023-12-23 11:51:35 +01:00
fd1340f9e4 Add nice loading indicator to the host tree 2023-12-22 17:05:32 +01:00
426b0b8468 Slightly modified icons in grid 2023-12-22 16:33:10 +01:00
990c1d3d00 Add padding for field inputs 2023-12-22 16:25:18 +01:00
79417fcaa3 Updated readme slightly 2023-12-22 16:04:13 +01:00
7687b094c8 Changed os.ModePerm (777) file permissions to 644 and 755 2023-12-22 16:00:54 +01:00
773bcf65fd Add option to save shell output 2023-12-22 15:56:00 +01:00
82b8f1e300 Remove path prefix for website 2023-12-22 15:36:12 +01:00
3a5bee4a81 Added CNAME for Rolens website 2023-12-22 15:29:08 +01:00
ef9318576a Removed Zenity dependency 2023-12-22 15:26:29 +01:00
208db82ba7 Update packages 2023-12-22 15:10:56 +01:00
ddb3fefae5 Bump golang.org/x/crypto from 0.1.0 to 0.17.0 (#67)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.1.0
to 0.17.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9d2ee975ef"><code>9d2ee97</code></a>
ssh: implement strict KEX protocol changes</li>
<li><a
href="4e5a26183e"><code>4e5a261</code></a>
ssh: close net.Conn on all NewServerConn errors</li>
<li><a
href="152cdb1503"><code>152cdb1</code></a>
x509roots/fallback: update bundle</li>
<li><a
href="fdfe1f8531"><code>fdfe1f8</code></a>
ssh: defer channel window adjustment</li>
<li><a
href="b8ffc16e10"><code>b8ffc16</code></a>
blake2b: drop Go 1.6, Go 1.8 compatibility</li>
<li><a
href="7e6fbd82c8"><code>7e6fbd8</code></a>
ssh: wrap errors from client handshake</li>
<li><a
href="bda2f3f5cf"><code>bda2f3f</code></a>
argon2: avoid clobbering BP</li>
<li><a
href="325b735346"><code>325b735</code></a>
ssh/test: skip TestSSHCLIAuth on Windows</li>
<li><a
href="1eadac50a5"><code>1eadac5</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="b2d7c26edb"><code>b2d7c26</code></a>
ssh: add (*Client).DialContext method</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/crypto/compare/v0.1.0...v0.17.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.1.0&new-version=0.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/garraflavatra/rolens/network/alerts).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Romein van Buren <romein@vburen.nl>
2023-12-22 15:04:44 +01:00
5b35172db9 Update screen shot 2023-12-22 14:38:20 +01:00
7f2adb2df2 Bump required Go version to 1.20 2023-12-22 14:15:46 +01:00
2040a356fd Finish sliding active tab indicator 2023-12-22 14:04:38 +01:00
9eac9ae935 Add sliding active tab indicator 2023-12-22 14:02:58 +01:00
d1b1c7daa5 UI improvements 2023-12-22 13:30:05 +01:00
db5a526e07 Bump postcss from 8.4.27 to 8.4.31 in /frontend (#64)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to
8.4.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.4.31</h2>
<ul>
<li>Fixed <code>\r</code> parsing to fix CVE-2023-44270.</li>
</ul>
<h2>8.4.30</h2>
<ul>
<li>Improved source map performance (by <a
href="https://github.com/romainmenke"><code>@​romainmenke</code></a>).</li>
</ul>
<h2>8.4.29</h2>
<ul>
<li>Fixed <code>Node#source.offset</code> (by <a
href="https://github.com/idoros"><code>@​idoros</code></a>).</li>
<li>Fixed docs (by <a
href="https://github.com/coliff"><code>@​coliff</code></a>).</li>
</ul>
<h2>8.4.28</h2>
<ul>
<li>Fixed <code>Root.source.end</code> for better source map (by <a
href="https://github.com/romainmenke"><code>@​romainmenke</code></a>).</li>
<li>Fixed <code>Result.root</code> types when <code>process()</code> has
no parser.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.4.31</h2>
<ul>
<li>Fixed <code>\r</code> parsing to fix CVE-2023-44270.</li>
</ul>
<h2>8.4.30</h2>
<ul>
<li>Improved source map performance (by Romain Menke).</li>
</ul>
<h2>8.4.29</h2>
<ul>
<li>Fixed <code>Node#source.offset</code> (by Ido Rosenthal).</li>
<li>Fixed docs (by Christian Oliff).</li>
</ul>
<h2>8.4.28</h2>
<ul>
<li>Fixed <code>Root.source.end</code> for better source map (by Romain
Menke).</li>
<li>Fixed <code>Result.root</code> types when <code>process()</code> has
no parser.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90208de880"><code>90208de</code></a>
Release 8.4.31 version</li>
<li><a
href="58cc860b4c"><code>58cc860</code></a>
Fix carrier return parsing</li>
<li><a
href="4fff8e4cdc"><code>4fff8e4</code></a>
Improve pnpm test output</li>
<li><a
href="cd43ed1232"><code>cd43ed1</code></a>
Update dependencies</li>
<li><a
href="caa916bdcb"><code>caa916b</code></a>
Update dependencies</li>
<li><a
href="8972f76923"><code>8972f76</code></a>
Typo</li>
<li><a
href="11a5286f78"><code>11a5286</code></a>
Typo</li>
<li><a
href="45c5501777"><code>45c5501</code></a>
Release 8.4.30 version</li>
<li><a
href="bc3c341f58"><code>bc3c341</code></a>
Update linter</li>
<li><a
href="b2be58a2eb"><code>b2be58a</code></a>
Merge pull request <a
href="https://redirect.github.com/postcss/postcss/issues/1881">#1881</a>
from romainmenke/improve-sourcemap-performance--phil...</li>
<li>Additional commits viewable in <a
href="https://github.com/postcss/postcss/compare/8.4.27...8.4.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.4.27&new-version=8.4.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/garraflavatra/rolens/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 20:51:31 +02:00
621af884b5 Centered modal 2023-08-27 15:25:37 +02:00
a01c56ab69 Fixed selected item highlighting bug in grid 2023-08-26 20:39:47 +02:00
1b834de091 Updated host menu items 2023-08-26 20:27:44 +02:00
70245bd38b Point to wails doctor documentation 2023-08-26 20:24:19 +02:00
9be36fda7f Corrected some imports 2023-08-07 18:28:21 +02:00
4370672d60 Lint code 2023-08-07 18:21:45 +02:00
7cebafeff3 Use yeslint! as configuration for ESLint 2023-08-07 17:51:30 +02:00
5b15782d7d Moved editors and grid components to their own directories 2023-08-07 16:59:22 +02:00
f33cac7f5c DB/collection dropping bugfix
After dropping a collection or database, the hosttree was not reloaded
correctly. Now, dropped databases and collections are correctly removed
from the host tree.
2023-08-07 14:57:31 +02:00
b49faea073 Added newline in changelog 2023-08-07 14:49:12 +02:00
239af3590d Added deadline for counting documents
Set a deadline for counting documents, and added a button to count
documents if the deadline has been exceeded.
2023-07-24 20:38:30 +02:00
0e376866a7 Open file tab by default 2023-07-24 20:07:29 +02:00
d677b825e1 Disable spellcheck and autocomplete on all inputs 2023-07-21 21:49:29 +02:00
3ca561b4b4 Added collection copy feature (fixes #63) 2023-07-21 21:40:08 +02:00
7e5e2127ff Docs: note that shell is currently under development 2023-07-21 19:35:21 +02:00
24b3180eb5 build.js recreates build output dir after it has been removed (fixes #62) 2023-07-21 19:28:44 +02:00
3827ae3482 Bump word-wrap from 1.2.3 to 1.2.4 in /frontend (#61)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3
to 1.2.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jonschlinkert/word-wrap/releases">word-wrap's
releases</a>.</em></p>
<blockquote>
<h2>1.2.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Remove default indent by <a
href="https://github.com/mohd-akram"><code>@​mohd-akram</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/24">jonschlinkert/word-wrap#24</a></li>
<li>🔒fix: CVE 2023 26115 (2) by <a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/41">jonschlinkert/word-wrap#41</a></li>
<li>🔒 fix: CVE-2023-26115 by <a
href="https://github.com/aashutoshrathi"><code>@​aashutoshrathi</code></a>
in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/33">jonschlinkert/word-wrap#33</a></li>
<li>chore: publish workflow by <a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/42">jonschlinkert/word-wrap#42</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/mohd-akram"><code>@​mohd-akram</code></a> made
their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/24">jonschlinkert/word-wrap#24</a></li>
<li><a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> made
their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/41">jonschlinkert/word-wrap#41</a></li>
<li><a
href="https://github.com/aashutoshrathi"><code>@​aashutoshrathi</code></a>
made their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/33">jonschlinkert/word-wrap#33</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4">https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f64b188c72"><code>f64b188</code></a>
run verb to generate README</li>
<li><a
href="03ea08256b"><code>03ea082</code></a>
Merge pull request <a
href="https://redirect.github.com/jonschlinkert/word-wrap/issues/42">#42</a>
from jonschlinkert/chore/publish-workflow</li>
<li><a
href="420dce9a24"><code>420dce9</code></a>
Merge pull request <a
href="https://redirect.github.com/jonschlinkert/word-wrap/issues/41">#41</a>
from jonschlinkert/fix/CVE-2023-26115-2</li>
<li><a
href="bfa694edf5"><code>bfa694e</code></a>
Update .github/workflows/publish.yml</li>
<li><a
href="ace0b3c78f"><code>ace0b3c</code></a>
chore: bump version to 1.2.4</li>
<li><a
href="6fd7275946"><code>6fd7275</code></a>
chore: add publish workflow</li>
<li><a
href="30d6daf60f"><code>30d6daf</code></a>
chore: fix test</li>
<li><a
href="655929cabe"><code>655929c</code></a>
chore: remove package-lock</li>
<li><a
href="49e08bbc32"><code>49e08bb</code></a>
chore: added an additional testcase</li>
<li><a
href="9f626935f3"><code>9f62693</code></a>
fix: cve 2023-26115</li>
<li>Additional commits viewable in <a
href="https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=word-wrap&package-manager=npm_and_yarn&previous-version=1.2.3&new-version=1.2.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/garraflavatra/rolens/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 16:57:15 +02:00
3556098bc5 Updated the changelog 2023-07-19 21:31:39 +02:00
5deab93162 Find view: confirm deletion (fixes #58) 2023-07-19 21:07:50 +02:00
3d13dd974e Docs: styled stub warning 2023-07-19 20:53:15 +02:00
43fce1f27e Added shell documentation 2023-07-19 20:45:05 +02:00
77747c10c2 Added horizontal/vertical view toggle to the shell (#37) 2023-07-19 20:18:40 +02:00
9d577ac429 Added functionality to save and import shell scripts (#37) 2023-07-19 20:01:15 +02:00
ae5002d356 Updated the changelog 2023-07-09 12:56:44 +02:00
6027ab155a Find view: paste ID and press Enter (fixes #55) 2023-07-08 13:17:30 +02:00
5476df5fe9 Preserve state between tabs (cc #37) (fixes #56) 2023-07-08 13:07:55 +02:00
64fb9ed173 Added shell to changelog 2023-07-02 11:13:58 +02:00
770a204be2 Moved icon SVGs out of icons.svelte 2023-07-02 11:00:20 +02:00
7a5354c5f4 Report stderr in shell (#37) 2023-07-02 10:10:45 +02:00
61142844fa Shell feature (#44)
Resolves #37: adds a shell tab where the user can write mongo shell
scripts and execute them.
2023-07-01 21:34:32 +02:00
121 changed files with 2246 additions and 1028 deletions

6
.gitattributes vendored
View File

@ -1,5 +1,9 @@
# Let GitHub Linguist ignore documentation and Wails generated files
# https://github.com/github-linguist/linguist/blob/master/docs/overrides.md
/frontend/src/components/icon.svelte linguist-vendored
/frontend/wailsjs/**/* linguist-generated
/website/**/* linguist-documentation
# Exclude certain files from exports
/.github/**/* export-ignore
/.vscode/**/* export-ignore
/website/**/* export-ignore

View File

@ -1,10 +1,8 @@
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
- push
- pull_request
jobs:
build:
@ -16,22 +14,23 @@ jobs:
platform:
- windows-2019
- windows-2022
- macos-11
- macos-12
- macos-13
- macos-14
- ubuntu-20.04
- ubuntu-22.04
go-version: [1.18]
node-version: [16]
- ubuntu-24.04
go-version: [ 1.21 ]
node-version: [ 18 ]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: go.sum
@ -48,7 +47,7 @@ jobs:
run: sudo apt-get install gtk+-3.0 webkit2gtk-4.0
- name: Set up Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
@ -67,7 +66,7 @@ jobs:
run: ./build/linux/ci_generate.sh "${{ matrix.platform }}"
- name: Upload generated binaries
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: rolens-${{ matrix.platform }}
path: releases/*
@ -94,7 +93,7 @@ jobs:
run: build/ci_bundle.sh
- name: Upload the bundle as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: rolens-bundle
path: bundle

View File

@ -22,24 +22,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "16"
- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v5
- name: Install dependencies
run: cd website; npm ci
run: npm ci
working-directory: website
- name: Build site
run: cd website; npm run build
run: npm run build
working-directory: website
- name: Upload built website
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
path: ./website-dist
@ -54,4 +56,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

View File

@ -1,19 +1,17 @@
name: Linter
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
- push
- pull_request
jobs:
eslint:
name: Run ESlint
name: Run ESLint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci

View File

@ -6,7 +6,7 @@
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
"source.organizeImports": "explicit"
},
"editor.suggest.snippetsPreventQuickSuggestions": false
},

View File

@ -1,6 +1,25 @@
## [Unreleased]
## [v0.3.0]
New features:
* Added log view (#53, #54).
* Added a shell script editor (#37), plus export/import feature.
* Added collection duplication feature (#63).
* Find view: paste ID and press Enter (#55).
Patches:
* Preserve state after switching to another tab (#56).
* Find view: ask for confirmation before negligently deleting documents when the user has clicked the '-' button (#58).
* Set a deadline for counting documents, and added a button to count documents if the deadline has been exceeded.
* Changed os.ModePerm (777) file permissions to 644 and 755.
* UI improvements.
* Bumped minimum reqiured Go version from 1.18 to 1.20.
Bugfixes:
* Build script recreates the build output directory after it has been removed (#62).
* After dropping a collection or database, the hosttree was not reloaded correctly. Now, dropped databases and collections are correctly removed from the host tree.
## [v0.2.2]
@ -36,3 +55,4 @@ Initial release.
[v0.2.0]: https://github.com/garraflavatra/rolens/releases/tag/v0.2.0
[v0.2.1]: https://github.com/garraflavatra/rolens/releases/tag/v0.2.1
[v0.2.2]: https://github.com/garraflavatra/rolens/releases/tag/v0.2.2
[v0.3.0]: https://github.com/garraflavatra/rolens/releases/tag/v0.3.0

View File

@ -8,7 +8,7 @@ Robust, blazing-fast, comprehensive, yet simple [MongoDB](https://www.mongodb.co
## Why another MongoDB client?
This project arose from all flaws of similar tools many of which are slow, complicated, heavy, and fairly unwieldy. They mostly require a reasonably high level of knowledge on how to operate the program.
This project arose from all flaws of similar tools many of which are slow, complicated, heavy, and fairly unwieldy. They mostly require quite a high level of knowledge on how to operate the program.
**Rolens aims to be the intuitive, lightweight counterpart of these overengineered tools.**
@ -17,7 +17,7 @@ This project arose from all flaws of similar tools many of which are slow, compl
- [x] **Low overhead**: Typical query results against a local database are returned whithin milliseconds.
- [x] **Intuitive interface**: You know MongoDB? You know Rolens.
![Impression of Rolens's interface](./docs/images/home-impression.png)
![Impression of Rolens's interface](./docs/images/home-impression.webp)
This project is heavily inspired by the excellent [MongoHub](https://github.com/bububa/MongoHub-Mac) application, which sadly has not been updated since 2011.
@ -62,6 +62,7 @@ At this point, Rolens is comparable to MongoHub regarding features. It cannot ha
* Database management
- See stats
- Create dumps with `mongodump`
- Write and execute shell scripts
* Collections
- See stats
- Find, insert, update, & remove
@ -75,11 +76,10 @@ At this point, Rolens is comparable to MongoHub regarding features. It cannot ha
## Wishlist
* User management
* Shell _([under development](https://github.com/garraflavatra/rolens/pull/44))_
## Author and license
© [Romein van Buren](mailto:romein@vburen.nl) 2023. The source code and compiled binaries are released under the GNU GPLv3 license — see [`LICENSE`](./LICENSE) for the full license text.
© [Romein van Buren](mailto:romein@vburen.nl) 2022-2024. The source code and compiled binaries are released under the GNU GPLv3 license — see [`LICENSE`](./LICENSE) for the full license text.
## Credits

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
const { execSync, spawn } = require('child_process');
const { readFileSync, statSync, rmdirSync } = require('fs');
const { readFileSync, statSync, rmdirSync, mkdirSync } = require('fs');
// Check that the script is run from the root.
@ -50,18 +50,18 @@ function isNullish(val) {
return val === undefined || val === null;
}
// Check that Go ^1.18 is installed.
// Check that Go ^1.20 is installed.
try {
const goMinorVersion = /go1\.([0-9][0-9])/.exec(
execSync('go version').toString()
)?.pop();
if (isNullish(goMinorVersion) || (parseInt(goMinorVersion) < 18)) {
if (isNullish(goMinorVersion) || (parseInt(goMinorVersion) < 20)) {
throw new Error();
}
} catch {
missingDependencies.push({ name: 'Go ^1.18 ^16', url: 'https://go.dev/doc/install' });
missingDependencies.push({ name: 'Go ^1.20', url: 'https://go.dev/doc/install' });
}
// Check that Node.js ^16 is installed.
@ -145,6 +145,12 @@ if (missingDependencies.length > 0) {
console.log('Cleaning output directory...');
try { rmdirSync('./build/bin'); } catch {}
try {
mkdirSync('./build/bin');
}
catch (err) {
console.log('Failed to create build output directory!');
}
// Build Rolens.
@ -154,8 +160,25 @@ console.log();
const proc = spawn('wails', [ 'build', '-clean', isWindows ? '-nsis' : '' ]);
if (!quiet) {
proc.stdout.on('data', data => process.stdout.write(data));
const suppressMessages = [
'Wails CLI',
'If Wails is useful',
'https://github.com/sponsors/leaanthony',
];
proc.stdout.on('data', data => {
for (let i = 0; i < suppressMessages.length; i++) {
if (data.toString().indexOf(suppressMessages[i]) !== -1) {
return;
}
}
process.stdout.write(data);
});
proc.stderr.on('data', data => process.stderr.write(data));
}
proc.on('exit', code => process.exit(code));
proc.on('exit', code => {
console.log();
process.exit(code);
});

View File

@ -12,12 +12,12 @@
# - ubuntu-22.04
#
# Bundles to choose from:
# - rolens-macos-11-amd64.tar.gz
# - rolens-macos-11-arm64.tar.gz
# - rolens-macos-12-amd64.tar.gz
# - rolens-macos-12-arm64.tar.gz
# - rolens-macos-13-amd64.tar.gz
# - rolens-macos-13-arm64.tar.gz
# - rolens-macos-11-amd64.zip
# - rolens-macos-11-arm64.zip
# - rolens-macos-12-amd64.zip
# - rolens-macos-12-arm64.zip
# - rolens-macos-13-amd64.zip
# - rolens-macos-13-arm64.zip
# - rolens-ubuntu-20.04-amd64.tar.gz
# - rolens-ubuntu-22.04-amd64.tar.gz
# - rolens-windows-2019-amd64.zip
@ -32,8 +32,8 @@ version=$(<./build/version.txt)
mkdir bundle
# macOS apps
mv artifacts/*/rolens-macos-11-amd64.tar.gz "bundle/rolens-$version-macos-11+-amd64.tar.gz"
mv artifacts/*/rolens-macos-11-arm64.tar.gz "bundle/rolens-$version-macos-11+-arm64.tar.gz"
mv artifacts/*/rolens-macos-11-amd64.zip "bundle/rolens-$version-macos-11+-amd64.zip"
mv artifacts/*/rolens-macos-11-arm64.zip "bundle/rolens-$version-macos-11+-arm64.zip"
# Windows installers
mv artifacts/*/rolens-windows-2019-amd64-installer.zip "bundle/rolens-$version-windows-10+-amd64-installer.zip"

View File

@ -13,48 +13,27 @@ cat > build/darwin/dmg_settings.json << EOF
"background": "$(pwd)/build/darwin/dmg_background.png",
"icon-size": 100,
"window": {
"size": { "width": 155, "height": 250 },
"position": { "x": 360, "y": 360 }
"size": { "width": 750, "height": 400 }
},
"contents": [
{ "x": 750, "y": 500, "type": "link", "path": "/Applications" },
{ "x": 595, "y": 250, "type": "file", "path": "$(pwd)/build/bin/Rolens.app" }
{ "x": 600, "y": 175, "type": "link", "path": "/Applications" },
{ "x": 150, "y": 175, "type": "file", "path": "$(pwd)/build/bin/Rolens.app" }
]
}
EOF
# AMD/Intel
wails build -platform darwin/amd64
# create-dmg \
# --volname Rolens \
# --window-size 155 250 \
# --volicon build/appicon.png \
# --eula LICENSE \
# --app-drop-link 750 500 \
# --icon-size 100 \
# --background build/darwin/dmg_background.png \
# --add-file Rolens.app build/bin/Rolens.app 595 250 \
# build/bin/Rolens.dmg emptydir
# appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
tar -czvf releases/rolens-$1-amd64.tar.gz --directory build/bin Rolens.app
appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
zip -j releases/rolens-$1-amd64.zip build/bin/Rolens.dmg
# Cleanup
rm -rf build/bin/Rolens.app
rm -rf build/bin/Rolens.dmg
# ARM/AppleM1
wails build -platform darwin/arm64
# create-dmg \
# --volname Rolens \
# --window-size 155 250 \
# --volicon build/appicon.png \
# --eula LICENSE \
# --app-drop-link 750 500 \
# --icon-size 100 \
# --background build/darwin/dmg_background.png \
# --add-file Rolens.app build/bin/Rolens.app 595 250 \
# build/bin/Rolens.dmg emptydir
# appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
tar -czvf releases/rolens-$1-arm64.tar.gz --directory build/bin Rolens.app
appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
zip -j releases/rolens-$1-arm64.zip build/bin/Rolens.dmg
# Cleanup
rm -rf build/bin/Rolens.app

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -28,5 +28,5 @@ Feel free to contact me if you have questions! Send an e-mail to romein@vburen.n
Author and license
------------------
© Romein van Buren 2023. The source code and compiled binaries are released
© Romein van Buren 2022-2024. The source code and compiled binaries are released
under the GNU GPLv3 license — see LICENSE for the full license text.

View File

@ -3,7 +3,7 @@ title: Colophon
order: 900
---
Rolens is © [Romein van Buren](mailto:romein@vburen.nl) 2023. The source code and compiled binaries are released under the GNU GPLv3 license — see [`LICENSE`](https://github.com/garraflavatra/rolens/blob/main/LICENSE) for the full license text.
Rolens is © [Romein van Buren](mailto:romein@vburen.nl) 2022-2024. The source code and compiled binaries are released under the GNU GPLv3 license — see [`LICENSE`](https://github.com/garraflavatra/rolens/blob/main/LICENSE) for the full license text.
## Credits

View File

@ -8,9 +8,9 @@ If you just want to install Rolens, please refer to the [installation document](
## Prerequisites
Rolens is written in Go, so you should download the Go compiler from [the download page](https://go.dev/dl/). The minimum version required is 1.18. You can confirm whether it's installed correctly by running `go version` and checking that it outputs something similar to `go1.18.2`.
Rolens is written in Go, so you should download the Go compiler from [the download page](https://go.dev/dl/). The minimum version required is 1.20. You can confirm whether it's installed correctly by running `go version` and checking that it outputs something similar to `go1.20.4` or higher.
Furthermore, you need to have [Wails ^3.1](https://wails.io/docs/gettingstarted/installation) installed: `go install github.com/wailsapp/wails/v2/cmd/wails@latest`. Wails may have platform-specific dependencies; you can consult `wails doctor` to find out what dependencies Wails needs and how to install them.
Furthermore, you need to have [Wails ^3.1](https://wails.io/docs/gettingstarted/installation) installed: `go install github.com/wailsapp/wails/v2/cmd/wails@latest`. Wails may have platform-specific dependencies; you can consult [`wails doctor`](https://wails.io/docs/reference/cli#doctor) to find out what dependencies Wails needs and how to install them.
In order to compile the frontend, [Node.js](https://nodejs.org/en/download) ^16.0 and the [npm](https://npmjs.com) package manager ^8.0 (included in Node.js) are required. To confirm the installed versions of those tools, execute `node -v` and `npm -v`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
docs/images/shell.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -15,7 +15,7 @@ This project arose from all flaws of similar tools many of which are slow, compl
- **Low overhead**: Typical query results against a local database are returned whithin milliseconds.
- **Intuitive interface**: You know MongoDB? You know Rolens.
![Impression of Rolens's interface](./images/home-impression.png)
![Impression of Rolens's interface](./images/home-impression.webp)
This project is heavily inspired by the excellent [MongoHub](https://github.com/bububa/MongoHub-Mac) application, which sadly has not been updated since 2011.

17
docs/user-guide/shell.md Normal file
View File

@ -0,0 +1,17 @@
---
title: Shell
summary: "Write and execute MongoDB shell scripts within Rolens"
parent: User guide
order: 60
stub: true
---
Rolens has a shell feature: it provides an editor for writing shell scripts and executing them against a local or external host, database, or even a single collection. You can find it under the _Shell_ tab.
_Note: this feature is currently in development and will be shipped with version 0.3.0. You can [follow the development on GitHub](https://github.com/garraflavatra/rolens)._
![The shell tab](/images/shell.png)
## Requirements
To use the script editor, you need to install the official [`mongosh` tool](https://www.mongodb.com/docs/mongodb-shell/) from MongoDB. You can [download it](https://www.mongodb.com/try/download/shell) from the MongoDB web site, or run `brew install mongosh` if you use a Mac.

View File

@ -6,6 +6,11 @@ order: 900
<p>You can use the following shortcuts to manage hosts and connections.</p>
<p>
<a href="javascript:window.print()">Print this page</a> if you would like to
mount it on your wall for quick reference!
</p>
{% for item in shortcuts %}
<h2>{{ item[0] }}</h2>
{% render "shortcuts", shortcuts: item[1] %}

View File

@ -7,14 +7,6 @@
</head>
<body>
<div id="app"></div>
<div id="app-loading">
<div class="ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div id="dialogoutlets"></div>
<script src="./src/main.js" type="module"></script>
</body>

View File

@ -16,11 +16,12 @@
"date-fns": "^2.30.0"
},
"devDependencies": {
"@garraflavatra/yeslint": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"eslint": "^8.43.0",
"eslint-config-svelte3": "github:johbog/eslint-config-svelte3",
"svelte": "^3.59.1",
"vite": "^3.2.7"
"vite": "^3.2.10"
}
},
"node_modules/@babel/runtime": {
@ -212,6 +213,24 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@garraflavatra/yeslint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@garraflavatra/yeslint/-/yeslint-1.0.0.tgz",
"integrity": "sha512-LvpQq+buhjjGMtzdzgj5iFCOG9ZGvbgOsFMvtRCYiqpzzNjhXsfO4IGUrJuVL+AwWUzNPJNTzKI5OyQZbh07Jg==",
"dev": true,
"dependencies": {
"eslint-plugin-import": "^2.27.5"
},
"peerDependencies": {
"eslint": "^7.9.0 || ^8.0.0",
"eslint-plugin-svelte": "^2.32.4"
},
"peerDependenciesMeta": {
"eslint-plugin-svelte": {
"optional": true
}
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@ -654,6 +673,18 @@
"node": ">= 8"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -1349,20 +1380,22 @@
}
},
"node_modules/eslint-plugin-svelte": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.30.0.tgz",
"integrity": "sha512-2/qj0BJsfM0U2j4EjGb7iC/0nbUvXx1Gn78CdtyuXpi/rSomLPCPwnsZsloXMzlt6Xwe8LBlpRvZObSKEHLP5A==",
"version": "2.32.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.32.4.tgz",
"integrity": "sha512-VJ12i2Iogug1jvhwxSlognnfGj76P5gks/V4pUD4SCSVQOp14u47MNP0zAG8AQR3LT0Fi1iUvIFnY4l9z5Rwbg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@jridgewell/sourcemap-codec": "^1.4.14",
"debug": "^4.3.1",
"esutils": "^2.0.3",
"known-css-properties": "^0.27.0",
"known-css-properties": "^0.28.0",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"svelte-eslint-parser": "^0.30.0"
"postcss-selector-parser": "^6.0.11",
"semver": "^7.5.3",
"svelte-eslint-parser": "^0.32.2"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
@ -1372,7 +1405,7 @@
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0",
"svelte": "^3.37.0 || ^4.0.0-0"
"svelte": "^3.37.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@ -1380,6 +1413,21 @@
}
}
},
"node_modules/eslint-plugin-svelte/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/eslint-scope": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
@ -2157,9 +2205,9 @@
}
},
"node_modules/known-css-properties": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz",
"integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz",
"integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==",
"dev": true
},
"node_modules/levn": {
@ -2205,6 +2253,18 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/magic-string": {
"version": "0.26.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
@ -2245,10 +2305,16 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -2423,9 +2489,9 @@
"dev": true
},
"node_modules/postcss": {
"version": "8.4.20",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@ -2435,10 +2501,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@ -2491,6 +2561,41 @@
"postcss": "^8.3.3"
}
},
"node_modules/postcss-scss": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz",
"integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss-scss"
}
],
"engines": {
"node": ">=12.0"
},
"peerDependencies": {
"postcss": "^8.4.19"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -2655,9 +2760,9 @@
}
},
"node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
@ -2831,14 +2936,16 @@
}
},
"node_modules/svelte-eslint-parser": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.30.0.tgz",
"integrity": "sha512-H0Cn2TKr70DU9p/Gb04CfwtS7eK28MYumrHYPaDNkIFbfwGDLADpbERBn7u8G1Rcm2RMr2/mL6mq0J2e8iKFlA==",
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.32.2.tgz",
"integrity": "sha512-Ok9D3A4b23iLQsONrjqtXtYDu5ZZ/826Blaw2LeFZVTg1pwofKDG4mz3/GYTax8fQ0plRGHI6j+d9VQYy5Lo/A==",
"dev": true,
"dependencies": {
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0"
"espree": "^9.0.0",
"postcss": "^8.4.25",
"postcss-scss": "^4.0.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -2847,7 +2954,7 @@
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
"svelte": "^3.37.0 || ^4.0.0-0"
"svelte": "^3.37.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@ -2947,10 +3054,16 @@
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/vite": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"version": "3.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz",
"integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
@ -3067,9 +3180,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@ -3081,6 +3194,12 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
@ -3248,6 +3367,15 @@
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
"dev": true
},
"@garraflavatra/yeslint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@garraflavatra/yeslint/-/yeslint-1.0.0.tgz",
"integrity": "sha512-LvpQq+buhjjGMtzdzgj5iFCOG9ZGvbgOsFMvtRCYiqpzzNjhXsfO4IGUrJuVL+AwWUzNPJNTzKI5OyQZbh07Jg==",
"dev": true,
"requires": {
"eslint-plugin-import": "^2.27.5"
}
},
"@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@ -3567,6 +3695,12 @@
"which": "^2.0.1"
}
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -4007,20 +4141,33 @@
}
},
"eslint-plugin-svelte": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.30.0.tgz",
"integrity": "sha512-2/qj0BJsfM0U2j4EjGb7iC/0nbUvXx1Gn78CdtyuXpi/rSomLPCPwnsZsloXMzlt6Xwe8LBlpRvZObSKEHLP5A==",
"version": "2.32.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.32.4.tgz",
"integrity": "sha512-VJ12i2Iogug1jvhwxSlognnfGj76P5gks/V4pUD4SCSVQOp14u47MNP0zAG8AQR3LT0Fi1iUvIFnY4l9z5Rwbg==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@jridgewell/sourcemap-codec": "^1.4.14",
"debug": "^4.3.1",
"esutils": "^2.0.3",
"known-css-properties": "^0.27.0",
"known-css-properties": "^0.28.0",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"svelte-eslint-parser": "^0.30.0"
"postcss-selector-parser": "^6.0.11",
"semver": "^7.5.3",
"svelte-eslint-parser": "^0.32.2"
},
"dependencies": {
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"eslint-scope": {
@ -4563,9 +4710,9 @@
"dev": true
},
"known-css-properties": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz",
"integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz",
"integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==",
"dev": true
},
"levn": {
@ -4599,6 +4746,15 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"magic-string": {
"version": "0.26.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
@ -4630,9 +4786,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true
},
"natural-compare": {
@ -4757,12 +4913,12 @@
"dev": true
},
"postcss": {
"version": "8.4.20",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@ -4784,6 +4940,23 @@
"dev": true,
"requires": {}
},
"postcss-scss": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz",
"integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==",
"dev": true,
"requires": {}
},
"postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -4880,9 +5053,9 @@
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true
},
"shebang-command": {
@ -5004,14 +5177,16 @@
"dev": true
},
"svelte-eslint-parser": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.30.0.tgz",
"integrity": "sha512-H0Cn2TKr70DU9p/Gb04CfwtS7eK28MYumrHYPaDNkIFbfwGDLADpbERBn7u8G1Rcm2RMr2/mL6mq0J2e8iKFlA==",
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.32.2.tgz",
"integrity": "sha512-Ok9D3A4b23iLQsONrjqtXtYDu5ZZ/826Blaw2LeFZVTg1pwofKDG4mz3/GYTax8fQ0plRGHI6j+d9VQYy5Lo/A==",
"dev": true,
"requires": {
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0"
"espree": "^9.0.0",
"postcss": "^8.4.25",
"postcss-scss": "^4.0.6"
}
},
"svelte-hmr": {
@ -5086,10 +5261,16 @@
"punycode": "^2.1.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"vite": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"version": "3.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz",
"integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
@ -5148,9 +5329,9 @@
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wrappy": {
@ -5159,6 +5340,12 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",

View File

@ -17,23 +17,20 @@
"date-fns": "^2.30.0"
},
"devDependencies": {
"@garraflavatra/yeslint": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"eslint": "^8.43.0",
"eslint-config-svelte3": "github:johbog/eslint-config-svelte3",
"svelte": "^3.59.1",
"vite": "^3.2.7"
"vite": "^3.2.10"
},
"eslintConfig": {
"extends": "svelte3",
"ignorePatterns": ["dist", "wailsjs"],
"extends": "./node_modules/@garraflavatra/yeslint/configs/svelte.js",
"ignorePatterns": [
"dist",
"wailsjs"
],
"rules": {
"svelte/html-quotes": [
"warn",
{
"prefer": "double",
"dynamic": { "quoted": false }
}
],
"svelte/no-useless-mustaches": "off",
"svelte/no-extra-reactive-curlies": "off"
}

View File

@ -1 +1 @@
7fc6d7b66151030191ded7136d96970e
015746ba33749cd2864cd336088387ef

View File

@ -1,17 +1,15 @@
<script>
import BlankState from '$components/blankstate.svelte';
import ContextMenu from '$components/contextmenu.svelte';
import dialogs from '$lib/dialogs';
import contextMenu from '$lib/stores/contextmenu';
import environment from '$lib/stores/environment';
import hostTree from '$lib/stores/hosttree';
import applicationInited from '$lib/stores/inited';
import windowTitle from '$lib/stores/windowtitle';
import Connection from '$organisms/connection/index.svelte';
import { EventsOn } from '$wails/runtime';
import { tick } from 'svelte';
import AboutDialog from './dialogs/about.svelte';
import SettingsDialog from './dialogs/settings/index.svelte';
import { fly } from 'svelte/transition';
import BlankState from '$components/blankstate.svelte';
import Connection from '$organisms/connection/index.svelte';
import ContextMenu from '$components/contextmenu.svelte';
import contextMenu from '$lib/stores/contextmenu.js';
import hostTree from '$lib/stores/hosttree.js';
import applicationInited from '$lib/stores/inited.js';
import windowTitle from '$lib/stores/windowtitle.js';
let showWelcomeScreen = undefined;
@ -28,26 +26,15 @@
await tick();
hostTree.newHost();
}
function showAboutDialog() {
dialogs.new(AboutDialog);
}
function showSettings() {
dialogs.new(SettingsDialog);
}
EventsOn('OpenPreferences', showSettings);
EventsOn('OpenAboutModal', showAboutDialog);
</script>
<svelte:window on:contextmenu|preventDefault />
<div id="root" class="platform-{$environment?.platform}">
<div id="root">
<div class="titlebar">{$windowTitle}</div>
{#if $applicationInited && (showWelcomeScreen !== undefined)}
<main class:empty={showWelcomeScreen}>
<main class:empty={showWelcomeScreen} in:fly={{ y: 10 }}>
{#if showWelcomeScreen}
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
<button class="button" on:click={createFirstHost}>Add your first host</button>
@ -65,30 +52,23 @@
<style>
.titlebar {
height: 0;
background-color: #00002a;
height: var(--titlebar-height);
--wails-draggable: drag;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
#root.platform-darwin .titlebar {
height: var(--darwin-titlebar-height);
background-color: #ddd;
}
main {
height: 100vh;
height: calc(100vh - var(--titlebar-height));
display: grid;
grid-template: 1fr / minmax(300px, 0.3fr) 1fr;
}
main.empty {
grid-template: 1fr / 1fr;
}
#root.platform-darwin main {
height: calc(100vh - var(--darwin-titlebar-height));
}
main > :global(*) {
overflow: auto;
@ -98,12 +78,4 @@
main > :global(.addressbar) {
grid-column: 1 / 3;
}
.databaselist {
overflow: scroll;
}
.button.create {
margin-top: 0.5rem;
}
</style>

View File

@ -50,7 +50,7 @@
<svelte:window on:keydown={keydown} />
{#if items && position}
<div class="backdrop" on:pointerdown={close}></div>
<div class="backdrop" on:pointerdown={close} />
<nav>
<ul class="contextmenu" role="" style:left="{position[0]}px" style:top="{position[1]}px">
{#each items as item, index}
@ -101,7 +101,7 @@
text-align: left;
}
button.selected {
background-color: #00008b;
background-color: var(--selection);
color: #fff;
}

View File

@ -1,6 +1,5 @@
<script>
import { indentWithTab } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { indentOnInput } from '@codemirror/language';
import { EditorState } from '@codemirror/state';
import { EditorView, keymap } from '@codemirror/view';
@ -9,7 +8,7 @@
export let text = '';
export let editor = undefined;
export let readonly = false;
export let extensions = [];
const dispatch = createEventDispatcher();
let editorParent;
@ -19,9 +18,7 @@
extensions: [
basicSetup,
keymap.of([ indentWithTab, indentOnInput ]),
javascript(),
EditorState.tabSize.of(4),
EditorState.readOnly.of(readonly),
EditorView.updateListener.of(e => {
if (!e.docChanged) {
return;
@ -29,6 +26,7 @@
text = e.state.doc.toString();
dispatch('updated', { text });
}),
...extensions,
],
});
@ -38,19 +36,11 @@
state: editorState,
});
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: text,
},
});
dispatch('inited', { editor });
});
</script>
<div bind:this={editorParent} class="editor"></div>
<div bind:this={editorParent} class="editor" />
<style>
.editor {

View File

@ -1,10 +1,10 @@
<script>
import { daysAbbr, months } from '$lib/constants';
import { daysAbbr, months } from '$lib/constants.js';
import { addDays, getWeek, isDate, isSameDay, startOfWeek } from 'date-fns';
import { onMount } from 'svelte';
import Clock from './clock.svelte';
import Icon from './icon.svelte';
import Modal from './modal.svelte';
import Clock from '../clock.svelte';
import Icon from '../icon.svelte';
import Modal from '../modal.svelte';
export let value;
export let show = false;
@ -97,7 +97,7 @@
<table class="calendar">
<thead>
<tr>
<th></th>
<th />
{#each daysAbbr as dayName}
<th>{dayName}</th>
{/each}
@ -173,7 +173,7 @@
padding-bottom: 8px;
}
.calendar .day button.active {
background-color: #00008b;
background-color: var(--selection);
color: #fff;
}
.calendar .day button.notinmonth {

View File

@ -1,16 +1,22 @@
<script>
import { OpenDirectory } from '$wails/go/ui/UI';
import { ChooseDirectory } from '$wails/go/app/App.js';
export let value = '';
export let id = '';
export let title = 'Choose a directory';
async function selectDir() {
value = await OpenDirectory(title) || value;
value = await ChooseDirectory(title) || value;
}
</script>
<input type="text" on:pointerdown={selectDir} readonly {id} {value} />
<input
type="text"
on:pointerdown={selectDir}
readonly
{id}
{value}
/>
<style>
input {

View File

@ -1,10 +1,10 @@
<script>
import input from '$lib/actions/input';
import { canBeObjectId, numericInputTypes } from '$lib/mongo';
import input from '$lib/actions/input.js';
import { canBeObjectId, numericInputTypes } from '$lib/mongo/index.js';
import { ObjectId } from 'bson';
import { onMount } from 'svelte';
import Datepicker from './datepicker.svelte';
import Icon from './icon.svelte';
import Icon from '../icon.svelte';
export let column = {};
export let value = undefined;
@ -70,7 +70,12 @@
<div class="forminput {type}">
<div class="field">
{#if type === 'string'}
<input type="text" bind:value use:input={{ type, onValid, onInvalid, mandatory, autofocus }} autocomplete="off" spellcheck="false" />
<input
type="text"
bind:value
use:input={{ type, onValid, onInvalid, mandatory, autofocus }}
autocomplete="off"
spellcheck="false" />
{:else if type === 'objectid'}
<input
type="text"
@ -79,7 +84,11 @@
use:input={{ type, onValid, onInvalid, mandatory, autofocus }}
/>
{:else if numericInputTypes.includes(type)}
<input type="number" bind:value use:input={{ type, onValid, onInvalid, mandatory, autofocus }} />
<input
type="number"
bind:value
use:input={{ type, onValid, onInvalid, mandatory, autofocus }}
/>
{:else if type === 'bool'}
<select bind:value on:change={selectChange} bind:this={selectInput}>
<option value={undefined} disabled={mandatory}>Unset</option>
@ -98,18 +107,38 @@
<Icon name="edit" />
</button>
{/if}
<button class="button-small" type="button" title="Generate random object id" on:click={generateObjectId}>
<button
class="button-small"
type="button"
title="Generate random object id"
on:click={generateObjectId}
>
<Icon name="reload" />
</button>
{:else if type === 'date'}
<button class="button-small" type="button" title="Edit date" on:click={() => showDatepicker = true}>
<button
class="button-small"
type="button"
title="Edit date"
on:click={() => showDatepicker = true}
>
<Icon name="edit" />
</button>
<button class="button-small" type="button" title="Set date to now" on:click={() => value = new Date()}>
<button
class="button-small"
type="button"
title="Set date to now"
on:click={() => value = new Date()}
>
<Icon name="o" />
</button>
{/if}
<button class="button-small" type="button" title="Reset field to default value" on:click={() => value = undefined}>
<button
class="button-small"
type="button"
title="Reset field to default value"
on:click={() => value = undefined}
>
<Icon name="trash" />
</button>
</div>

View File

@ -0,0 +1,30 @@
<script>
import { javascript } from '@codemirror/lang-javascript';
import { onMount } from 'svelte';
import CodeEditor from './codeeditor.svelte';
export let text = '';
export let editor = undefined;
export let readonly = false;
const extensions = [ javascript() ];
onMount(() => {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: text,
},
});
});
</script>
<CodeEditor
bind:editor
bind:text
on:inited
on:updated
{extensions}
{readonly}
/>

View File

@ -1,9 +1,9 @@
<script>
import { pathsAreEqual, resolveKeypath, setValue } from '$lib/objects';
import contextMenu from '$lib/stores/contextmenu';
import { pathsAreEqual, resolveKeypath, setValue } from '$lib/objects.js';
import contextMenu from '$lib/stores/contextmenu.js';
import { createEventDispatcher } from 'svelte';
import FormInput from './forminput.svelte';
import Icon from './icon.svelte';
import FormInput from '$components/editors/forminput.svelte';
import Icon from '$components/icon.svelte';
export let items = [];
export let columns = [];
@ -52,7 +52,10 @@
return obj;
}
else if ((typeof obj === 'object') && (obj !== null)) {
return Object.entries(obj).map(([ k, item ]) => {
return Object.entries(obj).map(([
k,
item,
]) => {
return { ...item, [key]: k };
});
}
@ -67,7 +70,10 @@
}
activeKey = itemKey;
activePath = [ ...path.slice(0, level), itemKey ];
activePath = [
...path.slice(0, level),
itemKey,
];
dispatch('select', { level, itemKey, index, path: activePath });
}
@ -126,12 +132,17 @@
</script>
{#each _items as item, index}
{@const selected = canSelect && pathsAreEqual(activePath, [
...path,
item[key],
])}
<tr
on:click={() => select(item[key], index)}
on:dblclick={() => doubleClick(item[key], index)}
on:contextmenu|preventDefault={evt => showContextMenu(evt, item)}
class:selectable={canSelect}
class:selected={canSelect && pathsAreEqual(activePath, [ ...path, item[key] ])}
class:selected
class:striped
>
{#if !hideChildrenToggles}
@ -145,6 +156,10 @@
<Icon name={childrenOpen[item[key]] ? 'chev-d' : 'chev-r'} />
</button>
{/if}
{#if item.loading}
<span class="spinner" style:margin-left="{level * 10}px" />
{/if}
</td>
{/if}
@ -157,7 +172,11 @@
{#each columns as column, columnIndex}
<td class:right={column.right} title={keypathProxies[index][column.key]}>
{#if column.inputType}
<FormInput {column} bind:value={keypathProxies[index][column.key]} bind:valid={validity[columnIndex]} />
<FormInput
{column}
bind:value={keypathProxies[index][column.key]}
bind:valid={validity[columnIndex]}
/>
{:else}
<div class="value" style:margin-left="{level * 10}px">
{formatValue(keypathProxies[index][column.key])}
@ -168,7 +187,12 @@
{#if canRemoveItems}
<td class="has-button">
<button class="button-small" type="button" on:click|stopPropagation={() => removeItem(index, item[key])} on:dblclick|stopPropagation>
<button
class="button-small"
type="button"
on:click|stopPropagation={() => removeItem(index, item[key])}
on:dblclick|stopPropagation
>
<Icon name="x" />
</button>
</td>
@ -184,7 +208,10 @@
{hideChildrenToggles}
{canSelect}
{canRemoveItems}
path={[ ...path, item[key] ]}
path={[
...path,
item[key],
]}
items={item.children}
level={level + 1}
bind:activePath
@ -203,28 +230,41 @@
cursor: pointer;
}
tr.selectable.selected td {
background-color: #00008b !important;
color: #fff;
background-color: var(--selection) !important;
}
td {
padding: 2px;
padding: 4px 2px;
text-overflow: ellipsis;
}
td.has-toggle {
width: 20px;
position: relative;
width: 1.5em;
}
td.has-toggle .spinner {
position: absolute;
top: 0.3em;
left: 0.22em;
width: 1em;
height: 1em;
border: 1px solid var(--primary);
border-radius: 50px;
border-top-color: transparent;
border-left-color: transparent;
animation: .6s linear 0 spin;
animation-iteration-count: infinite;
}
td.has-icon {
padding: 0;
width: 17px;
width: 1.5em;
}
td.has-icon :global(svg) {
width: 13px;
height: 13px;
width: 1em;
height: 1em;
}
td .value {
height: 15px;
height: 1.2em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -232,14 +272,13 @@
}
button.toggle {
color: inherit;
margin: 2px 0 0 3px;
padding: 0;
margin: 0;
vertical-align: top;
color: inherit;
}
button.toggle :global(svg) {
width: 13px;
height: 13px;
width: 0.9em;
height: 0.9em;
vertical-align: top;
}

View File

@ -1,8 +1,8 @@
<script>
import { onDestroy } from 'svelte';
import BlankState from './blankstate.svelte';
import BlankState from '../blankstate.svelte';
import GridItems from './grid-items.svelte';
import Icon from './icon.svelte';
import Icon from '../icon.svelte';
export let columns = [];
export let items = [];
@ -57,17 +57,17 @@
<thead>
<tr>
{#if !hideChildrenToggles}
<th class="has-toggle"></th>
<th class="has-toggle" />
{/if}
<th class="has-icon"></th>
<th class="has-icon" />
{#each columns as column}
<th scope="col">{column.title || ''}</th>
{/each}
{#if canRemoveItems}
<th class="has-button"></th>
<th class="has-button" />
{/if}
</tr>
</thead>

View File

@ -1,5 +1,5 @@
<script>
import { isBsonBuiltin } from '$lib/mongo';
import { isBsonBuiltin } from '$lib/mongo/index.js';
import { isDate } from 'date-fns';
import Grid from './grid.svelte';
@ -22,7 +22,6 @@
].filter(c => !!c);
$: if (data) {
// items = dissectObject(data).map(item => ({ ...item, menu: getRootMenu(item.key, item) }));
items = [];
if (Array.isArray(data)) {

View File

@ -1,6 +1,9 @@
<script>
import icons from '$lib/icons.json';
export let name = '';
export let spin = false;
export let rotation = 0;
if (name === 'loading') {
spin = true;
@ -8,25 +11,30 @@
</script>
<style>
:global(.field) svg {
width: 13px;
height: 13px;
margin-right: 2px;
svg {
transition: transform 0.25s;
will-change: transform;
width: 1.2em;
height: 1.2em;
}
svg.spinning {
animation: spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
:global(.field) svg {
width: 1em;
height: 1em;
margin-right: 2px;
}
:global(.button) svg {
height: 13px;
height: 1em;
width: auto;
vertical-align: bottom;
}
@keyframes spinning {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
svg.spinning {
animation: spinning 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
:global(.blankstate .button) svg {
height: 1.25em;
vertical-align: -3px;
margin-right: 4px;
}
</style>
@ -41,113 +49,7 @@
stroke-linecap="round"
stroke-linejoin="round"
class:spinning={spin}
style:transform="rotate({rotation}deg)"
>
{#if name === 'radio'}
<circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
{:else if name === 'chev-l'}
<polyline points="15 18 9 12 15 6"></polyline>
{:else if name === 'chev-r'}
<polyline points="9 18 15 12 9 6"></polyline>
{:else if name === 'chev-d'}
<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 === 'arr-d'}
<path d="M12 5v14M19 12l-7 7-7-7" />
{: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>
{:else if name === 'x'}
<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>
{:else if name === '+'}
<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>
{:else if name === '-'}
<line x1="5" y1="12" x2="19" y2="12"></line>
{:else if name === 'reload'}
<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
{:else if name === 'clipboard'}
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8"
y="2"
width="8"
height="4"
rx="1"
ry="1"></rect>
{:else if name === 'edit'}
<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
{:else if name === 'check'}
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>
{:else if name === 'list'}
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" />
{:else if name === 'table'}
<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 === 'form'}
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" />
{: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" />
{:else if name === 'zap'}
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" />
{:else if name === 'server'}
<rect x="2"
y="2"
width="20"
height="8"
rx="2"
ry="2" /><rect x="2"
y="14"
width="20"
height="8"
rx="2"
ry="2" /><path d="M6 6h.01M6 18h.01" />
{:else if name === 'text'}
<path d="M4 7V4h16v3M9 20h6M12 4v16" />
{:else if name === 'hash'}
<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18" />
{:else if name === 'toggle-l'}
<rect x="1"
y="5"
width="22"
height="14"
rx="7"
ry="7" /><circle cx="8" cy="12" r="3" />
{:else if name === 'cal'}
<rect x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2" /><path d="M16 2v4M8 2v4M3 10h18" />
{:else if name === 'code'}
<path d="m16 18 6-6-6-6M8 6l-6 6 6 6" />
{:else if name === 'target'}
<circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" />
{:else if name === 'trash'}
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6" />
{:else if name === 'anchor'}
<circle cx="12" cy="5" r="3" /><path d="M12 22V8M5 12H2a10 10 0 0 0 20 0h-3" />
{:else if name === 'o'}
<circle cx="12" cy="12" r="10" />
{:else if name === 'info'}
<circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" />
{:else if name === 'play'}
<path d="m5 3 14 9-14 9V3z" />
{:else if name === 'upload'}
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" />
{:else if name === 'save'}
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" /><path d="M17 21v-8H7v8M7 3v5h8" />
{:else if name === 're'}
<path d="m17 1 4 4-4 4" /><path d="M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
{:else if name === 'chart'}
<path d="M18 20V10M12 20V4M6 20v-6" />
{:else if name === '?'}
<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line>
{:else if name === '!'}
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01" />
{:else if name === 'loading'}
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
{:else if name === 'doc'}
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" />
{/if}
{@html icons[name] || ''}
</svg>

View File

@ -3,8 +3,9 @@
</script>
<script>
import { Beep } from '$wails/go/ui/UI';
import { createEventDispatcher } from 'svelte';
import { fade, fly } from 'svelte/transition';
import { Beep } from '$wails/go/ui/UI.js';
import Icon from './icon.svelte';
export let show = true;
@ -35,6 +36,7 @@
}
function close() {
show = false;
dispatch('close');
}
</script>
@ -42,8 +44,8 @@
<svelte:window on:keydown={keydown} />
{#if show}
<div class="modal outer" on:pointerdown|self={Beep}>
<div class="inner" style:max-width={width || '80vw'}>
<div class="modal outer" on:pointerdown|self={Beep} transition:fade={{ duration: 200 }}>
<div class="inner" style:max-width={width || '80vw'} transition:fly={{ y: 20, duration: 200 }}>
{#if title}
<header>
<div class="title">{title}</div>
@ -73,19 +75,14 @@
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
margin: 0;
padding-top: 50px;
padding: 2rem;
--wails-draggable: drag;
}
:global(#root.platform-darwin) .outer {
margin-top: var(--darwin-titlebar-height);
}
.inner {
max-height: 80vh;
background-color: #fff;
margin-left: auto;
margin-right: auto;
margin-bottom: auto;
margin: auto;
width: 100%;
border-radius: var(--radius);
display: flex;

View File

@ -1,10 +1,10 @@
<script>
import { looseJsonIsValid } from '$lib/strings';
import { looseJsonIsValid } from '$lib/strings.js';
import { EJSON } from 'bson';
import { createEventDispatcher, onDestroy } from 'svelte';
import Icon from './icon.svelte';
import Modal from './modal.svelte';
import ObjectEditor from './objecteditor.svelte';
import ObjectEditor from './editors/objecteditor.svelte';
export let data;
export let readonly = false;

View File

@ -1,40 +1,61 @@
<script>
import { createEventDispatcher, onMount } from 'svelte';
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
import Icon from './icon.svelte';
export let tabs = [];
export let selectedKey = {};
export let selectedKey = '';
export let canAddTab = false;
export let multiline = false;
export let compact = true;
const dispatch = createEventDispatcher();
const maxPixelsPerMultilineTab = 120;
const activeIndicatorLeft = tweened(0, { easing: cubicOut, duration: 400 });
const activeIndicatorRight = tweened(0, { easing: cubicOut, duration: 400 });
const liElements = {};
let navEl;
let pixelsPerTab = 0;
$: tabs && navEl && updateMeasurements();
function updateMeasurements() {
pixelsPerTab = (navEl.offsetWidth ?? 0) / tabs.length;
}
function select(tabKey) {
selectedKey = tabKey;
dispatch('select', tabKey);
}
function moveActiveIndicator(target = liElements[selectedKey]) {
if (!compact) {
return;
}
const navRect = navEl.getBoundingClientRect();
const itemRect = target.getBoundingClientRect();
$activeIndicatorLeft = itemRect.x - navRect.x;
$activeIndicatorRight = navRect.right - itemRect.right;
}
onMount(() => {
window.addEventListener('resize', updateMeasurements);
if (selectedKey) {
moveActiveIndicator(liElements[selectedKey]);
}
});
</script>
<nav class="tabs" class:multiline={multiline || (pixelsPerTab < maxPixelsPerMultilineTab)} bind:this={navEl}>
<svelte:window on:resize={() => moveActiveIndicator()} />
<nav class="tabs" class:compact bind:this={navEl}>
<ul>
{#each tabs as tab (tab.key)}
<li class:active={tab.key === selectedKey}>
<li
class="tab"
class:active={tab.key === selectedKey}
class:closable={tab.closable}
bind:this={liElements[tab.key]}
on:mouseenter={event => moveActiveIndicator(event.target)}
on:mouseleave={() => moveActiveIndicator()}
>
<button class="tab" on:click={() => select(tab.key)}>
{#if tab.icon} <Icon name={tab.icon} /> {/if}
<span class="label">{tab.title}</span>
</button>
{#if tab.closable}
<button class="button-small" on:click={() => dispatch('closeTab', tab.key)}>
<Icon name="x" />
@ -44,39 +65,42 @@
{/each}
{#if canAddTab}
<li class="tab add">
<button class="tab" on:click={() => dispatch('addTab')}>
<li>
<button class="button-small" on:click={() => dispatch('addTab')}>
<Icon name="+" />
</button>
</li>
{/if}
</ul>
{#if compact}
<span
class="activeindicator"
style:left="{$activeIndicatorLeft}px"
style:right="{$activeIndicatorRight}px"
/>
{/if}
</nav>
<style>
nav {
position: relative;
}
ul {
overflow-x: auto;
display: flex;
list-style: none;
}
li {
display: inline-block;
flex: 1;
position: relative;
}
li.add {
flex: 0 1;
}
.tabs :global(svg) {
width: 13px;
height: 13px;
nav.tabs :global(svg) {
vertical-align: bottom;
}
li.active :global(svg) {
color: #fff;
}
button.tab {
width: 100%;
@ -96,20 +120,50 @@
border-right: 1px solid #ccc;
}
li.active button.tab {
color: #fff;
background-color: #00008b;
border-color: #00008b;
background-color: var(--selection);
border-color: var(--primary);
cursor: not-allowed;
}
nav.tabs.multiline button.tab .label {
display: block;
margin-top: 4px;
button.tab .label {
margin-top: 5px;
display: inline-block;
}
.button-small {
nav.tabs .button-small {
margin: 12px 0 0 4px;
}
li.closable .button-small {
display: none;
position: absolute;
top: 0;
right: 7px;
top: 7px;
}
li.closable.active {
padding-right: 20px;
}
li.closable.active .button-small {
display: block;
}
nav.tabs.compact {
border-bottom: 1px solid #aaa;
}
nav.tabs.compact li {
border-bottom: 2px solid transparent;
}
nav.tabs.compact button.tab {
border: none;
color: inherit;
background-color: transparent;
}
.activeindicator {
display: block;
position: absolute;
bottom: -1px;
height: 2px;
background-color: var(--primary);
}
</style>

View File

@ -1,7 +1,7 @@
<script>
import Modal from '$components/modal.svelte';
import alink from '$lib/actions/alink';
import environment from '$lib/stores/environment';
import alink from '$lib/actions/alink.js';
import environment from '$lib/stores/environment.js';
</script>
<Modal width="400px" title=" " on:close>
@ -19,7 +19,7 @@
<hr />
<div class="info">
<p class="copy">© Romein van Buren, 2023.</p>
<p class="copy">© Romein van Buren, 2022-2024.</p>
<p>
<a href="https://garraflavatra.github.io/rolens/" use:alink>Documentation</a> |
<a href="https://github.com/garraflavatra/rolens" use:alink>GitHub</a> |
@ -30,6 +30,10 @@
</Modal>
<style>
.brand, .info {
line-height: 1.2;
}
.brand {
display: flex;
align-items: center;
@ -40,9 +44,9 @@
flex: 0 1 125px;
}
.brand .title {
font-size: 2.25rem;
font-size: 2.25em;
font-weight: 600;
line-height: 2.5rem;
margin-bottom: .25em;
}
.brand .title .version {
font-size: 80%;
@ -51,7 +55,6 @@
}
.brand .description {
font-size: 1.5rem;
line-height: 1.6rem;
}
hr {
@ -60,7 +63,6 @@
.info {
font-size: 1.25rem;
line-height: 1.25rem;
margin: 0 1rem 1rem;
text-align: center;
}

View File

@ -1,8 +1,8 @@
<script>
import DirectoryChooser from '$components/directorychooser.svelte';
import DirectoryChooser from '$components/editors/directorychooser.svelte';
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input';
import settings from '$lib/stores/settings';
import input from '$lib/actions/input.js';
import settings from '$lib/stores/settings.js';
</script>
<Modal title="Preferences" on:close>
@ -15,7 +15,13 @@
<label for="defaultSort">Default sort query</label>
<label class="field">
<input type="text" class="code" bind:value={$settings.defaultSort} id="defaultSort" use:input={{ type: 'json' }} />
<input
type="text"
class="code"
bind:value={$settings.defaultSort}
id="defaultSort"
use:input={{ type: 'json' }}
/>
</label>
<label for="autosubmitQuery">Autosubmit query</label>

View File

@ -1,4 +1,4 @@
import { BrowserOpenURL } from '$wails/runtime/runtime';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
export default function alink(node) {
node.addEventListener('click', e => {

View File

@ -1,4 +1,4 @@
import environment from '$lib/stores/environment';
import environment from '$lib/stores/environment.js';
import { get } from 'svelte/store';
export function controlKeyDown(event) {

View File

@ -1,6 +1,6 @@
import { isInt } from '$lib/math';
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo';
import { jsonLooseParse } from '$lib/strings';
import { isInt } from '$lib/math.js';
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo/index.js';
import { jsonLooseParse } from '$lib/strings.js';
export default function input(node, { autofocus, type, onValid, onInvalid, mandatory } = {
autofocus: false,
@ -10,6 +10,9 @@ export default function input(node, { autofocus, type, onValid, onInvalid, manda
mandatory: false,
}) {
node.setAttribute('spellcheck', false);
node.setAttribute('autocomplete', false);
const getMessage = () => {
const checkInteger = () => (isInt(node.value) ? false : 'Value must be an integer');
const checkNumberBoundaries = boundaries => {

View File

@ -1,3 +1,4 @@
import { AskConfirmation } from '$wails/go/app/App.js';
import InputDialog from '../dialogs/input.svelte';
function newDialog(dialogComponent, data = {}) {
@ -5,11 +6,17 @@ function newDialog(dialogComponent, data = {}) {
outlet.className = 'dialogoutlet';
document.getElementById('dialogoutlets').appendChild(outlet);
const instance = new dialogComponent({ target: outlet, props: data });
const instance = new dialogComponent({
target: outlet,
intro: true,
props: data,
});
instance.$close = function() {
instance.$destroy();
outlet.remove();
setTimeout(() => {
instance.$destroy();
outlet.remove();
}, 200);
};
instance.$on('close', instance.$close);
@ -29,6 +36,10 @@ function enterText(title = '', description = '', value = '') {
});
}
const dialogs = { new: newDialog, enterText };
function confirm(message = '') {
return AskConfirmation(message);
}
const dialogs = { new: newDialog, enterText, confirm };
export default dialogs;

View File

@ -0,0 +1,46 @@
{
"radio": "<circle cx=\"12\" cy=\"12\" r=\"2\"></circle><path d=\"M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14\"></path>",
"chev-l": "<polyline points=\"15 18 9 12 15 6\"></polyline>",
"chev-r": "<polyline points=\"9 18 15 12 9 6\"></polyline>",
"chev-d": "<polyline points=\"6 9 12 15 18 9\"></polyline>",
"chev-u": "<path d=\"m18 15-6-6-6 6\"/>",
"chevs-l": "<path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/>",
"chevs-r": "<path d=\"m13 17 5-5-5-5M6 17l5-5-5-5\"/>",
"arr-d": "<path d=\"M12 5v14M19 12l-7 7-7-7\"/>",
"arr-r": "<path d=\"M5 12h14M12 5l7 7-7 7\"/>",
"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>",
"x": "<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>",
"+": "<line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"></line><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>",
"-": "<line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>",
"reload": "<polyline points=\"23 4 23 10 17 10\"></polyline><polyline points=\"1 20 1 14 7 14\"></polyline><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"></path>",
"clipboard": "<path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"></path><rect x=\"8\" y=\"2\" width=\"8\" height=\"4\" rx=\"1\" ry=\"1\"></rect>",
"edit": "<path d=\"M12 20h9\"></path><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"></path>",
"check": "<path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"></path><polyline points=\"22 4 12 14.01 9 11.01\"></polyline>",
"list": "<path d=\"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\"/>",
"table": "<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\"/>",
"form": "<path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><path d=\"M14 2v6h6M16 13H8M16 17H8M10 9H8\"/>",
"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\"/>",
"zap": "<path d=\"M13 2 3 14h9l-1 8 10-12h-9l1-8z\"/>",
"server": "<rect x=\"2\" y=\"2\" width=\"20\" height=\"8\" rx=\"2\" ry=\"2\"/><rect x=\"2\" y=\"14\" width=\"20\" height=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/>",
"text": "<path d=\"M4 7V4h16v3M9 20h6M12 4v16\"/>",
"hash": "<path d=\"M4 9h16M4 15h16M10 3 8 21M16 3l-2 18\"/>",
"toggle-l": "<rect x=\"1\" y=\"5\" width=\"22\" height=\"14\" rx=\"7\" ry=\"7\"/><circle cx=\"8\" cy=\"12\" r=\"3\"/>",
"cal": "<rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><path d=\"M16 2v4M8 2v4M3 10h18\"/>",
"code": "<path d=\"m16 18 6-6-6-6M8 6l-6 6 6 6\"/>",
"target": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/>",
"trash": "<path d=\"M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6\"/>",
"anchor": "<circle cx=\"12\" cy=\"5\" r=\"3\"/><path d=\"M12 22V8M5 12H2a10 10 0 0 0 20 0h-3\"/>",
"o": "<circle cx=\"12\" cy=\"12\" r=\"10\"/>",
"info": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/>",
"play": "<path d=\"m5 3 14 9-14 9V3z\"/>",
"upload": "<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12\"/>",
"save": "<path d=\"M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z\"/><path d=\"M17 21v-8H7v8M7 3v5h8\"/>",
"re": "<path d=\"m17 1 4 4-4 4\"/><path d=\"M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4\"/><path d=\"M21 13v2a4 4 0 0 1-4 4H3\"/>",
"chart": "<path d=\"M18 20V10M12 20V4M6 20v-6\"/>",
"?": "<circle cx=\"12\" cy=\"12\" r=\"10\"></circle><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line>",
"!": "<path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01\"/>",
"loading": "<path d=\"M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83\"/>",
"shell": "<path d=\"m4 17 6-6-6-6M12 19h8\"/>",
"doc": "<path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><path d=\"M14 2v6h6M16 13H8M16 17H8M10 9H8\"/>",
"columns": "<path d=\"M12 3h7a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-7m0-18H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7m0-18v18\"/>"
}

View File

@ -1,5 +1,5 @@
<script>
import { locales } from '$lib/mongo';
import { locales } from './index.js';
const defaultCollation = {
locale: 'en_US',

View File

@ -1,27 +0,0 @@
import { StartProgressBar, StopProgressBar } from '$wails/go/ui/UI';
let taskCounter = 0;
export function startProgress(taskDescription = 'Loading…') {
const taskIndex = ++taskCounter;
let started = false;
const debouncer = setTimeout(() => {
StartProgressBar(taskIndex, taskDescription);
started = true;
}, 150);
const task = {
id: taskIndex,
description: taskDescription,
end: () => {
clearTimeout(debouncer);
if (started) {
StopProgressBar(taskIndex);
}
},
};
return task;
}

View File

@ -1,4 +1,4 @@
import { Environment } from '$wails/go/app/App';
import { Environment } from '$wails/go/app/App.js';
import { writable } from 'svelte/store';
const { set, subscribe } = writable({});
@ -11,5 +11,10 @@ async function reload() {
reload();
subscribe(env => {
// @ts-ignore
document.body.dataset.platform = env?.platform;
});
const environment = { reload, subscribe };
export default environment;

View File

@ -1,21 +1,23 @@
import dialogs from '$lib/dialogs';
import { startProgress } from '$lib/progress';
import dialogs from '$lib/dialogs.js';
import { get, writable } from 'svelte/store';
import applicationInited from './inited';
import queries from './queries';
import windowTitle from './windowtitle';
import applicationInited from './inited.js';
import queries from './queries.js';
import windowTitle from './windowtitle.js';
import ExportDialog from '$organisms/connection/collection/dialogs/export.svelte';
import IndexDetailDialog from '$organisms/connection/collection/dialogs/indexdetail.svelte';
import QueryChooserDialog from '$organisms/connection/collection/dialogs/querychooser.svelte';
import DumpDialog from '$organisms/connection/database/dialogs/dump.svelte';
import HostDetailDialog from '$organisms/connection/host/dialogs/hostdetail.svelte';
import DuplicateDialog from '$organisms/connection/collection/dialogs/duplicate.svelte';
import {
CreateIndex,
DropCollection,
DropDatabase,
DropIndex,
DuplicateCollection,
ExecuteShellScript,
GetIndexes,
HostLogs,
Hosts,
@ -27,7 +29,7 @@ import {
RemoveHost,
RenameCollection,
TruncateCollection
} from '$wails/go/app/App';
} from '$wails/go/app/App.js';
const { set, subscribe } = writable({});
const getValue = () => get({ subscribe });
@ -37,7 +39,11 @@ async function refresh() {
const hosts = await Hosts();
const hostTree = getValue();
for (const [ hostKey, hostDetails ] of Object.entries(hosts)) {
for (const [
hostKey,
hostDetails,
] of Object.entries(hosts)) {
hostTree[hostKey] = hostTree[hostKey] || {};
const host = hostTree[hostKey];
host.key = hostKey;
@ -45,7 +51,17 @@ async function refresh() {
host.uri = hostDetails.uri;
host.open = async function() {
const { databases: dbNames, status, statusError, systemInfo, systemInfoError } = await OpenConnection(hostKey);
host.loading = true;
set(hostTree);
const {
databases: dbNames,
status,
statusError,
systemInfo,
systemInfoError,
} = await OpenConnection(hostKey);
host.status = status;
host.statusError = statusError;
host.systemInfo = systemInfo;
@ -60,7 +76,10 @@ async function refresh() {
host.databases[dbKey] = host.databases[dbKey] || {};
}
for (const [ dbKey, database ] of Object.entries(host.databases)) {
for (const [
dbKey,
database,
] of Object.entries(host.databases)) {
if (!database.new && !dbNames.includes(dbKey)) {
delete host.databases[dbKey];
continue;
@ -73,6 +92,9 @@ async function refresh() {
delete database.new;
database.open = async function() {
database.loading = true;
set(hostTree);
const { collections: collNames, stats, statsError } = await OpenDatabase(hostKey, dbKey);
database.stats = stats;
database.statsError = statsError;
@ -85,7 +107,10 @@ async function refresh() {
database.collections[collKey] = database.collections[collKey] || {};
}
for (const [ collKey, collection ] of Object.entries(database.collections)) {
for (const [
collKey,
collection,
] of Object.entries(database.collections)) {
if (!collection.new && !collNames.includes(collKey)) {
delete database.collections[collKey];
continue;
@ -94,6 +119,7 @@ async function refresh() {
collection.key = collKey;
collection.dbKey = dbKey;
collection.hostKey = hostKey;
collection.viewKey = 'list';
collection.indexes = collection.indexes || [];
delete collection.new;
@ -110,20 +136,45 @@ async function refresh() {
collection.rename = async function() {
const newCollKey = await dialogs.enterText('Rename collection', `Enter a new name for collection ${collKey}.`, collKey);
if (newCollKey && (newCollKey !== collKey)) {
const progress = startProgress(`Renaming collection "${collKey}" to "${newCollKey}"…`);
const ok = await RenameCollection(hostKey, dbKey, collKey, newCollKey);
await database.open();
progress.end();
return ok;
}
};
collection.duplicate = async function() {
const dialog = dialogs.new(DuplicateDialog, { host, dbKey, collKey });
return new Promise(resolve => {
dialog.$on('duplicate', async event => {
const success = await DuplicateCollection(
hostKey,
dbKey,
collKey,
event.detail.newHost,
event.detail.newDb,
event.detail.newColl
);
if (success) {
await refresh();
dialog.$close();
resolve();
}
});
});
};
collection.export = function(query) {
const dialog = dialogs.new(ExportDialog, { collection, query });
return new Promise(resolve => {
dialog.$on('export', async event => {
const success = await PerformFindExport(hostKey, dbKey, collKey, JSON.stringify(event.detail.exportInfo));
const success = await PerformFindExport(
hostKey,
dbKey,
collKey,
JSON.stringify(event.detail.exportInfo)
);
if (success) {
dialog.$close();
resolve();
@ -158,6 +209,7 @@ async function refresh() {
collection.drop = async function() {
const success = await DropCollection(hostKey, dbKey, collKey);
if (success) {
delete database.collections[collKey];
await refresh();
}
};
@ -197,10 +249,17 @@ async function refresh() {
return new Promise(resolve => {
dialog.$on('create', async event => {
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(event.detail.index));
const newIndexName = await CreateIndex(
collection.hostKey,
collection.dbKey,
collection.key,
JSON.stringify(event.detail.index)
);
if (newIndexName) {
dialog.$close();
}
resolve(newIndexName);
});
});
@ -224,10 +283,17 @@ async function refresh() {
});
});
};
collection.executeShellScript = async function(script) {
const result = await ExecuteShellScript(hostKey, dbKey, collKey, script);
return result;
};
}
await refresh();
windowTitle.setSegments(dbKey, host.name, 'Rolens');
database.loading = false;
set(hostTree);
};
database.dump = function() {
@ -245,14 +311,11 @@ async function refresh() {
};
database.drop = async function() {
const progress = startProgress(`Dropping database "${dbKey}"…`);
const success = await DropDatabase(hostKey, dbKey);
if (success) {
delete host.databases[dbKey];
await refresh();
}
progress.end();
};
database.newCollection = async function() {
@ -262,9 +325,21 @@ async function refresh() {
await database.open();
}
};
database.executeShellScript = async function(script) {
const result = await ExecuteShellScript(hostKey, dbKey, '', script);
return result;
};
}
await refresh();
host.loading = false;
set(hostTree);
};
host.executeShellScript = async function(script) {
const result = await ExecuteShellScript(hostKey, '', '', script);
return result;
};
host.newDatabase = async function() {

View File

@ -1,6 +1,6 @@
import { derived } from 'svelte/store';
import environment from './environment';
import applicationSettings from './settings';
import environment from './environment.js';
import applicationSettings from './settings.js';
let alreadyInited = false;

View File

@ -1,4 +1,4 @@
import { RemoveQuery, SavedQueries, SaveQuery, UpdateQueries } from '$wails/go/app/App';
import { RemoveQuery, SavedQueries, SaveQuery, UpdateQueries } from '$wails/go/app/App.js';
import { get, writable } from 'svelte/store';
const { set, subscribe } = writable({});

View File

@ -1,4 +1,4 @@
import { Settings, UpdateSettings } from '$wails/go/app/App';
import { Settings, UpdateSettings } from '$wails/go/app/App.js';
import { writable } from 'svelte/store';
const { set, subscribe } = writable({});

View File

@ -1,4 +1,4 @@
import { ReportSharedStateVariable } from '$wails/go/app/App';
import { ReportSharedStateVariable } from '$wails/go/app/App.js';
import { writable } from 'svelte/store';
function sharedStateStore(name) {

View File

@ -1,6 +1,6 @@
import dialogs from '$lib/dialogs';
import dialogs from '$lib/dialogs.js';
import ViewConfigDialog from '$organisms/connection/collection/dialogs/viewconfig.svelte';
import { UpdateViewStore, Views } from '$wails/go/app/App';
import { UpdateViewStore, Views } from '$wails/go/app/App.js';
import { get, writable } from 'svelte/store';
const { set, subscribe } = writable({});

View File

@ -1,4 +1,4 @@
import { WindowSetTitle } from '$wails/runtime/runtime';
import { WindowSetTitle } from '$wails/runtime/runtime.js';
import { writable } from 'svelte/store';
const { set, subscribe } = writable('Rolens');

View File

@ -22,3 +22,7 @@ export function looseJsonIsValid(json) {
return false;
}
}
export function stringCouldBeID(string) {
return /^[a-zA-Z0-9_-]{1,}$/.test(string);
}

View File

@ -1,15 +1,20 @@
import './styles/loading.css';
import './styles/reset.css';
import './styles/style.css';
import { LogError } from '$wails/runtime';
import { EventsOn, LogError } from '$wails/runtime/runtime.js';
import dialogs from '$lib/dialogs.js';
import App from './app.svelte';
import AboutDialog from './dialogs/about.svelte';
import SettingsDialog from './dialogs/settings/index.svelte';
window.addEventListener('unhandledrejection', event => {
LogError('Unhandled JS rejection: ' + event.reason);
});
// @ts-ignore Argument IS correct.
EventsOn('global.about', () => dialogs.new(AboutDialog));
EventsOn('global.settings', () => dialogs.new(SettingsDialog));
const app = new App({ target: document.getElementById('app') });
export default app;

View File

@ -2,12 +2,12 @@
import Details from '$components/details.svelte';
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import ObjectEditor from '$components/objecteditor.svelte';
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
import ObjectEditor from '$components/editors/objecteditor.svelte';
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo/index.js';
import Collation from '$lib/mongo/collation.svelte';
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
import { Aggregate } from '$wails/go/app/App';
import { BrowserOpenURL } from '$wails/runtime/runtime';
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings.js';
import { Aggregate } from '$wails/go/app/App.js';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
import { onMount } from 'svelte';
export let collection;
@ -68,7 +68,8 @@
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="field">
<ObjectEditor bind:text={stage.data}
<ObjectEditor
bind:text={stage.data}
on:inited={e => {
e.detail.editor.dispatch({
changes: {
@ -80,7 +81,6 @@
anchor: 3,
},
});
e.detail.editor.focus();
}} />
</label>
</Details>

View File

@ -1,9 +1,9 @@
<script>
import FormInput from '$components/forminput.svelte';
import FormInput from '$components/editors/forminput.svelte';
import Hint from '$components/hint.svelte';
import Icon from '$components/icon.svelte';
import { inputTypes } from '$lib/mongo';
import { resolveKeypath, setValue } from '$lib/objects';
import { inputTypes } from '$lib/mongo/index.js';
import { resolveKeypath, setValue } from '$lib/objects.js';
export let item = {};
export let view = {};
@ -50,7 +50,12 @@
</span>
</div>
<div class="input">
<FormInput {column} bind:value={keypathProxy[column.key]} bind:valid={validity[column.key]} autofocus={index === 0} />
<FormInput
{column}
bind:value={keypathProxy[column.key]}
bind:valid={validity[column.key]}
autofocus={index === 0}
/>
</div>
</label>
{:else}

View File

@ -0,0 +1,90 @@
<script>
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input.js';
import hostTree from '$lib/stores/hosttree.js';
import { createEventDispatcher } from 'svelte';
export let host = {};
export let dbKey = '';
export let collKey = '';
const dispatch = createEventDispatcher();
let newHost = host.key;
let newDb = dbKey;
let newColl = `${collKey}-duplicate`;
function duplicate() {
dispatch('duplicate', { newHost, newDb, newColl });
}
</script>
<Modal title="Duplicate collection" width="500px" on:close>
<div class="duplicate">
<div class="origin">
<div class="field">
<span class="label">{host.name || host.uri}</span>
<!-- <input type="text" readonly value={host.name || host.uri} /> -->
</div>
<div class="field">
<span class="label">{dbKey}</span>
<!-- <input type="text" readonly value={dbKey} /> -->
</div>
<div class="field">
<span class="label">{collKey}</span>
<!-- <input type="text" readonly value={collKey} /> -->
</div>
</div>
<div class="arrow">
<Icon name="arr-r" />
</div>
<div class="destination">
<label class="field">
<span class="label">Host</span>
<select bind:value={newHost}>
{#each Object.values($hostTree) as { key, name }}
<option value={key} selected={key === host.key}>{name}</option>
{/each}
</select>
</label>
<label class="field">
<span class="label">Database</span>
<input type="text" bind:value={newDb} use:input />
</label>
<label class="field">
<span class="label">Collection</span>
<input type="text" bind:value={newColl} use:input={{ autofocus: true }} />
</label>
</div>
</div>
<svelte:fragment slot="footer">
<button class="button" on:click={duplicate}>
<Icon name="play" /> Duplicate
</button>
</svelte:fragment>
</Modal>
<style>
.duplicate {
display: grid;
grid-template: auto / 1fr auto 1fr;
gap: 0.5rem;
}
.arrow {
align-self: center;
}
.field:not(:last-child) {
margin-bottom: 0.5rem;
}
.origin .field .label {
width: 100%;
display: inline-block;
background-color: #fff;
}
</style>

View File

@ -1,7 +1,7 @@
<script>
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import views from '$lib/stores/views';
import views from '$lib/stores/views.js';
import { createEventDispatcher } from 'svelte';
export let collection;
@ -16,7 +16,7 @@
}
</script>
<Modal title="Export results" width="450px" on:close>
<Modal title="Export results" width="500px" on:close>
<form on:submit|preventDefault={submit}>
<label class="field">
<span class="label">Export</span>
@ -40,7 +40,10 @@
<label class="field">
<span class="label">View to use</span>
<select bind:value={exportInfo.viewKey}>
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [ key, { name } ]}
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [
key,
{ name },
]}
<option value={key}>{name}</option>
{/each}
</select>

View File

@ -1,7 +1,7 @@
<script>
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input';
import input from '$lib/actions/input.js';
import { createEventDispatcher } from 'svelte';
export let collection;
@ -27,7 +27,12 @@
<form on:submit|preventDefault={create}>
<label class="field name">
<span class="label">Name</span>
<input type="text" placeholder="Optional" bind:value={index.name} use:input={{ autofocus: true }} />
<input
type="text"
placeholder="Optional"
bind:value={index.name}
use:input={{ autofocus: true }}
/>
</label>
<div class="toggles">
@ -62,8 +67,9 @@
<div class="row">
<label class="field">
<span class="label">Key</span>
<input type="text" placeholder="_id" bind:value={rule.key}>
<input type="text" placeholder="_id" bind:value={rule.key} />
</label>
<label class="field">
<select bind:value={rule.sort}>
<option value={1}>Ascending</option>
@ -71,6 +77,7 @@
<option value="hashed" disabled={index.model.length > 1}>Hashed</option>
</select>
</label>
<button type="button" class="button danger" on:click={() => removeRule(ruleIndex)} disabled={index.model.length < 2}>
<Icon name="-" />
</button>
@ -85,6 +92,7 @@
<button class="button" on:click={addRule} disabled={index.model.some(r => r.sort === 'hashed')}>
<Icon name="+" /> Add rule
</button>
<button class="button" on:click={create} disabled={!index.model.length || index.model.some(r => !r.key)}>
<Icon name="+" /> Create index
</button>

View File

@ -1,11 +1,11 @@
<script>
import Grid from '$components/grid.svelte';
import Grid from '$components/grid/grid.svelte';
import Hint from '$components/hint.svelte';
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input';
import hostTree from '$lib/stores/hosttree';
import queries from '$lib/stores/queries';
import input from '$lib/actions/input.js';
import hostTree from '$lib/stores/hosttree.js';
import queries from '$lib/stores/queries.js';
import { createEventDispatcher } from 'svelte';
export let queryToSave = undefined;
@ -71,7 +71,7 @@
<input type="text" bind:value={queryToSave.name} use:input={{ autofocus: true }} />
</label>
<label class="field">
<textarea bind:value={queryToSave.remarks} placeholder="Remarks…" use:input></textarea>
<textarea bind:value={queryToSave.remarks} placeholder="Remarks…" use:input />
</label>
{/if}

View File

@ -2,9 +2,9 @@
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import TabBar from '$components/tabbar.svelte';
import input from '$lib/actions/input';
import { randomString } from '$lib/math';
import views from '$lib/stores/views';
import input from '$lib/actions/input.js';
import { randomString } from '$lib/math.js';
import views from '$lib/stores/views.js';
export let collection;
export let firstItem = {};
@ -114,7 +114,12 @@
{#key collection.viewKey}
<label class="field">
<span class="label">View name</span>
<input type="text" use:input={{ autofocus: true }} bind:value={$views[collection.viewKey].name} disabled={collection.viewKey === 'list'} />
<input
type="text"
use:input={{ autofocus: true }}
bind:value={$views[collection.viewKey].name}
disabled={collection.viewKey === 'list'}
/>
</label>
{/key}
<label class="field">
@ -128,7 +133,11 @@
{#if $views[collection.viewKey].type === 'list'}
<div class="flex">
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[collection.viewKey].hideObjectIndicators} />
<input
type="checkbox"
id="hideObjectIndicators"
bind:checked={$views[collection.viewKey].hideObjectIndicators}
/>
<label for="hideObjectIndicators">
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
</label>
@ -185,16 +194,41 @@
</span>
</label>
<button class="button" type="button" on:click={() => addColumn(columnIndex)} title="Add column before this one">
<button
class="button"
type="button"
on:click={() => addColumn(columnIndex)}
title="Add column before this one"
>
<Icon name="+" />
</button>
<button class="button" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
<button
class="button"
type="button"
on:click={() => moveColumn(columnIndex, -1)}
disabled={columnIndex === 0}
title="Move column one position up"
>
<Icon name="chev-u" />
</button>
<button class="button" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[collection.viewKey].columns.length - 1} title="Move column one position down">
<button
class="button"
type="button"
on:click={() => moveColumn(columnIndex, 1)}
disabled={columnIndex === $views[collection.viewKey].columns.length - 1}
title="Move column one position down"
>
<Icon name="chev-d" />
</button>
<button class="button danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
<button
class="button danger"
type="button"
on:click={() => removeColumn(columnIndex)}
title="Remove this column"
>
<Icon name="x" />
</button>
</div>
@ -202,10 +236,16 @@
<p>No columns yet</p>
{/each}
</div>
<button class="button" on:click={addColumn}>
<Icon name="+" /> Add column
</button>
<button class="button" on:click={addSuggestedColumns} disabled={!Object.keys(firstItem || {}).length}>
<button
class="button"
on:click={addSuggestedColumns}
disabled={!Object.keys(firstItem || {}).length}
>
<Icon name="zap" /> Add suggested columns
</button>
{/if}

View File

@ -1,19 +1,19 @@
<script>
import Grid from '$components/grid.svelte';
import Grid from '$components/grid/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import input from '$lib/actions/input';
import { deepClone } from '$lib/objects';
import { startProgress } from '$lib/progress';
import applicationSettings from '$lib/stores/settings';
import views from '$lib/stores/views';
import { convertLooseJson } from '$lib/strings';
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
import input from '$lib/actions/input.js';
import dialogs from '$lib/dialogs.js';
import { deepClone } from '$lib/objects.js';
import applicationSettings from '$lib/stores/settings.js';
import views from '$lib/stores/views.js';
import { convertLooseJson, stringCouldBeID } from '$lib/strings.js';
import { CountItems, FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App.js';
import { EJSON } from 'bson';
import { onMount } from 'svelte';
export let collection;
export let visible = false;
const defaults = {
query: '{}',
@ -25,15 +25,21 @@
let form = { ...defaults };
let result = {};
let countResult = {};
let submittedForm = {};
let queryField;
let activePath = [];
let objectViewerData;
let querying = false;
let counting = false;
let objectViewerSuccessMessage = '';
let viewsForCollection = {};
// $: 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;
@ -42,19 +48,27 @@
}
async function submitQuery() {
if (querying) {
if (querying || !visible) {
return;
}
if (stringCouldBeID(form.query)) {
form.query = `{ "_id": "${form.query}" }`;
}
querying = `Querying ${collection.key}…`;
activePath = [];
const newResult = await FindItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify({
fields: convertLooseJson(form.fields || defaults.fields),
limit: form.limit ?? defaults.limit,
query: convertLooseJson(form.query) || defaults.query,
skip: form.skip ?? defaults.skip,
sort: convertLooseJson(form.sort) || defaults.sort,
}));
const newResult = await FindItems(
collection.hostKey,
collection.dbKey,
collection.key, JSON.stringify({
fields: convertLooseJson(form.fields || defaults.fields),
limit: form.limit ?? defaults.limit,
query: convertLooseJson(form.query) || defaults.query,
skip: form.skip ?? defaults.skip,
sort: convertLooseJson(form.sort) || defaults.sort,
})
);
if (newResult) {
newResult.results = newResult.results?.map(s => EJSON.parse(s, { relaxed: false }));
@ -66,6 +80,17 @@
querying = false;
}
async function countItems() {
counting = true;
countResult = await CountItems(
collection.hostKey,
collection.dbKey,
collection.key,
convertLooseJson(form.query) || defaults.query
);
counting = false;
}
async function refresh() {
if ($applicationSettings.autosubmitQuery) {
await submitQuery();
@ -115,7 +140,18 @@
if (!activePath[0]) {
return;
}
const ok = await RemoveItemById(collection.hostKey, collection.dbKey, collection.key, activePath[0]);
const sure = await dialogs.confirm('Are you sure you wish to delete this item?');
if (!sure) {
return;
}
const ok = await RemoveItemById(
collection.hostKey,
collection.dbKey,
collection.key,
activePath[0]
);
if (ok) {
await submitQuery();
}
@ -141,7 +177,6 @@
}
async function saveDocument(event) {
const progress = startProgress('Performing update…');
const success = await UpdateFoundDocument(
collection.hostKey,
collection.dbKey,
@ -154,12 +189,10 @@
objectViewerSuccessMessage = 'Document has been saved!';
submitQuery();
}
progress.end();
}
$: collection && refresh();
onMount(refresh);
$: visible && refresh();
</script>
<div class="find">
@ -167,12 +200,13 @@
<div class="formrow one">
<label class="field">
<span class="label">Query or id</span>
<input type="text"
<input
type="text"
class="code"
placeholder={defaults.query}
autocomplete="off"
spellcheck="false"
use:input={{ type: 'json', autofocus: true }}
use:input
bind:this={queryField}
bind:value={form.query}
/>
@ -208,7 +242,8 @@
<label class="field">
<span class="label">Skip</span>
<input type="number"
<input
type="number"
min="0"
bind:value={form.skip}
use:input
@ -219,7 +254,8 @@
<label class="field">
<span class="label">Limit</span>
<input type="number"
<input
type="number"
min="0"
bind:value={form.limit}
use:input
@ -281,15 +317,33 @@
</div>
<div class="controls">
<div>
{#key result}
<span class="flash-green">Results: {result.total || 0}</span>
{/key}
<div class="count">
{#if counting}
<span>Counting items…</span>
{:else if countResult?.error}
<span>{countResult.error}</span>
{:else if countResult?.total === -1}
<span>Something went wrong</span>
{:else if countResult?.total}
<!-- svelte-ignore a11y-invalid-attribute -->
<a href="" on:click|preventDefault={countItems}>Results: {countResult.total}</a>
{:else if result?.total === -1}
<!-- svelte-ignore a11y-invalid-attribute -->
<a href="" on:click|preventDefault={countItems}>Count items</a>
{:else if result?.total}
{#key result}
<span class="flash-green">Results: {result.total || 0}</span>
{/key}
{/if}
</div>
<div>
<label class="field inline">
<select bind:value={collection.viewKey}>
{#each Object.entries(viewsForCollection) as [ key, view ]}
{#each Object.entries(viewsForCollection) as [
key,
view,
]}
<option value={key}>{view.name}</option>
{/each}
</select>
@ -297,19 +351,49 @@
<Icon name="cog" />
</button>
</label>
<button class="button danger" on:click={removeActive} disabled={!activePath?.length} title="Drop selected item">
<button
class="button danger"
on:click={removeActive}
disabled={!activePath?.length}
title="Drop selected item"
>
<Icon name="-" />
</button>
<button class="button" on:click={first} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)} title="First page">
<button
class="button"
on:click={first}
disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)}
title="First page"
>
<Icon name="chevs-l" />
</button>
<button class="button" on:click={prev} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)} title="Previous {submittedForm.limit} items">
<button
class="button"
on:click={prev}
disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results || (activePage === 0)}
title="Previous {submittedForm.limit} items"
>
<Icon name="chev-l" />
</button>
<button class="button" on:click={next} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results || !lastPage || (activePage >= lastPage)} title="Next {submittedForm.limit} items">
<button
class="button"
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" />
</button>
<button class="button" on:click={last} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results || !lastPage || (activePage >= lastPage)} title="Last page">
<button
class="button"
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>
@ -318,11 +402,24 @@
</div>
{#if objectViewerData}
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
<ObjectViewer
bind:data={objectViewerData}
saveable
on:save={saveDocument}
bind:successMessage={objectViewerSuccessMessage}
/>
{/if}
<datalist id="limits">
{#each [ 1, 5, 10, 25, 50, 100, 200 ] as value}
{#each [
1,
5,
10,
25,
50,
100,
200,
] as value}
<option {value} />
{/each}
</datalist>
@ -379,4 +476,8 @@
justify-content: space-between;
align-items: center;
}
.count {
text-overflow: ellipsis;
}
</style>

View File

@ -1,7 +1,6 @@
<script>
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime';
import { tick } from 'svelte';
import Aggregate from './aggregate.svelte';
@ -9,61 +8,53 @@
import Indexes from './indexes.svelte';
import Insert from './insert.svelte';
import Remove from './remove.svelte';
import Shell from '../shell.svelte';
import Stats from './stats.svelte';
import Update from './update.svelte';
export let host;
export let database;
export let collection;
export let hostKey;
export let dbKey;
export let collKey;
export let tab = 'find';
let find;
const tabs = {
stats: { icon: 'chart', title: 'Stats', component: Stats },
find: { icon: 'db', title: 'Find', component: Find },
insert: { icon: '+', title: 'Insert', component: Insert },
update: { icon: 'edit', title: 'Update', component: Update },
remove: { icon: 'trash', title: 'Remove', component: Remove },
indexes: { icon: 'list', title: 'Indexes', component: Indexes },
aggregate: { icon: 're', title: 'Aggregate', component: Aggregate },
shell: { icon: 'shell', title: 'Shell', component: Shell },
};
$: if (collection) {
collection.hostKey = hostKey;
collection.dbKey = dbKey;
collection.key = collKey;
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
}
$: if (hostKey || dbKey || collKey) {
tab = 'find';
}
EventsOn('OpenCollectionTab', name => (tab = name || tab));
async function catchQuery(event) {
tab = 'find';
await tick();
find.performQuery(event.detail);
tabs.find.instance.performQuery(event.detail);
}
</script>
<div class="view" class:empty={!collection}>
{#if collection}
{#key collection}
<TabBar tabs={[
{ key: 'stats', icon: 'chart', title: 'Stats' },
{ key: 'find', icon: 'db', title: 'Find' },
{ key: 'insert', icon: '+', title: 'Insert' },
{ key: 'update', icon: 'edit', title: 'Update' },
{ key: 'remove', icon: 'trash', title: 'Remove' },
{ key: 'indexes', icon: 'list', title: 'Indexes' },
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
]}
bind:selectedKey={tab} />
<TabBar tabs={Object.values(tabs)} bind:selectedKey={tab} />
<div class="container">
{#if tab === 'stats'} <Stats {collection} />
{:else if tab === 'find'} <Find {collection} bind:this={find} />
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} />
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
{:else if tab === 'remove'} <Remove {collection} />
{:else if tab === 'indexes'} <Indexes {collection} />
{:else if tab === 'aggregate'} <Aggregate {collection} />
{/if}
{#each Object.values(tabs) as view}
<div class="container" class:hidden={tab !== view.key}>
<svelte:component
this={view.component}
visible={tab === view.key}
on:performFind={catchQuery}
{host}
{database}
{collection}
/>
</div>
{/key}
{/each}
{:else}
<BlankState label="Select a collection to continue" />
{/if}
@ -87,6 +78,9 @@
min-height: 0;
min-width: 0;
}
.container.hidden {
display: none;
}
.container > :global(*) {
width: 100%;
}

View File

@ -1,9 +1,9 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import { onMount } from 'svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
export let collection;
export let visible = false;
let activePath = [];
let _indexes = [];
@ -11,6 +11,10 @@
let busy = false;
async function refresh() {
if (!visible) {
return;
}
busy = 'Fetching indexes…';
error = await collection.getIndexes();
@ -49,7 +53,7 @@
}
}
onMount(refresh);
$: visible && refresh();
</script>
<div class="indexes">
@ -69,9 +73,11 @@
<button class="button" on:click={refresh}>
<Icon name="reload" spin={busy} /> Reload
</button>
<button class="button" on:click={createIndex}>
<Icon name="+" /> Create index…
</button>
<button class="button danger" on:click={dropIndex} disabled={!_indexes.length || !activePath[0]}>
<Icon name="x" /> Drop selected
</button>

View File

@ -1,19 +1,20 @@
<script>
import Details from '$components/details.svelte';
import Grid from '$components/grid.svelte';
import Grid from '$components/grid/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectEditor from '$components/objecteditor.svelte';
import ObjectEditor from '$components/editors/objecteditor.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import { randomString } from '$lib/math';
import { inputTypes } from '$lib/mongo';
import views from '$lib/stores/views';
import { capitalise, convertLooseJson, jsonLooseParse } from '$lib/strings';
import { InsertItems } from '$wails/go/app/App';
import { randomString } from '$lib/math.js';
import { inputTypes } from '$lib/mongo/index.js';
import views from '$lib/stores/views.js';
import { capitalise, convertLooseJson, jsonLooseParse } from '$lib/strings.js';
import { InsertItems } from '$wails/go/app/App.js';
import { EJSON } from 'bson';
import { createEventDispatcher, onMount } from 'svelte';
import Form from './components/form.svelte';
export let collection;
export let visible = false;
const dispatch = createEventDispatcher();
const formValidity = {};
@ -98,6 +99,8 @@
views.openConfig(collection);
}
$: visible && editor.focus();
onMount(() => {
if (collection.viewKey === 'list') {
editor.dispatch({
@ -110,7 +113,6 @@
anchor: 3,
},
});
editor.focus();
}
});
</script>

View File

@ -1,11 +1,12 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectEditor from '$components/objecteditor.svelte';
import { convertLooseJson } from '$lib/strings';
import { RemoveItems } from '$wails/go/app/App';
import ObjectEditor from '$components/editors/objecteditor.svelte';
import { convertLooseJson } from '$lib/strings.js';
import { RemoveItems } from '$wails/go/app/App.js';
import { onMount } from 'svelte';
export let collection;
export let visible = false;
let json = '';
let many = true;
@ -23,6 +24,8 @@
);
}
$: visible && editor.focus();
onMount(() => {
editor.dispatch({
changes: {
@ -34,7 +37,6 @@
anchor: 3,
},
});
editor.focus();
});
</script>

View File

@ -1,6 +1,6 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
export let collection;

View File

@ -1,10 +1,10 @@
<script>
import Icon from '$components/icon.svelte';
import input from '$lib/actions/input';
import { atomicUpdateOperators } from '$lib/mongo';
import { deepClone } from '$lib/objects';
import { convertLooseJson, jsonLooseParse } from '$lib/strings';
import { UpdateItems } from '$wails/go/app/App';
import input from '$lib/actions/input.js';
import { atomicUpdateOperators } from '$lib/mongo/index.js';
import { deepClone } from '$lib/objects.js';
import { convertLooseJson, jsonLooseParse } from '$lib/strings.js';
import { UpdateItems } from '$wails/go/app/App.js';
export let collection = {};
@ -27,7 +27,8 @@
});
// function buildCode(form) {
// let operation = '{ ' + form.parameters.filter(p => p.type).map(p => `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
// let operation = '{ ' + form.parameters.filter(p => p.type).map(p =>
// `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
// if (operation === '{ }') {
// operation = '{}';
// }
@ -38,7 +39,8 @@
// form.many && (options += 'multi: true');
// (form.upsert || form.many) && (options += ' }');
// const code = `db.${collection.key}.update(${form.query || '{}'}, ${operation}${options});`;
// const code = `db.${collection.key}.update(${form.query || '{}'},
// ${operation}${options});`;
// return code;
// }
@ -48,7 +50,13 @@
f.parameters = f.parameters.map(param => {
return { ...param, value: convertLooseJson(param.value) };
});
updatedCount = await UpdateItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(f));
updatedCount = await UpdateItems(
collection.hostKey,
collection.dbKey,
collection.key,
JSON.stringify(f)
);
}
function removeParam(index) {
@ -114,7 +122,12 @@
<label class="field">
<span class="label">Filter</span>
<input type="text" class="code" bind:value={form.query} use:input={{ type: 'json', autofocus: true }} placeholder={'{}'} />
<input
type="text"
class="code"
bind:value={form.query}
use:input={{ type: 'json', autofocus: true }}
placeholder={'{}'} />
</label>
<fieldset class="parameters">
@ -132,7 +145,13 @@
</optgroup>
{/each}
</select>
<input type="text" class="code" bind:value={param.value} placeholder={'{}'} use:input={{ type: 'json' }} />
<input
type="text"
class="code"
bind:value={param.value}
placeholder={'{}'}
use:input={{ type: 'json' }}
/>
</label>
<button class="button" disabled={form.parameters.length >= allOperators.length} on:click={() => addParameter()} type="button">

View File

@ -1,11 +1,10 @@
<script>
import DirectoryChooser from '$components/directorychooser.svelte';
import Grid from '$components/grid.svelte';
import DirectoryChooser from '$components/editors/directorychooser.svelte';
import Grid from '$components/grid/grid.svelte';
import Modal from '$components/modal.svelte';
import { startProgress } from '$lib/progress';
import hostTree from '$lib/stores/hosttree';
import applicationSettings from '$lib/stores/settings';
import { OpenConnection, OpenDatabase } from '$wails/go/app/App';
import hostTree from '$lib/stores/hosttree.js';
import applicationSettings from '$lib/stores/settings.js';
import { OpenConnection, OpenDatabase } from '$wails/go/app/App.js';
import { createEventDispatcher } from 'svelte';
export let info = {};
@ -23,7 +22,6 @@
info.collKeys = [];
if (hostKey) {
const progress = startProgress(`Opening connection to host "${hostKey}"`);
const databases = await OpenConnection(hostKey);
if (databases && !$hostTree[hostKey]) {
@ -32,8 +30,6 @@
$hostTree[hostKey].databases[dbKey] = $hostTree[hostKey].databases[dbKey] || { collections: {} };
});
}
progress.end();
}
}
@ -42,14 +38,11 @@
info.dbKey = dbKey;
if (dbKey) {
const progress = startProgress(`Opening database "${dbKey}"`);
const collections = await OpenDatabase(info.hostKey, dbKey);
for (const collKey of collections?.sort() || []) {
$hostTree[info.hostKey].databases[dbKey].collections[collKey] = {};
}
progress.end();
}
}

View File

@ -1,21 +1,22 @@
<script>
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime';
import { EventsOn } from '$wails/runtime/runtime.js';
import Shell from '../shell.svelte';
import Stats from './stats.svelte';
export let host;
export let database;
export let hostKey;
export let dbKey;
export let tab = 'stats';
$: if (database) {
database.hostKey = hostKey;
database.dbKey = dbKey;
}
const tabs = {
stats: { icon: 'chart', title: 'Database stats', component: Stats },
shell: { icon: 'shell', title: 'Shell', component: Shell },
};
$: if (hostKey || dbKey) {
tab = 'stats';
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
}
EventsOn('OpenStatsTab', name => (tab = name || tab));
@ -24,11 +25,13 @@
<div class="view" class:empty={!database}>
{#if database}
{#key database}
<TabBar tabs={[ { key: 'stats', icon: 'chart', title: 'Database stats' } ]} bind:selectedKey={tab} />
<div class="container">
{#if tab === 'stats'} <Stats {database} />
{/if}
</div>
<TabBar tabs={Object.values(tabs)} bind:selectedKey={tab} />
{#each Object.values(tabs) as view}
<div class="container" class:hidden={tab !== view.key}>
<svelte:component this={view.component} visible={tab === view.key} {host} {database} />
</div>
{/each}
{/key}
{:else}
<BlankState label="Select a database to continue" />
@ -53,6 +56,9 @@
min-height: 0;
min-width: 0;
}
.container.hidden {
display: none;
}
.container > :global(*) {
width: 100%;
}

View File

@ -1,6 +1,6 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
export let database;

View File

@ -1,8 +1,8 @@
<script>
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input';
import hostTree from '$lib/stores/hosttree';
import { AddHost, UpdateHost } from '$wails/go/app/App';
import input from '$lib/actions/input.js';
import hostTree from '$lib/stores/hosttree.js';
import { AddHost, UpdateHost } from '$wails/go/app/App.js';
import { createEventDispatcher, onMount } from 'svelte';
export let hostKey = '';
@ -54,7 +54,13 @@
<label class="field">
<span class="label">Connection string</span>
<input type="text" placeholder="mongodb://..." bind:value={form.uri} spellcheck="false" use:input />
<input
type="text"
placeholder="mongodb://..."
bind:value={form.uri}
spellcheck="false"
use:input
/>
</label>
</form>

View File

@ -1,22 +1,25 @@
<script>
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime';
import { EventsOn } from '$wails/runtime/runtime.js';
import Logs from './logs.svelte';
import Shell from '../shell.svelte';
import Status from './status.svelte';
import SystemInfo from './systeminfo.svelte';
export let host;
export let hostKey;
export let tab = 'status';
$: if (host) {
host.hostKey = hostKey;
}
const tabs = {
status: { icon: 'chart', title: 'Host status', component: Status },
shell: { icon: 'shell', title: 'Shell', component: Shell },
logs: { icon: 'doc', title: 'Logs', component: Logs },
systemInfo: { icon: 'server', title: 'System info', component: SystemInfo },
};
$: if (hostKey) {
tab = 'status';
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
}
EventsOn('OpenStatusTab', name => (tab = name || tab));
@ -25,21 +28,13 @@
<div class="view" class:empty={!host}>
{#if host}
{#key host}
<TabBar
tabs={[
{ key: 'status', icon: 'chart', title: 'Host status' },
{ key: 'logs', icon: 'doc', title: 'Logs' },
{ key: 'systemInfo', icon: 'server', title: 'System info' },
]}
bind:selectedKey={tab}
/>
<TabBar tabs={Object.values(tabs)} bind:selectedKey={tab} />
<div class="container">
{#if tab === 'status'} <Status {host} />
{:else if tab === 'logs'} <Logs {host} />
{:else if tab === 'systemInfo'} <SystemInfo {host} />
{/if}
</div>
{#each Object.values(tabs) as view}
<div class="container" class:hidden={tab !== view.key}>
<svelte:component this={view.component} visible={tab === view.key} {host} />
</div>
{/each}
{/key}
{:else}
<BlankState label="Select a host to continue" />
@ -64,6 +59,9 @@
min-height: 0;
min-width: 0;
}
.container.hidden {
display: none;
}
.container > :global(*) {
width: 100%;
}

View File

@ -1,13 +1,14 @@
<script>
import Grid from '$components/grid.svelte';
import Grid from '$components/grid/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import input from '$lib/actions/input';
import { logComponents, logLevels } from '$lib/mongo';
import { BrowserOpenURL } from '$wails/runtime/runtime';
import input from '$lib/actions/input.js';
import { logComponents, logLevels } from '$lib/mongo/index.js';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
import { onDestroy } from 'svelte';
export let host;
export let visible = false;
const autoReloadIntervals = [ 1, 2, 5, 10, 30, 60 ];
let filter = 'global';
@ -31,6 +32,10 @@
}
async function refresh() {
if (!visible) {
return;
}
let _logs = [];
({ logs: _logs, total, error } = await host.getLogs(filter));
logs = [];
@ -64,6 +69,8 @@
setTimeout(() => copySucceeded = false, 1500);
}
$: visible && !logs && refresh();
onDestroy(() => {
if (interval) {
clearInterval(interval);
@ -75,7 +82,13 @@
<div class="formrow">
<label class="field">
<span class="label">Auto reload (seconds)</span>
<input type="number" class="autoreloadinput" bind:value={autoReloadInterval} list="autoreloadintervals" use:input />
<input
type="number"
class="autoreloadinput"
bind:value={autoReloadInterval}
list="autoreloadintervals"
use:input
/>
</label>
<label class="field">

View File

@ -1,6 +1,6 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
export let host;

View File

@ -1,6 +1,6 @@
<script>
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
export let host;

View File

@ -1,26 +1,33 @@
<script>
import Grid from '$components/grid.svelte';
import hostTree from '$lib/stores/hosttree';
import Grid from '$components/grid/grid.svelte';
import hostTree from '$lib/stores/hosttree.js';
export let path = [];
</script>
<Grid
striped={false}
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
columns={[
{ key: 'name' },
{ key: 'count', right: true },
]}
items={Object.values($hostTree || {}).map(host => {
return {
id: host.key,
name: host.name,
loading: host.loading,
icon: 'server',
children: Object.values(host.databases || {})
.sort((a, b) => a.key.localeCompare(b))
.map(database => {
return {
id: database.key,
name: database.key,
icon: 'db',
loading: database.loading,
count: Object.keys(database.collections || {}).length || '',
icon: 'db',
children: Object.values(database.collections)
.sort((a, b) => a.key.localeCompare(b))
.map(collection => {
@ -33,6 +40,8 @@
{ label: 'Dump collection (BSON via mongodump)…', fn: collection.dump },
{ separator: true },
{ label: 'Rename collection…', fn: collection.rename },
{ label: 'Duplicate collection…', fn: collection.duplicate },
{ separator: true },
{ label: 'Truncate collection…', fn: collection.truncate },
{ label: 'Drop collection…', fn: collection.drop },
{ separator: true },
@ -40,6 +49,7 @@
],
};
}) || [],
menu: [
{ label: 'Dump database (BSON via mongodump)…', fn: database.dump },
{ label: 'Drop database…', fn: database.drop },
@ -49,6 +59,7 @@
],
};
}),
menu: [
{ label: 'New database…', fn: host.newDatabase },
{ separator: true },

View File

@ -1,18 +1,26 @@
<script>
import Icon from '$components/icon.svelte';
import hostTree from '$lib/stores/hosttree';
import sharedState from '$lib/stores/sharedstate';
import { EventsOn } from '$wails/runtime/runtime';
import hostTree from '$lib/stores/hosttree.js';
import sharedState from '$lib/stores/sharedstate.js';
import { EventsOn } from '$wails/runtime/runtime.js';
import CollectionView from './collection/index.svelte';
import DatabaseView from './database/index.svelte';
import HostView from './host/index.svelte';
import HostTree from './hosttree.svelte';
let path = [];
let prevPath = '';
let hostTab = '';
let dbTab = '';
let collTab = '';
$: if (path.join('.') !== prevPath) {
hostTab = 'status';
dbTab = 'stats';
collTab = 'find';
prevPath = path.join('.');
}
$: activeHostKey = path[0];
$: activeDbKey = path[1];
$: activeCollKey = path[2];
@ -55,26 +63,26 @@
</div>
{#if activeCollKey}
<CollectionView
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
hostKey={activeHostKey}
dbKey={activeDbKey}
collKey={activeCollKey}
bind:tab={collTab}
/>
{#key activeCollKey}
<CollectionView
host={$hostTree[activeHostKey]}
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
bind:tab={collTab}
/>
{/key}
{:else if activeDbKey}
<DatabaseView
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
hostKey={activeHostKey}
dbKey={activeDbKey}
bind:tab={dbTab}
/>
{#key activeDbKey}
<DatabaseView
host={$hostTree[activeHostKey]}
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
bind:tab={dbTab}
/>
{/key}
{:else if activeHostKey}
<HostView
host={$hostTree[activeHostKey]}
hostKey={activeHostKey}
bind:tab={hostTab}
/>
{#key activeHostKey}
<HostView host={$hostTree[activeHostKey]} bind:tab={hostTab} />
{/key}
{/if}
<style>

View File

@ -0,0 +1,238 @@
<script>
import BlankState from '$components/blankstate.svelte';
import CodeEditor from '$components/editors/codeeditor.svelte';
import Icon from '$components/icon.svelte';
import environment from '$lib/stores/environment.js';
import { OpenShellScript, SaveShellScript, SaveShellOuput } from '$wails/go/app/App.js';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
import { javascript } from '@codemirror/lang-javascript';
import { onDestroy, onMount } from 'svelte';
export let host = undefined;
export let database = undefined;
export let collection = undefined;
export let visible = false;
const placeholder = '// Write your script here...';
const extensions = [ javascript() ];
let script = '';
let result = {};
let horizontal = false;
let copySucceeded = false;
let timeout;
let busy = false;
let editor;
async function runScript() {
if (!$environment.hasMongoShell) {
return;
}
busy = true;
if (collection) {
result = await collection.executeShellScript(script);
}
else if (database) {
result = await database.executeShellScript(script);
}
else if (host) {
result = await host.executeShellScript(script);
}
busy = false;
}
async function loadScript() {
const _script = await OpenShellScript();
if (_script) {
script = _script;
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: script,
},
selection: {
from: 0,
anchor: 0,
to: 0,
head: 0,
},
});
}
}
async function saveScript() {
await SaveShellScript(
host?.key || '',
database?.key || '',
collection?.key || '',
script,
false // not temporary
);
}
async function saveOutput() {
await SaveShellOuput(result.output + '\n' + result.stderr);
}
async function copyErrorDescription() {
await navigator.clipboard.writeText(result.errorDescription);
copySucceeded = true;
timeout = setTimeout(() => copySucceeded = false, 1500);
}
function toggleView() {
horizontal = !horizontal;
}
function openMongoshInstallDocs() {
BrowserOpenURL('https://garraflavatra.github.io/rolens/user-guide/shell/');
}
$: visible && editor.focus();
onMount(() => {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: placeholder,
},
selection: {
from: 0,
anchor: 0,
to: placeholder.length,
head: placeholder.length,
},
});
editor.focus();
});
onDestroy(() => clearTimeout(timeout));
</script>
<div class="shell" class:horizontal>
<div class="overflow">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="field">
<CodeEditor bind:editor bind:text={script} {extensions} />
</label>
</div>
<div class="output">
{#if !$environment.hasMongoShell}
<BlankState
title="mongosh is required to run shell scripts"
label="Please refer to the documentation for more information."
icon="!"
>
<button class="button" on:click={openMongoshInstallDocs}>
<Icon name="info" /> Read the documentation
</button>
</BlankState>
{:else if busy}
<BlankState icon="loading" label="Executing…" />
{:else if result.errorTitle || result.errorDescription}
<BlankState title={result.errorTitle} label={result.errorDescription} icon="!">
<button class="button-small" on:click={copyErrorDescription}>
<Icon name={copySucceeded ? 'check' : 'clipboard'} /> Copy error message
</button>
</BlankState>
{:else}
<pre>{result.output || ''}{#if result.stderr}<div class="error">{result.stderr}</div>{/if}</pre>
{/if}
</div>
<div class="controls">
<button class="button" on:click={runScript}>
<Icon name="play" /> Run
</button>
<div class="field inline">
<button class="button secondary" on:click={loadScript}>
<Icon name="upload" /> Load script…
</button>
<button class="button secondary" on:click={saveScript}>
<Icon name="save" /> Save as…
</button>
</div>
<button class="button secondary" disabled={!result.output} on:click={saveOutput}>
<Icon name="upload" /> Save output as…
</button>
<button class="button viewtoggle" title="Toggle horizontal/vertical view" on:click={toggleView}>
<Icon name="columns" rotation={horizontal ? 90 : 0} />
</button>
{#key result}
<div class="status flash-green">
{#if result?.status}
Exit code: {result.status}
{/if}
</div>
{/key}
</div>
</div>
<style>
.shell {
display: grid;
grid-template: 1fr auto / 1fr 1fr;
}
.shell.horizontal {
grid-template: 1fr 1fr auto / 1fr;
}
.overflow {
overflow: auto;
}
.field {
height: 100%;
}
.field :global(.editor) {
border-radius: 0;
}
.output {
background-color: #2e3027;
color: #fff;
overflow: auto;
display: flex;
}
.output :global(*) {
color: #fff;
}
.output pre {
font-family: monospace;
padding: 0.5rem;
user-select: text;
-webkit-user-select: text;
cursor: text;
}
.output pre .error {
color: #ff8989;
margin-top: 2px;
}
.output :global(.blankstate) {
margin: auto;
padding: 0.5rem;
}
.controls {
margin-top: 0.5rem;
display: flex;
gap: 0.2rem;
align-items: center;
grid-column: 1 / 3;
}
.controls .viewtoggle {
margin-left: auto;
}
.shell.horizontal .controls {
grid-column: 1;
}
</style>

View File

@ -1,50 +0,0 @@
#app-loading {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
}
/* Source of the following CSS: https://loading.io/css/ (modified) */
#app-loading .ring {
display: inline-block;
position: relative;
width: 136px;
height: 136px;
margin: auto;
}
#app-loading .ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 120px;
height: 120px;
margin: 8px;
border: 8px solid;
border-radius: 50%;
animation: loading-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #f1dac8 transparent transparent transparent;
}
#app-loading .ring div:nth-child(1) {
animation-delay: -0.45s;
}
#app-loading .ring div:nth-child(2) {
animation-delay: -0.3s;
}
#app-loading .ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes loading-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -1,6 +1,9 @@
:root {
--darwin-titlebar-height: 36px;
--radius: 4px;
--titlebar-height: 36px;
--primary: #00008b;
--selection: rgba(0, 62, 205, 0.3);
}
html,
@ -14,8 +17,8 @@ body {
-webkit-user-select: none;
user-select: none;
cursor: default;
font-size: 13px;
line-height: 13px;
font-size: 15px;
line-height: 17px;
background-color: rgba(255, 255, 255, 0.45);
}
@ -42,7 +45,7 @@ p strong {
}
a {
color: rgb(0, 0, 204);
color: #0000cc;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
@ -66,8 +69,8 @@ select:disabled {
}
.button {
background-color: #00008b;
border: 1px solid #00008b;
background-color: var(--primary);
border: 1px solid var(--primary);
padding: 0.5rem;
border-radius: var(--radius);
color: #fff;
@ -142,7 +145,7 @@ select:disabled {
.field > textarea,
.field > select {
flex: 1;
padding: 0 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background-color: #fff;
appearance: none;
@ -151,8 +154,8 @@ select:disabled {
.field > textarea:focus,
.field > select:focus {
outline: none;
border-color: #00008b;
box-shadow: 0 0 0 3px rgba(0, 0, 139, 0.2);
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
.field > input.invalid,
.field > textarea.invalid,
@ -215,3 +218,8 @@ code,
.flash-green {
animation: 1s ease-out 0s 1 flashGreen;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -9,10 +9,10 @@ export default defineConfig({
plugins: [ svelte() ],
resolve: {
alias: {
'$components': currentDir + '/src/components',
'$organisms': currentDir + '/src/organisms',
'$wails': currentDir + '/wailsjs',
'$lib': currentDir + '/src/lib',
$components: currentDir + '/src/components',
$organisms: currentDir + '/src/organisms',
$wails: currentDir + '/wailsjs',
$lib: currentDir + '/src/lib',
},
},
});

21
frontend/wailsjs/go/app/App.d.ts generated vendored
View File

@ -1,7 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {app} from '../models';
import {map[string]app} from '../models';
import {menu} from '../models';
import {context} from '../models';
import {ui} from '../models';
@ -10,6 +9,12 @@ export function AddHost(arg1:string):Promise<string>;
export function Aggregate(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise<void>;
export function AskConfirmation(arg1:string):Promise<boolean>;
export function ChooseDirectory(arg1:string):Promise<string>;
export function CountItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<app.CountItemsResult>;
export function CreateIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
export function DropCollection(arg1:string,arg2:string,arg3:string):Promise<boolean>;
@ -18,15 +23,19 @@ export function DropDatabase(arg1:string,arg2:string):Promise<boolean>;
export function DropIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
export function DuplicateCollection(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string):Promise<boolean>;
export function Environment():Promise<app.EnvironmentInfo>;
export function ExecuteShellScript(arg1:string,arg2:string,arg3:string,arg4:string):Promise<app.ExecuteShellScriptResult>;
export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<app.FindItemsResult>;
export function GetIndexes(arg1:string,arg2:string,arg3:string):Promise<app.GetIndexesResult>;
export function HostLogs(arg1:string,arg2:string):Promise<app.HostLogsResult>;
export function Hosts():Promise<map[string]app.Host>;
export function Hosts():Promise<{[key: string]: app.Host}>;
export function InsertItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<any>;
@ -38,6 +47,8 @@ export function OpenConnection(arg1:string):Promise<app.OpenConnectionResult>;
export function OpenDatabase(arg1:string,arg2:string):Promise<app.OpenDatabaseResult>;
export function OpenShellScript():Promise<string>;
export function PerformDump(arg1:string):Promise<boolean>;
export function PerformFindExport(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
@ -60,7 +71,11 @@ export function ReportSharedStateVariable(arg1:string,arg2:string):Promise<void>
export function SaveQuery(arg1:string):Promise<string>;
export function SavedQueries():Promise<map[string]app.SavedQuery>;
export function SaveShellOuput(arg1:string):Promise<void>;
export function SaveShellScript(arg1:string,arg2:string,arg3:string,arg4:string,arg5:boolean):Promise<app.SaveShellScriptResult>;
export function SavedQueries():Promise<{[key: string]: app.SavedQuery}>;
export function Settings():Promise<app.Settings>;

View File

@ -10,6 +10,18 @@ export function Aggregate(arg1, arg2, arg3, arg4, arg5) {
return window['go']['app']['App']['Aggregate'](arg1, arg2, arg3, arg4, arg5);
}
export function AskConfirmation(arg1) {
return window['go']['app']['App']['AskConfirmation'](arg1);
}
export function ChooseDirectory(arg1) {
return window['go']['app']['App']['ChooseDirectory'](arg1);
}
export function CountItems(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['CountItems'](arg1, arg2, arg3, arg4);
}
export function CreateIndex(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['CreateIndex'](arg1, arg2, arg3, arg4);
}
@ -26,10 +38,18 @@ export function DropIndex(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['DropIndex'](arg1, arg2, arg3, arg4);
}
export function DuplicateCollection(arg1, arg2, arg3, arg4, arg5, arg6) {
return window['go']['app']['App']['DuplicateCollection'](arg1, arg2, arg3, arg4, arg5, arg6);
}
export function Environment() {
return window['go']['app']['App']['Environment']();
}
export function ExecuteShellScript(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['ExecuteShellScript'](arg1, arg2, arg3, arg4);
}
export function FindItems(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['FindItems'](arg1, arg2, arg3, arg4);
}
@ -66,6 +86,10 @@ export function OpenDatabase(arg1, arg2) {
return window['go']['app']['App']['OpenDatabase'](arg1, arg2);
}
export function OpenShellScript() {
return window['go']['app']['App']['OpenShellScript']();
}
export function PerformDump(arg1) {
return window['go']['app']['App']['PerformDump'](arg1);
}
@ -110,6 +134,14 @@ export function SaveQuery(arg1) {
return window['go']['app']['App']['SaveQuery'](arg1);
}
export function SaveShellOuput(arg1) {
return window['go']['app']['App']['SaveShellOuput'](arg1);
}
export function SaveShellScript(arg1, arg2, arg3, arg4, arg5) {
return window['go']['app']['App']['SaveShellScript'](arg1, arg2, arg3, arg4, arg5);
}
export function SavedQueries() {
return window['go']['app']['App']['SavedQueries']();
}

6
frontend/wailsjs/go/ui/UI.d.ts generated vendored
View File

@ -4,12 +4,6 @@ import {context} from '../models';
export function Beep():Promise<void>;
export function OpenDirectory(arg1:string):Promise<string>;
export function Reveal(arg1:string):Promise<void>;
export function StartProgressBar(arg1:number,arg2:string):Promise<void>;
export function Startup(arg1:context.Context):Promise<void>;
export function StopProgressBar(arg1:number):Promise<void>;

View File

@ -6,22 +6,10 @@ export function Beep() {
return window['go']['ui']['UI']['Beep']();
}
export function OpenDirectory(arg1) {
return window['go']['ui']['UI']['OpenDirectory'](arg1);
}
export function Reveal(arg1) {
return window['go']['ui']['UI']['Reveal'](arg1);
}
export function StartProgressBar(arg1, arg2) {
return window['go']['ui']['UI']['StartProgressBar'](arg1, arg2);
}
export function Startup(arg1) {
return window['go']['ui']['UI']['Startup'](arg1);
}
export function StopProgressBar(arg1) {
return window['go']['ui']['UI']['StopProgressBar'](arg1);
}

View File

@ -225,3 +225,11 @@ export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;

View File

@ -37,11 +37,11 @@ export function LogFatal(message) {
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
EventsOnMultiple(eventName, callback, -1);
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
@ -49,7 +49,7 @@ export function EventsOff(eventName, ...additionalEventNames) {
}
export function EventsOnce(eventName, callback) {
EventsOnMultiple(eventName, callback, 1);
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
@ -192,3 +192,11 @@ export function Hide() {
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}

48
go.mod
View File

@ -1,8 +1,10 @@
module github.com/garraflavatra/rolens
go 1.18
go 1.21
require github.com/wailsapp/wails/v2 v2.3.1
toolchain go1.21.5
require github.com/wailsapp/wails/v2 v2.7.1
require (
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6
@ -11,12 +13,9 @@ require (
)
require (
github.com/akavel/rsrc v0.10.2 // indirect
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josephspurrier/goversioninfo v1.4.0 // indirect
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect
golang.org/x/image v0.7.0 // indirect
github.com/leaanthony/u v1.1.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/wailsapp/go-webview2 v1.0.10 // indirect
)
require (
@ -27,35 +26,32 @@ require (
github.com/golang/snappy v0.0.1 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/labstack/echo/v4 v4.9.0 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/ncruces/zenity v0.10.9
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/samber/lo v1.27.1 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/tkrajina/go-reflector v0.5.5 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.3.0
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.3.1 => /Users/romeinvanburen/go/pkg/mod

126
go.sum
View File

@ -1,12 +1,8 @@
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA=
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw=
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6 h1:jFEK/SA/7E8lg9T33+y8D4Z0I782+bbiEjmyyklRzRQ=
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6/go.mod h1:/WeFVhhxMOGypVKS0w8DUJxUBbHypnWkUVnW7p5c9Pw=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@ -17,78 +13,85 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/ncruces/zenity v0.10.9 h1:TYdNwEj9HiDDcpdsIUecBMsQw7L80Aiu/IJMM0Tao1E=
github.com/ncruces/zenity v0.10.9/go.mod h1:FzjqP1loicusCFJTdIt5Oqbmoj2zySHpM0RsgJeeCbk=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w=
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
github.com/wailsapp/wails/v2 v2.7.1 h1:HAzp2c5ODOzsLC6ZMDVtNOB72ozM7/SJecJPB2Ur+UU=
github.com/wailsapp/wails/v2 v2.7.1/go.mod h1:oIJVwwso5fdOgprBYWXBBqtx6PaSvxg8/KTQHNGkadc=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
@ -97,36 +100,20 @@ github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCO
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -137,32 +124,19 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -25,6 +25,7 @@ type EnvironmentInfo struct {
HasMongoExport bool `json:"hasMongoExport"`
HasMongoDump bool `json:"hasMongoDump"`
HasMongoShell bool `json:"hasMongoShell"`
HomeDirectory string `json:"homeDirectory"`
DataDirectory string `json:"dataDirectory"`
@ -49,6 +50,9 @@ func NewApp(version string) *App {
_, err = exec.LookPath("mongoexport")
a.Env.HasMongoExport = err == nil
_, err = exec.LookPath("mongosh")
a.Env.HasMongoShell = err == nil
a.Env.HomeDirectory, err = os.UserHomeDir()
if err != nil {
panic(errors.New("encountered an error while getting home directory"))
@ -71,8 +75,8 @@ func NewApp(version string) *App {
panic(errors.New("unsupported platform"))
}
os.MkdirAll(a.Env.DataDirectory, os.ModePerm)
os.MkdirAll(a.Env.LogDirectory, os.ModePerm)
os.MkdirAll(a.Env.DataDirectory, 0755)
os.MkdirAll(a.Env.LogDirectory, 0755)
return a
}
@ -137,3 +141,50 @@ func (a *App) ReportSharedStateVariable(key, value string) {
a.State.Store(key, value)
wailsRuntime.LogDebug(a.ctx, fmt.Sprintf("State: %s=\"%s\"", key, value))
}
func (a *App) AskConfirmation(message string) bool {
var title string = ""
if runtime.GOOS == "darwin" {
title = message
message = ""
} else {
title = "Confirm"
}
button, err := wailsRuntime.MessageDialog(a.ctx, wailsRuntime.MessageDialogOptions{
Type: wailsRuntime.QuestionDialog,
Title: title,
Message: message,
Buttons: []string{"Yes", "No"},
DefaultButton: "Yes",
CancelButton: "No",
})
if err == nil {
return button == "Yes"
} else {
return false
}
}
func (a *App) ChooseDirectory(title string) string {
if title == "" {
title = "Choose a directory"
}
dir, err := wailsRuntime.OpenDirectoryDialog(a.ctx, wailsRuntime.OpenDialogOptions{
Title: title,
DefaultDirectory: a.Env.DownloadDirectory,
CanCreateDirectories: true,
})
if err != nil {
wailsRuntime.MessageDialog(a.ctx, wailsRuntime.MessageDialogOptions{
Title: "Error while opening directory",
Message: err.Error(),
Type: wailsRuntime.ErrorDialog,
})
}
return dir
}

View File

@ -24,12 +24,12 @@ func (a *App) Menu() *menu.Menu {
appMenu := menu.NewMenu()
aboutMenu := appMenu.AddSubmenu("Rolens")
aboutMenu.AddText("About Rolens", nil, menuCallbackEmit(a, "OpenAboutModal"))
aboutMenu.AddText("About Rolens", nil, menuCallbackEmit(a, "global.about"))
aboutMenu.AddSeparator()
aboutMenu.AddText("Preferences…", keys.CmdOrCtrl(","), menuCallbackEmit(a, "OpenPreferences"))
aboutMenu.AddText("Preferences…", keys.CmdOrCtrl(","), menuCallbackEmit(a, "global.settings"))
aboutMenu.AddSeparator()
aboutMenu.AddText("Open data directory", nil, func(cd *menu.CallbackData) { a.ui.Reveal(a.Env.DataDirectory) })
aboutMenu.AddText("Open log directory", nil, func(cd *menu.CallbackData) { a.ui.Reveal(a.Env.LogDirectory) })
aboutMenu.AddText("Open data directory", nil, func(cd *menu.CallbackData) { a.ui.Reveal(a.Env.DataDirectory) })
aboutMenu.AddText("Open log directory", nil, func(cd *menu.CallbackData) { a.ui.Reveal(a.Env.LogDirectory) })
aboutMenu.AddText("Purge logs…", nil, func(cd *menu.CallbackData) { a.PurgeLogDirectory() })
aboutMenu.AddSeparator()
@ -47,9 +47,10 @@ func (a *App) Menu() *menu.Menu {
hostMenu.AddText("New…", keys.OptionOrAlt("C"), menuCallbackEmit(a, "ui.host.new"))
hostMenu.AddText("Edit host…", keys.OptionOrAlt("H"), menuCallbackEmit(a, "ui.host.edit"))
hostMenu.AddSeparator()
hostMenu.AddText("Server status", nil, menuCallbackEmit(a, "ui.host.tab", "status"))
hostMenu.AddText("System info", nil, menuCallbackEmit(a, "ui.host.tab", "systemInfo"))
hostMenu.AddText("Host status", nil, menuCallbackEmit(a, "ui.host.tab", "status"))
hostMenu.AddText("Shell", nil, menuCallbackEmit(a, "ui.host.tab", "shell"))
hostMenu.AddText("Logs", nil, menuCallbackEmit(a, "ui.host.tab", "logs"))
hostMenu.AddText("System info", nil, menuCallbackEmit(a, "ui.host.tab", "systemInfo"))
hostMenu.AddSeparator()
hostMenu.AddText("Remove host…", nil, menuCallbackEmit(a, "ui.host.remove"))

View File

@ -3,7 +3,6 @@ package app
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"github.com/wailsapp/wails/v2/pkg/runtime"
@ -77,7 +76,7 @@ func (a *App) UpdateSettings(jsonData string) Settings {
}
filePath := path.Join(a.Env.DataDirectory, "settings.json")
err = ioutil.WriteFile(filePath, newJson, os.ModePerm)
err = ioutil.WriteFile(filePath, newJson, 0644)
if err != nil {
runtime.LogErrorf(a.ctx, "Could not update host list: %s", err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{

View File

@ -1,10 +1,12 @@
package app
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
type OpenCollectionResult struct {
@ -55,6 +57,77 @@ func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool
return true
}
func (a *App) DuplicateCollection(origHostKey, origDbKey, origCollKey, destHostKey, destDbKey, destCollKey string) bool {
runtime.LogDebugf(a.ctx, "Duplicating collection %s:%s.%s to %s:%s.%s", origHostKey, origDbKey, origCollKey, destHostKey, destDbKey, destCollKey)
if (origHostKey == destHostKey) && (origDbKey == destDbKey) && (origCollKey == destCollKey) {
runtime.LogInfof(a.ctx, "Duplicating collection: cannot duplicate to the same collection")
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.WarningDialog,
Title: "Error while duplicating collection",
Message: "The new collection name cannot equal the old one!",
})
return false
}
origHost, origCtx, origClose, err := a.connectToHost(origHostKey)
if err != nil {
return false
}
defer origClose()
var destHost *mongo.Client
var destCtx context.Context
if origHostKey != destHostKey {
var destClose func()
destHost, destCtx, destClose, err = a.connectToHost(destHostKey)
if err != nil {
return false
}
defer destClose()
} else {
destHost = origHost
destCtx = origCtx
}
destColl := destHost.Database(destDbKey).Collection(destCollKey)
origCur, err := origHost.Database(origDbKey).Collection(origCollKey).Find(origCtx, bson.D{})
if err != nil {
runtime.LogInfof(a.ctx, "Duplicating collection: could not create origin cursor: %s", err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.WarningDialog,
Title: "Error while duplicating collection",
Message: "Could not create origin cursor: " + err.Error(),
})
return false
}
var n int64 = 0
var ok = true
for origCur.Next(origCtx) {
_, err := destColl.InsertOne(destCtx, origCur.Current)
if err != nil {
ok = false
runtime.LogInfof(a.ctx, "Duplicating collection: could not insert item %d: %s", n, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.WarningDialog,
Title: "Error while duplicating collection",
Message: fmt.Sprintf("Could not insert item %d: %s", n, err.Error()),
})
}
n += 1
}
return ok
}
func (a *App) TruncateCollection(hostKey, dbKey, collKey string) bool {
choice, _ := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Title: "Confirm",

View File

@ -1,7 +1,9 @@
package app
import (
"context"
"encoding/json"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mongodb.org/mongo-driver/bson"
@ -23,6 +25,11 @@ type FindItemsResult struct {
ErrorDescription string `json:"errorDescription"`
}
type CountItemsResult struct {
Total int64 `json:"total"`
Error string `json:"error"`
}
func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindItemsResult) {
var form Query
@ -75,12 +82,14 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindIt
Sort: sort,
}
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, query, nil)
if err != nil {
ctx2, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second))
defer cancel()
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx2, query, nil)
if err == nil {
result.Total = total
} else {
result.Total = -1
runtime.LogWarningf(a.ctx, "Encountered an error while counting documents: %s", err.Error())
result.ErrorTitle = "Error while counting documents"
result.ErrorDescription = err.Error()
return
}
cur, err := client.Database(dbKey).Collection(collKey).Find(ctx, query, &opt)
@ -102,7 +111,6 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindIt
return
}
result.Total = total
result.Results = make([]string, 0)
for _, r := range results {
@ -119,6 +127,34 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindIt
return
}
func (a *App) CountItems(hostKey, dbKey, collKey, query string) (result CountItemsResult) {
var q bson.M
err := bson.UnmarshalExtJSON([]byte(query), true, &q)
if err != nil {
runtime.LogInfof(a.ctx, "Invalid count query: %s", err.Error())
result.Error = "Invalid query"
return
}
client, ctx, close, err := a.connectToHost(hostKey)
if err != nil {
return
}
defer close()
ctx, _ = context.WithDeadline(ctx, time.Now().Add(2*time.Minute))
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, q, nil)
if err == nil {
result.Total = total
} else {
result.Total = -1
result.Error = err.Error()
runtime.LogWarningf(a.ctx, "Encountered an error while counting documents: %s", err.Error())
}
return
}
func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson string) bool {
var id bson.M
if err := bson.UnmarshalExtJSON([]byte(idJson), true, &id); err != nil {

View File

@ -3,7 +3,6 @@ package app
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"github.com/wailsapp/wails/v2/pkg/runtime"
@ -25,7 +24,7 @@ func updateQueryFile(a *App, newData map[string]SavedQuery) error {
return err
}
err = ioutil.WriteFile(filePath, jsonData, os.ModePerm)
err = ioutil.WriteFile(filePath, jsonData, 0644)
return err
}

211
internal/app/host_shell.go Normal file
View File

@ -0,0 +1,211 @@
package app
import (
"fmt"
"net/url"
"os"
"os/exec"
"path"
"strings"
"github.com/google/uuid"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type ExecuteShellScriptResult struct {
Output string `json:"output"`
Stderr string `json:"stderr"`
Status int `json:"status"`
ErrorTitle string `json:"errorTitle"`
ErrorDescription string `json:"errorDescription"`
}
type SaveShellScriptResult struct {
Host Host `json:"host"`
Fname string `json:"filename"`
ErrorTitle string `json:"errorTitle"`
ErrorDescription string `json:"errorDescription"`
}
func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result ExecuteShellScriptResult) {
if !a.Env.HasMongoShell {
result.ErrorTitle = "mongosh not found"
result.ErrorDescription = "The mongosh executable is required to run a shell script. Please see https://www.mongodb.com/docs/mongodb-shell/install/"
return
}
saveRes := a.SaveShellScript(hostKey, dbKey, collKey, script, true)
if (saveRes.ErrorTitle != "") || (saveRes.ErrorDescription != "") {
result.ErrorTitle = saveRes.ErrorTitle
result.ErrorDescription = saveRes.ErrorDescription
return
}
var outbuf, errbuf strings.Builder
cmd := exec.Command("mongosh", "--file", saveRes.Fname, saveRes.Host.URI)
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err := cmd.Run()
if exiterr, ok := err.(*exec.ExitError); ok {
result.Status = exiterr.ExitCode()
} else if err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to execute: mongosh --file %s: %s", saveRes.Fname, err.Error())
result.ErrorTitle = "mongosh failure"
result.ErrorDescription = err.Error()
return
} else {
result.Status = 0
}
os.Remove(saveRes.Fname)
result.Output = outbuf.String()
result.Stderr = errbuf.String()
return
}
func (a *App) SaveShellScript(hostKey, dbKey, collKey, script string, temp bool) (result SaveShellScriptResult) {
hosts, err := a.Hosts()
if err != nil {
runtime.LogWarningf(a.ctx, "Shell: could not get hosts: %s", err.Error())
result.ErrorTitle = "Could not get hosts"
result.ErrorDescription = err.Error()
return
}
host, hostFound := hosts[hostKey]
if !hostFound {
runtime.LogWarningf(a.ctx, "Shell: host %s does not exist", host)
result.ErrorTitle = "The specified host does not seem to exist"
return
}
result.Host = host
id, err := uuid.NewRandom()
if err != nil {
runtime.LogErrorf(a.ctx, "Shell: failed to generate a UUID: %s", err.Error())
result.ErrorTitle = "Could not generate UUID"
result.ErrorDescription = err.Error()
return
}
if temp {
dirname, err := os.MkdirTemp(os.TempDir(), "rolens-script")
if err != nil {
runtime.LogErrorf(a.ctx, "Shell: failed to create temporary directory: %s", err.Error())
result.ErrorTitle = "Could not generate temporary directory for script"
result.ErrorDescription = err.Error()
return
}
result.Fname = path.Join(dirname, fmt.Sprintf("%s.mongosh.js", id.String()))
} else {
result.Fname, err = runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
DefaultFilename: "New Script.js",
DefaultDirectory: path.Join(a.Env.DataDirectory, "Shell Scripts"),
Title: "Save MongoDB Shell Script",
CanCreateDirectories: true,
Filters: []runtime.FileFilter{
{
DisplayName: "MongoDB Shell Script (*.js)",
Pattern: "*.js",
},
},
})
if err != nil {
runtime.LogErrorf(a.ctx, "Shell: failed to save script: %s", err.Error())
result.ErrorTitle = "Could not save shell script"
result.ErrorDescription = err.Error()
return
}
}
scriptHeader := fmt.Sprintf("// Namespace: %s.%s\n", dbKey, collKey)
if dbKey != "" {
url, err := url.Parse(host.URI)
if err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to parse host URI %s: %s", host.URI, err.Error())
result.ErrorTitle = "Could parse host URI"
result.ErrorDescription = err.Error()
return
}
url.Path = "/" + dbKey
scriptHeader = scriptHeader + fmt.Sprintf("db = connect('%s');\n", url.String())
}
if collKey != "" {
scriptHeader = scriptHeader + fmt.Sprintf("coll = db.getCollection('%s');\n", collKey)
}
scriptHeader = scriptHeader + "\n"
script = scriptHeader + strings.TrimLeft(strings.TrimRight(script, " \t\n"), "\n")
if err := os.WriteFile(result.Fname, []byte(script), 0755); err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s: %s", result.Fname, err.Error())
result.ErrorTitle = "Could not create temporary script file"
result.ErrorDescription = err.Error()
return
}
return
}
func (a *App) OpenShellScript() string {
dir := path.Join(a.Env.DataDirectory, "Shell Scripts")
os.MkdirAll(dir, 0755)
fname, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
DefaultDirectory: path.Join(a.Env.DataDirectory, "Shell Scripts"),
Title: "Load a MongoDB Shell Script",
CanCreateDirectories: true,
Filters: []runtime.FileFilter{
{
DisplayName: "MongoDB Shell Script (*.js)",
Pattern: "*.js",
},
},
})
if err != nil {
runtime.LogWarningf(a.ctx, "Shell: error opening script: %s", err.Error())
return ""
}
script, err := os.ReadFile(fname)
if err != nil {
runtime.LogWarningf(a.ctx, "Shell: error reading script %s: %s", fname, err.Error())
return ""
}
return string(script)
}
func (a *App) SaveShellOuput(output string) {
fname, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
DefaultFilename: "mongosh-output.txt",
DefaultDirectory: a.Env.DownloadDirectory,
Title: "Save mongosh output",
CanCreateDirectories: true,
})
if err != nil {
runtime.LogWarningf(a.ctx, "Shell: error exporting output to %s: %s", fname, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Title: "Error exporting output",
Message: err.Error(),
Type: runtime.ErrorDialog,
})
}
if err := os.WriteFile(fname, []byte(output), 0755); err != nil {
runtime.LogWarningf(a.ctx, "Shell: error writing shell output to %s: %s", fname, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Title: "Error writing shell output",
Message: err.Error(),
Type: runtime.ErrorDialog,
})
}
}

Some files were not shown because too many files have changed in this diff Show More