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

9 Commits

81 changed files with 575 additions and 1505 deletions

6
.gitattributes vendored
View File

@ -1,9 +1,5 @@
# 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,22 +1,6 @@
## [Unreleased]
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.
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]

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
const { execSync, spawn } = require('child_process');
const { readFileSync, statSync, rmdirSync, mkdirSync } = require('fs');
const { readFileSync, statSync, rmdirSync } = require('fs');
// Check that the script is run from the root.
@ -145,12 +145,6 @@ 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.
@ -160,25 +154,8 @@ console.log();
const proc = spawn('wails', [ 'build', '-clean', isWindows ? '-nsis' : '' ]);
if (!quiet) {
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.stdout.on('data', data => process.stdout.write(data));
proc.stderr.on('data', data => process.stderr.write(data));
}
proc.on('exit', code => {
console.log();
process.exit(code);
});
proc.on('exit', code => process.exit(code));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 KiB

View File

@ -1,17 +0,0 @@
---
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,11 +6,6 @@ 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

@ -22,7 +22,6 @@
"include": [
"src/**/*.d.ts",
"src/**/*.js",
"src/**/*.ts",
"src/**/*.svelte"
]
}

View File

@ -16,7 +16,6 @@
"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",
@ -213,24 +212,6 @@
"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",
@ -673,18 +654,6 @@
"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",
@ -1380,22 +1349,20 @@
}
},
"node_modules/eslint-plugin-svelte": {
"version": "2.32.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.32.4.tgz",
"integrity": "sha512-VJ12i2Iogug1jvhwxSlognnfGj76P5gks/V4pUD4SCSVQOp14u47MNP0zAG8AQR3LT0Fi1iUvIFnY4l9z5Rwbg==",
"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==",
"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.28.0",
"known-css-properties": "^0.27.0",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.0.11",
"semver": "^7.5.3",
"svelte-eslint-parser": "^0.32.2"
"svelte-eslint-parser": "^0.30.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
@ -1405,7 +1372,7 @@
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0",
"svelte": "^3.37.0 || ^4.0.0"
"svelte": "^3.37.0 || ^4.0.0-0"
},
"peerDependenciesMeta": {
"svelte": {
@ -1413,21 +1380,6 @@
}
}
},
"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",
@ -2205,9 +2157,9 @@
}
},
"node_modules/known-css-properties": {
"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==",
"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==",
"dev": true
},
"node_modules/levn": {
@ -2253,18 +2205,6 @@
"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",
@ -2305,16 +2245,10 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -2489,9 +2423,9 @@
"dev": true
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"version": "8.4.20",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
"dev": true,
"funding": [
{
@ -2501,14 +2435,10 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@ -2561,41 +2491,6 @@
"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",
@ -2936,16 +2831,14 @@
}
},
"node_modules/svelte-eslint-parser": {
"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==",
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.30.0.tgz",
"integrity": "sha512-H0Cn2TKr70DU9p/Gb04CfwtS7eK28MYumrHYPaDNkIFbfwGDLADpbERBn7u8G1Rcm2RMr2/mL6mq0J2e8iKFlA==",
"dev": true,
"dependencies": {
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"postcss": "^8.4.25",
"postcss-scss": "^4.0.6"
"espree": "^9.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -2954,7 +2847,7 @@
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
"svelte": "^3.37.0 || ^4.0.0"
"svelte": "^3.37.0 || ^4.0.0-0"
},
"peerDependenciesMeta": {
"svelte": {
@ -3054,12 +2947,6 @@
"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",
@ -3180,9 +3067,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"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==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@ -3194,12 +3081,6 @@
"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",
@ -3367,15 +3248,6 @@
"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",
@ -3695,12 +3567,6 @@
"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",
@ -4141,33 +4007,20 @@
}
},
"eslint-plugin-svelte": {
"version": "2.32.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.32.4.tgz",
"integrity": "sha512-VJ12i2Iogug1jvhwxSlognnfGj76P5gks/V4pUD4SCSVQOp14u47MNP0zAG8AQR3LT0Fi1iUvIFnY4l9z5Rwbg==",
"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==",
"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.28.0",
"known-css-properties": "^0.27.0",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.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"
}
}
"svelte-eslint-parser": "^0.30.0"
}
},
"eslint-scope": {
@ -4710,9 +4563,9 @@
"dev": true
},
"known-css-properties": {
"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==",
"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==",
"dev": true
},
"levn": {
@ -4746,15 +4599,6 @@
"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",
@ -4786,9 +4630,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"natural-compare": {
@ -4913,12 +4757,12 @@
"dev": true
},
"postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"version": "8.4.20",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
"dev": true,
"requires": {
"nanoid": "^3.3.6",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@ -4940,23 +4784,6 @@
"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",
@ -5177,16 +5004,14 @@
"dev": true
},
"svelte-eslint-parser": {
"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==",
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.30.0.tgz",
"integrity": "sha512-H0Cn2TKr70DU9p/Gb04CfwtS7eK28MYumrHYPaDNkIFbfwGDLADpbERBn7u8G1Rcm2RMr2/mL6mq0J2e8iKFlA==",
"dev": true,
"requires": {
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"postcss": "^8.4.25",
"postcss-scss": "^4.0.6"
"espree": "^9.0.0"
}
},
"svelte-hmr": {
@ -5261,12 +5086,6 @@
"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",
@ -5329,9 +5148,9 @@
}
},
"word-wrap": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"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==",
"dev": true
},
"wrappy": {
@ -5340,12 +5159,6 @@
"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,7 +17,6 @@
"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",
@ -25,12 +24,16 @@
"vite": "^3.2.7"
},
"eslintConfig": {
"extends": "./node_modules/@garraflavatra/yeslint/configs/svelte.js",
"ignorePatterns": [
"dist",
"wailsjs"
],
"extends": "svelte3",
"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 @@
015746ba33749cd2864cd336088387ef
7fc6d7b66151030191ded7136d96970e

View File

@ -1,14 +1,14 @@
<script>
import BlankState from '$components/blankstate.svelte';
import ContextMenu from '$components/contextmenu.svelte';
import dialogs from '$lib/dialogs.js';
import contextMenu from '$lib/stores/contextmenu.js';
import environment from '$lib/stores/environment.js';
import hostTree from '$lib/stores/hosttree.js';
import applicationInited from '$lib/stores/inited.js';
import windowTitle from '$lib/stores/windowtitle.js';
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/runtime.js';
import { EventsOn } from '$wails/runtime';
import { tick } from 'svelte';
import AboutDialog from './dialogs/about.svelte';
import SettingsDialog from './dialogs/settings/index.svelte';

View File

@ -40,7 +40,7 @@
});
</script>
<div bind:this={editorParent} class="editor" />
<div bind:this={editorParent} class="editor"></div>
<style>
.editor {

View File

@ -50,7 +50,7 @@
<svelte:window on:keydown={keydown} />
{#if items && position}
<div class="backdrop" on:pointerdown={close} />
<div class="backdrop" on:pointerdown={close}></div>
<nav>
<ul class="contextmenu" role="" style:left="{position[0]}px" style:top="{position[1]}px">
{#each items as item, index}

View File

@ -1,10 +1,10 @@
<script>
import { daysAbbr, months } from '$lib/constants.js';
import { daysAbbr, months } from '$lib/constants';
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}

View File

@ -1,5 +1,5 @@
<script>
import { OpenDirectory } from '$wails/go/ui/UI.js';
import { OpenDirectory } from '$wails/go/ui/UI';
export let value = '';
export let id = '';
@ -10,13 +10,7 @@
}
</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.js';
import { canBeObjectId, numericInputTypes } from '$lib/mongo/index.js';
import input from '$lib/actions/input';
import { canBeObjectId, numericInputTypes } from '$lib/mongo';
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,12 +70,7 @@
<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"
@ -84,11 +79,7 @@
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>
@ -107,38 +98,18 @@
<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

@ -1,9 +1,9 @@
<script>
import { pathsAreEqual, resolveKeypath, setValue } from '$lib/objects.js';
import contextMenu from '$lib/stores/contextmenu.js';
import { pathsAreEqual, resolveKeypath, setValue } from '$lib/objects';
import contextMenu from '$lib/stores/contextmenu';
import { createEventDispatcher } from 'svelte';
import FormInput from '$components/editors/forminput.svelte';
import Icon from '$components/icon.svelte';
import FormInput from './forminput.svelte';
import Icon from './icon.svelte';
export let items = [];
export let columns = [];
@ -131,7 +131,7 @@
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={canSelect && pathsAreEqual(activePath, [ ...path, item[key] ])}
class:striped
>
{#if !hideChildrenToggles}
@ -157,11 +157,7 @@
{#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])}
@ -172,12 +168,7 @@
{#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>

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 class="has-toggle"></th>
{/if}
<th class="has-icon" />
<th class="has-icon"></th>
{#each columns as column}
<th scope="col">{column.title || ''}</th>
{/each}
{#if canRemoveItems}
<th class="has-button" />
<th class="has-button"></th>
{/if}
</tr>
</thead>

View File

@ -1,9 +1,6 @@
<script>
import icons from '$lib/icons.json';
export let name = '';
export let spin = false;
export let rotation = 0;
if (name === 'loading') {
spin = true;
@ -11,33 +8,25 @@
</script>
<style>
@keyframes spinning {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
svg {
transition: transform 0.25s;
will-change: transform;
}
svg.spinning {
animation: spinning 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
:global(.field) svg {
width: 13px;
height: 13px;
margin-right: 2px;
}
:global(.button) svg {
height: 13px;
width: auto;
vertical-align: bottom;
}
:global(.blankstate .button) svg {
height: 17px;
vertical-align: -3px;
margin-right: 4px;
@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;
}
</style>
@ -52,7 +41,115 @@
stroke-linecap="round"
stroke-linejoin="round"
class:spinning={spin}
style:transform="rotate({rotation}deg)"
>
{@html icons[name] || ''}
{#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 === 'shell'}
<path d="m4 17 6-6-6-6M12 19h8" />
{: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}
</svg>

View File

@ -3,7 +3,7 @@
</script>
<script>
import { Beep } from '$wails/go/ui/UI.js';
import { Beep } from '$wails/go/ui/UI';
import { createEventDispatcher } from 'svelte';
import Icon from './icon.svelte';

View File

@ -7,7 +7,9 @@
export let editor = undefined;
export let readonly = false;
const extensions = [ javascript() ];
const extensions = [
javascript(),
];
onMount(() => {
editor.dispatch({
@ -20,8 +22,7 @@
});
</script>
<CodeEditor
bind:editor
<CodeEditor bind:editor
bind:text
on:inited
on:updated

View File

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

View File

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

View File

@ -3,7 +3,7 @@
import Icon from './icon.svelte';
export let tabs = [];
export let selectedKey = '';
export let selectedKey = {};
export let canAddTab = false;
export let multiline = false;

View File

@ -1,7 +1,7 @@
<script>
import Modal from '$components/modal.svelte';
import alink from '$lib/actions/alink.js';
import environment from '$lib/stores/environment.js';
import alink from '$lib/actions/alink';
import environment from '$lib/stores/environment';
</script>
<Modal width="400px" title=" " on:close>

View File

@ -1,8 +1,8 @@
<script>
import DirectoryChooser from '$components/editors/directorychooser.svelte';
import DirectoryChooser from '$components/directorychooser.svelte';
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input.js';
import settings from '$lib/stores/settings.js';
import input from '$lib/actions/input';
import settings from '$lib/stores/settings';
</script>
<Modal title="Preferences" on:close>
@ -15,13 +15,7 @@
<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.js';
import { BrowserOpenURL } from '$wails/runtime/runtime';
export default function alink(node) {
node.addEventListener('click', e => {

View File

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

View File

@ -1,6 +1,6 @@
import { isInt } from '$lib/math.js';
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo/index.js';
import { jsonLooseParse } from '$lib/strings.js';
import { isInt } from '$lib/math';
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo';
import { jsonLooseParse } from '$lib/strings';
export default function input(node, { autofocus, type, onValid, onInvalid, mandatory } = {
autofocus: false,
@ -10,11 +10,8 @@ export default function input(node, { autofocus, type, onValid, onInvalid, manda
mandatory: false,
}) {
node.setAttribute('spellcheck', false);
node.setAttribute('autocomplete', false);
const getMessage = () => {
const checkInteger = () => (Number.isInteger(node.value) ? false : 'Value must be an integer');
const checkInteger = () => (isInt(node.value) ? false : 'Value must be an integer');
const checkNumberBoundaries = boundaries => {
if (node.value < boundaries[0]) {
return `Input is too low for type ${type}`;

View File

@ -0,0 +1,7 @@
// Months
export const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
export const monthsAbbr = months.map(m => m.slice(0, 3));
// Days
export const days = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
export const daysAbbr = days.map(d => d.slice(0, 3));

View File

@ -1,27 +0,0 @@
export const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
export const days = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
];
export const daysAbbr = days.map(d => d.slice(0, 3));
export const monthsAbbr = months.map(m => m.slice(0, 3));

View File

@ -1,4 +1,3 @@
import { AskConfirmation } from '$wails/go/app/App.js';
import InputDialog from '../dialogs/input.svelte';
function newDialog(dialogComponent, data = {}) {
@ -30,10 +29,6 @@ function enterText(title = '', description = '', value = '') {
});
}
function confirm(message = '') {
return AskConfirmation(message);
}
const dialogs = { new: newDialog, enterText, confirm };
const dialogs = { new: newDialog, enterText };
export default dialogs;

View File

@ -1,46 +0,0 @@
{
"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,4 +1,13 @@
export function randInt(min: number, max: number) {
// https://stackoverflow.com/a/14794066
export function isInt(value) {
if (isNaN(value)) {
return false;
}
const x = parseFloat(value);
return (x | 0) === x;
}
export function randInt(min, max) {
return Math.round(Math.random() * (max - min) + min);
}

View File

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

View File

@ -1,4 +1,4 @@
import { StartProgressBar, StopProgressBar } from '$wails/go/ui/UI.js';
import { StartProgressBar, StopProgressBar } from '$wails/go/ui/UI';
let taskCounter = 0;

View File

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

View File

@ -1,23 +1,21 @@
import dialogs from '$lib/dialogs.js';
import { startProgress } from '$lib/progress.js';
import dialogs from '$lib/dialogs';
import { startProgress } from '$lib/progress';
import { get, writable } from 'svelte/store';
import applicationInited from './inited.js';
import queries from './queries.js';
import windowTitle from './windowtitle.js';
import applicationInited from './inited';
import queries from './queries';
import windowTitle from './windowtitle';
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,
@ -30,7 +28,7 @@ import {
RemoveHost,
RenameCollection,
TruncateCollection
} from '$wails/go/app/App.js';
} from '$wails/go/app/App';
const { set, subscribe } = writable({});
const getValue = () => get({ subscribe });
@ -48,14 +46,7 @@ async function refresh() {
host.uri = hostDetails.uri;
host.open = async function() {
const {
databases: dbNames,
status,
statusError,
systemInfo,
systemInfoError,
} = await OpenConnection(hostKey);
const { databases: dbNames, status, statusError, systemInfo, systemInfoError } = await OpenConnection(hostKey);
host.status = status;
host.statusError = statusError;
host.systemInfo = systemInfo;
@ -104,7 +95,6 @@ async function refresh() {
collection.key = collKey;
collection.dbKey = dbKey;
collection.hostKey = hostKey;
collection.viewKey = 'list';
collection.indexes = collection.indexes || [];
delete collection.new;
@ -121,10 +111,7 @@ 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 progress = startProgress(`Renaming collection "${collKey}" to "${newCollKey}"…`);
const ok = await RenameCollection(hostKey, dbKey, collKey, newCollKey);
await database.open();
progress.end();
@ -132,39 +119,12 @@ async function refresh() {
}
};
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();
@ -199,7 +159,6 @@ async function refresh() {
collection.drop = async function() {
const success = await DropCollection(hostKey, dbKey, collKey);
if (success) {
delete database.collections[collKey];
await refresh();
}
};
@ -239,17 +198,10 @@ 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);
});
});
@ -299,11 +251,14 @@ 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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
export function capitalise(string = '') {
const capitalised = string.charAt(0).toUpperCase() + string.slice(1);
return capitalised;
}
export function jsonLooseParse(json) {
const obj = new Function(`return (${json})`)();
return obj;
}
export function convertLooseJson(json) {
const j = JSON.stringify(jsonLooseParse(json));
return j;
}
export function looseJsonIsValid(json) {
try {
jsonLooseParse(json);
return true;
}
catch {
return false;
}
}

View File

@ -1,28 +0,0 @@
export function capitalise(string = ''): string {
const capitalised = string.charAt(0).toUpperCase() + string.slice(1);
return capitalised;
}
export function jsonLooseParse<T>(json: string): T {
const obj: T = new Function(`return (${json})`)();
return obj;
}
export function convertLooseJson(json: any) {
const j = JSON.stringify(jsonLooseParse(json));
return j;
}
export function looseJsonIsValid(json: string): boolean {
try {
jsonLooseParse(json);
return true;
}
catch {
return false;
}
}
export function stringCouldBeID(string: string) {
return /^[a-zA-Z0-9_-]{1,}$/.test(string);
}

View File

@ -2,7 +2,7 @@ import './styles/loading.css';
import './styles/reset.css';
import './styles/style.css';
import { LogError } from '$wails/runtime/runtime.js';
import { LogError } from '$wails/runtime';
import App from './app.svelte';
window.addEventListener('unhandledrejection', event => {

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/editors/objecteditor.svelte';
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo/index.js';
import ObjectEditor from '$components/objecteditor.svelte';
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
import Collation from '$lib/mongo/collation.svelte';
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings.js';
import { Aggregate } from '$wails/go/app/App.js';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
import { Aggregate } from '$wails/go/app/App';
import { BrowserOpenURL } from '$wails/runtime/runtime';
import { onMount } from 'svelte';
export let collection;
@ -68,8 +68,7 @@
<!-- 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: {
@ -81,6 +80,7 @@
anchor: 3,
},
});
e.detail.editor.focus();
}} />
</label>
</Details>

View File

@ -1,9 +1,9 @@
<script>
import FormInput from '$components/editors/forminput.svelte';
import FormInput from '$components/forminput.svelte';
import Hint from '$components/hint.svelte';
import Icon from '$components/icon.svelte';
import { inputTypes } from '$lib/mongo/index.js';
import { resolveKeypath, setValue } from '$lib/objects.js';
import { inputTypes } from '$lib/mongo';
import { resolveKeypath, setValue } from '$lib/objects';
export let item = {};
export let view = {};
@ -50,12 +50,7 @@
</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

@ -1,90 +0,0 @@
<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.js';
import views from '$lib/stores/views';
import { createEventDispatcher } from 'svelte';
export let collection;

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.js';
import input from '$lib/actions/input';
import { createEventDispatcher } from 'svelte';
export let collection;
@ -27,12 +27,7 @@
<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">
@ -67,9 +62,8 @@
<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>
@ -77,7 +71,6 @@
<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>
@ -92,7 +85,6 @@
<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/grid.svelte';
import Grid from '$components/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.js';
import hostTree from '$lib/stores/hosttree.js';
import queries from '$lib/stores/queries.js';
import input from '$lib/actions/input';
import hostTree from '$lib/stores/hosttree';
import queries from '$lib/stores/queries';
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 bind:value={queryToSave.remarks} placeholder="Remarks…" use:input></textarea>
</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.js';
import { randomString } from '$lib/math.js';
import views from '$lib/stores/views.js';
import input from '$lib/actions/input';
import { randomString } from '$lib/math';
import views from '$lib/stores/views';
export let collection;
export let firstItem = {};
@ -114,12 +114,7 @@
{#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">
@ -133,11 +128,7 @@
{#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>
@ -194,41 +185,16 @@
</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>
@ -236,16 +202,10 @@
<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,20 +1,19 @@
<script>
import Grid from '$components/grid/grid.svelte';
import Grid from '$components/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectGrid from '$components/grid/objectgrid.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import input from '$lib/actions/input.js';
import dialogs from '$lib/dialogs.js';
import { deepClone } from '$lib/objects.js';
import { startProgress } from '$lib/progress.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 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 { EJSON } from 'bson';
import { onMount } from 'svelte';
export let collection;
export let visible = false;
const defaults = {
query: '{}',
@ -26,21 +25,15 @@
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;
@ -49,27 +42,19 @@
}
async function submitQuery() {
if (querying || !visible) {
if (querying) {
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 }));
@ -81,17 +66,6 @@
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();
@ -141,18 +115,7 @@
if (!activePath[0]) {
return;
}
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]
);
const ok = await RemoveItemById(collection.hostKey, collection.dbKey, collection.key, activePath[0]);
if (ok) {
await submitQuery();
}
@ -196,7 +159,7 @@
}
$: collection && refresh();
$: visible && refresh();
onMount(refresh);
</script>
<div class="find">
@ -204,13 +167,12 @@
<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
use:input={{ type: 'json', autofocus: true }}
bind:this={queryField}
bind:value={form.query}
/>
@ -246,8 +208,7 @@
<label class="field">
<span class="label">Skip</span>
<input
type="number"
<input type="number"
min="0"
bind:value={form.skip}
use:input
@ -258,8 +219,7 @@
<label class="field">
<span class="label">Limit</span>
<input
type="number"
<input type="number"
min="0"
bind:value={form.limit}
use:input
@ -321,26 +281,11 @@
</div>
<div class="controls">
<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>
{#key result}
<span class="flash-green">Results: {result.total || 0}</span>
{/key}
</div>
<div>
<label class="field inline">
<select bind:value={collection.viewKey}>
@ -352,49 +297,19 @@
<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>
@ -403,12 +318,7 @@
</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">
@ -469,8 +379,4 @@
justify-content: space-between;
align-items: center;
}
.count {
text-overflow: ellipsis;
}
</style>

View File

@ -1,6 +1,7 @@
<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';
@ -12,49 +13,62 @@
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';
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 },
};
let find;
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
$: if (collection) {
collection.hostKey = hostKey;
collection.dbKey = dbKey;
collection.key = collKey;
}
$: if (hostKey || dbKey || collKey) {
tab = 'find';
}
EventsOn('OpenCollectionTab', name => (tab = name || tab));
async function catchQuery(event) {
tab = 'find';
await tick();
tabs.find.instance.performQuery(event.detail);
find.performQuery(event.detail);
}
</script>
<div class="view" class:empty={!collection}>
{#if collection}
<TabBar tabs={Object.values(tabs)} bind:selectedKey={tab} />
{#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' },
{ key: 'shell', icon: 'shell', title: 'Shell' },
]}
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}
on:performFind={catchQuery}
{host}
{database}
{collection}
/>
<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} />
{:else if tab === 'shell'} <Shell {collection} />
{/if}
</div>
{/each}
{/key}
{:else}
<BlankState label="Select a collection to continue" />
{/if}
@ -78,9 +92,6 @@
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/grid/objectgrid.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
import { onMount } from 'svelte';
export let collection;
export let visible = false;
let activePath = [];
let _indexes = [];
@ -11,10 +11,6 @@
let busy = false;
async function refresh() {
if (!visible) {
return;
}
busy = 'Fetching indexes…';
error = await collection.getIndexes();
@ -53,7 +49,7 @@
}
}
$: visible && refresh();
onMount(refresh);
</script>
<div class="indexes">
@ -73,11 +69,9 @@
<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,20 +1,19 @@
<script>
import Details from '$components/details.svelte';
import Grid from '$components/grid/grid.svelte';
import Grid from '$components/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectEditor from '$components/editors/objecteditor.svelte';
import ObjectEditor from '$components/objecteditor.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
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 { 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 { 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 = {};
@ -99,8 +98,6 @@
views.openConfig(collection);
}
$: visible && editor.focus();
onMount(() => {
if (collection.viewKey === 'list') {
editor.dispatch({
@ -113,6 +110,7 @@
anchor: 3,
},
});
editor.focus();
}
});
</script>

View File

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

View File

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

View File

@ -1,10 +1,10 @@
<script>
import Icon from '$components/icon.svelte';
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';
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';
export let collection = {};
@ -27,8 +27,7 @@
});
// 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 = '{}';
// }
@ -39,8 +38,7 @@
// 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;
// }
@ -50,13 +48,7 @@
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) {
@ -122,12 +114,7 @@
<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">
@ -145,13 +132,7 @@
</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,11 @@
<script>
import DirectoryChooser from '$components/editors/directorychooser.svelte';
import Grid from '$components/grid/grid.svelte';
import DirectoryChooser from '$components/directorychooser.svelte';
import Grid from '$components/grid.svelte';
import Modal from '$components/modal.svelte';
import { startProgress } from '$lib/progress.js';
import hostTree from '$lib/stores/hosttree.js';
import applicationSettings from '$lib/stores/settings.js';
import { OpenConnection, OpenDatabase } from '$wails/go/app/App.js';
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 { createEventDispatcher } from 'svelte';
export let info = {};

View File

@ -1,22 +1,23 @@
<script>
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime.js';
import { EventsOn } from '$wails/runtime/runtime';
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';
const tabs = {
stats: { icon: 'chart', title: 'Database stats', component: Stats },
shell: { icon: 'shell', title: 'Shell', component: Shell },
};
$: if (database) {
database.hostKey = hostKey;
database.dbKey = dbKey;
}
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
$: if (hostKey || dbKey) {
tab = 'stats';
}
EventsOn('OpenStatsTab', name => (tab = name || tab));
@ -25,13 +26,17 @@
<div class="view" class:empty={!database}>
{#if database}
{#key database}
<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}
<TabBar
tabs={[
{ key: 'stats', icon: 'chart', title: 'Database stats' },
{ key: 'shell', icon: 'shell', title: 'Shell' },
]}
bind:selectedKey={tab} />
<div class="container">
{#if tab === 'stats'} <Stats {database} />
{:else if tab === 'shell'} <Shell {database} />
{/if}
</div>
{/key}
{:else}
<BlankState label="Select a database to continue" />
@ -56,9 +61,6 @@
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/grid/objectgrid.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
export let database;

View File

@ -1,8 +1,8 @@
<script>
import Modal from '$components/modal.svelte';
import input from '$lib/actions/input.js';
import hostTree from '$lib/stores/hosttree.js';
import { AddHost, UpdateHost } from '$wails/go/app/App.js';
import input from '$lib/actions/input';
import hostTree from '$lib/stores/hosttree';
import { AddHost, UpdateHost } from '$wails/go/app/App';
import { createEventDispatcher, onMount } from 'svelte';
export let hostKey = '';
@ -54,13 +54,7 @@
<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,7 +1,7 @@
<script>
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime.js';
import { EventsOn } from '$wails/runtime/runtime';
import Logs from './logs.svelte';
import Shell from '../shell.svelte';
@ -9,17 +9,15 @@
import SystemInfo from './systeminfo.svelte';
export let host;
export let hostKey;
export let tab = 'status';
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 (host) {
host.hostKey = hostKey;
}
for (const key of Object.keys(tabs)) {
tabs[key].key = key;
$: if (hostKey) {
tab = 'status';
}
EventsOn('OpenStatusTab', name => (tab = name || tab));
@ -28,13 +26,23 @@
<div class="view" class:empty={!host}>
{#if host}
{#key host}
<TabBar tabs={Object.values(tabs)} bind:selectedKey={tab} />
<TabBar
tabs={[
{ key: 'status', icon: 'chart', title: 'Host status' },
{ key: 'shell', icon: 'shell', title: 'Shell' },
{ key: 'logs', icon: 'doc', title: 'Logs' },
{ key: 'systemInfo', icon: 'server', title: 'System info' },
]}
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} />
</div>
{/each}
<div class="container">
{#if tab === 'status'} <Status {host} />
{:else if tab === 'logs'} <Logs {host} />
{:else if tab === 'systemInfo'} <SystemInfo {host} />
{:else if tab === 'shell'} <Shell {host} />
{/if}
</div>
{/key}
{:else}
<BlankState label="Select a host to continue" />
@ -59,9 +67,6 @@
min-height: 0;
min-width: 0;
}
.container.hidden {
display: none;
}
.container > :global(*) {
width: 100%;
}

View File

@ -1,14 +1,13 @@
<script>
import Grid from '$components/grid/grid.svelte';
import Grid from '$components/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import input from '$lib/actions/input.js';
import { logComponents, logLevels } from '$lib/mongo/index.js';
import { BrowserOpenURL } from '$wails/runtime/runtime.js';
import input from '$lib/actions/input';
import { logComponents, logLevels } from '$lib/mongo';
import { BrowserOpenURL } from '$wails/runtime/runtime';
import { onDestroy } from 'svelte';
export let host;
export let visible = false;
const autoReloadIntervals = [ 1, 2, 5, 10, 30, 60 ];
let filter = 'global';
@ -32,10 +31,6 @@
}
async function refresh() {
if (!visible) {
return;
}
let _logs = [];
({ logs: _logs, total, error } = await host.getLogs(filter));
logs = [];
@ -69,8 +64,6 @@
setTimeout(() => copySucceeded = false, 1500);
}
$: visible && !logs && refresh();
onDestroy(() => {
if (interval) {
clearInterval(interval);
@ -82,13 +75,7 @@
<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/grid/objectgrid.svelte';
import ObjectGrid from '$components/objectgrid.svelte';
export let host;

View File

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

View File

@ -1,6 +1,6 @@
<script>
import Grid from '$components/grid/grid.svelte';
import hostTree from '$lib/stores/hosttree.js';
import Grid from '$components/grid.svelte';
import hostTree from '$lib/stores/hosttree';
export let path = [];
</script>
@ -33,8 +33,6 @@
{ 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 },

View File

@ -1,26 +1,18 @@
<script>
import Icon from '$components/icon.svelte';
import hostTree from '$lib/stores/hosttree.js';
import sharedState from '$lib/stores/sharedstate.js';
import { EventsOn } from '$wails/runtime/runtime.js';
import hostTree from '$lib/stores/hosttree';
import sharedState from '$lib/stores/sharedstate';
import { EventsOn } from '$wails/runtime/runtime';
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];
@ -63,26 +55,26 @@
</div>
{#if activeCollKey}
{#key activeCollKey}
<CollectionView
host={$hostTree[activeHostKey]}
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
bind:tab={collTab}
/>
{/key}
<CollectionView
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
hostKey={activeHostKey}
dbKey={activeDbKey}
collKey={activeCollKey}
bind:tab={collTab}
/>
{:else if activeDbKey}
{#key activeDbKey}
<DatabaseView
host={$hostTree[activeHostKey]}
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
bind:tab={dbTab}
/>
{/key}
<DatabaseView
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
hostKey={activeHostKey}
dbKey={activeDbKey}
bind:tab={dbTab}
/>
{:else if activeHostKey}
{#key activeHostKey}
<HostView host={$hostTree[activeHostKey]} bind:tab={hostTab} />
{/key}
<HostView
host={$hostTree[activeHostKey]}
hostKey={activeHostKey}
bind:tab={hostTab}
/>
{/if}
<style>

View File

@ -1,33 +1,24 @@
<script>
import BlankState from '$components/blankstate.svelte';
import CodeEditor from '$components/editors/codeeditor.svelte';
import CodeEditor from '$components/codeeditor.svelte';
import Icon from '$components/icon.svelte';
import environment from '$lib/stores/environment.js';
import { OpenShellScript, SaveShellScript } 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.hasMongoDump) {
return;
}
async function run() {
busy = true;
if (collection) {
@ -43,52 +34,12 @@
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 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: {
@ -105,11 +56,10 @@
});
editor.focus();
});
onDestroy(() => clearTimeout(timeout));
</script>
<div class="shell" class:horizontal>
<div class="shell">
<div class="overflow">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="field">
@ -118,17 +68,7 @@
</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}
{#if busy}
<BlankState icon="loading" label="Executing…" />
{:else if result.errorTitle || result.errorDescription}
<BlankState title={result.errorTitle} label={result.errorDescription} icon="!">
@ -137,28 +77,11 @@
</button>
</BlankState>
{:else}
<pre>{result.output || ''}{#if result.stderr}<div class="error">{result.stderr}</div>{/if}</pre>
<pre>{result.output || ''}</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 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}
@ -166,6 +89,10 @@
{/if}
</div>
{/key}
<button class="btn" on:click={run}>
<Icon name="play" /> Run
</button>
</div>
</div>
@ -174,9 +101,6 @@
display: grid;
grid-template: 1fr auto / 1fr 1fr;
}
.shell.horizontal {
grid-template: 1fr 1fr auto / 1fr;
}
.overflow {
overflow: auto;
@ -190,7 +114,7 @@
}
.output {
background-color: #2e3027;
background-color: #111;
color: #fff;
overflow: auto;
display: flex;
@ -205,10 +129,6 @@
-webkit-user-select: text;
cursor: text;
}
.output pre .error {
color: #ff8989;
margin-top: 2px;
}
.output :global(.blankstate) {
margin: auto;
padding: 0.5rem;
@ -217,14 +137,10 @@
.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;
.controls .status {
margin-right: auto;
}
</style>

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',
},
},
});

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

@ -10,10 +10,6 @@ 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 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>;
@ -22,8 +18,6 @@ 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>;
@ -46,8 +40,6 @@ 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>;
@ -70,8 +62,6 @@ export function ReportSharedStateVariable(arg1:string,arg2:string):Promise<void>
export function SaveQuery(arg1:string):Promise<string>;
export function SaveShellScript(arg1:string,arg2:string,arg3:string,arg4:string,arg5:boolean):Promise<app.SaveShellScriptResult>;
export function SavedQueries():Promise<map[string]app.SavedQuery>;
export function Settings():Promise<app.Settings>;

View File

@ -10,14 +10,6 @@ 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 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);
}
@ -34,10 +26,6 @@ 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']();
}
@ -82,10 +70,6 @@ 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);
}
@ -130,10 +114,6 @@ export function SaveQuery(arg1) {
return window['go']['app']['App']['SaveQuery'](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']();
}

View File

@ -141,28 +141,3 @@ 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
}
}

View File

@ -1,12 +1,10 @@
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 {
@ -57,77 +55,6 @@ 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,9 +1,7 @@
package app
import (
"context"
"encoding/json"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mongodb.org/mongo-driver/bson"
@ -25,11 +23,6 @@ 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
@ -82,14 +75,12 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindIt
Sort: sort,
}
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
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, query, nil)
if err != nil {
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)
@ -111,6 +102,7 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindIt
return
}
result.Total = total
result.Results = make([]string, 0)
for _, r := range results {
@ -127,34 +119,6 @@ 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

@ -6,7 +6,6 @@ import (
"os"
"os/exec"
"path"
"strings"
"github.com/google/uuid"
"github.com/wailsapp/wails/v2/pkg/runtime"
@ -14,19 +13,11 @@ import (
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"
@ -34,40 +25,8 @@ func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result
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
@ -75,12 +34,10 @@ func (a *App) SaveShellScript(hostKey, dbKey, collKey, script string, temp bool)
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())
@ -89,35 +46,14 @@ func (a *App) SaveShellScript(hostKey, dbKey, collKey, script string, temp bool)
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",
},
},
})
dirname := path.Join(a.Env.DataDirectory, "Shell Scripts")
fname := path.Join(dirname, fmt.Sprintf("%s.mongosh.js", id.String()))
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
}
if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to mkdir %s", err.Error())
result.ErrorTitle = "Could not create temporary directory"
result.ErrorDescription = err.Error()
return
}
scriptHeader := fmt.Sprintf("// Namespace: %s.%s\n", dbKey, collKey)
@ -139,46 +75,31 @@ func (a *App) SaveShellScript(hostKey, dbKey, collKey, script string, temp bool)
scriptHeader = scriptHeader + fmt.Sprintf("coll = db.getCollection('%s');\n", collKey)
}
scriptHeader = scriptHeader + "\n"
script = scriptHeader + strings.TrimLeft(strings.TrimRight(script, " \t\n"), "\n")
scriptHeader = scriptHeader + "\n// Start of user script\n"
script = scriptHeader + script
if err := os.WriteFile(result.Fname, []byte(script), os.ModePerm); err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s: %s", result.Fname, err.Error())
if err := os.WriteFile(fname, []byte(script), os.ModePerm); err != nil {
runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s", err.Error())
result.ErrorTitle = "Could not create temporary script file"
result.ErrorDescription = err.Error()
return
}
cmd := exec.Command("mongosh", "--file", fname, host.URI)
stdout, err := cmd.Output()
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", fname, err.Error())
result.ErrorTitle = "Could not execute script"
result.ErrorDescription = err.Error()
return
} else {
result.Status = 0
}
os.Remove(fname)
result.Output = string(stdout)
return
}
func (a *App) OpenShellScript() string {
dir := path.Join(a.Env.DataDirectory, "Shell Scripts")
os.MkdirAll(dir, os.ModePerm)
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)
}

View File

@ -39,11 +39,10 @@ navigationOptions:
{{ content }}
{% if stub %}
<div class="stubmessage">
<div class="wipdoc">
<p>
This Rolens documentation page is a work-in-progress! You could
<a href="{{ github.pageSourceUrl }}" target="_blank">help writing it</a>
by sending a PR — thanks!
Oops… this Rolens documentation page is a work-in-progress! You could
<a href="{{ github.pageSourceUrl }}" target="_blank">help writing it</a> — thanks!
</p>
</div>
{% endif %}

View File

@ -262,16 +262,6 @@ article {
}
}
.stubmessage {
padding: 1rem;
border: 1px solid #aeae27;
border-radius: 0.75rem;
background-color: rgba(255, 255, 0, 0.235);
}
.stubmessage > :last-child {
margin-bottom: 0;
}
footer {
margin-top: 3rem;
padding-top: 1rem;