mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-19 22:18:03 +00:00
Frontend reorganisation
This commit is contained in:
93
frontend/src/lib/actions.js
Normal file
93
frontend/src/lib/actions.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { canBeObjectId, int32, int64, isInt, uint64 } from './utils';
|
||||
|
||||
export function input(node, { autofocus, type, onValid, onInvalid, mandatory } = {
|
||||
autofocus: false,
|
||||
type: '',
|
||||
onValid: () => 0,
|
||||
onInvalid: () => 0,
|
||||
mandatory: false,
|
||||
}) {
|
||||
|
||||
const getMessage = () => {
|
||||
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}`;
|
||||
}
|
||||
else if (node.value > boundaries[1]) {
|
||||
return `Input is too high for type ${type}`;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'json':
|
||||
try {
|
||||
JSON.parse(node.value);
|
||||
return false;
|
||||
}
|
||||
catch {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
|
||||
case 'int': // int32
|
||||
return checkInteger() || checkNumberBoundaries(int32);
|
||||
|
||||
case 'long': // int64
|
||||
return checkInteger() || checkNumberBoundaries(int64);
|
||||
|
||||
case 'uint64':
|
||||
return checkInteger() || checkNumberBoundaries(uint64);
|
||||
|
||||
case 'string':
|
||||
if (mandatory && (!node.value)) {
|
||||
return 'This field cannot empty';
|
||||
}
|
||||
return false;
|
||||
|
||||
case 'objectid':
|
||||
return !canBeObjectId(node.value) && 'Invalid string representation of an ObjectId';
|
||||
|
||||
case 'double':
|
||||
case 'decimal':
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleInput = () => {
|
||||
const invalid = getMessage();
|
||||
if (invalid) {
|
||||
node.classList.add('invalid');
|
||||
node.setCustomValidity(invalid);
|
||||
node.reportValidity();
|
||||
onInvalid?.();
|
||||
}
|
||||
else {
|
||||
node.classList.remove('invalid');
|
||||
node.setCustomValidity('');
|
||||
node.reportValidity();
|
||||
onValid?.();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
node.select();
|
||||
};
|
||||
|
||||
node.addEventListener('focus', handleFocus);
|
||||
node.addEventListener('input', handleInput);
|
||||
|
||||
if (autofocus) {
|
||||
node.focus();
|
||||
}
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
node.removeEventListener('focus', handleFocus);
|
||||
node.removeEventListener('input', handleInput);
|
||||
},
|
||||
};
|
||||
}
|
20
frontend/src/lib/stores/busy.js
Normal file
20
frontend/src/lib/stores/busy.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const { update, subscribe } = writable(0);
|
||||
|
||||
subscribe(isBusy => {
|
||||
if (isBusy) {
|
||||
document.body.classList.add('busy');
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove('busy');
|
||||
}
|
||||
});
|
||||
|
||||
const busy = {
|
||||
start: () => update(v => ++v),
|
||||
end: () => update(v => --v),
|
||||
subscribe,
|
||||
};
|
||||
|
||||
export default busy;
|
3
frontend/src/lib/stores/connections.js
Normal file
3
frontend/src/lib/stores/connections.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const connections = writable({});
|
14
frontend/src/lib/stores/contextmenu.js
Normal file
14
frontend/src/lib/stores/contextmenu.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const { set, subscribe } = writable();
|
||||
|
||||
const contextMenu = {
|
||||
show: (evt, menu) => set(Object.keys(menu || {}).length ? {
|
||||
position: [ evt.clientX, evt.clientY ],
|
||||
items: menu,
|
||||
} : undefined),
|
||||
hide: () => set(undefined),
|
||||
subscribe,
|
||||
};
|
||||
|
||||
export default contextMenu;
|
15
frontend/src/lib/stores/environment.js
Normal file
15
frontend/src/lib/stores/environment.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { Environment } from '../../../wailsjs/go/app/App';
|
||||
|
||||
const { set, subscribe } = writable({});
|
||||
|
||||
async function reload() {
|
||||
const newEnv = await Environment();
|
||||
set(newEnv);
|
||||
return newEnv;
|
||||
}
|
||||
|
||||
reload();
|
||||
|
||||
const environment = { reload, subscribe };
|
||||
export default environment;
|
14
frontend/src/lib/stores/inited.js
Normal file
14
frontend/src/lib/stores/inited.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { derived } from 'svelte/store';
|
||||
import environment from './environment';
|
||||
import applicationSettings from './settings';
|
||||
|
||||
const applicationInited = derived([ environment, applicationSettings ], ([ env, settings ], set) => {
|
||||
if (env && settings) {
|
||||
set(true);
|
||||
|
||||
// Remove loading spinner.
|
||||
document.getElementById('app-loading')?.remove();
|
||||
}
|
||||
}, false);
|
||||
|
||||
export default applicationInited;
|
23
frontend/src/lib/stores/settings.js
Normal file
23
frontend/src/lib/stores/settings.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { Settings, UpdateSettings } from '../../../wailsjs/go/app/App';
|
||||
|
||||
const { set, subscribe } = writable({});
|
||||
let skipUpdate = true;
|
||||
|
||||
async function reload() {
|
||||
const newSettings = await Settings();
|
||||
set(newSettings);
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
reload();
|
||||
subscribe(newSettings => {
|
||||
if (skipUpdate) {
|
||||
skipUpdate = false;
|
||||
return;
|
||||
}
|
||||
UpdateSettings(JSON.stringify(newSettings || {}));
|
||||
});
|
||||
|
||||
const applicationSettings = { reload, set, subscribe };
|
||||
export default applicationSettings;
|
36
frontend/src/lib/stores/views.js
Normal file
36
frontend/src/lib/stores/views.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { UpdateViewStore, Views } from '../../../wailsjs/go/app/App';
|
||||
|
||||
const { set, subscribe } = writable({});
|
||||
let skipUpdate = true;
|
||||
|
||||
async function reload() {
|
||||
const newViewStore = await Views();
|
||||
set(newViewStore);
|
||||
return newViewStore;
|
||||
}
|
||||
|
||||
function forCollection(hostKey, dbKey, collKey) {
|
||||
const allViews = get({ subscribe });
|
||||
const entries = Object.entries(allViews).filter(v => (
|
||||
v[0] === 'list' || (
|
||||
v[1].host === hostKey &&
|
||||
v[1].database === dbKey &&
|
||||
v[1].collection === collKey
|
||||
)
|
||||
));
|
||||
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
reload();
|
||||
subscribe(newViewStore => {
|
||||
if (skipUpdate) {
|
||||
skipUpdate = false;
|
||||
return;
|
||||
}
|
||||
UpdateViewStore(JSON.stringify(newViewStore));
|
||||
});
|
||||
|
||||
const views = { reload, set, subscribe, forCollection };
|
||||
export default views;
|
138
frontend/src/lib/utils.js
Normal file
138
frontend/src/lib/utils.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { ObjectId } from 'bson';
|
||||
import { get } from 'svelte/store';
|
||||
import environment from './stores/environment';
|
||||
|
||||
// Calculate the min and max values of (un)signed integers with n bits
|
||||
export const intMin = bits => Math.pow(2, bits - 1) * -1;
|
||||
export const intMax = bits => Math.pow(2, bits - 1) - 1;
|
||||
export const uintMax = bits => Math.pow(2, bits) - 1;
|
||||
|
||||
// Boundaries for some ubiquitous integer types
|
||||
export const int32 = [ intMin(32), intMax(32) ];
|
||||
export const int64 = [ intMin(64), intMax(64) ];
|
||||
export const uint64 = [ 0, uintMax(64) ];
|
||||
|
||||
// Input types
|
||||
export const numericInputTypes = [ 'int', 'long', 'uint64', 'double', 'decimal' ];
|
||||
export const inputTypes = [ 'string', 'objectid', 'bool', 'date', ...numericInputTypes ];
|
||||
|
||||
// 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));
|
||||
|
||||
// Get a value from an object with a JSON path, from Webdesq core
|
||||
export function resolveKeypath(object, path) {
|
||||
const parts = path.split('.').flatMap(part => {
|
||||
const indexMatch = part.match(/\[\d+\]/g);
|
||||
if (indexMatch) {
|
||||
// Convert strings to numbers
|
||||
const indexes = indexMatch.map(index => Number(index.slice(1, -1)));
|
||||
const base = part.slice(0, part.indexOf(indexMatch[0]));
|
||||
return base.length ? [ base, ...indexes ] : indexes;
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
let result = object;
|
||||
while (result && parts.length) {
|
||||
result = result[parts.shift()];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set a value in an object with a JSON path, from Webdesq core
|
||||
export function setValue(object, path, value) {
|
||||
const parts = path.split('.').flatMap(part => {
|
||||
let indexMatch = part.match(/\[\d+\]/g);
|
||||
if (indexMatch) {
|
||||
// Convert strings to numbers
|
||||
const indexes = indexMatch.map(index => Number(index.slice(1, -1)));
|
||||
const base = part.slice(0, part.indexOf(indexMatch[0]));
|
||||
return base.length ? [ base, ...indexes ] : indexes;
|
||||
}
|
||||
indexMatch = part.match(/^\d+$/g);
|
||||
if (indexMatch) {
|
||||
// Convert strings to numbers
|
||||
const indexes = indexMatch.map(index => Number(index.slice(1, -1)));
|
||||
const base = part.slice(0, part.indexOf(indexMatch[0]));
|
||||
return base.length ? [ base, ...indexes ] : indexes;
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
let result = object;
|
||||
while (parts.length) {
|
||||
const part = parts.shift();
|
||||
if (!parts.length) {
|
||||
// No parts left, we can set the value
|
||||
result[part] = value;
|
||||
break;
|
||||
}
|
||||
if (!result[part]) {
|
||||
// Default value if none is found
|
||||
result[part] = (typeof parts[0] === 'number') ? [] : {};
|
||||
}
|
||||
result = result[part];
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
export function controlKeyDown(event) {
|
||||
const env = get(environment);
|
||||
// @ts-ignore
|
||||
if (env?.platform === 'darwin') {
|
||||
return event?.metaKey;
|
||||
}
|
||||
else {
|
||||
return event?.ctrlKey;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
export function randomString(length = 12) {
|
||||
const chars = 'qwertyuiopasdfghjklzxcvbnm1234567890';
|
||||
let output = '';
|
||||
|
||||
Array(length).fill('').forEach(() => {
|
||||
output += chars[randInt(0, chars.length - 1)];
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function isBsonBuiltin(value) {
|
||||
return (
|
||||
(typeof value === 'object') &&
|
||||
(value !== null) &&
|
||||
(typeof value._bsontype === 'string') &&
|
||||
(typeof value.inspect === 'function')
|
||||
);
|
||||
}
|
||||
|
||||
export function canBeObjectId(value) {
|
||||
try {
|
||||
new ObjectId(value);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user