Compare commits

...

1 Commits

Author SHA1 Message Date
Gregory Schier
4f9a7e9c88 2024.5.0 (#39) 2024-06-03 14:08:24 -07:00
197 changed files with 12283 additions and 3505 deletions

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- release
- beta
jobs:
build-artifacts:
permissions:
@@ -36,11 +37,11 @@ jobs:
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lint
- name: Run tests
run: npm test
- uses: tauri-apps/tauri-action@v0

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Yaak App</title>
<!-- <script src="http://localhost:8097"></script>-->
@@ -15,13 +15,13 @@
@media (prefers-color-scheme: dark) {
html, body {
background-color: black;
background-color: #1b1a29;
}
}
</style>
</head>
<body>
<body class="text-base">
<div id="root"></div>
<div id="cm-portal" class="cm-portal"></div>
<div id="react-portal"></div>

6
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@codemirror/commands": "^6.2.1",
"@codemirror/lang-javascript": "^6.1.4",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "^6.6.0",
@@ -54,7 +54,7 @@
"devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tanstack/react-query-devtools": "^5.35.5",
"@tauri-apps/cli": ">=2.0.0-beta.0",
"@tauri-apps/cli": "^2.0.0-beta.15",
"@types/node": "^18.7.10",
"@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",
@@ -81,7 +81,7 @@
"prettier": "^2.8.4",
"react-devtools": "^4.27.2",
"tailwindcss": "^3.2.7",
"typescript": "^5.3.3",
"typescript": "^5.4.5",
"vite": "^5.0.0",
"vite-plugin-svgr": "^4.2.0",
"vite-plugin-top-level-await": "^1.4.1",

View File

@@ -4,9 +4,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "npm run build:plugins && npm run tauri-dev:desktop",
"start": "npm run tauri-dev:desktop",
"tauri-dev:desktop": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
"tauri-dev:ios": "tauri ios dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
"tauri-dev:ios": "tauri ios dev --force-ip-prompt --config ./src-tauri/tauri-dev.conf.json",
"tauri-build": "tauri build",
"tauri": "tauri",
"build": "npm run build:frontend",
@@ -29,7 +29,7 @@
},
"dependencies": {
"@codemirror/commands": "^6.2.1",
"@codemirror/lang-javascript": "^6.1.4",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "^6.6.0",
@@ -74,7 +74,7 @@
"devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tanstack/react-query-devtools": "^5.35.5",
"@tauri-apps/cli": ">=2.0.0-beta.0",
"@tauri-apps/cli": "^2.0.0-beta.15",
"@types/node": "^18.7.10",
"@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",
@@ -101,7 +101,7 @@
"prettier": "^2.8.4",
"react-devtools": "^4.27.2",
"tailwindcss": "^3.2.7",
"typescript": "^5.3.3",
"typescript": "^5.4.5",
"vite": "^5.0.0",
"vite-plugin-svgr": "^4.2.0",
"vite-plugin-top-level-await": "^1.4.1",

View File

@@ -180,6 +180,7 @@ function importBody(rawBody: any): Pick<HttpRequest, 'body' | 'bodyType' | 'head
f.src != null
? {
enabled: !f.disabled,
contentType: f.contentType ?? null,
name: f.key ?? '',
file: f.src ?? '',
}
@@ -244,6 +245,7 @@ function convertTemplateSyntax<T>(obj: T): T {
}
const idCount: Partial<Record<Model['model'], number>> = {};
function generateId(model: Model['model']): string {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE settings SET (\n theme, appearance, update_channel\n ) = (?, ?, ?) WHERE id = 'default';\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0"
}

View File

@@ -1,56 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance, update_channel\n FROM settings\n WHERE id = 'default'\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "model",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 2,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "theme",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "appearance",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "update_channel",
"ordinal": 6,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb"
}

View File

@@ -0,0 +1,92 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap\n FROM settings\n WHERE id = 'default'\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "model",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 2,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "theme",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "appearance",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "theme_dark",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "theme_light",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "update_channel",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "interface_font_size",
"ordinal": 9,
"type_info": "Int64"
},
{
"name": "interface_scale",
"ordinal": 10,
"type_info": "Int64"
},
{
"name": "editor_font_size",
"ordinal": 11,
"type_info": "Int64"
},
{
"name": "editor_soft_wrap",
"ordinal": 12,
"type_info": "Bool"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "ca3485d87b060cd77c4114d2af544adf18f6f15341d9d5db40865e92a80da4e2"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n UPDATE settings SET (\n theme, appearance, theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "efd8ba41ea909b18dd520c57c1d464c5ae057b720cbbedcaec1513d43535632c"
}

1735
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,13 @@ tauri-build = { version = "2.0.0-beta", features = [] }
objc = "0.2.7"
cocoa = "0.25.0"
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.56.0", features = [
"Win32_Graphics_Dwm",
"Win32_Foundation",
"Win32_UI_Controls",
] }
[target.'cfg(target_os = "linux")'.dependencies]
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
@@ -34,7 +41,7 @@ reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "br
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
tauri = { version = "2.0.0-beta.19", features = ["config-toml", "devtools", "protocol-asset"] }
tauri = { version = "2.0.0-beta.22", features = ["config-toml", "devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2", features = ["colored"] }
@@ -43,6 +50,7 @@ tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", bra
tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tokio = { version = "1.36.0", features = ["sync"] }
uuid = "1.7.0"
log = "0.4.21"
@@ -51,3 +59,4 @@ reqwest_cookie_store = "0.6.0"
grpc = { path = "./grpc" }
tokio-stream = "0.1.15"
regex = "1.10.2"
hex_color = "3.0.0"

View File

@@ -39,6 +39,7 @@
}
]
},
"webview:allow-set-webview-zoom",
"window:allow-close",
"window:allow-is-fullscreen",
"window:allow-maximize",
@@ -48,6 +49,7 @@
"window:allow-set-title",
"window:allow-start-dragging",
"window:allow-unmaximize",
"window:allow-theme",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text"
]

View File

@@ -1,3 +0,0 @@
xcuserdata/
build/
Externals/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,116 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "AppIcon-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "AppIcon-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29x29@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "AppIcon-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "AppIcon-20x20@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "AppIcon-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "AppIcon-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "AppIcon-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "AppIcon-40x40@2x-1.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "AppIcon-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "AppIcon-512@2x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>development</string>
</dict>
</plist>

View File

@@ -1,21 +0,0 @@
# Uncomment the next line to define a global platform for your project
target 'yaak-app_iOS' do
platform :ios, '13.0'
# Pods for yaak-app_iOS
end
target 'yaak-app_macOS' do
platform :osx, '11.0'
# Pods for yaak-app_macOS
end
# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET'
end
end
end

View File

@@ -1,8 +0,0 @@
#pragma once
namespace ffi {
extern "C" {
void start_app();
}
}

View File

@@ -1,6 +0,0 @@
#include "bindings/bindings.h"
int main(int argc, char * argv[]) {
ffi::start_app();
return 0;
}

View File

@@ -1,90 +0,0 @@
name: yaak-app
options:
bundleIdPrefix: app.yaak
deploymentTarget:
iOS: 13.0
fileGroups: [../../src]
configs:
debug: debug
release: release
settingGroups:
app:
base:
PRODUCT_NAME: Yaak
PRODUCT_BUNDLE_IDENTIFIER: app.yaak.yaak-app
DEVELOPMENT_TEAM: 7PU3P6ELJ8
targetTemplates:
app:
type: application
sources:
- path: Sources
scheme:
environmentVariables:
RUST_BACKTRACE: full
RUST_LOG: info
settings:
groups: [app]
targets:
yaak-app_iOS:
type: application
platform: iOS
sources:
- path: Sources
- path: Assets.xcassets
- path: Externals
- path: yaak-app_iOS
- path: assets
buildPhase: resources
type: folder
info:
path: yaak-app_iOS/Info.plist
properties:
LSRequiresIPhoneOS: true
UILaunchStoryboardName: LaunchScreen
UIRequiredDeviceCapabilities: [arm64, metal]
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
CFBundleShortVersionString: 2024.3.10
CFBundleVersion: 2024.3.10
entitlements:
path: yaak-app_iOS/yaak-app_iOS.entitlements
scheme:
environmentVariables:
RUST_BACKTRACE: full
RUST_LOG: info
settings:
base:
ENABLE_BITCODE: false
ARCHS: [arm64, arm64-sim]
VALID_ARCHS: arm64 arm64-sim
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64
EXCLUDED_ARCHS[sdk=iphoneos*]: arm64-sim x86_64
groups: [app]
dependencies:
- framework: libapp_lib.a
embed: false
- sdk: CoreGraphics.framework
- sdk: Metal.framework
- sdk: MetalKit.framework
- sdk: QuartzCore.framework
- sdk: Security.framework
- sdk: UIKit.framework
- sdk: WebKit.framework
preBuildScripts:
- script: node tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
name: Build Rust Code
basedOnDependencyAnalysis: false
outputFiles:
- $(SRCROOT)/target/aarch64-apple-ios/${CONFIGURATION}/deps/libapp_lib.a
- $(SRCROOT)/target/x86_64-apple-ios/${CONFIGURATION}/deps/libapp_lib.a

View File

@@ -1,481 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
0AC23E163631EF3775908406 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDEF0D2E01608E7F464F71B6 /* WebKit.framework */; };
1B1BFDF8BC345D0D980E4427 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF0B8CF73BE8166011E2CEAB /* QuartzCore.framework */; };
36588BE1A75B386BB2FEDAC5 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A93A95F6B2F31FA92AA099E0 /* MetalKit.framework */; };
38E2C1B0E9FCC9A5FDE8876D /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF2908761467DF191C2A8939 /* Metal.framework */; };
8D518C1A67069BD7D339D055 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F34A96C5084EFDF1802A634 /* CoreGraphics.framework */; };
8DF67739DC49E577EB0FAE3F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 396F45DCFBE2C71866817528 /* Assets.xcassets */; };
A1D932F0E7399066AD07DC6D /* libapp_lib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D938BE0FA8770BA965AE1E /* libapp_lib.a */; };
AF0EEC868306E1D1C85994D0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5BF2E39256494269E65F8E /* Security.framework */; };
BE9FFDF51EB7DEBF707BB39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5415A3F2D217A47DD3BA40B3 /* UIKit.framework */; };
F0627C04787F4E187EF416F4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 2A615609009B3AE2728043E4 /* assets */; };
FEE5934F5FFB0FBE10883AF2 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = C754460F1DAF2D414038A7EA /* main.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
106BE62BE01A35403424018C /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = "<group>"; };
14F240DAC31C5C52D7B4BB96 /* window_ext.rs */ = {isa = PBXFileReference; path = window_ext.rs; sourceTree = "<group>"; };
1B5226A88D8B805E878524C8 /* updates.rs */ = {isa = PBXFileReference; path = updates.rs; sourceTree = "<group>"; };
1F34A96C5084EFDF1802A634 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
1F5A647F82A24722F3C830BB /* plugin.rs */ = {isa = PBXFileReference; path = plugin.rs; sourceTree = "<group>"; };
2A615609009B3AE2728043E4 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; };
396F45DCFBE2C71866817528 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
53872C1120171EDC4A6DFEDD /* analytics.rs */ = {isa = PBXFileReference; path = analytics.rs; sourceTree = "<group>"; };
5415A3F2D217A47DD3BA40B3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
5C1B6610F62B56E1947BEBBD /* http.rs */ = {isa = PBXFileReference; path = http.rs; sourceTree = "<group>"; };
6286C385ABAD2E04237679D7 /* yaak-app_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "yaak-app_iOS.entitlements"; sourceTree = "<group>"; };
75D938BE0FA8770BA965AE1E /* libapp_lib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp_lib.a; sourceTree = "<group>"; };
7B5BF2E39256494269E65F8E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
A2CC02313D71CECB68031D53 /* grpc.rs */ = {isa = PBXFileReference; path = grpc.rs; sourceTree = "<group>"; };
A6DA9B210723CA84891876F8 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = "<group>"; };
A93A95F6B2F31FA92AA099E0 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };
C754460F1DAF2D414038A7EA /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
D69BFB768591FDEEF65198EE /* yaak-app_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "yaak-app_iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
DDDE197D9C6BC5680EEEEA00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
DF2908761467DF191C2A8939 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
DF45D08D97DE587CABF9537E /* window_menu.rs */ = {isa = PBXFileReference; path = window_menu.rs; sourceTree = "<group>"; };
E1E84E267D81D6437901B1C6 /* render.rs */ = {isa = PBXFileReference; path = render.rs; sourceTree = "<group>"; };
E964D3637BAED49E34B91739 /* models.rs */ = {isa = PBXFileReference; path = models.rs; sourceTree = "<group>"; };
EDEF0D2E01608E7F464F71B6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
EF0B8CF73BE8166011E2CEAB /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
FB34CB48BB9F25D49F80D513 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D8E8888B0F3E4411B98AE8EE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A1D932F0E7399066AD07DC6D /* libapp_lib.a in Frameworks */,
8D518C1A67069BD7D339D055 /* CoreGraphics.framework in Frameworks */,
38E2C1B0E9FCC9A5FDE8876D /* Metal.framework in Frameworks */,
36588BE1A75B386BB2FEDAC5 /* MetalKit.framework in Frameworks */,
1B1BFDF8BC345D0D980E4427 /* QuartzCore.framework in Frameworks */,
AF0EEC868306E1D1C85994D0 /* Security.framework in Frameworks */,
BE9FFDF51EB7DEBF707BB39A /* UIKit.framework in Frameworks */,
0AC23E163631EF3775908406 /* WebKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0844ACEFE550685042AC6029 /* Products */ = {
isa = PBXGroup;
children = (
D69BFB768591FDEEF65198EE /* yaak-app_iOS.app */,
);
name = Products;
sourceTree = "<group>";
};
6D0B6FF641B88DAF74C78B00 /* Externals */ = {
isa = PBXGroup;
children = (
);
path = Externals;
sourceTree = "<group>";
};
8A07575CB8654BB9107F9A32 /* bindings */ = {
isa = PBXGroup;
children = (
A6DA9B210723CA84891876F8 /* bindings.h */,
);
path = bindings;
sourceTree = "<group>";
};
8F0B46911FBEF2B246BE3385 /* yaak-app */ = {
isa = PBXGroup;
children = (
C754460F1DAF2D414038A7EA /* main.mm */,
8A07575CB8654BB9107F9A32 /* bindings */,
);
path = "yaak-app";
sourceTree = "<group>";
};
90E982C0E9B45CBAAE66E16D /* Frameworks */ = {
isa = PBXGroup;
children = (
1F34A96C5084EFDF1802A634 /* CoreGraphics.framework */,
75D938BE0FA8770BA965AE1E /* libapp_lib.a */,
DF2908761467DF191C2A8939 /* Metal.framework */,
A93A95F6B2F31FA92AA099E0 /* MetalKit.framework */,
EF0B8CF73BE8166011E2CEAB /* QuartzCore.framework */,
7B5BF2E39256494269E65F8E /* Security.framework */,
5415A3F2D217A47DD3BA40B3 /* UIKit.framework */,
EDEF0D2E01608E7F464F71B6 /* WebKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
C88F9D29DC52F052255C35A3 = {
isa = PBXGroup;
children = (
2A615609009B3AE2728043E4 /* assets */,
396F45DCFBE2C71866817528 /* Assets.xcassets */,
6D0B6FF641B88DAF74C78B00 /* Externals */,
EBC83899FBFA4A3D0A92837F /* Sources */,
F3A6B45E25E23922AB1BDB34 /* src */,
D49CF68C9105CE84E2084C14 /* yaak-app_iOS */,
90E982C0E9B45CBAAE66E16D /* Frameworks */,
0844ACEFE550685042AC6029 /* Products */,
);
sourceTree = "<group>";
};
D49CF68C9105CE84E2084C14 /* yaak-app_iOS */ = {
isa = PBXGroup;
children = (
DDDE197D9C6BC5680EEEEA00 /* Info.plist */,
6286C385ABAD2E04237679D7 /* yaak-app_iOS.entitlements */,
);
path = "yaak-app_iOS";
sourceTree = "<group>";
};
EBC83899FBFA4A3D0A92837F /* Sources */ = {
isa = PBXGroup;
children = (
8F0B46911FBEF2B246BE3385 /* yaak-app */,
);
path = Sources;
sourceTree = "<group>";
};
F3A6B45E25E23922AB1BDB34 /* src */ = {
isa = PBXGroup;
children = (
53872C1120171EDC4A6DFEDD /* analytics.rs */,
A2CC02313D71CECB68031D53 /* grpc.rs */,
5C1B6610F62B56E1947BEBBD /* http.rs */,
FB34CB48BB9F25D49F80D513 /* lib.rs */,
106BE62BE01A35403424018C /* main.rs */,
E964D3637BAED49E34B91739 /* models.rs */,
1F5A647F82A24722F3C830BB /* plugin.rs */,
E1E84E267D81D6437901B1C6 /* render.rs */,
1B5226A88D8B805E878524C8 /* updates.rs */,
14F240DAC31C5C52D7B4BB96 /* window_ext.rs */,
DF45D08D97DE587CABF9537E /* window_menu.rs */,
);
name = src;
path = ../../src;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7C3E2AC18A0A227C2DF356E2 /* yaak-app_iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = C05E07AE7C7B25CACFADCDD4 /* Build configuration list for PBXNativeTarget "yaak-app_iOS" */;
buildPhases = (
5454ED506FC51D41C81A0318 /* Build Rust Code */,
C3495A2849227C6276D3876E /* Sources */,
E148188313FB67F061AB4E59 /* Resources */,
D8E8888B0F3E4411B98AE8EE /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "yaak-app_iOS";
productName = "yaak-app_iOS";
productReference = D69BFB768591FDEEF65198EE /* yaak-app_iOS.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
A8F6206BC76F061F1FEFD439 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
TargetAttributes = {
7C3E2AC18A0A227C2DF356E2 = {
DevelopmentTeam = 7PU3P6ELJ8;
};
};
};
buildConfigurationList = 24EF8D1B948FFF6B275FB0F4 /* Build configuration list for PBXProject "yaak-app" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
Base,
en,
);
mainGroup = C88F9D29DC52F052255C35A3;
projectDirPath = "";
projectRoot = "";
targets = (
7C3E2AC18A0A227C2DF356E2 /* yaak-app_iOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E148188313FB67F061AB4E59 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8DF67739DC49E577EB0FAE3F /* Assets.xcassets in Resources */,
F0627C04787F4E187EF416F4 /* assets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5454ED506FC51D41C81A0318 /* Build Rust Code */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Build Rust Code";
outputFileListPaths = (
);
outputPaths = (
"$(SRCROOT)/target/aarch64-apple-ios/${CONFIGURATION}/deps/libapp_lib.a",
"$(SRCROOT)/target/x86_64-apple-ios/${CONFIGURATION}/deps/libapp_lib.a",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "node tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C3495A2849227C6276D3876E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FEE5934F5FFB0FBE10883AF2 /* main.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
35D1DB294FFD067C835186C7 /* debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = debug;
};
368BB1E364597E7675463634 /* release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = (
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "yaak-app_iOS/yaak-app_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = 7PU3P6ELJ8;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
);
INFOPLIST_FILE = "yaak-app_iOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
"LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
"LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = "app.yaak.yaak-app";
PRODUCT_NAME = Yaak;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 arm64-sim";
};
name = release;
};
45382E89556BF93E8D1F1C2D /* debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = (
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "yaak-app_iOS/yaak-app_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = 7PU3P6ELJ8;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
);
INFOPLIST_FILE = "yaak-app_iOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
"LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
"LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = "app.yaak.yaak-app";
PRODUCT_NAME = Yaak;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 arm64-sim";
};
name = debug;
};
ABD0A3DD3D5C66C839496F44 /* release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
24EF8D1B948FFF6B275FB0F4 /* Build configuration list for PBXProject "yaak-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
35D1DB294FFD067C835186C7 /* debug */,
ABD0A3DD3D5C66C839496F44 /* release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = debug;
};
C05E07AE7C7B25CACFADCDD4 /* Build configuration list for PBXNativeTarget "yaak-app_iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
45382E89556BF93E8D1F1C2D /* debug */,
368BB1E364597E7675463634 /* release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = debug;
};
/* End XCConfigurationList section */
};
rootObject = A8F6206BC76F061F1FEFD439 /* Project object */;
}

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
<key>DisableBuildSystemDeprecationDiagnostic</key>
<true/>
</dict>
</plist>

View File

@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7C3E2AC18A0A227C2DF356E2"
BuildableName = "Yaak.app"
BlueprintName = "yaak-app_iOS"
ReferencedContainer = "container:yaak-app.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7C3E2AC18A0A227C2DF356E2"
BuildableName = "Yaak.app"
BlueprintName = "yaak-app_iOS"
ReferencedContainer = "container:yaak-app.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "RUST_BACKTRACE"
value = "full"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "RUST_LOG"
value = "info"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7C3E2AC18A0A227C2DF356E2"
BuildableName = "Yaak.app"
BlueprintName = "yaak-app_iOS"
ReferencedContainer = "container:yaak-app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "RUST_BACKTRACE"
value = "full"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "RUST_LOG"
value = "info"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "release"
shouldUseLaunchSchemeArgsEnv = "NO"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7C3E2AC18A0A227C2DF356E2"
BuildableName = "Yaak.app"
BlueprintName = "yaak-app_iOS"
ReferencedContainer = "container:yaak-app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "RUST_BACKTRACE"
value = "full"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "RUST_LOG"
value = "info"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2024.3.10</string>
<key>CFBundleVersion</key>
<string>2024.3.10</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
<string>metal</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["os:allow-os-type","event:allow-emit","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","event:allow-listen","event:allow-unlisten","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":true,"name":"protoc","sidecar":true}]},"window:allow-close","window:allow-is-fullscreen","window:allow-maximize","window:allow-minimize","window:allow-toggle-maximize","window:allow-set-decorations","window:allow-set-title","window:allow-start-dragging","window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["os:allow-os-type","event:allow-emit","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","event:allow-listen","event:allow-unlisten","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":true,"name":"protoc","sidecar":true}]},"webview:allow-set-webview-zoom","window:allow-close","window:allow-is-fullscreen","window:allow-maximize","window:allow-minimize","window:allow-toggle-maximize","window:allow-set-decorations","window:allow-set-title","window:allow-start-dragging","window:allow-unmaximize","window:allow-theme","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}

View File

@@ -2216,6 +2216,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2244,6 +2251,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2498,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [
@@ -5323,6 +5400,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5351,6 +5435,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5533,6 +5624,13 @@
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5540,6 +5638,13 @@
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
@@ -5547,6 +5652,13 @@
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5554,6 +5666,13 @@
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "webview:default -> Default permissions for the plugin.",
"type": "string",
@@ -5897,6 +6016,13 @@
"window:allow-minimize"
]
},
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string",
@@ -6331,6 +6457,13 @@
"window:deny-minimize"
]
},
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string",

View File

@@ -2216,6 +2216,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2244,6 +2251,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2498,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [
@@ -5323,6 +5400,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5351,6 +5435,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5533,6 +5624,13 @@
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5540,6 +5638,13 @@
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
@@ -5547,6 +5652,13 @@
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5554,6 +5666,13 @@
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "webview:default -> Default permissions for the plugin.",
"type": "string",
@@ -5897,6 +6016,13 @@
"window:allow-minimize"
]
},
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string",
@@ -6331,6 +6457,13 @@
"window:deny-minimize"
]
},
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string",

View File

@@ -2216,6 +2216,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2244,6 +2251,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2498,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [
@@ -5323,6 +5400,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5351,6 +5435,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5533,6 +5624,13 @@
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5540,6 +5638,13 @@
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
@@ -5547,6 +5652,13 @@
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5554,6 +5666,13 @@
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "webview:default -> Default permissions for the plugin.",
"type": "string",
@@ -5897,6 +6016,13 @@
"window:allow-minimize"
]
},
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string",
@@ -6331,6 +6457,13 @@
"window:deny-minimize"
]
},
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
ALTER TABLE settings
ADD COLUMN theme_dark TEXT DEFAULT 'yaak-dark' NOT NULL;
ALTER TABLE settings
ADD COLUMN theme_light TEXT DEFAULT 'yaak-light' NOT NULL;

View File

@@ -0,0 +1,4 @@
ALTER TABLE settings ADD COLUMN interface_font_size INTEGER DEFAULT 15 NOT NULL;
ALTER TABLE settings ADD COLUMN interface_scale INTEGER DEFAULT 1 NOT NULL;
ALTER TABLE settings ADD COLUMN editor_font_size INTEGER DEFAULT 13 NOT NULL;
ALTER TABLE settings ADD COLUMN editor_soft_wrap BOOLEAN DEFAULT 1 NOT NULL;

View File

@@ -131,6 +131,7 @@ function j(e) {
form: b(t.formdata).map(
(n) => n.src != null ? {
enabled: !n.disabled,
contentType: n.contentType ?? null,
name: n.key ?? "",
file: n.src ?? ""
} : {

View File

@@ -1,6 +1,6 @@
use std::fmt::Display;
use log::warn;
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::types::JsonValue;
@@ -19,6 +19,7 @@ const NUM_LAUNCHES_KEY: &str = "num_launches";
#[serde(rename_all = "snake_case")]
pub enum AnalyticsResource {
App,
Appearance,
CookieJar,
Dialog,
Environment,
@@ -29,9 +30,10 @@ pub enum AnalyticsResource {
HttpRequest,
HttpResponse,
KeyValue,
Sidebar,
Workspace,
Setting,
Sidebar,
Theme,
Workspace,
}
impl AnalyticsResource {
@@ -187,7 +189,7 @@ pub async fn track_event(
// Disable analytics actual sending in dev
if is_dev() {
// debug!("track: {} {} {:?}", event, attributes_json, params);
debug!("track: {}", event);
return;
}
@@ -247,4 +249,4 @@ async fn get_id(app_handle: &AppHandle) -> String {
pub async fn get_num_launches(app: &AppHandle) -> i32 {
get_key_value_int(app, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
}
}

View File

@@ -7,11 +7,11 @@ use std::sync::Arc;
use std::time::Duration;
use base64::Engine;
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use http::header::{ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use log::{error, info, warn};
use reqwest::{multipart, Url};
use reqwest::redirect::Policy;
use reqwest::{multipart, Url};
use sqlx::types::{Json, JsonValue};
use tauri::{Manager, WebviewWindow};
use tokio::sync::oneshot;
@@ -89,14 +89,24 @@ pub async fn send_http_request(
let uri = match http::Uri::from_str(url_string.as_str()) {
Ok(u) => u,
Err(e) => {
return response_err(response, e.to_string(), window).await;
return response_err(
response,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window,
)
.await;
}
};
// Yes, we're parsing both URI and URL because they could return different errors
let url = match Url::from_str(uri.to_string().as_str()) {
Ok(u) => u,
Err(e) => {
return response_err(response, e.to_string(), window).await;
return response_err(
response,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window,
)
.await;
}
};
@@ -290,7 +300,7 @@ pub async fn send_http_request(
.unwrap_or_default();
let name = render::render(name_raw, &workspace, environment_ref);
let part = if file_path.is_empty() {
let mut part = if file_path.is_empty() {
multipart::Part::text(render::render(
value_raw,
&workspace,
@@ -311,23 +321,24 @@ pub async fn send_http_request(
.as_str()
.unwrap_or_default();
multipart_form = multipart_form.part(
name,
if ct_raw.is_empty() {
part
} else {
let content_type = render::render(ct_raw, &workspace, environment_ref);
let filename = PathBuf::from(file_path)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
part.file_name(filename)
.mime_str(content_type.as_str())
.map_err(|e| e.to_string())?
},
);
if !ct_raw.is_empty() {
let content_type = render::render(ct_raw, &workspace, environment_ref);
part = part
.mime_str(content_type.as_str())
.map_err(|e| e.to_string())?;
}
if !file_path.is_empty() {
let filename = PathBuf::from(file_path)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
part = part.file_name(filename);
}
multipart_form = multipart_form.part(name, part);
}
}
headers.remove("Content-Type"); // reqwest will add this automatically

View File

@@ -6,72 +6,77 @@ extern crate objc;
use std::collections::HashMap;
use std::env::current_dir;
use std::fs;
use std::fs::{create_dir_all, File, read_to_string};
use std::fs::{create_dir_all, read_to_string, File};
use std::path::PathBuf;
use std::process::exit;
use std::str::FromStr;
use std::time::Duration;
use ::http::Uri;
use ::http::uri::InvalidUri;
use ::http::Uri;
use base64::Engine;
use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
use rand::random;
use serde_json::{json, Value};
use sqlx::{Pool, Sqlite, SqlitePool};
use sqlx::migrate::Migrator;
use sqlx::sqlite::SqliteConnectOptions;
use sqlx::types::Json;
use tauri::{AppHandle, RunEvent, State, WebviewUrl, WebviewWindow};
use tauri::{Manager, WindowEvent};
use sqlx::{Pool, Sqlite, SqlitePool};
use tauri::path::BaseDirectory;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
use tauri::{AppHandle, LogicalSize, RunEvent, State, WebviewUrl, WebviewWindow};
use tauri::{Manager, WindowEvent};
use tauri_plugin_log::{fern, Target, TargetKind};
use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex;
use ::grpc::{Code, deserialize_message, serialize_message, ServiceDefinition};
use ::grpc::manager::{DynamicMessage, GrpcHandle};
use window_ext::TrafficLightWindowExt;
use ::grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
use crate::analytics::{AnalyticsAction, AnalyticsResource};
use crate::grpc::metadata_to_map;
use crate::http::send_http_request;
use crate::http_request::send_http_request;
use crate::models::{
cancel_pending_grpc_connections, cancel_pending_responses, CookieJar,
create_http_response, delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar,
delete_environment, delete_folder, delete_grpc_connection, delete_grpc_request,
delete_http_request, delete_http_response, delete_workspace, duplicate_grpc_request,
duplicate_http_request, Environment, EnvironmentVariable, Folder, generate_model_id,
get_cookie_jar, get_environment, get_folder, get_grpc_connection,
cancel_pending_grpc_connections, cancel_pending_responses, create_http_response,
delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar, delete_environment,
delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request,
delete_http_response, delete_workspace, duplicate_grpc_request, duplicate_http_request,
generate_model_id, get_cookie_jar, get_environment, get_folder, get_grpc_connection,
get_grpc_request, get_http_request, get_http_response, get_key_value_raw,
get_or_create_settings, get_workspace, get_workspace_export_resources, GrpcConnection, GrpcEvent,
GrpcEventType, GrpcRequest, HttpRequest, HttpResponse,
KeyValue, list_cookie_jars, list_environments, list_folders, list_grpc_connections,
list_grpc_events, list_grpc_requests, list_http_requests, list_responses, list_workspaces,
ModelType, set_key_value_raw, Settings, update_response_if_id, update_settings, upsert_cookie_jar,
upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_workspace,
Workspace, WorkspaceExportResources,
get_or_create_settings, get_workspace, get_workspace_export_resources, list_cookie_jars,
list_environments, list_folders, list_grpc_connections, list_grpc_events, list_grpc_requests,
list_http_requests, list_responses, list_workspaces, set_key_value_raw, update_response_if_id,
update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_workspace, CookieJar,
Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcEvent, GrpcEventType,
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Settings, Workspace,
WorkspaceExportResources,
};
use crate::notifications::YaakNotifier;
use crate::plugin::{ImportResult, run_plugin_export_curl, run_plugin_import};
use crate::plugin::{run_plugin_export_curl, run_plugin_import, ImportResult};
use crate::render::render_request;
use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu;
mod analytics;
mod grpc;
mod http;
mod http_request;
mod models;
mod notifications;
mod plugin;
mod render;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
#[cfg(target_os = "windows")]
mod tauri_plugin_windows_window;
mod updates;
mod window_ext;
mod window_menu;
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
let pool = &*db.lock().await;
let p = app_handle
@@ -747,16 +752,16 @@ async fn cmd_import_data(
) -> Result<WorkspaceExportResources, String> {
let mut result: Option<ImportResult> = None;
let plugins = vec![
"importer-yaak",
"importer-insomnia",
"importer-postman",
"importer-insomnia",
"importer-yaak",
"importer-curl",
];
let file = fs::read_to_string(file_path)
let file = read_to_string(file_path)
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
for plugin_name in plugins {
let v = plugin::run_plugin_import(&w.app_handle(), plugin_name, file_contents)
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
.await
.map_err(|e| e.to_string())?;
if let Some(r) = v {
@@ -1290,6 +1295,15 @@ async fn cmd_update_folder(folder: Folder, w: WebviewWindow) -> Result<Folder, S
upsert_folder(&w, folder).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_write_file_dev(pathname: &str, contents: &str) -> Result<(), String> {
if !is_dev() {
panic!("Cannot write arbitrary files when not in dev mode");
}
fs::write(pathname, contents).map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_delete_folder(w: WebviewWindow, folder_id: &str) -> Result<Folder, String> {
delete_folder(&w, folder_id)
@@ -1491,8 +1505,19 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
}
#[tauri::command]
async fn cmd_new_window(window: WebviewWindow, url: &str) -> Result<(), String> {
create_window(&window.app_handle(), Some(url));
async fn cmd_new_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
create_window(&app_handle, url);
Ok(())
}
#[tauri::command]
async fn cmd_new_nested_window(
window: WebviewWindow,
url: &str,
label: &str,
title: &str,
) -> Result<(), String> {
create_nested_window(&window, label, url, title);
Ok(())
}
@@ -1519,14 +1544,31 @@ async fn cmd_check_for_updates(
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_fs::init());
#[cfg(target_os = "windows")]
{
builder = builder.plugin(tauri_plugin_windows_window::init());
}
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_plugin_mac_window::init());
}
#[cfg(target_os = "linux")]
{
builder = builder; // Don't complain about not being mut
}
builder
.plugin(
tauri_plugin_log::Builder::default()
.targets([
@@ -1652,6 +1694,7 @@ pub fn run() {
cmd_list_http_responses,
cmd_list_workspaces,
cmd_metadata,
cmd_new_nested_window,
cmd_new_window,
cmd_request_to_curl,
cmd_dismiss_notification,
@@ -1667,13 +1710,20 @@ pub fn run() {
cmd_update_http_request,
cmd_update_settings,
cmd_update_workspace,
cmd_write_file_dev,
])
.register_uri_scheme_protocol("yaak", |_app, _req| {
debug!("Testing yaak protocol");
tauri::http::Response::builder()
.body("Success".as_bytes().to_vec())
.unwrap()
})
.build(tauri::generate_context!())
.expect("error while running tauri application")
.run(|app_handle, event| {
match event {
RunEvent::Ready => {
create_window(app_handle, None);
create_window(app_handle, "/");
let h = app_handle.clone();
tauri::async_runtime::spawn(async move {
let info = analytics::track_launch_event(&h).await;
@@ -1719,36 +1769,30 @@ fn is_dev() -> bool {
}
}
fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
let menu = app_menu(handle).unwrap();
// This causes the window to not be clickable (in AppImage), so disable on Linux
#[cfg(not(target_os = "linux"))]
handle.set_menu(menu).expect("Failed to set app menu");
let window_num = handle.webview_windows().len();
let window_id = format!("wnd_{}", window_num);
fn create_nested_window(
window: &WebviewWindow,
label: &str,
url: &str,
title: &str,
) -> WebviewWindow {
info!("Create new nested window label={label}");
let mut win_builder = tauri::WebviewWindowBuilder::new(
handle,
window_id,
WebviewUrl::App(url.unwrap_or_default().into()),
window,
format!("nested_{}_{}", window.label(), label),
WebviewUrl::App(url.into()),
)
.resizable(true)
.fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows
.inner_size(1100.0, 600.0)
.position(
// Randomly offset so windows don't stack exactly
100.0 + random::<f64>() * 30.0,
100.0 + random::<f64>() * 30.0,
)
.title(handle.package_info().name.to_string());
.title(title)
.parent(&window)
.unwrap()
.inner_size(DEFAULT_WINDOW_WIDTH * 0.7, DEFAULT_WINDOW_HEIGHT * 0.9);
// Add macOS-only things
#[cfg(target_os = "macos")]
{
win_builder = win_builder
// .menu(app_menu)
.hidden_title(true)
.title_bar_style(TitleBarStyle::Overlay);
}
@@ -1756,17 +1800,56 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
// Add non-MacOS things
#[cfg(not(target_os = "macos"))]
{
// Doesn't seem to work from Rust, here, so we do it in JS
win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
// Tauri doesn't support shadows when hiding decorations, so we add our own
// #[cfg(any(windows, target_os = "macos"))]
// set_shadow(&win, true).unwrap();
win
}
let win2 = win.clone();
fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
#[allow(unused_variables)]
let menu = app_menu(handle).unwrap();
// This causes the window to not be clickable (in AppImage), so disable on Linux
#[cfg(not(target_os = "linux"))]
handle.set_menu(menu).expect("Failed to set app menu");
let window_num = handle.webview_windows().len();
let label = format!("main_{}", window_num);
info!("Create new window label={label}");
let mut win_builder =
tauri::WebviewWindowBuilder::new(handle, label, WebviewUrl::App(url.into()))
.resizable(true)
.fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows
.inner_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)
.position(
// Randomly offset so windows don't stack exactly
100.0 + random::<f64>() * 30.0,
100.0 + random::<f64>() * 30.0,
)
.title(handle.package_info().name.to_string());
// Add macOS-only things
#[cfg(target_os = "macos")]
{
win_builder = win_builder
.hidden_title(true)
.title_bar_style(TitleBarStyle::Overlay);
}
// Add non-MacOS things
#[cfg(not(target_os = "macos"))]
{
// Doesn't seem to work from Rust, here, so we do it in main.tsx
win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
let webview_window = win.clone();
win.on_menu_event(move |w, event| {
if !w.is_focused().unwrap() {
return;
@@ -1775,48 +1858,39 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
match event.id().0.as_str() {
"quit" => exit(0),
"close" => w.close().unwrap(),
"zoom_reset" => w.emit("zoom", 0).unwrap(),
"zoom_in" => w.emit("zoom", 1).unwrap(),
"zoom_out" => w.emit("zoom", -1).unwrap(),
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
"zoom_in" => w.emit("zoom_in", true).unwrap(),
"zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(),
"duplicate_request" => w.emit("duplicate_request", true).unwrap(),
"refresh" => win2.eval("location.reload()").unwrap(),
"open_feedback" => {
_ = win2
_ = webview_window
.app_handle()
.shell()
.open("https://yaak.canny.io", None)
}
"toggle_devtools" => {
if win2.is_devtools_open() {
win2.close_devtools();
// Commands for development
"dev.reset_size" => webview_window
.set_size(LogicalSize::new(
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT,
))
.unwrap(),
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
"dev.generate_theme_css" => {
w.emit("generate_theme_css", true).unwrap();
}
"dev.toggle_devtools" => {
if webview_window.is_devtools_open() {
webview_window.close_devtools();
} else {
win2.open_devtools();
webview_window.open_devtools();
}
}
_ => {}
}
});
let win3 = win.clone();
win.on_window_event(move |e| {
let apply_offset = || {
win3.position_traffic_lights();
};
match e {
WindowEvent::Resized(..) => apply_offset(),
WindowEvent::ThemeChanged(..) => apply_offset(),
WindowEvent::Focused(..) => apply_offset(),
WindowEvent::ScaleFactorChanged { .. } => apply_offset(),
WindowEvent::CloseRequested { .. } => {
// api.prevent_close();
}
_ => {}
}
});
win.position_traffic_lights();
win
}

View File

@@ -52,7 +52,13 @@ pub struct Settings {
pub updated_at: NaiveDateTime,
pub theme: String,
pub appearance: String,
pub theme_dark: String,
pub theme_light: String,
pub update_channel: String,
pub interface_font_size: i64,
pub interface_scale: i64,
pub editor_font_size: i64,
pub editor_soft_wrap: bool,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
@@ -883,7 +889,9 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
Settings,
r#"
SELECT
id, model, created_at, updated_at, theme, appearance, update_channel
id, model, created_at, updated_at, theme, appearance,
theme_dark, theme_light, update_channel,
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
FROM settings
WHERE id = 'default'
"#,
@@ -919,12 +927,19 @@ pub async fn update_settings(
sqlx::query!(
r#"
UPDATE settings SET (
theme, appearance, update_channel
) = (?, ?, ?) WHERE id = 'default';
theme, appearance, theme_dark, theme_light, update_channel,
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
) = (?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
"#,
settings.theme,
settings.appearance,
settings.update_channel
settings.theme_dark,
settings.theme_light,
settings.update_channel,
settings.interface_font_size,
settings.interface_scale,
settings.editor_font_size,
settings.editor_soft_wrap,
)
.execute(&db)
.await?;

View File

@@ -1,16 +1,16 @@
use std::rc::Rc;
use boa_engine::{
Context, js_string, JsNativeError, JsValue, Module, module::SimpleModuleLoader,
property::Attribute, Source,
};
use boa_engine::builtins::promise::PromiseState;
use boa_engine::{
js_string, module::SimpleModuleLoader, property::Attribute, Context, JsNativeError, JsValue,
Module, Source,
};
use boa_runtime::Console;
use log::{debug, error};
use serde::{Deserialize, Serialize};
use serde_json::json;
use tauri::{AppHandle, Manager};
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager};
use crate::models::{HttpRequest, WorkspaceExportResources};

View File

@@ -0,0 +1,450 @@
use hex_color::HexColor;
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{
plugin::{Builder, TauriPlugin},
Manager, Runtime, Window, WindowEvent,
};
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("mac_window")
.on_window_ready(|window| {
#[cfg(target_os = "macos")]
{
setup_traffic_light_positioner(&window);
let h = window.app_handle();
let window_for_theme = window.clone();
let id1 = h.listen("yaak_bg_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
let color = HexColor::parse_rgb(payload).unwrap();
update_window_theme(window_for_theme.clone(), color);
});
let window_for_title = window.clone();
let id2 = h.listen("yaak_title_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
update_window_title(window_for_title.clone(), payload.to_string());
});
let h = h.clone();
window.on_window_event(move |e| {
match e {
WindowEvent::Destroyed => {
h.unlisten(id1);
h.unlisten(id2);
}
_ => {}
};
});
}
return;
})
.build()
}
#[cfg(target_os = "macos")]
fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
use cocoa::{appkit::NSWindow, base::nil, foundation::NSString};
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
let label = window.label().to_string();
let _ = window.run_on_main_thread(move || {
let win_title = NSString::alloc(nil).init_str(&title);
let handle = window_handle;
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}
}
#[cfg(target_os = "macos")]
fn update_window_theme<R: Runtime>(window: Window<R>, color: HexColor) {
use cocoa::appkit::{
NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
};
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
let label = window.label().to_string();
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
let _ = window.run_on_main_thread(move || {
let handle = window_handle;
let selected_appearance = if brightness >= 128 {
NSAppearance(NSAppearanceNameVibrantLight)
} else {
NSAppearance(NSAppearanceNameVibrantDark)
};
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64, label: String) {
if label.starts_with("nested_") {
return;
}
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let ns_window = ns_window_handle.0 as cocoa::base::id;
unsafe {
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize =
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
}
#[cfg(target_os = "macos")]
#[derive(Debug)]
struct WindowState<R: Runtime> {
window: Window<R>,
}
#[cfg(target_os = "macos")]
pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
use cocoa::appkit::NSWindow;
use cocoa::base::{id, BOOL};
use cocoa::delegate;
use cocoa::foundation::NSUInteger;
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
position_traffic_lights(
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
window.label().to_string(),
);
// Ensure they stay in place while resizing the window.
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
this: &Object,
func: F,
) {
let ptr = unsafe {
let x: *mut c_void = *this.get_ivar("app_box");
&mut *(x as *mut WindowState<R>)
};
func(ptr);
}
unsafe {
let ns_win = window
.ns_window()
.expect("NS Window should exist to mount traffic light delegate.")
as id;
let current_delegate: id = ns_win.delegate();
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, windowShouldClose: sender]
}
}
extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillClose: notification];
}
}
extern "C" fn on_window_did_resize<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
let id = state
.window
.ns_window()
.expect("NS window should exist on state to handle resize")
as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidResize: notification];
}
}
extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidMove: notification];
}
}
extern "C" fn on_window_did_change_backing_properties(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
}
}
extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidBecomeKey: notification];
}
}
extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidResignKey: notification];
}
}
extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, draggingEntered: notification]
}
}
extern "C" fn on_prepare_for_drag_operation(
this: &Object,
_cmd: Sel,
notification: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, prepareForDragOperation: notification]
}
}
extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, performDragOperation: sender]
}
}
extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, concludeDragOperation: notification];
}
}
extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, draggingExited: notification];
}
}
extern "C" fn on_window_will_use_full_screen_presentation_options(
this: &Object,
_cmd: Sel,
window: id,
proposed_options: NSUInteger,
) -> NSUInteger {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
}
}
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-enter-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
}
}
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-enter-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
}
}
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-exit-fullscreen", ())
.expect("Failed to emit event");
let id = state.window.ns_window().expect("Failed to emit event") as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
}
}
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-exit-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
}
}
extern "C" fn on_window_did_fail_to_enter_full_screen(
this: &Object,
_cmd: Sel,
window: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
}
}
extern "C" fn on_effective_appearance_did_change(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
}
}
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![
super_del,
effectiveAppearanceDidChangedOnMainThread: notification
];
}
}
// Are we de-allocing this properly ? (I miss safe Rust :( )
let window_label = window.label().to_string();
let app_state = WindowState {
window: window.clone(),
};
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
ns_win.setDelegate_(delegate!(&delegate_name, {
window: id = ns_win,
app_box: *mut c_void = app_box,
toolbar: id = cocoa::base::nil,
super_delegate: id = current_delegate,
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
}))
}
}

View File

@@ -0,0 +1,97 @@
use hex_color::HexColor;
use tauri::{Manager, Runtime, Window, WindowEvent};
use std::mem::transmute;
use std::{ffi::c_void, mem::size_of, ptr};
use tauri::plugin::{Builder, TauriPlugin};
use windows::Win32::UI::Controls::{
WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU,
};
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::{BOOL, HWND};
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
use windows::Win32::Graphics::Dwm::DWMWA_USE_IMMERSIVE_DARK_MODE;
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("windows_window")
.on_window_ready(|window| {
#[cfg(target_os = "windows")]
setup_win_window(window);
return;
})
.build()
}
fn hex_color_to_colorref(color: HexColor) -> COLORREF {
// TODO: Remove this unsafe, This operation doesn't need to be unsafe!
unsafe { COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0])) }
}
struct WinThemeAttribute {
#[allow(dead_code)]
flag: u32,
#[allow(dead_code)]
mask: u32,
}
#[cfg(target_os = "windows")]
fn update_bg_color(hwnd: &HWND, bg_color: HexColor) {
let use_dark_mode = BOOL::from(true);
let final_color = hex_color_to_colorref(bg_color);
unsafe {
DwmSetWindowAttribute(
HWND(hwnd.0),
DWMWA_USE_IMMERSIVE_DARK_MODE,
ptr::addr_of!(use_dark_mode) as *const c_void,
size_of::<BOOL>().try_into().unwrap(),
)
.unwrap();
DwmSetWindowAttribute(
HWND(hwnd.0),
DWMWA_CAPTION_COLOR,
ptr::addr_of!(final_color) as *const c_void,
size_of::<COLORREF>().try_into().unwrap(),
)
.unwrap();
let flags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
let options = WinThemeAttribute { flag: flags, mask };
SetWindowThemeAttribute(
HWND(hwnd.0),
WTA_NONCLIENT,
ptr::addr_of!(options) as *const c_void,
size_of::<WinThemeAttribute>().try_into().unwrap(),
)
.unwrap();
}
}
#[cfg(target_os = "windows")]
pub fn setup_win_window<R: Runtime>(window: Window<R>) {
let win_handle = window.hwnd().unwrap();
let win_clone = win_handle.clone();
let event_id = window.listen("yaak_bg_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
let color = HexColor::parse_rgb(payload).unwrap();
update_bg_color(&HWND(win_clone.0), color);
});
let h = window.app_handle().clone();
window.on_window_event(move |e| match e {
WindowEvent::Destroyed => {
h.unlisten(event_id);
}
_ => {}
})
}

View File

@@ -19,6 +19,7 @@ pub struct YaakUpdater {
pub enum UpdateMode {
Stable,
Beta,
Alpha,
}
impl Display for UpdateMode {
@@ -26,6 +27,7 @@ impl Display for UpdateMode {
let s = match self {
UpdateMode::Stable => "stable",
UpdateMode::Beta => "beta",
UpdateMode::Alpha => "alpha",
};
write!(f, "{}", s)
}
@@ -35,6 +37,7 @@ impl UpdateMode {
pub fn new(mode: &str) -> UpdateMode {
match mode {
"beta" => UpdateMode::Beta,
"alpha" => UpdateMode::Alpha,
_ => UpdateMode::Stable,
}
}
@@ -53,13 +56,8 @@ impl YaakUpdater {
) -> Result<bool, tauri_plugin_updater::Error> {
self.last_update_check = SystemTime::now();
let enabled = !is_dev();
info!("Checking for updates mode={} enabled={}", mode, enabled);
info!("Checking for updates mode={}", mode);
if !enabled {
return Ok(false);
}
let update_check_result = app_handle
.updater_builder()
.header("X-Update-Mode", mode.to_string())?
@@ -67,8 +65,7 @@ impl YaakUpdater {
.check()
.await;
match update_check_result
{
match update_check_result {
Ok(Some(update)) => {
let h = app_handle.clone();
app_handle
@@ -121,6 +118,11 @@ impl YaakUpdater {
return Ok(false);
}
// Don't check if dev
if is_dev() {
return Ok(false);
}
self.force_check(app_handle, mode).await
}
}

View File

@@ -1,53 +0,0 @@
use tauri::WebviewWindow;
const TRAFFIC_LIGHT_OFFSET_X: f64 = 13.0;
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 18.0;
pub trait TrafficLightWindowExt {
fn position_traffic_lights(&self);
}
impl TrafficLightWindowExt for WebviewWindow {
#[cfg(not(target_os = "macos"))]
fn position_traffic_lights(&self) {
// No-op on other platforms
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(&self) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let window = self.ns_window().unwrap() as cocoa::base::id;
let x = TRAFFIC_LIGHT_OFFSET_X;
let y = TRAFFIC_LIGHT_OFFSET_Y;
unsafe {
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize =
window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
}
}

View File

@@ -126,12 +126,16 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
"Develop",
true,
&[
&MenuItemBuilder::with_id("refresh".to_string(), "Refresh")
&MenuItemBuilder::with_id("dev.refresh".to_string(), "Refresh")
.accelerator("CmdOrCtrl+Shift+r")
.build(app_handle)?,
&MenuItemBuilder::with_id("toggle_devtools".to_string(), "Open Devtools")
&MenuItemBuilder::with_id("dev.toggle_devtools".to_string(), "Open Devtools")
.accelerator("CmdOrCtrl+Option+i")
.build(app_handle)?,
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
.build(app_handle)?,
&MenuItemBuilder::with_id("dev.generate_theme_css".to_string(), "Generate Theme CSS")
.build(app_handle)?,
],
)?,
],

View File

@@ -6,7 +6,7 @@
"frontendDist": "../dist"
},
"productName": "Yaak",
"version": "2024.4.2",
"version": "2024.5.0",
"identifier": "app.yaak.desktop",
"app": {
"withGlobalTauri": false,
@@ -22,6 +22,14 @@
}
},
"plugins": {
"deep-link": {
"mobile": [],
"desktop": {
"schemes": [
"yaak"
]
}
},
"updater": {
"endpoints": [
"https://update.yaak.app/check/{{target}}/{{arch}}/{{current_version}}"
@@ -42,13 +50,13 @@
"icons/release/icon.icns",
"icons/release/icon.ico"
],
"longDescription": "The best cross-platform visual API client",
"longDescription": "A cross-platform desktop app for interacting with REST, GraphQL, and gRPC",
"resources": [
"migrations/*",
"plugins/*",
"protoc-vendored/include/*"
],
"shortDescription": "The best API client",
"shortDescription": "Desktop API client",
"targets": [
"deb",
"appimage",
@@ -57,6 +65,9 @@
"dmg",
"updater"
],
"iOS": {
"developmentTeam": "7PU3P6ELJ8"
},
"macOS": {
"exceptionDomain": "",
"entitlements": "macos/entitlements.plist",

View File

@@ -3,6 +3,7 @@ import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
import { DefaultLayout } from './DefaultLayout';
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
import RouteError from './RouteError';
import { Settings } from './Settings/Settings';
import Workspace from './Workspace';
const router = createBrowserRouter([
@@ -36,6 +37,12 @@ const router = createBrowserRouter([
path: '/workspaces/:workspaceId/environments/:environmentId/requests/:requestId',
element: <RedirectLegacyEnvironmentURLs />,
},
{
path: routePaths.workspaceSettings({
workspaceId: ':workspaceId',
}),
element: <Settings />,
},
],
},
]);

View File

@@ -45,11 +45,11 @@ export function BinaryFileEditor({
return (
<VStack space={2}>
<HStack space={2} alignItems="center">
<Button variant="border" color="gray" size="sm" onClick={handleClick}>
<HStack space={2}>
<Button variant="border" color="secondary" size="sm" onClick={handleClick}>
Choose File
</Button>
<div className="text-xs font-mono truncate rtl pr-3 text-gray-800">
<div className="text-sm font-mono truncate rtl pr-3 text-fg">
{/* Special character to insert ltr text in rtl element without making things wonky */}
&#x200E;
{filePath ?? 'Select File'}
@@ -57,22 +57,22 @@ export function BinaryFileEditor({
</HStack>
{filePath != null && mimeType !== contentType && !ignoreContentType.value && (
<Banner className="mt-3 !py-5">
<div className="text-sm mb-4 text-center">
<div className="mb-4 text-center">
<div>Set Content-Type header</div>
<InlineCode>{mimeType}</InlineCode> for current request?
</div>
<HStack space={1.5} justifyContent="center">
<Button size="sm" variant="border" onClick={() => ignoreContentType.set(true)}>
Ignore
</Button>
<Button
variant="solid"
color="gray"
size="xs"
color="primary"
size="sm"
onClick={() => onChangeContentType(mimeType)}
>
Set Header
</Button>
<Button size="xs" variant="border" onClick={() => ignoreContentType.set(true)}>
Ignore
</Button>
</HStack>
</Banner>
)}

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { useMemo, useCallback, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
@@ -117,8 +117,8 @@ function CommandPaletteItem({
<button
onClick={onClick}
className={classNames(
'w-full h-xs flex items-center rounded px-1.5 text-gray-600',
active && 'bg-highlightSecondary text-gray-800',
'w-full h-xs flex items-center rounded px-1.5 text-fg-subtle',
active && 'bg-background-highlight-secondary text-fg',
)}
>
{children}

View File

@@ -28,7 +28,7 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
return (
<div className="pb-2">
<table className="w-full text-xs mb-auto min-w-full max-w-full divide-y">
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-background-highlight">
<thead>
<tr>
<th className="py-2 text-left">Domain</th>
@@ -36,13 +36,13 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
<th className="py-2 pl-4"></th>
</tr>
</thead>
<tbody className="divide-y">
<tbody className="divide-y divide-background-highlight-secondary">
{cookieJar?.cookies.map((c) => (
<tr key={c.domain + c.raw_cookie}>
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
{cookieDomain(c)}
</td>
<td className="py-2 pl-4 select-text cursor-text font-mono text-gray-700 whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
<td className="py-2 pl-4 select-text cursor-text font-mono text-fg-subtle whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
{c.raw_cookie}
</td>
<td className="max-w-0 w-10">
@@ -53,11 +53,6 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
title="Delete"
className="ml-auto"
onClick={async () => {
console.log(
'DELETE COOKIE',
c,
cookieJar.cookies.filter((c2) => c2 !== c).length,
);
await updateCookieJar.mutateAsync({
...cookieJar,
cookies: cookieJar.cookies.filter((c2) => c2 !== c),

View File

@@ -2,12 +2,26 @@ import { Outlet } from 'react-router-dom';
import { DialogProvider } from './DialogContext';
import { GlobalHooks } from './GlobalHooks';
import { ToastProvider } from './ToastContext';
import classNames from 'classnames';
import { useOsInfo } from '../hooks/useOsInfo';
import { motion } from 'framer-motion';
export function DefaultLayout() {
const osInfo = useOsInfo();
return (
<DialogProvider>
<ToastProvider>
<Outlet />
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.1, delay: 0.1 }}
className={classNames(
'w-full h-full',
osInfo?.osType === 'linux' && 'border border-background-highlight-secondary',
)}
>
<Outlet />
</motion.div>
<GlobalHooks />
</ToastProvider>
</DialogProvider>

View File

@@ -14,7 +14,7 @@ export const DropMarker = memo(
'relative w-full h-0 overflow-visible pointer-events-none',
)}
>
<div className="absolute z-50 left-2 right-2 -bottom-[0.1rem] h-[0.2rem] bg-blue-500/50 rounded-full" />
<div className="absolute z-50 left-2 right-2 -bottom-[0.1rem] h-[0.2rem] bg-fg-primary rounded-full" />
</div>
);
},

View File

@@ -1,5 +1,6 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React from 'react';
interface Props {
children: ReactNode;
@@ -11,7 +12,8 @@ export function EmptyStateText({ children, className }: Props) {
<div
className={classNames(
className,
'rounded-lg border border-dashed border-highlight h-full py-2 text-gray-400 flex items-center justify-center',
'rounded-lg border border-dashed border-background-highlight',
'h-full py-2 text-fg-subtler flex items-center justify-center italic',
)}
>
{children}

View File

@@ -71,8 +71,8 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
size="sm"
className={classNames(
className,
'text-gray-800 !px-2 truncate',
activeEnvironment == null && 'text-opacity-disabled italic',
'text-fg !px-2 truncate',
activeEnvironment == null && 'text-fg-subtler italic',
)}
{...buttonProps}
>

View File

@@ -68,7 +68,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
color="custom"
title="Add sub environment"
icon="plusCircle"
iconClassName="text-gray-500 group-hover:text-gray-700"
iconClassName="text-fg-subtler group-hover:text-fg-subtle"
className="group"
onClick={handleCreateEnvironment}
/>
@@ -97,7 +97,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
secondSlot={() =>
activeWorkspace != null && (
<EnvironmentEditor
className="pt-2 border-l border-highlight"
className="pt-2 border-l border-background-highlight-secondary"
environment={selectedEnvironment}
workspace={activeWorkspace}
/>
@@ -175,7 +175,7 @@ const EnvironmentEditor = function ({
<Heading className="w-full flex items-center gap-1">
<div>{environment?.name ?? 'Global Variables'}</div>
<IconButton
iconClassName="text-gray-600"
iconClassName="text-fg-subtler"
size="sm"
icon={valueVisibility.value ? 'eye' : 'eyeClosed'}
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
@@ -244,7 +244,7 @@ function SidebarButton({
size="xs"
className={classNames(
'w-full',
active ? 'text-gray-800 bg-highlightSecondary' : 'text-gray-600 hover:text-gray-700',
active ? 'text-fg bg-background-active' : 'text-fg-subtle hover:text-fg',
)}
justify="start"
onClick={onClick}
@@ -281,7 +281,7 @@ function SidebarButton({
},
},
{
key: 'delete',
key: 'delete-environment',
variant: 'danger',
label: 'Delete',
leftSlot: <Icon icon="trash" size="sm" />,

View File

@@ -90,7 +90,7 @@ export function ExportDataDialog({
/>
</td>
<td
className="py-1 pl-4 text-gray-700 whitespace-nowrap overflow-x-auto hide-scrollbars"
className="py-1 pl-4 text-fg whitespace-nowrap overflow-x-auto hide-scrollbars"
onClick={() => setSelectedWorkspaces((prev) => ({ ...prev, [w.id]: !prev[w.id] }))}
>
{w.name} {w.id === activeWorkspace.id ? '(current workspace)' : ''}
@@ -100,7 +100,7 @@ export function ExportDataDialog({
</tbody>
</table>
<HStack space={2} justifyContent="end">
<Button className="focus" color="gray" onClick={onHide}>
<Button className="focus" variant="border" onClick={onHide}>
Cancel
</Button>
<Button

View File

@@ -30,6 +30,8 @@ export function FormUrlencodedEditor({ body, forceUpdateKey, onChange }: Props)
<PairEditor
valueAutocompleteVariables
nameAutocompleteVariables
namePlaceholder="entry_name"
valuePlaceholder="Value"
pairs={pairs}
onChange={handleChange}
forceUpdateKey={forceUpdateKey}

View File

@@ -1,32 +1,38 @@
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { getCurrent } from '@tauri-apps/api/webviewWindow';
import { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
import { useClipboardText } from '../hooks/useClipboardText';
import { useCommandPalette } from '../hooks/useCommandPalette';
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
import { environmentsQueryKey } from '../hooks/useEnvironments';
import { foldersQueryKey } from '../hooks/useFolders';
import { useGlobalCommands } from '../hooks/useGlobalCommands';
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
import { grpcEventsQueryKey } from '../hooks/useGrpcEvents';
import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests';
import { useHotKey } from '../hooks/useHotKey';
import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
import { keyValueQueryKey } from '../hooks/useKeyValue';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { useNotificationToast } from '../hooks/useNotificationToast';
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { settingsQueryKey } from '../hooks/useSettings';
import { useSyncAppearance } from '../hooks/useSyncAppearance';
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
import { settingsQueryKey, useSettings } from '../hooks/useSettings';
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
import { workspacesQueryKey } from '../hooks/useWorkspaces';
import { useZoom } from '../hooks/useZoom';
import type { Model } from '../lib/models';
import { modelsEq } from '../lib/models';
import { setPathname } from '../lib/persistPathname';
import { useNotificationToast } from '../hooks/useNotificationToast';
const DEFAULT_FONT_SIZE = 16;
import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin';
import { githubLight } from '../lib/theme/themes/github';
import { hotdogStandDefault } from '../lib/theme/themes/hotdog-stand';
import { monokaiProDefault } from '../lib/theme/themes/monokai-pro';
import { rosePineDefault } from '../lib/theme/themes/rose-pine';
import { yaakDark } from '../lib/theme/themes/yaak';
import { getThemeCSS } from '../lib/theme/window';
export function GlobalHooks() {
// Include here so they always update, even if no component references them
@@ -34,8 +40,8 @@ export function GlobalHooks() {
useRecentEnvironments();
useRecentRequests();
useSyncAppearance();
useSyncWindowTitle();
// Other useful things
useSyncThemeToDocument();
useGlobalCommands();
useCommandPalette();
useNotificationToast();
@@ -43,12 +49,6 @@ export function GlobalHooks() {
const queryClient = useQueryClient();
const { wasUpdatedExternally } = useRequestUpdateKey(null);
// Listen for location changes and update the pathname
const location = useLocation();
useEffect(() => {
setPathname(location.pathname).catch(console.error);
}, [location.pathname]);
interface ModelPayload {
model: Model;
windowLabel: string;
@@ -63,6 +63,8 @@ export function GlobalHooks() {
? httpResponsesQueryKey(model)
: model.model === 'folder'
? foldersQueryKey(model)
: model.model === 'environment'
? environmentsQueryKey(model)
: model.model === 'grpc_connection'
? grpcConnectionsQueryKey(model)
: model.model === 'grpc_event'
@@ -97,10 +99,8 @@ export function GlobalHooks() {
queryClient.setQueryData<Model[]>(queryKey, (values = []) => {
const index = values.findIndex((v) => modelsEq(v, model)) ?? -1;
if (index >= 0) {
// console.log('UPDATED', payload);
return [...values.slice(0, index), model, ...values.slice(index + 1)];
} else {
// console.log('CREATED', payload);
return pushToFront ? [model, ...(values ?? [])] : [...(values ?? []), model];
}
});
@@ -118,6 +118,8 @@ export function GlobalHooks() {
queryClient.setQueryData(httpResponsesQueryKey(model), removeById(model));
} else if (model.model === 'folder') {
queryClient.setQueryData(foldersQueryKey(model), removeById(model));
} else if (model.model === 'environment') {
queryClient.setQueryData(environmentsQueryKey(model), removeById(model));
} else if (model.model === 'grpc_request') {
queryClient.setQueryData(grpcRequestsQueryKey(model), removeById(model));
} else if (model.model === 'grpc_connection') {
@@ -133,26 +135,42 @@ export function GlobalHooks() {
}
});
useListenToTauriEvent<number>(
'zoom',
({ payload: zoomDelta }) => {
const fontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
const settings = useSettings();
useEffect(() => {
if (settings == null) {
return;
}
let newFontSize;
if (zoomDelta === 0) {
newFontSize = DEFAULT_FONT_SIZE;
} else if (zoomDelta > 0) {
newFontSize = Math.min(fontSize * 1.1, DEFAULT_FONT_SIZE * 5);
} else if (zoomDelta < 0) {
newFontSize = Math.max(fontSize * 0.9, DEFAULT_FONT_SIZE * 0.4);
}
const { interfaceScale, interfaceFontSize, editorFontSize } = settings;
getCurrent().setZoom(interfaceScale).catch(console.error);
document.documentElement.style.setProperty('font-size', `${interfaceFontSize}px`);
document.documentElement.style.setProperty('--editor-font-size', `${editorFontSize}px`);
}, [settings]);
document.documentElement.style.fontSize = `${newFontSize}px`;
},
{
target: { kind: 'WebviewWindow', label: getCurrent().label },
},
);
// Handle Zoom. Note, Mac handles it in app menu, so need to also handle keyboard
// shortcuts for Windows/Linux
const zoom = useZoom();
useHotKey('app.zoom_in', () => zoom.zoomIn);
useListenToTauriEvent('zoom_in', () => zoom.zoomIn);
useHotKey('app.zoom_out', () => zoom.zoomOut);
useListenToTauriEvent('zoom_out', () => zoom.zoomOut);
useHotKey('app.zoom_reset', () => zoom.zoomReset);
useListenToTauriEvent('zoom_reset', () => zoom.zoomReset);
const [, copy] = useClipboardText();
useListenToTauriEvent('generate_theme_css', () => {
const themesCss = [
yaakDark,
monokaiProDefault,
rosePineDefault,
catppuccinMacchiato,
githubLight,
hotdogStandDefault,
]
.map(getThemeCSS)
.join('\n\n');
copy(themesCss);
});
return null;
}

View File

@@ -88,7 +88,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
<Button
key="introspection"
size="xs"
color={error ? 'danger' : 'gray'}
color={error ? 'danger' : 'secondary'}
isLoading={isLoading}
onClick={() => {
dialog.show({
@@ -105,7 +105,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
refetch();
}}
className="ml-auto"
color="secondary"
color="primary"
size="sm"
>
Try Again

View File

@@ -98,9 +98,10 @@ export function GrpcConnectionLayout({ style }: Props) {
<div
style={style}
className={classNames(
'x-theme-responsePane',
'max-h-full h-full grid grid-rows-[minmax(0,1fr)] grid-cols-1',
'bg-gray-50 dark:bg-gray-100 rounded-md border border-highlight',
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
'bg-background rounded-md border border-background-highlight',
'shadow relative',
)}
>
{grpc.go.error ? (

View File

@@ -2,9 +2,11 @@ import classNames from 'classnames';
import { format } from 'date-fns';
import type { CSSProperties } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useGrpcConnections } from '../hooks/useGrpcConnections';
import { useGrpcEvents } from '../hooks/useGrpcEvents';
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import type { GrpcEvent, GrpcRequest } from '../lib/models';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { JsonAttributeTree } from './core/JsonAttributeTree';
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
@@ -13,8 +15,6 @@ import { SplitLayout } from './core/SplitLayout';
import { HStack, VStack } from './core/Stacks';
import { EmptyStateText } from './EmptyStateText';
import { RecentConnectionsDropdown } from './RecentConnectionsDropdown';
import { Button } from './core/Button';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
interface Props {
style?: CSSProperties;
@@ -31,11 +31,11 @@ interface Props {
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
const [activeEventId, setActiveEventId] = useState<string | null>(null);
const connections = useGrpcConnections(activeRequest.id ?? null);
const activeConnection = connections[0] ?? null;
const events = useGrpcEvents(activeConnection?.id ?? null);
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
const [showingLarge, setShowingLarge] = useState<boolean>(false);
const { activeConnection, connections, setPinnedConnectionId } =
usePinnedGrpcConnection(activeRequest);
const events = useGrpcEvents(activeConnection?.id ?? null);
const activeEvent = useMemo(
() => events.find((m) => m.id === activeEventId) ?? null,
@@ -61,19 +61,17 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
firstSlot={() =>
activeConnection && (
<div className="w-full grid grid-rows-[auto_minmax(0,1fr)] items-center">
<HStack className="pl-3 mb-1 font-mono" alignItems="center">
<HStack alignItems="center" space={2}>
<HStack className="pl-3 mb-1 font-mono">
<HStack space={2}>
<span>{events.length} messages</span>
{activeConnection.elapsed === 0 && (
<Icon icon="refresh" size="sm" spin className="text-gray-600" />
<Icon icon="refresh" size="sm" spin className="text-fg-subtler" />
)}
</HStack>
<RecentConnectionsDropdown
connections={connections}
activeConnection={activeConnection}
onPinned={() => {
// todo
}}
onPinnedConnectionId={setPinnedConnectionId}
/>
</HStack>
<div className="overflow-y-auto h-full">
@@ -107,7 +105,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
Message {activeEvent.eventType === 'client_message' ? 'Sent' : 'Received'}
</div>
{!showLarge && activeEvent.content.length > 1000 * 1000 ? (
<VStack space={2} className="text-sm italic text-gray-500">
<VStack space={2} className="italic text-fg-subtler">
Message previews larger than 1MB are hidden
<div>
<Button
@@ -119,7 +117,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
}, 500);
}}
isLoading={showingLarge}
color="gray"
color="secondary"
variant="border"
size="xs"
>
@@ -138,7 +136,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
{activeEvent.content}
</div>
{activeEvent.error && (
<div className="select-text cursor-text text-xs font-mono py-1 text-orange-700">
<div className="select-text cursor-text text-sm font-mono py-1 text-fg-warning">
{activeEvent.error}
</div>
)}
@@ -183,21 +181,21 @@ function EventRow({
className={classNames(
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
'px-1.5 py-1 font-mono cursor-default group focus:outline-none rounded',
isActive && '!bg-highlight text-gray-900',
'text-gray-800 hover:text-gray-900',
isActive && '!bg-background-highlight-secondary !text-fg',
'text-fg-subtle hover:text-fg',
)}
>
<Icon
className={
eventType === 'server_message'
? 'text-blue-600'
? 'text-fg-info'
: eventType === 'client_message'
? 'text-violet-600'
? 'text-fg-primary'
: eventType === 'error' || (status != null && status > 0)
? 'text-orange-600'
? 'text-fg-danger'
: eventType === 'connection_end'
? 'text-green-600'
: 'text-gray-700'
? 'text-fg-success'
: 'text-fg-subtle'
}
title={
eventType === 'server_message'
@@ -222,11 +220,11 @@ function EventRow({
: 'info'
}
/>
<div className={classNames('w-full truncate text-2xs')}>
<div className={classNames('w-full truncate text-xs')}>
{content.slice(0, 1000)}
{error && <span className="text-orange-600"> ({error})</span>}
{error && <span className="text-fg-warning"> ({error})</span>}
</div>
<div className={classNames('opacity-50 text-2xs')}>
<div className={classNames('opacity-50 text-xs')}>
{format(createdAt + 'Z', 'HH:mm:ss.SSS')}
</div>
</button>

View File

@@ -199,17 +199,17 @@ export function GrpcConnectionSetupPane({
label: 'Refresh',
type: 'default',
key: 'custom',
leftSlot: <Icon className="text-gray-600" size="sm" icon="refresh" />,
leftSlot: <Icon className="text-fg-subtler" size="sm" icon="refresh" />,
},
]}
>
<Button
size="sm"
variant="border"
rightSlot={<Icon className="text-gray-600" size="sm" icon="chevronDown" />}
rightSlot={<Icon className="text-fg-subtler" size="sm" icon="chevronDown" />}
disabled={isStreaming || services == null}
className={classNames(
'font-mono text-xs min-w-[5rem] !ring-0',
'font-mono text-sm min-w-[5rem] !ring-0',
paneSize < 400 && 'flex-1',
)}
>
@@ -221,14 +221,14 @@ export function GrpcConnectionSetupPane({
{isStreaming && (
<>
<IconButton
className="border border-highlight"
className="border border-background-highlight-secondary"
size="sm"
title="Cancel"
onClick={onCancel}
icon="x"
/>
<IconButton
className="border border-highlight"
className="border border-background-highlight-secondary"
size="sm"
title="Commit"
onClick={onCommit}
@@ -237,7 +237,7 @@ export function GrpcConnectionSetupPane({
</>
)}
<IconButton
className="border border-highlight"
className="border border-background-highlight-secondary"
size="sm"
title={isStreaming ? 'Connect' : 'Send'}
hotkeyAction="grpc_request.send"
@@ -247,7 +247,7 @@ export function GrpcConnectionSetupPane({
</>
) : (
<IconButton
className="border border-highlight"
className="border border-background-highlight-secondary"
size="sm"
title={methodType === 'unary' ? 'Send' : 'Connect'}
hotkeyAction="grpc_request.send"
@@ -275,7 +275,6 @@ export function GrpcConnectionSetupPane({
<GrpcEditor
onChange={handleChangeMessage}
services={services}
className="bg-gray-50"
reflectionError={reflectionError}
reflectionLoading={reflectionLoading}
request={activeRequest}

View File

@@ -133,12 +133,12 @@ export function GrpcEditor({
size="xs"
color={
reflectionLoading
? 'gray'
: reflectionUnavailable
? 'secondary'
: reflectionUnavailable
? 'info'
: reflectionError
? 'danger'
: 'gray'
: 'secondary'
}
isLoading={reflectionLoading}
onClick={() => {

View File

@@ -40,7 +40,6 @@ export function GrpcProtoSelection({ requestId }: Props) {
<HStack space={2} justifyContent="start" className="flex-row-reverse">
<Button
color="primary"
size="sm"
onClick={async () => {
const selected = await open({
title: 'Select Proto Files',
@@ -60,8 +59,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
<Button
isLoading={grpc.reflect.isFetching}
disabled={grpc.reflect.isFetching}
color="gray"
size="sm"
color="secondary"
onClick={() => grpc.reflect.refetch()}
>
Refresh Schema
@@ -103,25 +101,24 @@ export function GrpcProtoSelection({ requestId }: Props) {
)}
{protoFiles.length > 0 && (
<table className="w-full divide-y">
<table className="w-full divide-y divide-background-highlight">
<thead>
<tr>
<th className="text-gray-600">
<span className="font-mono text-sm">*.proto</span> Files
<th className="text-fg-subtler">
<span className="font-mono">*.proto</span> Files
</th>
<th></th>
</tr>
</thead>
<tbody className="divide-y">
<tbody className="divide-y divide-background-highlight">
{protoFiles.map((f, i) => (
<tr key={f + i} className="group">
<td className="pl-1 text-sm font-mono">{f.split('/').pop()}</td>
<td className="pl-1 font-mono">{f.split('/').pop()}</td>
<td className="w-0 py-0.5">
<IconButton
title="Remove file"
size="sm"
icon="trash"
className="ml-auto opacity-30 transition-opacity group-hover:opacity-100"
className="ml-auto opacity-50 transition-opacity group-hover:opacity-100"
onClick={async () => {
await protoFilesKv.set(protoFiles.filter((p) => p !== f));
}}

View File

@@ -0,0 +1,38 @@
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { useIsFullscreen } from '../hooks/useIsFullscreen';
import { useOsInfo } from '../hooks/useOsInfo';
interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
size: 'md' | 'lg';
ignoreStoplights?: boolean;
}
export function HeaderSize({
className,
style,
size,
ignoreStoplights,
...props
}: HeaderSizeProps) {
const platform = useOsInfo();
const fullscreen = useIsFullscreen();
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen && !ignoreStoplights;
return (
<div
data-tauri-drag-region
style={style}
className={classNames(
className,
'pt-[1px] w-full border-b border-background-highlight min-w-0',
stoplightsVisible ? 'pl-20 pr-1' : 'pl-1',
size === 'md' && 'h-[27px]',
size === 'lg' && 'h-[38px]',
)}
>
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */}
<div className="h-full w-full overflow-x-auto hide-scrollbars grid" {...props} />
</div>
);
}

View File

@@ -1,9 +1,9 @@
import { motion } from 'framer-motion';
import React, { useState } from 'react';
import { Button } from './core/Button';
import { useClipboardText } from '../hooks/useClipboardText';
import { useImportCurl } from '../hooks/useImportCurl';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { motion } from 'framer-motion';
export function ImportCurlButton() {
const [clipboardText] = useClipboardText();
@@ -23,7 +23,7 @@ export function ImportCurlButton() {
<Button
size="xs"
variant="border"
color="secondary"
color="primary"
leftSlot={<Icon icon="paste" size="sm" />}
isLoading={isLoading}
onClick={() => {

View File

@@ -0,0 +1,15 @@
import type { ReactNode } from 'react';
import { useAppInfo } from '../hooks/useAppInfo';
interface Props {
children: ReactNode;
}
export function IsDev({ children }: Props) {
const appInfo = useAppInfo();
if (!appInfo?.isDev) {
return null;
}
return <>{children}</>;
}

View File

@@ -1,10 +1,10 @@
import { hotkeyActions } from '../hooks/useHotKey';
import { HotKeyList } from './core/HotKeyList';
export const KeyboardShortcutsDialog = () => {
export function KeyboardShortcutsDialog() {
return (
<div className="h-full w-full pb-2">
<HotKeyList hotkeys={hotkeyActions} />
</div>
);
};
}

View File

@@ -2,6 +2,7 @@ import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';
import { motion } from 'framer-motion';
import type { ReactNode } from 'react';
import React from 'react';
import { Portal } from './Portal';
interface Props {
@@ -43,10 +44,16 @@ export function Overlay({
onClick={onClose}
className={classNames(
'absolute inset-0',
variant === 'default' && 'bg-gray-600/30 dark:bg-black/30 backdrop-blur-sm',
variant === 'default' && 'bg-background-backdrop backdrop-blur-sm',
)}
/>
<div className="bg-red-100">{children}</div>
{children}
{/* Show draggable region at the top */}
{/* TODO: Figure out tauri drag region and also make clickable still */}
{variant === 'default' && (
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
)}
</motion.div>
</FocusTrap>
)}

View File

@@ -11,12 +11,17 @@ import { HStack } from './core/Stacks';
interface Props {
connections: GrpcConnection[];
activeConnection: GrpcConnection;
onPinned: (r: GrpcConnection) => void;
onPinnedConnectionId: (id: string) => void;
}
export function RecentConnectionsDropdown({ activeConnection, connections, onPinned }: Props) {
export function RecentConnectionsDropdown({
activeConnection,
connections,
onPinnedConnectionId,
}: Props) {
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
const latestConnectionId = connections[0]?.id ?? 'n/a';
return (
<Dropdown
@@ -38,19 +43,19 @@ export function RecentConnectionsDropdown({ activeConnection, connections, onPin
...connections.slice(0, 20).map((c) => ({
key: c.id,
label: (
<HStack space={2} alignItems="center">
<HStack space={2}>
{formatDistanceToNowStrict(c.createdAt + 'Z')} ago &bull;{' '}
<span className="font-mono text-xs">{c.elapsed}ms</span>
<span className="font-mono text-sm">{c.elapsed}ms</span>
</HStack>
),
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: () => onPinned(c),
onSelect: () => onPinnedConnectionId(c.id),
})),
]}
>
<IconButton
title="Show connection history"
icon="chevronDown"
icon={activeConnection?.id === latestConnectionId ? 'chevronDown' : 'pin'}
className="ml-auto"
size="sm"
iconSize="md"

View File

@@ -86,8 +86,8 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
hotkeyAction="request_switcher.toggle"
className={classNames(
className,
'text-gray-800 text-sm truncate pointer-events-auto',
activeRequest === null && 'text-opacity-disabled italic',
'text-fg truncate pointer-events-auto',
activeRequest === null && 'text-fg-subtler italic',
)}
>
{fallbackRequestName(activeRequest)}

View File

@@ -1,26 +1,30 @@
import classNames from 'classnames';
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
import type { HttpResponse } from '../lib/models';
import { Dropdown } from './core/Dropdown';
import { pluralize } from '../lib/pluralize';
import { HStack } from './core/Stacks';
import { StatusTag } from './core/StatusTag';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';
import { StatusTag } from './core/StatusTag';
interface Props {
responses: HttpResponse[];
activeResponse: HttpResponse;
onPinnedResponse: (r: HttpResponse) => void;
onPinnedResponseId: (id: string) => void;
className?: string;
}
export const RecentResponsesDropdown = function ResponsePane({
activeResponse,
responses,
onPinnedResponse,
onPinnedResponseId,
className,
}: Props) {
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
const latestResponseId = responses[0]?.id ?? 'n/a';
return (
<Dropdown
@@ -39,23 +43,24 @@ export const RecentResponsesDropdown = function ResponsePane({
disabled: responses.length === 0,
},
{ type: 'separator', label: 'History' },
...responses.slice(0, 20).map((r) => ({
...responses.slice(0, 20).map((r: HttpResponse) => ({
key: r.id,
label: (
<HStack space={2} alignItems="center">
<StatusTag className="text-xs" response={r} />
<span>&bull;</span> <span className="font-mono text-xs">{r.elapsed}ms</span>
<HStack space={2}>
<StatusTag className="text-sm" response={r} />
<span>&rarr;</span>{' '}
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
</HStack>
),
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: () => onPinnedResponse(r),
onSelect: () => onPinnedResponseId(r.id),
})),
]}
>
<IconButton
title="Show response history"
icon="chevronDown"
className="ml-auto"
icon={activeResponse?.id === latestResponseId ? 'chevronDown' : 'pin'}
className={classNames(className, 'm-0.5')}
size="sm"
iconSize="md"
/>

View File

@@ -1,3 +1,4 @@
import classNames from 'classnames';
import { memo, useMemo } from 'react';
import { usePrompt } from '../hooks/usePrompt';
import { Button } from './core/Button';
@@ -57,7 +58,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
return (
<RadioDropdown value={method} items={radioItems} extraItems={extraItems} onChange={onChange}>
<Button size="xs" className={className}>
<Button size="xs" className={classNames(className, 'text-fg-subtle hover:text-fg')}>
{method.toUpperCase()}
</Button>
</RadioDropdown>

View File

@@ -3,10 +3,12 @@ import type { CSSProperties } from 'react';
import React, { memo, useCallback, useMemo, useState } from 'react';
import { createGlobalState } from 'react-use';
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
import { useImportCurl } from '../hooks/useImportCurl';
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useRequests } from '../hooks/useRequests';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
import { useSendRequest } from '../hooks/useSendRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import { tryFormatJson } from '../lib/formatters';
@@ -29,6 +31,7 @@ import { BearerAuth } from './BearerAuth';
import { BinaryFileEditor } from './BinaryFileEditor';
import { CountBadge } from './core/CountBadge';
import { Editor } from './core/Editor';
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
@@ -38,9 +41,6 @@ import { GraphQLEditor } from './GraphQLEditor';
import { HeadersEditor } from './HeadersEditor';
import { UrlBar } from './UrlBar';
import { UrlParametersEditor } from './UrlParameterEditor';
import { useImportCurl } from '../hooks/useImportCurl';
import { useRequests } from '../hooks/useRequests';
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
interface Props {
style: CSSProperties;
@@ -109,11 +109,14 @@ export const RequestPane = memo(function RequestPane({
if (bodyType === BODY_TYPE_NONE) {
newContentType = null;
} else if (
bodyType === BODY_TYPE_FORM_URLENCODED ||
bodyType === BODY_TYPE_FORM_MULTIPART ||
bodyType === BODY_TYPE_JSON ||
bodyType === BODY_TYPE_OTHER ||
bodyType === BODY_TYPE_XML
activeRequest.method.toLowerCase() !== 'put' &&
activeRequest.method.toLowerCase() !== 'patch' &&
activeRequest.method.toLowerCase() !== 'post' &&
(bodyType === BODY_TYPE_FORM_URLENCODED ||
bodyType === BODY_TYPE_FORM_MULTIPART ||
bodyType === BODY_TYPE_JSON ||
bodyType === BODY_TYPE_OTHER ||
bodyType === BODY_TYPE_XML)
) {
patch.method = 'POST';
newContentType = bodyType === BODY_TYPE_OTHER ? 'text/plain' : bodyType;
@@ -181,6 +184,7 @@ export const RequestPane = memo(function RequestPane({
activeRequest.authenticationType,
activeRequest.bodyType,
activeRequest.headers,
activeRequest.method,
activeRequest.urlParameters,
handleContentTypeChange,
updateRequest,
@@ -317,7 +321,6 @@ export const RequestPane = memo(function RequestPane({
useTemplating
autocompleteVariables
placeholder="..."
className="!bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}
contentType="application/json"
@@ -330,7 +333,6 @@ export const RequestPane = memo(function RequestPane({
useTemplating
autocompleteVariables
placeholder="..."
className="!bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}
contentType="text/xml"
@@ -340,7 +342,6 @@ export const RequestPane = memo(function RequestPane({
<GraphQLEditor
forceUpdateKey={forceUpdateKey}
baseRequest={activeRequest}
className="!bg-gray-50"
defaultValue={`${activeRequest.body?.text ?? ''}`}
onChange={handleBodyTextChange}
/>
@@ -370,7 +371,6 @@ export const RequestPane = memo(function RequestPane({
useTemplating
autocompleteVariables
placeholder="..."
className="!bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}
onChange={handleBodyTextChange}

View File

@@ -32,7 +32,7 @@ export function ResizeHandle({
className={classNames(
className,
'group z-10 flex',
// 'bg-blue-100/10', // For debugging
// 'bg-fg-info', // For debugging
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
justify === 'center' && 'justify-center',
justify === 'end' && 'justify-end',

View File

@@ -2,8 +2,8 @@ import classNames from 'classnames';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { createGlobalState } from 'react-use';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useResponseViewMode } from '../hooks/useResponseViewMode';
import type { HttpRequest } from '../lib/models';
import { isResponseLoading } from '../lib/models';
@@ -20,9 +20,11 @@ import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { RecentResponsesDropdown } from './RecentResponsesDropdown';
import { ResponseHeaders } from './ResponseHeaders';
import { AudioViewer } from './responseViewers/AudioViewer';
import { CsvViewer } from './responseViewers/CsvViewer';
import { ImageViewer } from './responseViewers/ImageViewer';
import { TextViewer } from './responseViewers/TextViewer';
import { VideoViewer } from './responseViewers/VideoViewer';
import { WebPageViewer } from './responseViewers/WebPageViewer';
interface Props {
@@ -34,7 +36,7 @@ interface Props {
const useActiveTab = createGlobalState<string>('body');
export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) {
const { activeResponse, setPinnedResponse, responses } = usePinnedHttpResponse(activeRequest);
const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequest);
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
const [activeTab, setActiveTab] = useActiveTab();
const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null);
@@ -73,9 +75,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
style={style}
className={classNames(
className,
'x-theme-responsePane',
'max-h-full h-full',
'bg-gray-50 dark:bg-gray-100 rounded-md border border-highlight',
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
'bg-background rounded-md border border-background-highlight',
'relative',
)}
>
{activeResponse == null ? (
@@ -89,41 +92,41 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
) : (
<div className="h-full w-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1">
<HStack
alignItems="center"
className={classNames(
'text-gray-700 text-sm w-full flex-shrink-0',
'text-fg-subtle w-full flex-shrink-0',
// Remove a bit of space because the tabs have lots too
'-mb-1.5',
)}
>
{activeResponse && (
<HStack alignItems="center" className="w-full">
<div className="whitespace-nowrap px-3">
<HStack space={2}>
<StatusTag showReason response={activeResponse} />
{activeResponse.elapsed > 0 && (
<>
<span>&bull;</span>
<DurationTag
headers={activeResponse.elapsedHeaders}
total={activeResponse.elapsed}
/>
</>
)}
{!!activeResponse.contentLength && (
<>
<span>&bull;</span>
<SizeTag contentLength={activeResponse.contentLength} />
</>
)}
</HStack>
</div>
<HStack
space={2}
className="whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm"
>
<StatusTag showReason response={activeResponse} />
{activeResponse.elapsed > 0 && (
<>
<span>&bull;</span>
<DurationTag
headers={activeResponse.elapsedHeaders}
total={activeResponse.elapsed}
/>
</>
)}
{!!activeResponse.contentLength && (
<>
<span>&bull;</span>
<SizeTag contentLength={activeResponse.contentLength} />
</>
)}
<RecentResponsesDropdown
responses={responses}
activeResponse={activeResponse}
onPinnedResponse={setPinnedResponse}
/>
<div className="ml-auto">
<RecentResponsesDropdown
responses={responses}
activeResponse={activeResponse}
onPinnedResponseId={setPinnedResponseId}
/>
</div>
</HStack>
)}
</HStack>
@@ -138,7 +141,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
onChangeValue={setActiveTab}
label="Response"
tabs={tabs}
className="ml-3 mr-1"
className="ml-3 mr-3 mb-3"
tabListClassName="mt-1.5"
>
<TabContent value="headers">
@@ -146,19 +149,27 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
</TabContent>
<TabContent value="body">
{!activeResponse.contentLength ? (
<EmptyStateText>Empty Body</EmptyStateText>
<div className="pb-2 h-full">
<EmptyStateText>Empty Body</EmptyStateText>
</div>
) : contentType?.startsWith('image') ? (
<ImageViewer className="pb-2" response={activeResponse} />
) : contentType?.startsWith('audio') ? (
<AudioViewer response={activeResponse} />
) : contentType?.startsWith('video') ? (
<VideoViewer response={activeResponse} />
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
<div className="text-sm italic text-gray-500">
Cannot preview text responses larger than 2MB
</div>
<EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>
) : viewMode === 'pretty' && contentType?.includes('html') ? (
<WebPageViewer response={activeResponse} />
) : contentType?.match(/csv|tab-separated/) ? (
<CsvViewer className="pb-2" response={activeResponse} />
) : (
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
<TextViewer
className="-mr-2" // Pull to the right
response={activeResponse}
pretty={viewMode === 'pretty'}
/>
)}
</TabContent>
</Tabs>

View File

@@ -26,7 +26,7 @@ export default function RouteError() {
>
Go Home
</Button>
<Button color="secondary" onClick={() => window.location.reload()}>
<Button color="info" onClick={() => window.location.reload()}>
Refresh
</Button>
</VStack>

View File

@@ -0,0 +1,64 @@
import { getCurrent } from '@tauri-apps/api/webviewWindow';
import classNames from 'classnames';
import React from 'react';
import { useKeyPressEvent, useLocalStorage } from 'react-use';
import { useOsInfo } from '../../hooks/useOsInfo';
import { capitalize } from '../../lib/capitalize';
import { HStack } from '../core/Stacks';
import { TabContent, Tabs } from '../core/Tabs/Tabs';
import { HeaderSize } from '../HeaderSize';
import { WindowControls } from '../WindowControls';
import { SettingsAppearance } from './SettingsAppearance';
import { SettingsGeneral } from './SettingsGeneral';
enum Tab {
General = 'general',
Appearance = 'appearance',
}
const tabs = [Tab.General, Tab.Appearance];
export const Settings = () => {
const osInfo = useOsInfo();
const [tab, setTab] = useLocalStorage<string>('settings_tab', Tab.General);
// Close settings window on escape
// TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window
useKeyPressEvent('Escape', () => getCurrent().close());
return (
<div className={classNames('grid grid-rows-[auto_minmax(0,1fr)] h-full')}>
<HeaderSize
data-tauri-drag-region
ignoreStoplights
size="md"
className="x-theme-appHeader bg-background text-fg-subtle flex items-center justify-center border-b border-background-highlight text-sm font-semibold"
>
<HStack
space={2}
justifyContent="center"
className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none"
>
<div className={classNames(osInfo?.osType === 'macos' ? 'text-center' : 'pl-2')}>
Settings
</div>
<WindowControls className="ml-auto" onlyX />
</HStack>
</HeaderSize>
<Tabs
value={tab}
addBorders
label="Settings"
onChangeValue={setTab}
tabs={tabs.map((value) => ({ value, label: capitalize(value) }))}
>
<TabContent value={Tab.General} className="pt-3 overflow-y-auto h-full px-4">
<SettingsGeneral />
</TabContent>
<TabContent value={Tab.Appearance} className="pt-3 overflow-y-auto h-full px-4">
<SettingsAppearance />
</TabContent>
</Tabs>
</div>
);
};

View File

@@ -0,0 +1,206 @@
import React from 'react';
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
import { useSettings } from '../../hooks/useSettings';
import { useThemes } from '../../hooks/useThemes';
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
import { trackEvent } from '../../lib/analytics';
import { clamp } from '../../lib/clamp';
import { isThemeDark } from '../../lib/theme/window';
import type { ButtonProps } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { Editor } from '../core/Editor';
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import type { SelectProps } from '../core/Select';
import { Select } from '../core/Select';
import { Separator } from '../core/Separator';
import { HStack, VStack } from '../core/Stacks';
const fontSizes = [
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
].map((n) => ({ label: `${n}`, value: `${n}` }));
const buttonColors: ButtonProps['color'][] = [
'primary',
'info',
'success',
'notice',
'warning',
'danger',
'secondary',
'default',
];
const icons: IconProps['icon'][] = [
'info',
'box',
'update',
'alert',
'arrowBigRightDash',
'download',
'copy',
'magicWand',
'settings',
'trash',
'sparkles',
'pencil',
'paste',
'search',
'sendHorizontal',
];
export function SettingsAppearance() {
const workspace = useActiveWorkspace();
const settings = useSettings();
const updateSettings = useUpdateSettings();
const appearance = useResolvedAppearance();
const { themes } = useThemes();
const activeTheme = useResolvedTheme();
if (settings == null || workspace == null) {
return null;
}
const lightThemes: SelectProps<string>['options'] = themes
.filter((theme) => !isThemeDark(theme))
.map((theme) => ({
label: theme.name,
value: theme.id,
}));
const darkThemes: SelectProps<string>['options'] = themes
.filter((theme) => isThemeDark(theme))
.map((theme) => ({
label: theme.name,
value: theme.id,
}));
return (
<VStack space={2} className="mb-4">
<Select
size="sm"
name="interfaceFontSize"
label="Font Size"
labelPosition="left"
value={`${settings.interfaceFontSize}`}
options={fontSizes}
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
/>
<Select
size="sm"
name="editorFontSize"
label="Editor Font Size"
labelPosition="left"
value={`${settings.editorFontSize}`}
options={fontSizes}
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
/>
<Checkbox
checked={settings.editorSoftWrap}
title="Wrap Editor Lines"
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
/>
<Separator className="my-4" />
<Select
name="appearance"
label="Appearance"
labelPosition="top"
size="sm"
value={settings.appearance}
onChange={(appearance) => {
trackEvent('appearance', 'update', { appearance });
updateSettings.mutateAsync({ appearance });
}}
options={[
{ label: 'Automatic', value: 'system' },
{ label: 'Light', value: 'light' },
{ label: 'Dark', value: 'dark' },
]}
/>
<HStack space={2}>
{(settings.appearance === 'system' || settings.appearance === 'light') && (
<Select
hideLabel
leftSlot={<Icon icon="sun" />}
name="lightTheme"
label="Light Theme"
size="sm"
className="flex-1"
value={activeTheme.light.id}
options={lightThemes}
onChange={(themeLight) => {
trackEvent('theme', 'update', { theme: themeLight, appearance: 'light' });
updateSettings.mutateAsync({ ...settings, themeLight });
}}
/>
)}
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
<Select
hideLabel
name="darkTheme"
className="flex-1"
label="Dark Theme"
leftSlot={<Icon icon="moon" />}
size="sm"
value={activeTheme.dark.id}
options={darkThemes}
onChange={(themeDark) => {
trackEvent('theme', 'update', { theme: themeDark, appearance: 'dark' });
updateSettings.mutateAsync({ ...settings, themeDark });
}}
/>
)}
</HStack>
<VStack
space={3}
className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded overflow-x-auto"
>
<HStack className="text-fg font-bold" space={2}>
Theme Preview{' '}
<Icon icon={appearance === 'dark' ? 'moon' : 'sun'} className="text-fg-subtle" />
</HStack>
<HStack space={1.5} className="w-full">
{buttonColors.map((c, i) => (
<IconButton
key={c}
color={c}
size="2xs"
iconSize="xs"
icon={icons[i % icons.length]!}
iconClassName="text-fg"
title={`${c}`}
/>
))}
{buttonColors.map((c, i) => (
<IconButton
key={c}
color={c}
variant="border"
size="2xs"
iconSize="xs"
icon={icons[i % icons.length]!}
iconClassName="text-fg"
title={`${c}`}
/>
))}
</HStack>
<Editor
defaultValue={[
'let foo = { // Demo code editor',
' foo: ("bar" || "baz" ?? \'qux\'),',
' baz: [1, 10.2, null, false, true],',
'};',
].join('\n')}
heightMode="auto"
contentType="application/javascript"
/>
</VStack>
</VStack>
);
}

View File

@@ -0,0 +1,165 @@
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
import React, { useState } from 'react';
import { useLocalStorage } from 'react-use';
import { useThemes } from '../../hooks/useThemes';
import { capitalize } from '../../lib/capitalize';
import { yaakDark } from '../../lib/theme/themes/yaak';
import { getThemeCSS } from '../../lib/theme/window';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import { Editor } from '../core/Editor';
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import { InlineCode } from '../core/InlineCode';
import { Input } from '../core/Input';
import { Separator } from '../core/Separator';
import { HStack, VStack } from '../core/Stacks';
const buttonColors = [
'primary',
'secondary',
'info',
'success',
'warning',
'danger',
'default',
] as const;
const icons: IconProps['icon'][] = [
'info',
'box',
'update',
'alert',
'arrowBigRightDash',
'download',
'copy',
'magicWand',
'settings',
'trash',
'sparkles',
'pencil',
'paste',
'search',
'sendHorizontal',
];
export function SettingsDesign() {
const themes = useThemes();
const [exportDir, setExportDir] = useLocalStorage<string | null>('theme_export_dir', null);
const [loadingExport, setLoadingExport] = useState<boolean>(false);
const saveThemes = () => {
setLoadingExport(true);
setTimeout(async () => {
const allThemesCSS = themes.themes.map(getThemeCSS).join('\n\n');
const coreThemeCSS = [yaakDark].map(getThemeCSS).join('\n\n');
try {
await invoke('cmd_write_file_dev', {
pathname: exportDir + '/themes-all.css',
contents: allThemesCSS,
});
await invoke('cmd_write_file_dev', {
pathname: exportDir + '/themes-slim.css',
contents: coreThemeCSS,
});
} catch (err) {
console.log('FAILED', err);
}
setLoadingExport(false);
}, 500);
};
return (
<div className="p-2 flex flex-col gap-3">
<VStack space={2}>
<InlineCode>{exportDir}</InlineCode>
<HStack space={2}>
<Button
size="sm"
color="secondary"
variant="border"
onClick={() => {
open({ directory: true }).then(setExportDir);
}}
>
Change Export Dir
</Button>
<Button
disabled={exportDir == null}
isLoading={loadingExport}
size="sm"
color="primary"
variant="border"
onClick={saveThemes}
>
Export CSS
</Button>
</HStack>
</VStack>
<Separator className="my-6" />
<Input
label="Field Label"
name="demo"
placeholder="Placeholder"
size="sm"
rightSlot={<IconButton title="search" size="xs" className="w-8 m-0.5" icon="search" />}
/>
<Editor
defaultValue={[
'// Demo code editor',
'let foo = {',
' foo: ("bar" || "baz" ?? \'qux\'),',
' baz: [1, 10.2, null, false, true],',
'};',
].join('\n')}
heightMode="auto"
contentType="application/javascript"
/>
<div className="flex flex-col gap-1">
<div className="flex flex-wrap gap-1">
{buttonColors.map((c, i) => (
<Button key={c} color={c} size="sm" leftSlot={<Icon size="sm" icon={icons[i]!} />}>
{capitalize(c).slice(0, 4)}
</Button>
))}
</div>
<div className="flex flex-wrap gap-1">
{buttonColors.map((c, i) => (
<Button
key={c}
color={c}
variant="border"
size="sm"
leftSlot={<Icon size="sm" icon={icons[i]!} />}
>
{capitalize(c).slice(0, 4)}
</Button>
))}
</div>
<div className="flex gap-1">
{icons.map((v, i) => (
<IconButton
color={buttonColors[i % buttonColors.length]}
title={v}
variant="border"
size="sm"
key={v}
icon={v}
/>
))}
</div>
</div>
<div className="flex flex-col gap-1">
<Banner color="primary">Primary banner</Banner>
<Banner color="secondary">Secondary banner</Banner>
<Banner color="danger">Danger banner</Banner>
<Banner color="warning">Warning banner</Banner>
<Banner color="success">Success banner</Banner>
</div>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More