diff --git a/application/package.json b/application/package.json
index 2923d66..a678469 100644
--- a/application/package.json
+++ b/application/package.json
@@ -1,7 +1,7 @@
{
"name": "kbs-electron",
"productName": "Keyboard Sounds",
- "version": "1.1.0",
+ "version": "1.1.1",
"description": "https://keyboardsounds.net/",
"main": ".webpack/main",
"repository": {
diff --git a/application/src/api/core.js b/application/src/api/core.js
index 521d2d0..b243ece 100644
--- a/application/src/api/core.js
+++ b/application/src/api/core.js
@@ -170,6 +170,28 @@ const kbs = {
return "";
},
+ selectExportPath: async function(profileToExport) {
+ if (this.openFileDialogIsOpen) {
+ return;
+ }
+
+ this.openFileDialogIsOpen = true;
+ const res = await dialog.showSaveDialog(this.mainWindow, {
+ title: `Export Profile '${profileToExport}'`,
+ defaultPath: `${profileToExport}.zip`,
+ filters: [
+ { name: 'Zip Archive', extensions: ['zip'] }
+ ]
+ });
+ this.openFileDialogIsOpen = false;
+ this.mainWindow.show();
+ this.mainWindow.focus();
+ if (!res.canceled) {
+ return res.filePath
+ }
+ return "";
+ },
+
executeDaemonCommand: async function(command) {
const status = await this.status();
if (status.status !== 'running') {
diff --git a/application/src/ui/pages/profiles.jsx b/application/src/ui/pages/profiles.jsx
index 61814e9..a602d3e 100644
--- a/application/src/ui/pages/profiles.jsx
+++ b/application/src/ui/pages/profiles.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
@@ -10,16 +10,19 @@ import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import InputAdornment from '@mui/material/InputAdornment';
+import Dialog from '@mui/material/Dialog';
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
import AddIcon from '@mui/icons-material/Add';
import SearchIcon from '@mui/icons-material/Search';
import IosShareIcon from '@mui/icons-material/IosShare';
import FileOpenIcon from '@mui/icons-material/FileOpenOutlined';
+import CloseIcon from '@mui/icons-material/Close';
+import SaveIcon from '@mui/icons-material/Save';
import { Chip, CircularProgress } from "@mui/material";
import { execute } from "../execute";
-function ProfileListItem({ statusLoaded, status, profile: { name, author, description } }) {
+function ProfileListItem({ statusLoaded, status, profile: { name, author, description }, onExport }) {
const [isDeleting, setIsDeleting] = useState(false);
return (
@@ -31,7 +34,7 @@ function ProfileListItem({ statusLoaded, status, profile: { name, author, descri
)}
-
+
@@ -102,12 +105,123 @@ function ProfileListItem({ statusLoaded, status, profile: { name, author, descri
const Profiles = ({statusLoaded, status, profilesLoaded, profiles}) => {
const [profileSearchValue, setProfileSearchValue] = useState('');
+ const [exportProfileDialogOpen, setExportProfileDialogOpen] = useState(false);
+ const [exportPath, setExportPath] = useState("");
+ const [exportingProfile, setExportingProfile] = useState(false);
+ const [profileToExport, setProfileToExport] = useState("");
+
+ useEffect(() => {
+ if (!exportProfileDialogOpen) {
+ setExportPath("");
+ }
+ }, [exportProfileDialogOpen]);
+
+ const selectExportPath = () => {
+ execute(`selectExportPath ${profileToExport}`).then((path) => {
+ if (path) {
+ setExportPath(path);
+ }
+ });
+ };
+
+ const exportProfile = () => {
+ if (exportPath === "") {
+ return;
+ }
+
+ setExportingProfile(true);
+ execute(`export-profile --name "${profileToExport}" --output "${exportPath}"`)
+ .then(() => {
+ setExportingProfile(false);
+ setExportProfileDialogOpen(false);
+ setProfileToExport("");
+ });
+ };
return (
+
+
{
},
}}>
{profilesLoaded && profiles.filter(p => profileSearchValue === "" || p.name.toLowerCase().includes(profileSearchValue.toLowerCase())).map((profile) => (
-
+ {
+ setProfileToExport(profile.name);
+ setExportProfileDialogOpen(true);
+ }} />
))}
diff --git a/docs/backend.md b/docs/backend.md
index f8eb112..932943b 100644
--- a/docs/backend.md
+++ b/docs/backend.md
@@ -38,6 +38,12 @@ $ kbs stop
## Manage Profiles
```bash
+# Create a new profile
+$ kbs new -n "My Profile"
+
+# Export an existing profile
+$ kbs export-profile -n "My Profile" -o "My Profile.zip"
+
# List downloadable profiles
$ kbs list-profiles --remote
diff --git a/docs/custom-profiles.md b/docs/custom-profiles.md
index fca2b95..f33c7f8 100644
--- a/docs/custom-profiles.md
+++ b/docs/custom-profiles.md
@@ -5,6 +5,7 @@ This application supports custom profiles in which you can provide your own WAV
## Index
- [Importing a profile](#importing-a-profile)
+- [Exporting an existing profile](#exporting-an-existing-profile)
- [Creating a new Profile](#creating-a-new-profile)
- [Editing a Profile](#editing-a-profile)
- [Compiling a Profile](#compiling-a-profile)
@@ -18,6 +19,14 @@ Profiles can be imported from a ZIP file using the [`add-profile`](./backend.md#
$ kbs add-profile -z "./my-profile.zip"
```
+## Exporting an existing profile
+
+Profiles can be exported from the command line using the [`export-profile`](./backend.md#manage-profiles) action.
+
+```bash
+$ kbs export-profile -n my-profile -o "./my-profile.zip"
+```
+
## Creating a new Profile
Create a new profile using the following command:
diff --git a/keyboardsounds/main.py b/keyboardsounds/main.py
index 878b9f8..d0b56b2 100644
--- a/keyboardsounds/main.py
+++ b/keyboardsounds/main.py
@@ -68,6 +68,7 @@ def main():
f" %(prog)s