use editor data

This commit is contained in:
Jan Prochazka 2021-03-07 11:38:02 +01:00
parent 00d5b25baa
commit f4fe5b9b53
7 changed files with 204 additions and 8 deletions

View File

@ -6,7 +6,7 @@
</script> </script>
<div bind:clientWidth bind:clientHeight class="ace-container"> <div bind:clientWidth bind:clientHeight class="ace-container">
<AceEditorCore {...$$props} width={clientWidth} height={clientHeight} /> <AceEditorCore {...$$props} width={clientWidth} height={clientHeight} on:input/>
</div> </div>
<style> <style>

View File

@ -18,4 +18,4 @@
} }
</script> </script>
<AceEditor {mode} /> <AceEditor {mode} {...$$props} on:input />

View File

@ -0,0 +1,148 @@
import _ from 'lodash';
import { writable, derived } from 'svelte/store';
import { onMount, onDestroy } from 'svelte';
import localforage from 'localforage';
import { changeTab } from '../utility/common';
function getParsedLocalStorage(key) {
const value = localStorage.getItem(key);
if (value != null) {
try {
const res = JSON.parse(value);
return res;
} catch (e) {
// console.log('FAILED LOAD FROM STORAGE', e);
// console.log('VALUE', value);
localStorage.removeItem(key);
}
}
return null;
}
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null }) {
const localStorageKey = `tabdata_editor_${tabid}`;
let changeCounter = 0;
let savedCounter = 0;
const editorState = writable({
errorMessage: null,
isLoading: true,
value: null,
});
const editorValue = derived(editorState, $state => $state.value);
let initialData = null;
let value = null;
// const valueRef = React.useRef(null);
const initialLoad = async () => {
if (loadFromArgs) {
try {
const init = await loadFromArgs();
changeTab(tabid, tab => ({
...tab,
props: _.omit(tab.props, ['initialArgs']),
}));
editorState.update(x => ({
...x,
value: init,
}));
value = init;
initialData = init;
// mark as not saved
changeCounter += 1;
} catch (err) {
const message = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed';
editorState.update(x => ({
...x,
errorMessage: message,
}));
console.error(err.response);
}
} else {
const initFallback = getParsedLocalStorage(localStorageKey);
if (initFallback != null) {
editorState.update(x => ({
...x,
value: initFallback,
}));
value = initFallback;
// move to local forage
await localforage.setItem(localStorageKey, initFallback);
localStorage.removeItem(localStorageKey);
initialData = initFallback;
} else {
const init = await localforage.getItem(localStorageKey);
if (init) {
editorState.update(x => ({
...x,
value: init,
}));
value = init;
initialData = init;
}
}
}
editorState.update(x => ({
...x,
isLoading: false,
}));
};
const saveToStorage = async () => {
if (value == null) return;
try {
await localforage.setItem(localStorageKey, value);
localStorage.removeItem(localStorageKey);
savedCounter = changeCounter;
} catch (err) {
console.error(err);
}
};
const saveToStorageSync = () => {
if (value == null) return;
if (savedCounter == changeCounter) return; // all saved
// on window unload must be synchronous actions, save to local storage instead
localStorage.setItem(localStorageKey, JSON.stringify(value));
};
const saveToStorageDebounced = _.debounce(saveToStorage, 5000);
const setEditorData = newValue => {
if (_.isFunction(newValue)) {
value = newValue(value);
} else {
if (newValue != null) value = newValue;
}
editorState.update(x => ({
...x,
value,
}));
changeCounter += 1;
saveToStorageDebounced();
};
onMount(() => {
window.addEventListener('beforeunload', saveToStorageSync);
initialLoad();
});
onDestroy(() => {
saveToStorage();
window.removeEventListener('beforeunload', saveToStorageSync);
});
return {
editorState,
editorValue,
setEditorData,
initialData,
saveToStorage,
saveToStorageSync,
initialLoad,
};
}

View File

@ -1,17 +1,33 @@
<script lang="ts"> <script lang="ts">
import VerticalSplitter from '../elements/VerticalSplitter.svelte'; import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import SqlEditor from '../query/SqlEditor.svelte'; import SqlEditor from '../query/SqlEditor.svelte';
import useEditorData from '../query/useEditorData';
import { extensions } from '../stores';
import applySqlTemplate from '../utility/applySqlTemplate';
import { useConnectionInfo } from '../utility/metadataLoaders'; import { useConnectionInfo } from '../utility/metadataLoaders';
export let tabid; export let tabid;
export let conid; export let conid;
export let database; export let database;
export let initialArgs;
$: connection = useConnectionInfo({ conid }); $: connection = useConnectionInfo({ conid });
const { editorState, setEditorData } = useEditorData({
tabid,
loadFromArgs:
initialArgs && initialArgs.sqlTemplate
? () => applySqlTemplate(initialArgs.sqlTemplate, $extensions, $$props)
: null,
});
</script> </script>
<VerticalSplitter> <VerticalSplitter>
<svelte:fragment slot="1"> <svelte:fragment slot="1">
<SqlEditor engine={$connection && $connection.engine} /> <SqlEditor
engine={$connection && $connection.engine}
value={$editorState.value || ''}
on:input={e => setEditorData(e.detail)}
/>
</svelte:fragment> </svelte:fragment>
</VerticalSplitter> </VerticalSplitter>

View File

@ -0,0 +1,32 @@
import { getDbCore, getConnectionInfo, getSqlObjectInfo } from './metadataLoaders';
import sqlFormatter from 'sql-formatter';
import { driverBase, findEngineDriver } from 'dbgate-tools';
export default async function applySqlTemplate(sqlTemplate, extensions, props) {
if (sqlTemplate == 'CREATE TABLE') {
const tableInfo = await getDbCore(props, props.objectTypeField || 'tables');
const connection = await getConnectionInfo(props);
const driver = findEngineDriver(connection, extensions) || driverBase;
const dmp = driver.createDumper();
if (tableInfo) dmp.createTable(tableInfo);
return dmp.s;
}
if (sqlTemplate == 'CREATE OBJECT') {
const objectInfo = await getSqlObjectInfo(props);
if (objectInfo) {
if (objectInfo.requiresFormat && objectInfo.createSql) return sqlFormatter.format(objectInfo.createSql);
else return objectInfo.createSql;
}
}
if (sqlTemplate == 'EXECUTE PROCEDURE') {
const procedureInfo = await getSqlObjectInfo(props);
const connection = await getConnectionInfo(props);
const driver = findEngineDriver(connection, extensions) || driverBase;
const dmp = driver.createDumper();
if (procedureInfo) dmp.put('^execute %f', procedureInfo);
return dmp.s;
}
return null;
}

View File

@ -1,9 +1,7 @@
import { openedTabs } from '../stores'; import { openedTabs } from '../stores';
export class LoadingToken { export class LoadingToken {
constructor() { isCanceled = false;
this.isCanceled = false;
}
cancel() { cancel() {
this.isCanceled = true; this.isCanceled = true;
@ -14,8 +12,8 @@ export function sleep(milliseconds) {
return new Promise(resolve => window.setTimeout(() => resolve(null), milliseconds)); return new Promise(resolve => window.setTimeout(() => resolve(null), milliseconds));
} }
export function changeTab(tabid, setOpenedTabs, changeFunc) { export function changeTab(tabid, changeFunc) {
setOpenedTabs(files => files.map(tab => (tab.tabid == tabid ? changeFunc(tab) : tab))); openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? changeFunc(tab) : tab)));
} }
export function setSelectedTabFunc(files, tabid) { export function setSelectedTabFunc(files, tabid) {

View File

@ -13,5 +13,7 @@
"strictNullChecks": false, "strictNullChecks": false,
"strict": false, "strict": false,
"target": "es6", "target": "es6",
// "allowJs": true,
// "checkJs": true,
} }
} }