Skip to content

Commit

Permalink
feat: database file import functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
macjuul committed Jan 11, 2024
1 parent f429103 commit f605aec
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ publish = false
tauri-build = { version = "1.4.0", features = [] }

[dependencies]
tauri = { version = "1.4.1", features = [ "dialog-open", "fs-read-file", "devtools", "dialog-save", "fs-write-file", "shell-open", "window-set-always-on-top", "window-set-title", "window-show"] }
tauri = { version = "1.4.1", features = [ "path-all", "dialog-open", "fs-read-file", "devtools", "dialog-save", "fs-write-file", "shell-open", "window-set-always-on-top", "window-set-title", "window-show"] }
tauri-plugin-localhost = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
portpicker = "0.1"

Expand Down
3 changes: 3 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"dialog": {
"open": true,
"save": true
},
"path": {
"all": true
}
},
"bundle": {
Expand Down
7 changes: 6 additions & 1 deletion src/adapter/base.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Result } from "~/typings/utilities";

export interface OpenedFile {
name: string;
content: string;
}

export interface SurrealistAdapter {

/**
Expand Down Expand Up @@ -87,6 +92,6 @@ export interface SurrealistAdapter {
title: string,
filters: any,
multiple: boolean
): Promise<string | null>;
): Promise<OpenedFile[]>;

}
18 changes: 10 additions & 8 deletions src/adapter/browser.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Result } from "~/typings/utilities";
import { SurrealistAdapter } from "./base";
import { OpenedFile, SurrealistAdapter } from "./base";

/**
* Surrealist adapter for running as web app
Expand Down Expand Up @@ -71,7 +71,7 @@ export class BrowserAdapter implements SurrealistAdapter {
return true;
}

public async openFile(): Promise<string | null> {
public async openFile(): Promise<OpenedFile[]> {
const el = document.createElement('input');

el.type = 'file';
Expand All @@ -81,13 +81,15 @@ export class BrowserAdapter implements SurrealistAdapter {

return new Promise((resolve, reject) => {
el.addEventListener('change', async () => {
const text = await el.files?.[0]?.text();
const files = [...(el.files ?? [])];
const tasks = files.map(async (file) => ({
name: file.name,
content: await file.text(),
}));

if (typeof text == 'string') {
resolve(text);
} else {
resolve(null);
}
const results = await Promise.all(tasks);

resolve(results);
});

el.addEventListener('error', async () => {
Expand Down
24 changes: 16 additions & 8 deletions src/adapter/desktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { invoke } from "@tauri-apps/api/tauri";
import { appWindow } from "@tauri-apps/api/window";
import { open as openURL } from "@tauri-apps/api/shell";
import { save, open } from "@tauri-apps/api/dialog";
import { basename } from "@tauri-apps/api/path";
import { listen } from "@tauri-apps/api/event";
import { Stack, Text } from "@mantine/core";
import { showNotification } from "@mantine/notifications";
import { store } from "~/store";
import { SurrealistAdapter } from "./base";
import { OpenedFile, SurrealistAdapter } from "./base";
import { printLog } from "~/util/helpers";
import { readTextFile, writeBinaryFile, writeTextFile } from "@tauri-apps/api/fs";
import { Result } from "~/typings/utilities";
Expand Down Expand Up @@ -115,18 +116,25 @@ export class DesktopAdapter implements SurrealistAdapter {
title: string,
filters: any,
multiple: boolean
): Promise<string | null> {
const url = await open({
): Promise<OpenedFile[]> {
const result = await open({
title,
filters,
multiple
});

if (!url) {
return null;
}

return readTextFile(url as string);
const urls = typeof result === "string"
? [result]
: result === null
? []
: result;

const tasks = urls.map(async (url) => ({
name: await basename(url),
content: await readTextFile(url)
}));

return Promise.all(tasks);
}

private initDatabaseEvents() {
Expand Down
1 change: 1 addition & 0 deletions src/components/Exporter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function Exporter() {
color={isLight ? "light.0" : "dark.4"}
title="Export database to file"
onClick={openExporter}
loading={isExporting}
disabled={!isOnline}
>
<Icon path={mdiDownload} color={isOnline ? (isLight ? "light.8" : "white") : undefined} />
Expand Down
148 changes: 148 additions & 0 deletions src/components/Importer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Button, Group, Modal, Paper } from "@mantine/core";
import { SURQL_FILTERS } from "~/constants";
import { useIsConnected } from "~/hooks/connection";
import { useStable } from "~/hooks/stable";
import { useIsLight } from "~/hooks/theme";
import { useRef, useState } from "react";
import { Icon } from "../Icon";
import { mdiFileDocument, mdiUpload } from "@mdi/js";
import { adapter } from "~/adapter";
import { showNotification } from "@mantine/notifications";
import { showError } from "~/util/helpers";
import { ModalTitle } from "../ModalTitle";
import { useDisclosure } from "@mantine/hooks";
import { Spacer } from "../Spacer";
import { Text } from "@mantine/core";
import { fetchDatabaseSchema } from "~/util/schema";
import { getActiveSurreal } from "~/util/connection";
import { OpenedFile } from "~/adapter/base";

export function Importer() {
const isLight = useIsLight();
const isOnline = useIsConnected();
const [showConfirm, showConfirmHandle] = useDisclosure();
const [isImporting, setIsImporting] = useState(false);

const importFile = useRef<OpenedFile | null>(null);

const startImport = useStable(async () => {
try {
const [file] = await adapter.openFile(
'Import query file',
SURQL_FILTERS,
false
);

if (!file) {
return;
}

importFile.current = file;
showConfirmHandle.open();
} finally {
setIsImporting(false);
}
});

const confirmImport = useStable(async () => {
try {
setIsImporting(true);

await getActiveSurreal().query(importFile.current!.content);

showNotification({
title: 'Import successful',
message: 'The database was successfully imported',
});

fetchDatabaseSchema();
} catch(err: any) {
console.error(err);

showError("Import failed", "There was an error importing the database");
} finally {
setIsImporting(false);
showConfirmHandle.close();
}
});

return (
<>
<Button
px="xs"
color={isLight ? "light.0" : "dark.4"}
title="Import database from file"
onClick={startImport}
loading={isImporting}
disabled={!isOnline}
>
<Icon path={mdiUpload} color={isOnline ? (isLight ? "light.8" : "white") : undefined} />
</Button>

<Modal
opened={showConfirm}
onClose={showConfirmHandle.close}
size="sm"
title={<ModalTitle>Import database</ModalTitle>}
>
<Paper
p="sm"
mb="md"
radius="md"
bg={isLight ? "light.1" : "dark.5"}
>
<Group
spacing={6}
position="center"
noWrap
>
<Icon
mt={-1}
path={mdiFileDocument}
color={isLight ? "light.9" : "light.0"}
/>
<Text
truncate
c={isLight ? "light.9" : "light.0"}
weight={600}
>
{importFile.current?.name}
</Text>
</Group>
</Paper>

<Text
mb="xl"
color={isLight ? "light.7" : "light.3"}
>
Are you sure you want to import the selected file?
</Text>

<Text
mb="xl"
color={isLight ? "light.7" : "light.3"}
>
While existing data will be preserved, it may be overwritten by the imported data.
</Text>

<Group>
<Button
variant="light"
color={isLight ? "light.5" : "light.3"}
onClick={showConfirmHandle.close}
>
Close
</Button>
<Spacer />
<Button
onClick={confirmImport}
loading={isImporting}
>
Import
<Icon path={mdiUpload} right />
</Button>
</Group>
</Modal>
</>
);
}
3 changes: 3 additions & 0 deletions src/components/Toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useTabsList } from "~/hooks/environment";
import { ViewTab } from "../ViewTab";
import { Exporter } from "../Exporter";
import { updateSession, setShowQueryListing, setQueryListingMode } from "~/stores/config";
import { Importer } from "../Importer";

export interface ToolbarProps {
viewMode: ViewMode;
Expand Down Expand Up @@ -116,6 +117,8 @@ export function Toolbar(props: ToolbarProps) {
<LocalDatabase />
)}

<Importer />

<Exporter />

<Settings />
Expand Down
10 changes: 5 additions & 5 deletions src/views/query/QueryPane/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classes from './style.module.scss';
import { editor } from "monaco-editor";
import { mdiClose, mdiDatabase, mdiPlusBoxMultiple, mdiUpload } from "@mdi/js";
import { mdiClose, mdiDatabase, mdiFileDocument, mdiPlusBoxMultiple } from "@mdi/js";
import { useStable } from "~/hooks/stable";
import { useActiveSession } from "~/hooks/environment";
import { store } from "~/store";
Expand Down Expand Up @@ -45,10 +45,10 @@ export function QueryPane() {
});

const handleUpload = useStable(async () => {
const query = await adapter.openFile('Load query from file', SURQL_FILTERS, false);
const [file] = await adapter.openFile('Load query from file', SURQL_FILTERS, false);

if (typeof query == 'string') {
setQueryForced(query);
if (file) {
setQueryForced(file.content);
}
});

Expand Down Expand Up @@ -89,7 +89,7 @@ export function QueryPane() {
</ActionIcon>

<ActionIcon onClick={handleUpload} title="Load from file">
<Icon color="light.4" path={mdiUpload} />
<Icon color="light.4" path={mdiFileDocument} />
</ActionIcon>
</Group>
}
Expand Down

0 comments on commit f605aec

Please sign in to comment.