Compare commits

..

12 Commits

Author SHA1 Message Date
Gregory Schier
919465cdbb Beta 2 2023-11-19 17:41:58 -08:00
Gregory Schier
de3730fa4f Network entitlement 2023-11-19 17:41:46 -08:00
Gregory Schier
aff26fdd46 Try sandboxing again 2023-11-19 17:06:31 -08:00
Gregory Schier
3c0edf06af Remove sandboxing 2023-11-17 09:33:16 -08:00
Gregory Schier
cb8939db88 touch 2023-11-17 08:00:04 -08:00
Gregory Schier
bf4b3213c4 Out of beta 2023-11-17 07:53:26 -08:00
Gregory Schier
633d7c52c4 Tweak 2023-11-17 07:52:03 -08:00
Gregory Schier
0401cb92aa Format GraphQL variables 2023-11-17 07:51:03 -08:00
Gregory Schier
bff6c668a0 Drag into folder (Closes #8) 2023-11-17 07:36:01 -08:00
Gregory Schier
ee87e65763 Mostly move some stuff around 2023-11-16 18:53:34 -08:00
Gregory Schier
f165a0b827 Better update logic 2023-11-14 14:28:06 -08:00
Gregory Schier
f7426dc8ce Better dropdown menu 2023-11-14 10:56:56 -08:00
27 changed files with 174 additions and 160 deletions

View File

@@ -13,12 +13,13 @@ jobs:
include: include:
- os: macos-12 - os: macos-12
target: aarch64-apple-darwin target: aarch64-apple-darwin
- os: macos-latest # - os: macos-latest
target: x86_64-apple-darwin # target: x86_64-apple-darwin
- os: windows-2022 # - os: windows-2022
target: x86_64-pc-windows-msvc # target: x86_64-pc-windows-msvc
- os: ubuntu-20.04 # # Re-enable Linux when context menu is supported
target: x86_64-unknown-linux-gnu # - os: ubuntu-20.04
# target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Desktop" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="npm run tauri build -- --target universal-apple-darwin" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs>
<env name="TAURI_KEY_PASSWORD" value="fishhook-upstream-wash-assured" />
<env name="TAURI_PRIVATE_KEY" value="dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5OGxWaytTa3dIa2xXVUltQzRGUXIzd2lYQ2NpV0ZhQURSbWJWZ1NrK0tnY0FBQkFBQUFBQUFBQUFBQUlBQUFBQUV2M1VKdVRyVHpHSzhQdGc2ZVFtOVNsMU5tNEVSN280cFNrbXhncW9tdjNXaFJZUTJqUzQ5Q01zWTJWRVhaY1pGNHNjR1NFR3JmcWFRN09NdWdGMXpZVXhzejR4V3lDV1JpZHlnbW5LNS9vMFFtRlZjbUl4YjZSNzhlMmk3ait5SExYcG5QZUkxOFE9Cg==" />
</envs>
<method v="2" />
</configuration>
</component>

12
.run/Dev Desktop.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Dev Desktop" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="start" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>

9
package-lock.json generated
View File

@@ -41,6 +41,7 @@
"react-helmet-async": "^1.3.0", "react-helmet-async": "^1.3.0",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.8.1",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"slugify": "^1.6.6",
"tauri-plugin-context-menu": "^0.5.0", "tauri-plugin-context-menu": "^0.5.0",
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1", "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
@@ -7312,6 +7313,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View File

@@ -56,6 +56,7 @@
"react-helmet-async": "^1.3.0", "react-helmet-async": "^1.3.0",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.8.1",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"slugify": "^1.6.6",
"tauri-plugin-context-menu": "^0.5.0", "tauri-plugin-context-menu": "^0.5.0",
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1", "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1",
"uuid": "^9.0.0" "uuid": "^9.0.0"

70
src-tauri/Cargo.lock generated
View File

@@ -123,17 +123,6 @@ dependencies = [
"critical-section", "critical-section",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -535,30 +524,6 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "clap"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags 1.3.2",
"clap_lex",
"indexmap 1.9.3",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "cobs" name = "cobs"
version = "0.2.3" version = "0.2.3"
@@ -1756,15 +1721,6 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.3" version = "0.3.3"
@@ -2162,7 +2118,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi 0.3.3", "hermit-abi",
"rustix", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@@ -2705,7 +2661,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [ dependencies = [
"hermit-abi 0.3.3", "hermit-abi",
"libc", "libc",
] ]
@@ -2883,12 +2839,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@@ -4523,7 +4473,6 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.21.5", "base64 0.21.5",
"bytes", "bytes",
"clap",
"cocoa 0.24.1", "cocoa 0.24.1",
"dirs-next", "dirs-next",
"embed_plist", "embed_plist",
@@ -4782,21 +4731,6 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "termcolor"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]] [[package]]
name = "thin-slice" name = "thin-slice"
version = "0.1.1" version = "0.1.1"

View File

@@ -30,7 +30,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"] } serde_json = { version = "1.0", features = ["raw_value"] }
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] } sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
tauri = { version = "1.3", features = [ tauri = { version = "1.3", features = [
"cli",
"config-toml", "config-toml",
"devtools", "devtools",
"fs-read-file", "fs-read-file",

View File

@@ -2,7 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key> <key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -666,6 +666,8 @@ fn main() {
create_dir_all(dir.clone()).expect("Problem creating App directory!"); create_dir_all(dir.clone()).expect("Problem creating App directory!");
let p = dir.join("db.sqlite"); let p = dir.join("db.sqlite");
File::options().write(true).create(true).open(&p).expect("Problem creating database file!");
let p_string = p.to_string_lossy().replace(' ', "%20"); let p_string = p.to_string_lossy().replace(' ', "%20");
let url = format!("sqlite://{}?mode=rwc", p_string); let url = format!("sqlite://{}?mode=rwc", p_string);
println!("Connecting to database at {}", url); println!("Connecting to database at {}", url);

View File

@@ -4,6 +4,8 @@ use log::info;
use tauri::{AppHandle, updater, Window, Wry}; use tauri::{AppHandle, updater, Window, Wry};
use tauri::api::dialog; use tauri::api::dialog;
use crate::is_dev;
// Check for updates every 3 hours // Check for updates every 3 hours
const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60 * 3; const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60 * 3;
@@ -28,6 +30,10 @@ impl YaakUpdater {
app_handle: &AppHandle<Wry>, app_handle: &AppHandle<Wry>,
mode: UpdateMode, mode: UpdateMode,
) -> Result<(), updater::Error> { ) -> Result<(), updater::Error> {
if is_dev() {
info!("Skipping update check because we are in dev mode");
return Ok(());
}
self.last_update_check = SystemTime::now(); self.last_update_check = SystemTime::now();
let update_mode = get_update_mode_str(mode); let update_mode = get_update_mode_str(mode);
info!("Checking for updates mode={}", update_mode); info!("Checking for updates mode={}", update_mode);
@@ -38,13 +44,40 @@ impl YaakUpdater {
.await .await
{ {
Ok(update) => { Ok(update) => {
if dialog::blocking::ask( let h = app_handle.clone();
dialog::ask(
None::<&Window>, None::<&Window>,
"Update available", "Update Available",
"An update is available. Would you like to download and install it now?", format!(
) { "{} is available. Would you like to download and install it now?",
_ = update.download_and_install().await; update.latest_version()
} ),
|confirmed| {
if !confirmed {
return;
}
tauri::async_runtime::spawn(async move {
match update.download_and_install().await {
Ok(_) => {
if dialog::blocking::ask(
None::<&Window>,
"Update Installed",
format!("Would you like to restart the app?",),
) {
h.restart();
}
}
Err(e) => {
dialog::message(
None::<&Window>,
"Update Failed",
format!("The update failed to install: {}", e),
);
}
}
});
},
);
Ok(()) Ok(())
} }
Err(updater::Error::UpToDate) => Ok(()), Err(updater::Error::UpToDate) => Ok(()),

View File

@@ -8,26 +8,10 @@
}, },
"package": { "package": {
"productName": "Yaak", "productName": "Yaak",
"version": "2023.3.0-beta.1" "version": "2023.4.0-beta.2"
}, },
"tauri": { "tauri": {
"windows": [], "windows": [],
"cli": {
"description": "Yaak CLI",
"longDescription": "This is the Yaak CLI, yo",
"beforeHelp": "u can use it to build, develop and manage your Yaak application.",
"afterHelp": "Have fun!",
"args": [],
"subcommands": {
"import": {
"args": [{
"name": "file",
"short": "f",
"takesValue": true
}]
}
}
},
"allowlist": { "allowlist": {
"all": false, "all": false,
"os": { "os": {
@@ -72,7 +56,7 @@
"icons/release/icon.icns", "icons/release/icon.icns",
"icons/release/icon.ico" "icons/release/icon.ico"
], ],
"identifier": "co.schier.yaak", "identifier": "app.yaak.desktop",
"longDescription": "The best cross-platform visual API client", "longDescription": "The best cross-platform visual API client",
"resources": [ "resources": [
"migrations/*", "migrations/*",

View File

@@ -14,12 +14,13 @@ import { responsesQueryKey } from '../hooks/useResponses';
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle'; import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
import { workspacesQueryKey } from '../hooks/useWorkspaces'; import { workspacesQueryKey } from '../hooks/useWorkspaces';
import { trackPage } from '../lib/analytics'; import { trackPage } from '../lib/analytics';
import { DEFAULT_FONT_SIZE } from '../lib/constants';
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore'; import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models'; import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
import { modelsEq } from '../lib/models'; import { modelsEq } from '../lib/models';
import { setPathname } from '../lib/persistPathname'; import { setPathname } from '../lib/persistPathname';
const DEFAULT_FONT_SIZE = 16;
export function GlobalHooks() { export function GlobalHooks() {
// Include here so they always update, even // Include here so they always update, even
// if no component references them // if no component references them

View File

@@ -2,6 +2,7 @@ import { updateSchema } from 'cm6-graphql';
import type { EditorView } from 'codemirror'; import type { EditorView } from 'codemirror';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL'; import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
import { tryFormatJson } from '../lib/formatters';
import type { HttpRequest } from '../lib/models'; import type { HttpRequest } from '../lib/models';
import { Button } from './core/Button'; import { Button } from './core/Button';
import type { EditorProps } from './core/Editor'; import type { EditorProps } from './core/Editor';
@@ -89,10 +90,10 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
onClick={() => { onClick={() => {
dialog.show({ dialog.show({
title: 'Introspection Failed', title: 'Introspection Failed',
size: 'sm', size: 'dynamic',
id: 'introspection-failed', id: 'introspection-failed',
render: () => ( render: () => (
<div className="whitespace-pre-wrap"> <>
<FormattedError>{error ?? 'unknown'}</FormattedError> <FormattedError>{error ?? 'unknown'}</FormattedError>
<div className="w-full mt-3"> <div className="w-full mt-3">
<Button <Button
@@ -107,7 +108,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
Try Again Try Again
</Button> </Button>
</div> </div>
</div> </>
), ),
}); });
}} }}
@@ -121,6 +122,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
<Separator variant="primary" /> <Separator variant="primary" />
<p className="pt-1 text-gray-500 text-sm">Variables</p> <p className="pt-1 text-gray-500 text-sm">Variables</p>
<Editor <Editor
format={tryFormatJson}
contentType="application/json" contentType="application/json"
defaultValue={JSON.stringify(variables, null, 2)} defaultValue={JSON.stringify(variables, null, 2)}
heightMode="auto" heightMode="auto"

View File

@@ -32,7 +32,7 @@ export function Overlay({
return ( return (
<Portal name={portalName}> <Portal name={portalName}>
{open && ( {open && (
<FocusTrap> <FocusTrap>
<motion.div <motion.div
className={classNames('fixed inset-0', zIndexes[zIndex])} className={classNames('fixed inset-0', zIndexes[zIndex])}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}

View File

@@ -255,7 +255,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
defaultValue={`${activeRequest?.body?.text ?? ''}`} defaultValue={`${activeRequest?.body?.text ?? ''}`}
contentType="application/json" contentType="application/json"
onChange={handleBodyTextChange} onChange={handleBodyTextChange}
format={(v) => tryFormatJson(v)} format={tryFormatJson}
/> />
) : activeRequest.bodyType === BODY_TYPE_XML ? ( ) : activeRequest.bodyType === BODY_TYPE_XML ? (
<Editor <Editor

View File

@@ -20,7 +20,7 @@ const drag = { gridArea: 'drag' };
const DEFAULT = 0.5; const DEFAULT = 0.5;
const MIN_WIDTH_PX = 10; const MIN_WIDTH_PX = 10;
const MIN_HEIGHT_PX = 30; const MIN_HEIGHT_PX = 30;
const STACK_VERTICAL_WIDTH = 650; const STACK_VERTICAL_WIDTH = 600;
export const RequestResponse = memo(function RequestResponse({ style }: Props) { export const RequestResponse = memo(function RequestResponse({ style }: Props) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);

View File

@@ -78,6 +78,11 @@ export function Sidebar({ className }: Props) {
namespace: NAMESPACE_NO_SYNC, namespace: NAMESPACE_NO_SYNC,
}); });
const isCollapsed = useCallback(
(id: string) => collapsed.value?.[id] ?? false,
[collapsed.value],
);
const { tree, treeParentMap, selectableRequests } = useMemo<{ const { tree, treeParentMap, selectableRequests } = useMemo<{
tree: TreeNode | null; tree: TreeNode | null;
treeParentMap: Record<string, TreeNode>; treeParentMap: Record<string, TreeNode>;
@@ -258,13 +263,21 @@ export function Sidebar({ className }: Props) {
const handleMove = useCallback<DraggableSidebarItemProps['onMove']>( const handleMove = useCallback<DraggableSidebarItemProps['onMove']>(
(id, side) => { (id, side) => {
const hoveredTree = treeParentMap[id] ?? null; let hoveredTree = treeParentMap[id] ?? null;
const dragIndex = hoveredTree?.children.findIndex((n) => n.item.id === id) ?? -99; const dragIndex = hoveredTree?.children.findIndex((n) => n.item.id === id) ?? -99;
const hoveredIndex = dragIndex + (side === 'above' ? 0 : 1); const hoveredItem = hoveredTree?.children[dragIndex]?.item ?? null;
let hoveredIndex = dragIndex + (side === 'above' ? 0 : 1);
if (hoveredItem?.model === 'folder' && side === 'below' && !isCollapsed(hoveredItem.id)) {
// Move into folder if it's open and we're moving below it
hoveredTree = hoveredTree?.children.find((n) => n.item.id === id) ?? null;
hoveredIndex = 0;
}
setHoveredTree(hoveredTree); setHoveredTree(hoveredTree);
setHoveredIndex(hoveredIndex); setHoveredIndex(hoveredIndex);
}, },
[treeParentMap], [isCollapsed, treeParentMap],
); );
const handleDragStart = useCallback<DraggableSidebarItemProps['onDragStart']>((id: string) => { const handleDragStart = useCallback<DraggableSidebarItemProps['onDragStart']>((id: string) => {
@@ -340,11 +353,8 @@ export function Sidebar({ className }: Props) {
], ],
); );
if (tree == null) { // Not ready to render yet
return null; if (tree == null || collapsed.value == null) {
}
if (collapsed.value == null) {
return null; return null;
} }
@@ -364,7 +374,7 @@ export function Sidebar({ className }: Props) {
treeParentMap={treeParentMap} treeParentMap={treeParentMap}
selectedId={selectedId} selectedId={selectedId}
selectedTree={selectedTree} selectedTree={selectedTree}
collapsed={collapsed.value} isCollapsed={isCollapsed}
tree={tree} tree={tree}
focused={hasFocus} focused={hasFocus}
draggingId={draggingId} draggingId={draggingId}
@@ -392,7 +402,7 @@ interface SidebarItemsProps {
handleEnd: (id: string) => void; handleEnd: (id: string) => void;
handleDragStart: (id: string) => void; handleDragStart: (id: string) => void;
onSelect: (requestId: string) => void; onSelect: (requestId: string) => void;
collapsed: Record<string, boolean>; isCollapsed: (id: string) => boolean;
} }
function SidebarItems({ function SidebarItems({
@@ -403,7 +413,7 @@ function SidebarItems({
draggingId, draggingId,
onSelect, onSelect,
treeParentMap, treeParentMap,
collapsed, isCollapsed,
hoveredTree, hoveredTree,
hoveredIndex, hoveredIndex,
handleEnd, handleEnd,
@@ -438,16 +448,16 @@ function SidebarItems({
onSelect={onSelect} onSelect={onSelect}
onDragStart={handleDragStart} onDragStart={handleDragStart}
useProminentStyles={focused} useProminentStyles={focused}
collapsed={collapsed} isCollapsed={isCollapsed}
child={child} child={child}
> >
{child.item.model === 'folder' && {child.item.model === 'folder' &&
!collapsed[child.item.id] && !isCollapsed(child.item.id) &&
draggingId !== child.item.id && ( draggingId !== child.item.id && (
<SidebarItems <SidebarItems
treeParentMap={treeParentMap} treeParentMap={treeParentMap}
tree={child} tree={child}
collapsed={collapsed} isCollapsed={isCollapsed}
draggingId={draggingId} draggingId={draggingId}
hoveredTree={hoveredTree} hoveredTree={hoveredTree}
hoveredIndex={hoveredIndex} hoveredIndex={hoveredIndex}
@@ -478,12 +488,10 @@ type SidebarItemProps = {
itemModel: string; itemModel: string;
useProminentStyles?: boolean; useProminentStyles?: boolean;
selected?: boolean; selected?: boolean;
onSelect: (id: string) => void;
draggable?: boolean; draggable?: boolean;
children?: ReactNode; children?: ReactNode;
collapsed: Record<string, boolean>;
child: TreeNode; child: TreeNode;
}; } & Pick<SidebarItemsProps, 'isCollapsed' | 'onSelect'>;
const SidebarItem = forwardRef(function SidebarItem( const SidebarItem = forwardRef(function SidebarItem(
{ {
@@ -496,7 +504,7 @@ const SidebarItem = forwardRef(function SidebarItem(
useProminentStyles, useProminentStyles,
selected, selected,
onSelect, onSelect,
collapsed, isCollapsed,
child, child,
}: SidebarItemProps, }: SidebarItemProps,
ref: ForwardedRef<HTMLLIElement>, ref: ForwardedRef<HTMLLIElement>,
@@ -679,7 +687,7 @@ const SidebarItem = forwardRef(function SidebarItem(
icon="chevronRight" icon="chevronRight"
className={classNames( className={classNames(
'-ml-0.5 mr-2 transition-transform', '-ml-0.5 mr-2 transition-transform',
!collapsed[itemId] && 'transform rotate-90', !isCollapsed(itemId) && 'transform rotate-90',
)} )}
/> />
)} )}

View File

@@ -8,11 +8,10 @@ import type {
} from 'react'; } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useWindowSize } from 'react-use'; import { useWindowSize } from 'react-use';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { useOsInfo } from '../hooks/useOsInfo'; import { useOsInfo } from '../hooks/useOsInfo';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useSidebarWidth } from '../hooks/useSidebarWidth'; import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { HStack } from './core/Stacks'; import { HStack } from './core/Stacks';
import { Overlay } from './Overlay'; import { Overlay } from './Overlay';
@@ -27,6 +26,8 @@ const head = { gridArea: 'head' };
const body = { gridArea: 'body' }; const body = { gridArea: 'body' };
const drag = { gridArea: 'drag' }; const drag = { gridArea: 'drag' };
const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
export default function Workspace() { export default function Workspace() {
const { setWidth, width, resetWidth } = useSidebarWidth(); const { setWidth, width, resetWidth } = useSidebarWidth();
const { hide, show, hidden, toggle } = useSidebarHidden(); const { hide, show, hidden, toggle } = useSidebarHidden();
@@ -66,9 +67,9 @@ export default function Workspace() {
e.preventDefault(); // Prevent text selection and things e.preventDefault(); // Prevent text selection and things
const newWidth = startWidth + (e.clientX - mouseStartX); const newWidth = startWidth + (e.clientX - mouseStartX);
if (newWidth < 100) { if (newWidth < 100) {
hide(); hide();
resetWidth(); resetWidth();
} else { } else {
show(); show();
setWidth(newWidth); setWidth(newWidth);
} }
@@ -121,9 +122,9 @@ export default function Workspace() {
)} )}
> >
{floating ? ( {floating ? (
<Overlay open={!hidden} portalName="sidebar" onClose={hide} zIndex={10}> <Overlay open={!hidden} portalName="sidebar" onClose={hide}>
<motion.div <motion.div
initial={{ opacity: 0, x: -10 }} initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
className={classNames( className={classNames(
'absolute top-0 left-0 bottom-0 bg-gray-100 border-r border-highlight w-[14rem]', 'absolute top-0 left-0 bottom-0 bg-gray-100 border-r border-highlight w-[14rem]',

View File

@@ -3,6 +3,7 @@ import classNames from 'classnames';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes'; import { useAppRoutes } from '../hooks/useAppRoutes';
import { useAppVersion } from '../hooks/useAppVersion';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useExportData } from '../hooks/useExportData'; import { useExportData } from '../hooks/useExportData';
@@ -40,6 +41,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const dialog = useDialog(); const dialog = useDialog();
const prompt = usePrompt(); const prompt = usePrompt();
const routes = useAppRoutes(); const routes = useAppRoutes();
const appVersion = useAppVersion();
const [updateMode, setUpdateMode] = useUpdateMode(); const [updateMode, setUpdateMode] = useUpdateMode();
const items: DropdownItem[] = useMemo(() => { const items: DropdownItem[] = useMemo(() => {
@@ -159,7 +161,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
leftSlot: <Icon icon="upload" />, leftSlot: <Icon icon="upload" />,
onSelect: () => exportData.mutate(), onSelect: () => exportData.mutate(),
}, },
{ type: 'separator' }, { type: 'separator', label: `v${appVersion.data}` },
{ {
key: 'appearance', key: 'appearance',
label: 'Toggle Theme', label: 'Toggle Theme',

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { memo } from 'react'; import React, { memo } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks'; import { HStack } from './core/Stacks';
@@ -17,7 +16,6 @@ interface Props {
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) { export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
const activeWorkspace = useActiveWorkspace();
return ( return (
<HStack <HStack
@@ -29,13 +27,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center"> <HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
<SidebarActions /> <SidebarActions />
<HStack alignItems="center"> <HStack alignItems="center">
<WorkspaceActionsDropdown <WorkspaceActionsDropdown />
leftSlot={
<div className="w-4 h-4 leading-4 rounded text-[0.8em] bg-[#1B88DE] bg-opacity-80 text-white mr-1">
{activeWorkspace?.name[0]?.toUpperCase()}
</div>
}
/>
<Icon icon="chevronRight" className="text-gray-900 text-opacity-disabled" /> <Icon icon="chevronRight" className="text-gray-900 text-opacity-disabled" />
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" /> <EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
</HStack> </HStack>

View File

@@ -72,7 +72,7 @@ export function Dialog({
<span /> <span />
)} )}
{description && <p id={descriptionId}>{description}</p>} {description && <p id={descriptionId}>{description}</p>}
<div className="h-full w-full">{children}</div> <div className="h-full w-full grid grid-cols-[minmax(0,1fr)]">{children}</div>
{/*Put close at the end so that it's the last thing to be tabbed to*/} {/*Put close at the end so that it's the last thing to be tabbed to*/}
{!hideX && ( {!hideX && (
<IconButton <IconButton

View File

@@ -5,11 +5,12 @@ interface Props {
} }
export function FormattedError({ children }: Props) { export function FormattedError({ children }: Props) {
console.log('ERROR', children);
return ( return (
<pre <pre
className={classNames( className={classNames(
'text-sm select-auto cursor-text bg-gray-100 p-3 rounded', 'w-full text-sm select-auto cursor-text bg-gray-100 p-3 rounded',
'whitespace-normal border border-red-500 border-dashed', 'whitespace-pre border border-red-500 border-dashed overflow-x-auto',
)} )}
> >
{children} {children}

View File

@@ -180,8 +180,8 @@ export const PairEditor = memo(function PairEditor({
forceUpdateKey={forceUpdateKey} forceUpdateKey={forceUpdateKey}
nameAutocomplete={nameAutocomplete} nameAutocomplete={nameAutocomplete}
valueAutocomplete={valueAutocomplete} valueAutocomplete={valueAutocomplete}
namePlaceholder={isLast ? namePlaceholder : ''} namePlaceholder={namePlaceholder}
valuePlaceholder={isLast ? valuePlaceholder : ''} valuePlaceholder={valuePlaceholder}
nameValidate={nameValidate} nameValidate={nameValidate}
valueValidate={valueValidate} valueValidate={valueValidate}
onChange={handleChange} onChange={handleChange}

View File

@@ -0,0 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { getVersion } from '@tauri-apps/api/app';
export function useAppVersion() {
return useQuery<string>(['appVersion'], getVersion);
}

View File

@@ -1,17 +1,12 @@
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import type { SaveDialogOptions } from '@tauri-apps/api/dialog';
import { save } from '@tauri-apps/api/dialog'; import { save } from '@tauri-apps/api/dialog';
import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import slugify from 'slugify';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert'; import { useAlert } from './useAlert';
const saveArgs: SaveDialogOptions = {
title: 'Export Data',
defaultPath: 'yaak-export.json',
};
export function useExportData() { export function useExportData() {
const workspaceId = useActiveWorkspaceId(); const workspace = useActiveWorkspace();
const alert = useAlert(); const alert = useAlert();
return useMutation({ return useMutation({
@@ -19,12 +14,18 @@ export function useExportData() {
alert({ title: 'Export Failed', body: err }); alert({ title: 'Export Failed', body: err });
}, },
mutationFn: async () => { mutationFn: async () => {
const exportPath = await save(saveArgs); if (workspace == null) return;
const workspaceSlug = slugify(workspace.name, { lower: true });
const exportPath = await save({
title: 'Export Data',
defaultPath: `yaak.${workspaceSlug}.json`,
});
if (exportPath == null) { if (exportPath == null) {
return; return;
} }
await invoke('export_data', { workspaceId, exportPath }); await invoke('export_data', { workspaceId: workspace.id, exportPath });
}, },
}); });
} }

View File

@@ -44,8 +44,9 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
} }
if (response.status < 200 || response.status >= 300) { if (response.status < 200 || response.status >= 300) {
const text = await getResponseBodyText(response);
return Promise.reject( return Promise.reject(
new Error(`Request failed with status ${response.status}.\n\n${response.body}`), new Error(`Request failed with status ${response.status}.\n\n${text}`),
); );
} }

View File

@@ -1,2 +0,0 @@
export const DEFAULT_FONT_SIZE = 16;
export const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;