mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-15 07:07:45 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f9a7e9c88 |
18
.github/workflows/ci-js.yml
vendored
18
.github/workflows/ci-js.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
|
||||
name: CI (JS)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
36
.github/workflows/ci-rust.yml
vendored
36
.github/workflows/ci-rust.yml
vendored
@@ -1,36 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
paths:
|
||||
- src-tauri/**
|
||||
- .github/workflows/**
|
||||
|
||||
name: CI (Rust)
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src-tauri
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Check/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- run: cargo check --all
|
||||
- run: cargo test --all
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Generate Artifacts
|
||||
on:
|
||||
push:
|
||||
tags: [ v* ]
|
||||
|
||||
branches:
|
||||
- release
|
||||
- beta
|
||||
jobs:
|
||||
build-artifacts:
|
||||
permissions:
|
||||
@@ -32,17 +33,6 @@ jobs:
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||
run: |
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,5 +27,4 @@ dist-ssr
|
||||
*.sqlite
|
||||
*.sqlite-*
|
||||
|
||||
.cargo
|
||||
plugins/**/build
|
||||
.cargo
|
||||
1
.tauriignore
Normal file
1
.tauriignore
Normal file
@@ -0,0 +1 @@
|
||||
plugins
|
||||
Binary file not shown.
784
package-lock.json
generated
784
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "yaak",
|
||||
"name": "yaak-app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -52,7 +52,6 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"focus-trap-react": "^10.1.1",
|
||||
"format-graphql": "^1.4.0",
|
||||
"framer-motion": "^9.0.4",
|
||||
@@ -65,7 +64,6 @@
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-pdf": "^9.0.0",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-use": "^17.4.0",
|
||||
"slugify": "^1.6.6",
|
||||
@@ -76,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",
|
||||
@@ -105,7 +103,6 @@
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-plugin-top-level-await": "^1.4.1",
|
||||
"vitest": "^1.3.0"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { HttpRequest } from '../../../src-web/lib/models';
|
||||
|
||||
const NEWLINE = '\\\n ';
|
||||
|
||||
export function pluginHookExport(_: any, request: Partial<HttpRequest>) {
|
||||
export function pluginHookExport(request: Partial<HttpRequest>) {
|
||||
const xs = ['curl'];
|
||||
|
||||
// Add method and URL all on first line
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { pluginHookExport } from '../src';
|
||||
|
||||
const ctx = {};
|
||||
|
||||
describe('exporter-curl', () => {
|
||||
test('Exports GET with params', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
urlParameters: [
|
||||
{ name: 'a', value: 'aaa' },
|
||||
@@ -20,7 +18,7 @@ describe('exporter-curl', () => {
|
||||
});
|
||||
test('Exports POST with url form data', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
@@ -39,7 +37,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Exports PUT with multipart form', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'PUT',
|
||||
bodyType: 'multipart/form-data',
|
||||
@@ -64,7 +62,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Exports JSON body', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/json',
|
||||
@@ -84,7 +82,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Exports multi-line JSON body', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/json',
|
||||
@@ -104,7 +102,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Exports headers', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
headers: [
|
||||
{ name: 'a', value: 'aaa' },
|
||||
{ name: 'b', value: 'bbb', enabled: true },
|
||||
@@ -116,7 +114,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Basic auth', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'basic',
|
||||
authentication: {
|
||||
@@ -129,7 +127,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Broken basic auth', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'basic',
|
||||
authentication: {},
|
||||
@@ -139,7 +137,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Digest auth', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'digest',
|
||||
authentication: {
|
||||
@@ -152,7 +150,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Bearer auth', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'bearer',
|
||||
authentication: {
|
||||
@@ -164,7 +162,7 @@ describe('exporter-curl', () => {
|
||||
|
||||
test('Broken bearer auth', () => {
|
||||
expect(
|
||||
pluginHookExport(ctx, {
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'bearer',
|
||||
authentication: {
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/exporter-curl'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import jp from 'jsonpath';
|
||||
|
||||
export function pluginHookResponseFilter(ctx, filter, text) {
|
||||
export function pluginHookResponseFilter(filter, text) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-jsonpath'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import xpath from 'xpath';
|
||||
import { DOMParser } from '@xmldom/xmldom';
|
||||
|
||||
export function pluginHookResponseFilter(ctx, filter, text) {
|
||||
export function pluginHookResponseFilter(filter, text) {
|
||||
const doc = new DOMParser().parseFromString(text, 'text/xml');
|
||||
const filtered = `${xpath.select(filter, doc)}`;
|
||||
return { filtered };
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-xpath'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ type Pair = string | boolean;
|
||||
|
||||
type PairsByName = Record<string, Pair[]>;
|
||||
|
||||
export function pluginHookImport(_: any, rawData: string) {
|
||||
export function pluginHookImport(rawData: string) {
|
||||
if (!rawData.match(/^\s*curl /)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ import { describe, expect, test } from 'vitest';
|
||||
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
|
||||
import { pluginHookImport } from '../src';
|
||||
|
||||
const ctx = {};
|
||||
|
||||
describe('importer-curl', () => {
|
||||
test('Imports basic GET', () => {
|
||||
expect(pluginHookImport(ctx, 'curl https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -19,7 +17,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Explicit URL', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --url https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --url https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -32,7 +30,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Missing URL', () => {
|
||||
expect(pluginHookImport(ctx, 'curl -X POST')).toEqual({
|
||||
expect(pluginHookImport('curl -X POST')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -45,7 +43,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('URL between', () => {
|
||||
expect(pluginHookImport(ctx, 'curl -v https://yaak.app -X POST')).toEqual({
|
||||
expect(pluginHookImport('curl -v https://yaak.app -X POST')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -59,7 +57,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Random flags', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -72,7 +70,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports --request method', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --request POST https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --request POST https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -86,7 +84,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports -XPOST method', () => {
|
||||
expect(pluginHookImport(ctx, 'curl -XPOST --request POST https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl -XPOST --request POST https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -101,10 +99,7 @@ describe('importer-curl', () => {
|
||||
|
||||
test('Imports multiple requests', () => {
|
||||
expect(
|
||||
pluginHookImport(
|
||||
ctx,
|
||||
'curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com',
|
||||
),
|
||||
pluginHookImport('curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com'),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
@@ -119,7 +114,7 @@ describe('importer-curl', () => {
|
||||
|
||||
test('Imports form data', () => {
|
||||
expect(
|
||||
pluginHookImport(ctx, 'curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
|
||||
pluginHookImport('curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
@@ -149,7 +144,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports data params as form url-encoded', () => {
|
||||
expect(pluginHookImport(ctx, 'curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -179,7 +174,7 @@ describe('importer-curl', () => {
|
||||
|
||||
test('Imports data params as text', () => {
|
||||
expect(
|
||||
pluginHookImport(ctx, 'curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
|
||||
pluginHookImport('curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
@@ -199,7 +194,6 @@ describe('importer-curl', () => {
|
||||
test('Imports multi-line JSON', () => {
|
||||
expect(
|
||||
pluginHookImport(
|
||||
ctx,
|
||||
`curl -H Content-Type:application/json -d $'{\n "foo":"bar"\n}' https://yaak.app`,
|
||||
),
|
||||
).toEqual({
|
||||
@@ -220,7 +214,7 @@ describe('importer-curl', () => {
|
||||
|
||||
test('Imports multiple headers', () => {
|
||||
expect(
|
||||
pluginHookImport(ctx, 'curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
|
||||
pluginHookImport('curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
@@ -240,7 +234,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports basic auth', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --user user:pass https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --user user:pass https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -258,7 +252,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports digest auth', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --digest --user user:pass https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --digest --user user:pass https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -276,7 +270,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports cookie as header', () => {
|
||||
expect(pluginHookImport(ctx, 'curl --cookie "foo=bar" https://yaak.app')).toEqual({
|
||||
expect(pluginHookImport('curl --cookie "foo=bar" https://yaak.app')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
@@ -290,7 +284,7 @@ describe('importer-curl', () => {
|
||||
});
|
||||
|
||||
test('Imports query params from the URL', () => {
|
||||
expect(pluginHookImport(ctx, 'curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
|
||||
expect(pluginHookImport('curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-curl'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
HttpRequest,
|
||||
Workspace,
|
||||
} from '../../../src-web/lib/models';
|
||||
import '../../../src-web/plugin/runtime.d.ts';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
|
||||
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface ExportResources {
|
||||
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
}
|
||||
|
||||
export function pluginHookImport(ctx: YaakContext, contents: string) {
|
||||
export function pluginHookImport(contents: string) {
|
||||
let parsed: any;
|
||||
|
||||
try {
|
||||
@@ -25,10 +25,8 @@ export function pluginHookImport(ctx: YaakContext, contents: string) {
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
parsed = parsed ?? YAML.parse(contents);
|
||||
} catch (e) {
|
||||
console.log('FAILED', e);
|
||||
}
|
||||
parsed = parseYaml(contents);
|
||||
} catch (e) {}
|
||||
|
||||
if (!isJSObject(parsed)) return;
|
||||
if (!Array.isArray(parsed.resources)) return;
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-insomnia'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,11 +13,7 @@ interface ExportResources {
|
||||
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
}
|
||||
|
||||
export function pluginHookImport(
|
||||
ctx: any,
|
||||
contents: string,
|
||||
): { resources: ExportResources } | undefined {
|
||||
console.log('CTX', ctx);
|
||||
export function pluginHookImport(contents: string): { resources: ExportResources } | undefined {
|
||||
const root = parseJSONToRecord(contents);
|
||||
if (root == null) return;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('importer-postman', () => {
|
||||
for (const fixture of fixtures) {
|
||||
test('Imports ' + fixture, () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const imported = pluginHookImport({}, contents);
|
||||
const imported = pluginHookImport(contents);
|
||||
const folder0 = newId('folder');
|
||||
const folder1 = newId('folder');
|
||||
expect(imported).toEqual({
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-postman'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function pluginHookImport(ctx: any, contents: string) {
|
||||
export function pluginHookImport(contents: string) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(contents);
|
||||
@@ -18,7 +18,7 @@ export function pluginHookImport(ctx: any, contents: string) {
|
||||
// Migrate v1 to v2 -- changes requests to httpRequests
|
||||
if ('requests' in parsed.resources) {
|
||||
parsed.resources.httpRequests = parsed.resources.requests;
|
||||
delete parsed.resources['requests'];
|
||||
delete parsed.resources.requests;
|
||||
}
|
||||
|
||||
return { resources: parsed.resources }; // Should already be in the correct format
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { pluginHookImport } from '../src';
|
||||
|
||||
const ctx = {};
|
||||
|
||||
describe('importer-yaak', () => {
|
||||
test('Skips invalid imports', () => {
|
||||
expect(pluginHookImport(ctx, 'not JSON')).toBeUndefined();
|
||||
expect(pluginHookImport(ctx, '[]')).toBeUndefined();
|
||||
expect(pluginHookImport(ctx, JSON.stringify({ resources: {} }))).toBeUndefined();
|
||||
expect(pluginHookImport('not JSON')).toBeUndefined();
|
||||
expect(pluginHookImport('[]')).toBeUndefined();
|
||||
expect(pluginHookImport(JSON.stringify({ resources: {} }))).toBeUndefined();
|
||||
});
|
||||
|
||||
test('converts schema 1 to 2', () => {
|
||||
const imported = pluginHookImport(
|
||||
ctx,
|
||||
JSON.stringify({
|
||||
yaakSchema: 1,
|
||||
resources: {
|
||||
|
||||
@@ -8,8 +8,6 @@ export default defineConfig({
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-yaak'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 open_workspace_new_window\n FROM settings\n WHERE id = 'default'\n ",
|
||||
"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": [
|
||||
{
|
||||
@@ -67,11 +67,6 @@
|
||||
"name": "editor_soft_wrap",
|
||||
"ordinal": 12,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "open_workspace_new_window",
|
||||
"ordinal": 13,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -90,9 +85,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "05dca7fe15ab1bf03952e94498ef3130e16f752da72782783696eb2cca4736d5"
|
||||
"hash": "ca3485d87b060cd77c4114d2af544adf18f6f15341d9d5db40865e92a80da4e2"
|
||||
}
|
||||
@@ -1,12 +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 open_workspace_new_window\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';\n ",
|
||||
"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": 10
|
||||
"Right": 9
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6b5edf45a6799cd7f87c23a3c7f818ad110d58c601f694a619d9345ae9e8e11d"
|
||||
"hash": "efd8ba41ea909b18dd520c57c1d464c5ae057b720cbbedcaec1513d43535632c"
|
||||
}
|
||||
1810
src-tauri/Cargo.lock
generated
1810
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
workspace = { members = ["grpc", "templates"] }
|
||||
workspace = { members = ["grpc"] }
|
||||
|
||||
[package]
|
||||
name = "yaak-app"
|
||||
@@ -8,7 +8,7 @@ edition = "2021"
|
||||
# Produce a library for mobile support
|
||||
[lib]
|
||||
name = "tauri_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "lib"]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
@@ -20,40 +20,43 @@ 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
|
||||
|
||||
[dependencies]
|
||||
grpc = { path = "./grpc" }
|
||||
templates = { path = "./templates" }
|
||||
anyhow = "1.0.86"
|
||||
base64 = "0.22.0"
|
||||
boa_engine = { version = "0.18.0", features = ["annex-b"] }
|
||||
boa_runtime = { version = "0.18.0" }
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
datetime = "0.5.2"
|
||||
deno_ast = { version = "0.39.0", features = ["transpiling"] }
|
||||
deno_console = "0.155.0"
|
||||
deno_core = { version = "0.284.0" }
|
||||
hex_color = "3.0.0"
|
||||
http = "1"
|
||||
log = "0.4.21"
|
||||
http = "0.2.10"
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
|
||||
reqwest_cookie_store = "0.8.0"
|
||||
reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json"] }
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = { version = "1.0.116", features = ["raw_value"] }
|
||||
serde_yaml = "0.9.34"
|
||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
||||
tauri = { version = "2.0.0-beta", features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.1.0-beta"
|
||||
tauri-plugin-dialog = "2.0.0-beta"
|
||||
tauri-plugin-fs = "2.0.0-beta"
|
||||
tauri-plugin-log = { version = "2.0.0-beta", features = ["colored"] }
|
||||
tauri-plugin-os = "2.0.0-beta"
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri-plugin-updater = "2.0.0-beta"
|
||||
tauri-plugin-window-state = "2.0.0-beta"
|
||||
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"] }
|
||||
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
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"] }
|
||||
tokio-stream = "0.1.15"
|
||||
uuid = "1.7.0"
|
||||
thiserror = "1.0.61"
|
||||
log = "0.4.21"
|
||||
datetime = "0.5.2"
|
||||
reqwest_cookie_store = "0.6.0"
|
||||
grpc = { path = "./grpc" }
|
||||
tokio-stream = "0.1.15"
|
||||
regex = "1.10.2"
|
||||
hex_color = "3.0.0"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2512,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": [
|
||||
|
||||
@@ -2512,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": [
|
||||
|
||||
@@ -18,5 +18,5 @@ anyhow = "1.0.79"
|
||||
hyper = { version = "0.14" }
|
||||
hyper-rustls = { version = "0.24.0", features = ["http2"] }
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
tauri = { version = "2.0.0-beta" }
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri = { version = "2.0.0-beta.16" }
|
||||
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
|
||||
@@ -185,22 +185,20 @@ impl GrpcHandle {
|
||||
pub async fn services_from_files(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
uri: &Uri,
|
||||
paths: Vec<PathBuf>,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
let pool = fill_pool_from_files(&self.app_handle, paths).await?;
|
||||
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
|
||||
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
|
||||
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||
Ok(self.services_from_pool(&pool))
|
||||
}
|
||||
pub async fn services_from_reflection(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
uri: &Uri,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
|
||||
let pool = fill_pool(&uri).await?;
|
||||
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
|
||||
let pool = fill_pool(uri).await?;
|
||||
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||
Ok(self.services_from_pool(&pool))
|
||||
}
|
||||
|
||||
@@ -236,10 +234,9 @@ impl GrpcHandle {
|
||||
pub async fn connect(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
uri: Uri,
|
||||
proto_files: Vec<PathBuf>,
|
||||
) -> Result<GrpcConnection, String> {
|
||||
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
|
||||
let pool = match self.pools.get(id) {
|
||||
Some(p) => p.clone(),
|
||||
None => match proto_files.len() {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<?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>
|
||||
<!-- Enable for v8 execution -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
<!-- <key>com.apple.security.network.client</key> <true/>-->
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
<!-- <key>com.apple.security.network.client</key> <true/>-->
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE settings ADD COLUMN open_workspace_new_window BOOLEAN NULL DEFAULT NULL;
|
||||
@@ -1,37 +1,36 @@
|
||||
const o = `\\
|
||||
`;
|
||||
function y(p, t) {
|
||||
var f, r, u, l, s, c;
|
||||
const n = ["curl"];
|
||||
t.method && n.push("-X", t.method), t.url && n.push(i(t.url)), n.push(o);
|
||||
for (const a of (t.urlParameters ?? []).filter(h))
|
||||
n.push("--url-query", i(`${a.name}=${a.value}`)), n.push(o);
|
||||
for (const a of (t.headers ?? []).filter(h))
|
||||
n.push("--header", i(`${a.name}: ${a.value}`)), n.push(o);
|
||||
if (Array.isArray((f = t.body) == null ? void 0 : f.form)) {
|
||||
const a = t.bodyType === "multipart/form-data" ? "--form" : "--data";
|
||||
for (const e of (((r = t.body) == null ? void 0 : r.form) ?? []).filter(h)) {
|
||||
function d(n) {
|
||||
var h, f, r, u, l, s;
|
||||
const t = ["curl"];
|
||||
n.method && t.push("-X", n.method), n.url && t.push(i(n.url)), t.push(o);
|
||||
for (const a of (n.urlParameters ?? []).filter(p))
|
||||
t.push("--url-query", i(`${a.name}=${a.value}`)), t.push(o);
|
||||
for (const a of (n.headers ?? []).filter(p))
|
||||
t.push("--header", i(`${a.name}: ${a.value}`)), t.push(o);
|
||||
if (Array.isArray((h = n.body) == null ? void 0 : h.form)) {
|
||||
const a = n.bodyType === "multipart/form-data" ? "--form" : "--data";
|
||||
for (const e of (((f = n.body) == null ? void 0 : f.form) ?? []).filter(p)) {
|
||||
if (e.file) {
|
||||
let d = `${e.name}=@${e.file}`;
|
||||
d += e.contentType ? `;type=${e.contentType}` : "", n.push(a, d);
|
||||
let c = `${e.name}=@${e.file}`;
|
||||
c += e.contentType ? `;type=${e.contentType}` : "", t.push(a, c);
|
||||
} else
|
||||
n.push(a, i(`${e.name}=${e.value}`));
|
||||
n.push(o);
|
||||
t.push(a, i(`${e.name}=${e.value}`));
|
||||
t.push(o);
|
||||
}
|
||||
} else
|
||||
typeof ((u = t.body) == null ? void 0 : u.text) == "string" && (n.push("--data-raw", `$${i(t.body.text)}`), n.push(o));
|
||||
return (t.authenticationType === "basic" || t.authenticationType === "digest") && (t.authenticationType === "digest" && n.push("--digest"), n.push(
|
||||
typeof ((r = n.body) == null ? void 0 : r.text) == "string" && (t.push("--data-raw", `$${i(n.body.text)}`), t.push(o));
|
||||
return (n.authenticationType === "basic" || n.authenticationType === "digest") && (n.authenticationType === "digest" && t.push("--digest"), t.push(
|
||||
"--user",
|
||||
i(`${((l = t.authentication) == null ? void 0 : l.username) ?? ""}:${((s = t.authentication) == null ? void 0 : s.password) ?? ""}`)
|
||||
), n.push(o)), t.authenticationType === "bearer" && (n.push("--header", i(`Authorization: Bearer ${((c = t.authentication) == null ? void 0 : c.token) ?? ""}`)), n.push(o)), n[n.length - 1] === o && n.splice(n.length - 1, 1), n.join(" ");
|
||||
i(`${((u = n.authentication) == null ? void 0 : u.username) ?? ""}:${((l = n.authentication) == null ? void 0 : l.password) ?? ""}`)
|
||||
), t.push(o)), n.authenticationType === "bearer" && (t.push("--header", i(`Authorization: Bearer ${((s = n.authentication) == null ? void 0 : s.token) ?? ""}`)), t.push(o)), t[t.length - 1] === o && t.splice(t.length - 1, 1), t.join(" ");
|
||||
}
|
||||
function i(p) {
|
||||
return `'${p.replace(/'/g, "\\'")}'`;
|
||||
function i(n) {
|
||||
return `'${n.replace(/'/g, "\\'")}'`;
|
||||
}
|
||||
function h(p) {
|
||||
return p.enabled !== !1 && !!p.name;
|
||||
function p(n) {
|
||||
return n.enabled !== !1 && !!n.name;
|
||||
}
|
||||
export {
|
||||
y as pluginHookExport
|
||||
d as pluginHookExport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../plugins/exporter-curl/src/index.ts"],"sourcesContent":["import { HttpRequest } from '../../../src-web/lib/models';\n\nconst NEWLINE = '\\\\\\n ';\n\nexport function pluginHookExport(_: any, request: Partial<HttpRequest>) {\n const xs = ['curl'];\n\n // Add method and URL all on first line\n if (request.method) xs.push('-X', request.method);\n if (request.url) xs.push(quote(request.url));\n\n xs.push(NEWLINE);\n\n // Add URL params\n for (const p of (request.urlParameters ?? []).filter(onlyEnabled)) {\n xs.push('--url-query', quote(`${p.name}=${p.value}`));\n xs.push(NEWLINE);\n }\n\n // Add headers\n for (const h of (request.headers ?? []).filter(onlyEnabled)) {\n xs.push('--header', quote(`${h.name}: ${h.value}`));\n xs.push(NEWLINE);\n }\n\n // Add form params\n if (Array.isArray(request.body?.form)) {\n const flag = request.bodyType === 'multipart/form-data' ? '--form' : '--data';\n for (const p of (request.body?.form ?? []).filter(onlyEnabled)) {\n if (p.file) {\n let v = `${p.name}=@${p.file}`;\n v += p.contentType ? `;type=${p.contentType}` : '';\n xs.push(flag, v);\n } else {\n xs.push(flag, quote(`${p.name}=${p.value}`));\n }\n xs.push(NEWLINE);\n }\n } else if (typeof request.body?.text === 'string') {\n // --data-raw $'...' to do special ANSI C quoting\n xs.push('--data-raw', `$${quote(request.body.text)}`);\n xs.push(NEWLINE);\n }\n\n // Add basic/digest authentication\n if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {\n if (request.authenticationType === 'digest') xs.push('--digest');\n xs.push(\n '--user',\n quote(`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`),\n );\n xs.push(NEWLINE);\n }\n\n // Add bearer authentication\n if (request.authenticationType === 'bearer') {\n xs.push('--header', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));\n xs.push(NEWLINE);\n }\n\n // Remove trailing newline\n if (xs[xs.length - 1] === NEWLINE) {\n xs.splice(xs.length - 1, 1);\n }\n\n return xs.join(' ');\n}\n\nfunction quote(arg: string): string {\n const escaped = arg.replace(/'/g, \"\\\\'\");\n return `'${escaped}'`;\n}\n\nfunction onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {\n return v.enabled !== false && !!v.name;\n}\n"],"names":["NEWLINE","pluginHookExport","_","request","_a","_b","_c","_d","_e","_f","xs","quote","p","onlyEnabled","h","flag","v","arg"],"mappings":"AAEA,MAAMA,IAAU;AAAA;AAEA,SAAAC,EAAiBC,GAAQC,GAA+B;AAFxE,MAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC;AAGQ,QAAAC,IAAK,CAAC,MAAM;AAGlB,EAAIP,EAAQ,UAAWO,EAAA,KAAK,MAAMP,EAAQ,MAAM,GAC5CA,EAAQ,OAAKO,EAAG,KAAKC,EAAMR,EAAQ,GAAG,CAAC,GAE3CO,EAAG,KAAKV,CAAO;AAGf,aAAWY,MAAMT,EAAQ,iBAAiB,IAAI,OAAOU,CAAW;AAC3D,IAAAH,EAAA,KAAK,eAAeC,EAAM,GAAGC,EAAE,IAAI,IAAIA,EAAE,KAAK,EAAE,CAAC,GACpDF,EAAG,KAAKV,CAAO;AAIjB,aAAWc,MAAMX,EAAQ,WAAW,IAAI,OAAOU,CAAW;AACrD,IAAAH,EAAA,KAAK,YAAYC,EAAM,GAAGG,EAAE,IAAI,KAAKA,EAAE,KAAK,EAAE,CAAC,GAClDJ,EAAG,KAAKV,CAAO;AAIjB,MAAI,MAAM,SAAQI,IAAAD,EAAQ,SAAR,gBAAAC,EAAc,IAAI,GAAG;AACrC,UAAMW,IAAOZ,EAAQ,aAAa,wBAAwB,WAAW;AAC1D,eAAAS,QAAMP,IAAAF,EAAQ,SAAR,gBAAAE,EAAc,SAAQ,CAAI,GAAA,OAAOQ,CAAW,GAAG;AAC9D,UAAID,EAAE,MAAM;AACV,YAAII,IAAI,GAAGJ,EAAE,IAAI,KAAKA,EAAE,IAAI;AAC5B,QAAAI,KAAKJ,EAAE,cAAc,SAASA,EAAE,WAAW,KAAK,IAC7CF,EAAA,KAAKK,GAAMC,CAAC;AAAA,MAAA;AAEZ,QAAAN,EAAA,KAAKK,GAAMJ,EAAM,GAAGC,EAAE,IAAI,IAAIA,EAAE,KAAK,EAAE,CAAC;AAE7C,MAAAF,EAAG,KAAKV,CAAO;AAAA,IACjB;AAAA,EACS;AAAA,IAAA,SAAOM,IAAAH,EAAQ,SAAR,gBAAAG,EAAc,SAAS,aAEpCI,EAAA,KAAK,cAAc,IAAIC,EAAMR,EAAQ,KAAK,IAAI,CAAC,EAAE,GACpDO,EAAG,KAAKV,CAAO;AAIjB,UAAIG,EAAQ,uBAAuB,WAAWA,EAAQ,uBAAuB,cACvEA,EAAQ,uBAAuB,YAAUO,EAAG,KAAK,UAAU,GAC5DA,EAAA;AAAA,IACD;AAAA,IACAC,EAAM,KAAGJ,IAAAJ,EAAQ,mBAAR,gBAAAI,EAAwB,aAAY,EAAE,MAAIC,IAAAL,EAAQ,mBAAR,gBAAAK,EAAwB,aAAY,EAAE,EAAE;AAAA,EAAA,GAE7FE,EAAG,KAAKV,CAAO,IAIbG,EAAQ,uBAAuB,aAC9BO,EAAA,KAAK,YAAYC,EAAM,2BAAyBF,IAAAN,EAAQ,mBAAR,gBAAAM,EAAwB,UAAS,EAAE,EAAE,CAAC,GACzFC,EAAG,KAAKV,CAAO,IAIbU,EAAGA,EAAG,SAAS,CAAC,MAAMV,KACxBU,EAAG,OAAOA,EAAG,SAAS,GAAG,CAAC,GAGrBA,EAAG,KAAK,GAAG;AACpB;AAEA,SAASC,EAAMM,GAAqB;AAElC,SAAO,IADSA,EAAI,QAAQ,MAAM,KAAK,CACrB;AACpB;AAEA,SAASJ,EAAYG,GAAkD;AACrE,SAAOA,EAAE,YAAY,MAAS,CAAC,CAACA,EAAE;AACpC;"}
|
||||
@@ -1,15 +1,15 @@
|
||||
var Xe = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
|
||||
function Nt(ce) {
|
||||
return ce && ce.__esModule && Object.prototype.hasOwnProperty.call(ce, "default") ? ce.default : ce;
|
||||
function Nt(le) {
|
||||
return le && le.__esModule && Object.prototype.hasOwnProperty.call(le, "default") ? le.default : le;
|
||||
}
|
||||
function Be(ce) {
|
||||
throw new Error('Could not dynamically require "' + ce + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
||||
function Be(le) {
|
||||
throw new Error('Could not dynamically require "' + le + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
||||
}
|
||||
var Ve = { exports: {} };
|
||||
/*! jsonpath 1.1.1 */
|
||||
(function(ce, Ne) {
|
||||
(function(le, Ne) {
|
||||
(function(T) {
|
||||
ce.exports = T();
|
||||
le.exports = T();
|
||||
})(function() {
|
||||
return function T(L, B, v) {
|
||||
function y(E, d) {
|
||||
@@ -1131,7 +1131,7 @@ var Ve = { exports: {} };
|
||||
function Ze() {
|
||||
var e = [], t;
|
||||
for (t = i, O("["); !_("]"); )
|
||||
_(",") ? (N(), e.push(null)) : (e.push(le()), _("]") || O(","));
|
||||
_(",") ? (N(), e.push(null)) : (e.push(ce()), _("]") || O(","));
|
||||
return N(), n.markEnd(n.createArrayExpression(e), t);
|
||||
}
|
||||
function _e(e, t) {
|
||||
@@ -1145,11 +1145,11 @@ var Ve = { exports: {} };
|
||||
function et() {
|
||||
var e, t, u, l, C, x;
|
||||
if (e = i, x = i, e.type === y.Identifier)
|
||||
return u = De(), e.value === "get" && !_(":") ? (t = De(), O("("), O(")"), l = _e([]), n.markEnd(n.createProperty("get", t, l), x)) : e.value === "set" && !_(":") ? (t = De(), O("("), e = i, e.type !== y.Identifier ? (O(")"), G(e, a.UnexpectedToken, e.value), l = _e([])) : (C = [ye()], O(")"), l = _e(C, e)), n.markEnd(n.createProperty("set", t, l), x)) : (O(":"), l = le(), n.markEnd(n.createProperty("init", u, l), x));
|
||||
return u = De(), e.value === "get" && !_(":") ? (t = De(), O("("), O(")"), l = _e([]), n.markEnd(n.createProperty("get", t, l), x)) : e.value === "set" && !_(":") ? (t = De(), O("("), e = i, e.type !== y.Identifier ? (O(")"), G(e, a.UnexpectedToken, e.value), l = _e([])) : (C = [ye()], O(")"), l = _e(C, e)), n.markEnd(n.createProperty("set", t, l), x)) : (O(":"), l = ce(), n.markEnd(n.createProperty("init", u, l), x));
|
||||
if (e.type === y.EOF || e.type === y.Punctuator)
|
||||
pe(e);
|
||||
else
|
||||
return t = De(), O(":"), l = le(), n.markEnd(n.createProperty("init", t, l), x);
|
||||
return t = De(), O(":"), l = ce(), n.markEnd(n.createProperty("init", t, l), x);
|
||||
}
|
||||
function tt() {
|
||||
var e = [], t, u, l, C, x = {}, j = String, W;
|
||||
@@ -1184,7 +1184,7 @@ var Ve = { exports: {} };
|
||||
function je() {
|
||||
var e = [];
|
||||
if (O("("), !_(")"))
|
||||
for (; r < h && (e.push(le()), !_(")")); )
|
||||
for (; r < h && (e.push(ce()), !_(")")); )
|
||||
O(",");
|
||||
return O(")"), e;
|
||||
}
|
||||
@@ -1300,17 +1300,17 @@ var Ve = { exports: {} };
|
||||
}
|
||||
function st() {
|
||||
var e, t, u, l, C;
|
||||
return C = i, e = at(), _("?") && (N(), t = p.allowIn, p.allowIn = !0, u = le(), p.allowIn = t, O(":"), l = le(), e = n.createConditionalExpression(e, u, l), n.markEnd(e, C)), e;
|
||||
return C = i, e = at(), _("?") && (N(), t = p.allowIn, p.allowIn = !0, u = ce(), p.allowIn = t, O(":"), l = ce(), e = n.createConditionalExpression(e, u, l), n.markEnd(e, C)), e;
|
||||
}
|
||||
function le() {
|
||||
function ce() {
|
||||
var e, t, u, l, C;
|
||||
return e = i, C = i, l = t = st(), Ye() && (be(t) || G({}, a.InvalidLHSInAssignment), c && t.type === E.Identifier && J(t.name) && G(e, a.StrictLHSAssignment), e = N(), u = le(), l = n.markEnd(n.createAssignmentExpression(e.value, t, u), C)), l;
|
||||
return e = i, C = i, l = t = st(), Ye() && (be(t) || G({}, a.InvalidLHSInAssignment), c && t.type === E.Identifier && J(t.name) && G(e, a.StrictLHSAssignment), e = N(), u = ce(), l = n.markEnd(n.createAssignmentExpression(e.value, t, u), C)), l;
|
||||
}
|
||||
function te() {
|
||||
var e, t = i;
|
||||
if (e = le(), _(",")) {
|
||||
if (e = ce(), _(",")) {
|
||||
for (e = n.createSequenceExpression([e]); r < h && _(","); )
|
||||
N(), e.expressions.push(le());
|
||||
N(), e.expressions.push(ce());
|
||||
n.markEnd(e, t);
|
||||
}
|
||||
return e;
|
||||
@@ -1330,7 +1330,7 @@ var Ve = { exports: {} };
|
||||
}
|
||||
function lt(e) {
|
||||
var t = null, u, l;
|
||||
return l = i, u = ye(), c && J(u.name) && G({}, a.StrictVarName), e === "const" ? (O("="), t = le()) : _("=") && (N(), t = le()), n.markEnd(n.createVariableDeclarator(u, t), l);
|
||||
return l = i, u = ye(), c && J(u.name) && G({}, a.StrictVarName), e === "const" ? (O("="), t = ce()) : _("=") && (N(), t = ce()), n.markEnd(n.createVariableDeclarator(u, t), l);
|
||||
}
|
||||
function Te(e) {
|
||||
var t = [];
|
||||
@@ -3176,17 +3176,16 @@ Expecting ` + se.join(", ") + ", got '" + (this.terminals_[w] || w) + "'" : ie =
|
||||
})(Ve);
|
||||
var Pt = Ve.exports;
|
||||
const Lt = /* @__PURE__ */ Nt(Pt);
|
||||
function Rt(ce, Ne, T) {
|
||||
let L;
|
||||
function Rt(le, Ne) {
|
||||
let T;
|
||||
try {
|
||||
L = JSON.parse(T);
|
||||
T = JSON.parse(Ne);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const B = Lt.query(L, Ne);
|
||||
return { filtered: JSON.stringify(B, null, 2) };
|
||||
const L = Lt.query(T, le);
|
||||
return { filtered: JSON.stringify(L, null, 2) };
|
||||
}
|
||||
export {
|
||||
Rt as pluginHookResponseFilter
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6116,11 +6116,10 @@ Tr.__DOMHandler = Dr;
|
||||
Tr.normalizeLineEndings = Rt;
|
||||
Tr.DOMParser = _t;
|
||||
var Cu = Tr.DOMParser;
|
||||
function yu(t, u, n) {
|
||||
const s = new Cu().parseFromString(n, "text/xml");
|
||||
return { filtered: `${Qt.select(u, s)}` };
|
||||
function yu(t, u) {
|
||||
const n = new Cu().parseFromString(u, "text/xml");
|
||||
return { filtered: `${Qt.select(t, n)}` };
|
||||
}
|
||||
export {
|
||||
yu as pluginHookResponseFilter
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -9,86 +9,86 @@ var j = "(?:" + [
|
||||
">\\&",
|
||||
"<\\&",
|
||||
"[&;()|<>]"
|
||||
].join("|") + ")", D = new RegExp("^" + j + "$"), _ = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, q = "'", G = '"', U = "$", R = "", z = 4294967296;
|
||||
].join("|") + ")", D = new RegExp("^" + j + "$"), q = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, _ = "'", G = '"', U = "$", $ = "", z = 4294967296;
|
||||
for (var L = 0; L < 4; L++)
|
||||
R += (z * Math.random()).toString(16);
|
||||
var J = new RegExp("^" + R);
|
||||
$ += (z * Math.random()).toString(16);
|
||||
var J = new RegExp("^" + $);
|
||||
function X(n, s) {
|
||||
for (var t = s.lastIndex, r = [], l; l = s.exec(n); )
|
||||
r.push(l), s.lastIndex === l.index && (s.lastIndex += 1);
|
||||
return s.lastIndex = t, r;
|
||||
for (var e = s.lastIndex, t = [], c; c = s.exec(n); )
|
||||
t.push(c), s.lastIndex === c.index && (s.lastIndex += 1);
|
||||
return s.lastIndex = e, t;
|
||||
}
|
||||
function F(n, s, t) {
|
||||
var r = typeof n == "function" ? n(t) : n[t];
|
||||
return typeof r > "u" && t != "" ? r = "" : typeof r > "u" && (r = "$"), typeof r == "object" ? s + R + JSON.stringify(r) + R : s + r;
|
||||
function F(n, s, e) {
|
||||
var t = typeof n == "function" ? n(e) : n[e];
|
||||
return typeof t > "u" && e != "" ? t = "" : typeof t > "u" && (t = "$"), typeof t == "object" ? s + $ + JSON.stringify(t) + $ : s + t;
|
||||
}
|
||||
function K(n, s, t) {
|
||||
t || (t = {});
|
||||
var r = t.escape || "\\", l = "(\\" + r + `['"` + _ + `]|[^\\s'"` + _ + "])+", h = new RegExp([
|
||||
function K(n, s, e) {
|
||||
e || (e = {});
|
||||
var t = e.escape || "\\", c = "(\\" + t + `['"` + q + `]|[^\\s'"` + q + "])+", m = new RegExp([
|
||||
"(" + j + ")",
|
||||
// control chars
|
||||
"(" + l + "|" + M + "|" + Q + ")+"
|
||||
].join("|"), "g"), p = X(n, h);
|
||||
if (p.length === 0)
|
||||
"(" + c + "|" + M + "|" + Q + ")+"
|
||||
].join("|"), "g"), f = X(n, m);
|
||||
if (f.length === 0)
|
||||
return [];
|
||||
s || (s = {});
|
||||
var g = !1;
|
||||
return p.map(function(d) {
|
||||
var e = d[0];
|
||||
if (!e || g)
|
||||
var w = !1;
|
||||
return f.map(function(r) {
|
||||
var a = r[0];
|
||||
if (!a || w)
|
||||
return;
|
||||
if (D.test(e))
|
||||
return { op: e };
|
||||
var c = !1, T = !1, m = "", O = !1, a;
|
||||
function $() {
|
||||
a += 1;
|
||||
var x, f, C = e.charAt(a);
|
||||
if (C === "{") {
|
||||
if (a += 1, e.charAt(a) === "}")
|
||||
throw new Error("Bad substitution: " + e.slice(a - 2, a + 1));
|
||||
if (x = e.indexOf("}", a), x < 0)
|
||||
throw new Error("Bad substitution: " + e.slice(a));
|
||||
f = e.slice(a, x), a = x;
|
||||
} else if (/[*@#?$!_-]/.test(C))
|
||||
f = C, a += 1;
|
||||
if (D.test(a))
|
||||
return { op: a };
|
||||
var x = !1, C = !1, d = "", O = !1, i;
|
||||
function T() {
|
||||
i += 1;
|
||||
var v, p, R = a.charAt(i);
|
||||
if (R === "{") {
|
||||
if (i += 1, a.charAt(i) === "}")
|
||||
throw new Error("Bad substitution: " + a.slice(i - 2, i + 1));
|
||||
if (v = a.indexOf("}", i), v < 0)
|
||||
throw new Error("Bad substitution: " + a.slice(i));
|
||||
p = a.slice(i, v), i = v;
|
||||
} else if (/[*@#?$!_-]/.test(R))
|
||||
p = R, i += 1;
|
||||
else {
|
||||
var w = e.slice(a);
|
||||
x = w.match(/[^\w\d_]/), x ? (f = w.slice(0, x.index), a += x.index - 1) : (f = w, a = e.length);
|
||||
var g = a.slice(i);
|
||||
v = g.match(/[^\w\d_]/), v ? (p = g.slice(0, v.index), i += v.index - 1) : (p = g, i = a.length);
|
||||
}
|
||||
return F(s, "", f);
|
||||
return F(s, "", p);
|
||||
}
|
||||
for (a = 0; a < e.length; a++) {
|
||||
var u = e.charAt(a);
|
||||
if (O = O || !c && (u === "*" || u === "?"), T)
|
||||
m += u, T = !1;
|
||||
else if (c)
|
||||
u === c ? c = !1 : c == q ? m += u : u === r ? (a += 1, u = e.charAt(a), u === G || u === r || u === U ? m += u : m += r + u) : u === U ? m += $() : m += u;
|
||||
else if (u === G || u === q)
|
||||
c = u;
|
||||
for (i = 0; i < a.length; i++) {
|
||||
var u = a.charAt(i);
|
||||
if (O = O || !x && (u === "*" || u === "?"), C)
|
||||
d += u, C = !1;
|
||||
else if (x)
|
||||
u === x ? x = !1 : x == _ ? d += u : u === t ? (i += 1, u = a.charAt(i), u === G || u === t || u === U ? d += u : d += t + u) : u === U ? d += T() : d += u;
|
||||
else if (u === G || u === _)
|
||||
x = u;
|
||||
else {
|
||||
if (D.test(u))
|
||||
return { op: e };
|
||||
return { op: a };
|
||||
if (V.test(u)) {
|
||||
g = !0;
|
||||
var b = { comment: n.slice(d.index + a + 1) };
|
||||
return m.length ? [m, b] : [b];
|
||||
w = !0;
|
||||
var b = { comment: n.slice(r.index + i + 1) };
|
||||
return d.length ? [d, b] : [b];
|
||||
} else
|
||||
u === r ? T = !0 : u === U ? m += $() : m += u;
|
||||
u === t ? C = !0 : u === U ? d += T() : d += u;
|
||||
}
|
||||
}
|
||||
return O ? { op: "glob", pattern: m } : m;
|
||||
}).reduce(function(d, e) {
|
||||
return typeof e > "u" ? d : d.concat(e);
|
||||
return O ? { op: "glob", pattern: d } : d;
|
||||
}).reduce(function(r, a) {
|
||||
return typeof a > "u" ? r : r.concat(a);
|
||||
}, []);
|
||||
}
|
||||
var Y = function(s, t, r) {
|
||||
var l = K(s, t, r);
|
||||
return typeof t != "function" ? l : l.reduce(function(h, p) {
|
||||
if (typeof p == "object")
|
||||
return h.concat(p);
|
||||
var g = p.split(RegExp("(" + R + ".*?" + R + ")", "g"));
|
||||
return g.length === 1 ? h.concat(g[0]) : h.concat(g.filter(Boolean).map(function(d) {
|
||||
return J.test(d) ? JSON.parse(d.split(R)[1]) : d;
|
||||
var Y = function(s, e, t) {
|
||||
var c = K(s, e, t);
|
||||
return typeof e != "function" ? c : c.reduce(function(m, f) {
|
||||
if (typeof f == "object")
|
||||
return m.concat(f);
|
||||
var w = f.split(RegExp("(" + $ + ".*?" + $ + ")", "g"));
|
||||
return w.length === 1 ? m.concat(w[0]) : m.concat(w.filter(Boolean).map(function(r) {
|
||||
return J.test(r) ? JSON.parse(r.split($)[1]) : r;
|
||||
}));
|
||||
}, []);
|
||||
}, Z = Y;
|
||||
@@ -115,138 +115,138 @@ const ae = "curl", se = "cURL", ie = "cURL command line tool", H = ["d", "data",
|
||||
// Request method
|
||||
H
|
||||
].flatMap((n) => n);
|
||||
function oe(n, s) {
|
||||
if (!s.match(/^\s*curl /))
|
||||
function oe(n) {
|
||||
if (!n.match(/^\s*curl /))
|
||||
return null;
|
||||
const t = [], r = s.replace(/\ncurl/g, "; curl");
|
||||
let l = [];
|
||||
const p = Z(r).flatMap((e) => typeof e == "string" && e.startsWith("-") && !e.startsWith("--") && e.length > 2 ? [e.slice(0, 2), e.slice(2)] : e);
|
||||
for (const e of p) {
|
||||
if (typeof e == "string") {
|
||||
e.startsWith("$") ? l.push(e.slice(1)) : l.push(e);
|
||||
const s = [], e = n.replace(/\ncurl/g, "; curl");
|
||||
let t = [];
|
||||
const m = Z(e).flatMap((r) => typeof r == "string" && r.startsWith("-") && !r.startsWith("--") && r.length > 2 ? [r.slice(0, 2), r.slice(2)] : r);
|
||||
for (const r of m) {
|
||||
if (typeof r == "string") {
|
||||
r.startsWith("$") ? t.push(r.slice(1)) : t.push(r);
|
||||
continue;
|
||||
}
|
||||
if ("comment" in e)
|
||||
if ("comment" in r)
|
||||
continue;
|
||||
const { op: c } = e;
|
||||
if (c === ";") {
|
||||
t.push(l), l = [];
|
||||
const { op: a } = r;
|
||||
if (a === ";") {
|
||||
s.push(t), t = [];
|
||||
continue;
|
||||
}
|
||||
if (c != null && c.startsWith("$")) {
|
||||
const T = c.slice(2, c.length - 1).replace(/\\'/g, "'");
|
||||
l.push(T);
|
||||
if (a != null && a.startsWith("$")) {
|
||||
const x = a.slice(2, a.length - 1).replace(/\\'/g, "'");
|
||||
t.push(x);
|
||||
continue;
|
||||
}
|
||||
c === "glob" && l.push(e.pattern);
|
||||
a === "glob" && t.push(r.pattern);
|
||||
}
|
||||
t.push(l);
|
||||
const g = {
|
||||
s.push(t);
|
||||
const f = {
|
||||
model: "workspace",
|
||||
id: N("workspace"),
|
||||
name: "Curl Import"
|
||||
};
|
||||
return {
|
||||
resources: {
|
||||
httpRequests: t.filter((e) => e[0] === "curl").map((e) => te(e, g.id)),
|
||||
workspaces: [g]
|
||||
httpRequests: s.filter((r) => r[0] === "curl").map((r) => te(r, f.id)),
|
||||
workspaces: [f]
|
||||
}
|
||||
};
|
||||
}
|
||||
function te(n, s) {
|
||||
const t = {}, r = [];
|
||||
for (let i = 1; i < n.length; i++) {
|
||||
let o = n[i];
|
||||
if (typeof o == "string" && (o = o.trim()), typeof o == "string" && o.match(/^-{1,2}[\w-]+/)) {
|
||||
const E = o[0] === "-" && o[1] !== "-";
|
||||
let v = o.replace(/^-{1,2}/, "");
|
||||
if (!ee.includes(v))
|
||||
const e = {}, t = [];
|
||||
for (let o = 1; o < n.length; o++) {
|
||||
let l = n[o];
|
||||
if (typeof l == "string" && (l = l.trim()), typeof l == "string" && l.match(/^-{1,2}[\w-]+/)) {
|
||||
const E = l[0] === "-" && l[1] !== "-";
|
||||
let h = l.replace(/^-{1,2}/, "");
|
||||
if (!ee.includes(h))
|
||||
continue;
|
||||
let y;
|
||||
const S = n[i + 1];
|
||||
E && v.length > 1 ? (y = v.slice(1), v = v.slice(0, 1)) : typeof S == "string" && !S.startsWith("-") ? (y = S, i++) : y = !0, t[v] = t[v] || [], t[v].push(y);
|
||||
const S = n[o + 1];
|
||||
E && h.length > 1 ? (y = h.slice(1), h = h.slice(0, 1)) : typeof S == "string" && !S.startsWith("-") ? (y = S, o++) : y = !0, e[h] = e[h] || [], e[h].push(y);
|
||||
} else
|
||||
o && r.push(o);
|
||||
l && t.push(l);
|
||||
}
|
||||
let l, h;
|
||||
const p = A(t, r[0] || "", ["url"]), [g, d] = W(p, "?");
|
||||
l = (d == null ? void 0 : d.split("&").map((i) => {
|
||||
const o = W(i, "=");
|
||||
return { name: o[0] ?? "", value: o[1] ?? "", enabled: !0 };
|
||||
})) ?? [], h = g ?? p;
|
||||
const [e, c] = A(t, "", ["u", "user"]).split(/:(.*)$/), T = A(t, !1, ["digest"]), m = e ? T ? "digest" : "basic" : null, O = e ? {
|
||||
username: e.trim(),
|
||||
password: (c ?? "").trim()
|
||||
} : {}, a = [
|
||||
...t.header || [],
|
||||
...t.H || []
|
||||
].map((i) => {
|
||||
const [o, E] = i.split(/:(.*)$/);
|
||||
let c, m;
|
||||
const f = A(e, t[0] || "", ["url"]), [w, r] = W(f, "?");
|
||||
c = (r == null ? void 0 : r.split("&").map((o) => {
|
||||
const l = W(o, "=");
|
||||
return { name: l[0] ?? "", value: l[1] ?? "", enabled: !0 };
|
||||
})) ?? [], m = w ?? f;
|
||||
const [a, x] = A(e, "", ["u", "user"]).split(/:(.*)$/), C = A(e, !1, ["digest"]), d = a ? C ? "digest" : "basic" : null, O = a ? {
|
||||
username: a.trim(),
|
||||
password: (x ?? "").trim()
|
||||
} : {}, i = [
|
||||
...e.header || [],
|
||||
...e.H || []
|
||||
].map((o) => {
|
||||
const [l, E] = o.split(/:(.*)$/);
|
||||
return E ? {
|
||||
name: (o ?? "").trim(),
|
||||
name: (l ?? "").trim(),
|
||||
value: E.trim(),
|
||||
enabled: !0
|
||||
} : {
|
||||
name: (o ?? "").trim().replace(/;$/, ""),
|
||||
name: (l ?? "").trim().replace(/;$/, ""),
|
||||
value: "",
|
||||
enabled: !0
|
||||
};
|
||||
}), $ = [
|
||||
...t.cookie || [],
|
||||
...t.b || []
|
||||
].map((i) => {
|
||||
const o = i.split("=", 1)[0], E = i.replace(`${o}=`, "");
|
||||
return `${o}=${E}`;
|
||||
}).join("; "), u = a.find((i) => i.name.toLowerCase() === "cookie");
|
||||
$ && u ? u.value += `; ${$}` : $ && a.push({
|
||||
}), T = [
|
||||
...e.cookie || [],
|
||||
...e.b || []
|
||||
].map((o) => {
|
||||
const l = o.split("=", 1)[0], E = o.replace(`${l}=`, "");
|
||||
return `${l}=${E}`;
|
||||
}).join("; "), u = i.find((o) => o.name.toLowerCase() === "cookie");
|
||||
T && u ? u.value += `; ${T}` : T && i.push({
|
||||
name: "Cookie",
|
||||
value: $,
|
||||
value: T,
|
||||
enabled: !0
|
||||
});
|
||||
const b = ne(t), x = a.find((i) => i.name.toLowerCase() === "content-type"), f = x ? x.value.split(";")[0] : null, C = [
|
||||
...t.form || [],
|
||||
...t.F || []
|
||||
].map((i) => {
|
||||
const o = i.split("="), E = o[0] ?? "", v = o[1] ?? "", y = {
|
||||
const b = ne(e), v = i.find((o) => o.name.toLowerCase() === "content-type"), p = v ? v.value.split(";")[0] : null, R = [
|
||||
...e.form || [],
|
||||
...e.F || []
|
||||
].map((o) => {
|
||||
const l = o.split("="), E = l[0] ?? "", h = l[1] ?? "", y = {
|
||||
name: E,
|
||||
enabled: !0
|
||||
};
|
||||
return v.indexOf("@") === 0 ? y.file = v.slice(1) : y.value = v, y;
|
||||
return h.indexOf("@") === 0 ? y.file = h.slice(1) : y.value = h, y;
|
||||
});
|
||||
let w = {}, I = null;
|
||||
const B = A(t, !1, ["G", "get"]);
|
||||
b.length > 0 && B ? l.push(...b) : b.length > 0 && (f == null || f === "application/x-www-form-urlencoded") ? (I = f ?? "application/x-www-form-urlencoded", w = {
|
||||
form: b.map((i) => ({
|
||||
...i,
|
||||
name: decodeURIComponent(i.name || ""),
|
||||
value: decodeURIComponent(i.value || "")
|
||||
let g = {}, I = null;
|
||||
const B = A(e, !1, ["G", "get"]);
|
||||
b.length > 0 && B ? c.push(...b) : b.length > 0 && (p == null || p === "application/x-www-form-urlencoded") ? (I = p ?? "application/x-www-form-urlencoded", g = {
|
||||
form: b.map((o) => ({
|
||||
...o,
|
||||
name: decodeURIComponent(o.name || ""),
|
||||
value: decodeURIComponent(o.value || "")
|
||||
}))
|
||||
}, a.push({
|
||||
}, i.push({
|
||||
name: "Content-Type",
|
||||
value: "application/x-www-form-urlencoded",
|
||||
enabled: !0
|
||||
})) : b.length > 0 ? (I = f === "application/json" || f === "text/xml" || f === "text/plain" ? f : "other", w = {
|
||||
text: b.map(({ name: i, value: o }) => i && o ? `${i}=${o}` : i || o).join("&")
|
||||
}) : C.length && (I = f ?? "multipart/form-data", w = {
|
||||
form: C
|
||||
}, f == null && a.push({
|
||||
})) : b.length > 0 ? (I = p === "application/json" || p === "text/xml" || p === "text/plain" ? p : "other", g = {
|
||||
text: b.map(({ name: o, value: l }) => o && l ? `${o}=${l}` : o || l).join("&")
|
||||
}) : R.length && (I = p ?? "multipart/form-data", g = {
|
||||
form: R
|
||||
}, p == null && i.push({
|
||||
name: "Content-Type",
|
||||
value: "multipart/form-data",
|
||||
enabled: !0
|
||||
}));
|
||||
let P = A(t, "", ["X", "request"]).toUpperCase();
|
||||
return P === "" && w && (P = "text" in w || "form" in w ? "POST" : "GET"), {
|
||||
let P = A(e, "", ["X", "request"]).toUpperCase();
|
||||
return P === "" && g && (P = "text" in g || "form" in g ? "POST" : "GET"), {
|
||||
id: N("http_request"),
|
||||
model: "http_request",
|
||||
workspaceId: s,
|
||||
name: "",
|
||||
urlParameters: l,
|
||||
url: h,
|
||||
urlParameters: c,
|
||||
url: m,
|
||||
method: P,
|
||||
headers: a,
|
||||
headers: i,
|
||||
authentication: O,
|
||||
authenticationType: m,
|
||||
body: w,
|
||||
authenticationType: d,
|
||||
body: g,
|
||||
bodyType: I,
|
||||
folderId: null,
|
||||
sortPriority: 0
|
||||
@@ -254,35 +254,35 @@ function te(n, s) {
|
||||
}
|
||||
const ne = (n) => {
|
||||
let s = [];
|
||||
for (const t of H) {
|
||||
const r = n[t];
|
||||
if (!(!r || r.length === 0))
|
||||
for (const l of r) {
|
||||
if (typeof l != "string")
|
||||
for (const e of H) {
|
||||
const t = n[e];
|
||||
if (!(!t || t.length === 0))
|
||||
for (const c of t) {
|
||||
if (typeof c != "string")
|
||||
continue;
|
||||
const [h, p] = l.split("=");
|
||||
l.startsWith("@") ? s.push({
|
||||
name: h ?? "",
|
||||
const [m, f] = c.split("=");
|
||||
c.startsWith("@") ? s.push({
|
||||
name: m ?? "",
|
||||
value: "",
|
||||
filePath: l.slice(1),
|
||||
filePath: c.slice(1),
|
||||
enabled: !0
|
||||
}) : s.push({
|
||||
name: h ?? "",
|
||||
value: t === "data-urlencode" ? encodeURIComponent(p ?? "") : p ?? "",
|
||||
name: m ?? "",
|
||||
value: e === "data-urlencode" ? encodeURIComponent(f ?? "") : f ?? "",
|
||||
enabled: !0
|
||||
});
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}, A = (n, s, t) => {
|
||||
for (const r of t)
|
||||
if (n[r] && n[r].length)
|
||||
return n[r][0];
|
||||
}, A = (n, s, e) => {
|
||||
for (const t of e)
|
||||
if (n[t] && n[t].length)
|
||||
return n[t][0];
|
||||
return s;
|
||||
};
|
||||
function W(n, s) {
|
||||
const t = n.indexOf(s);
|
||||
return t > -1 ? [n.slice(0, t), n.slice(t + 1)] : [n];
|
||||
const e = n.indexOf(s);
|
||||
return e > -1 ? [n.slice(0, e), n.slice(e + 1)] : [n];
|
||||
}
|
||||
const k = {};
|
||||
function N(n) {
|
||||
@@ -295,4 +295,3 @@ export {
|
||||
se as name,
|
||||
oe as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,73 +1,72 @@
|
||||
const _ = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", C = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", O = [C, _];
|
||||
function v(e, t) {
|
||||
var w;
|
||||
console.log("CTX", e);
|
||||
const a = q(t);
|
||||
if (a == null)
|
||||
const S = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", _ = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", O = [_, S];
|
||||
function v(e) {
|
||||
var g;
|
||||
const t = k(e);
|
||||
if (t == null)
|
||||
return;
|
||||
const s = i(a.info);
|
||||
if (!O.includes(s.schema) || !Array.isArray(a.item))
|
||||
const o = i(t.info);
|
||||
if (!O.includes(o.schema) || !Array.isArray(t.item))
|
||||
return;
|
||||
const u = k(a.auth), n = {
|
||||
const u = A(t.auth), s = {
|
||||
workspaces: [],
|
||||
environments: [],
|
||||
httpRequests: [],
|
||||
folders: []
|
||||
}, p = {
|
||||
}, n = {
|
||||
model: "workspace",
|
||||
id: b("workspace"),
|
||||
name: s.name || "Postman Import",
|
||||
description: s.description || "",
|
||||
variables: ((w = a.variable) == null ? void 0 : w.map((r) => ({
|
||||
id: h("workspace"),
|
||||
name: o.name || "Postman Import",
|
||||
description: o.description || "",
|
||||
variables: ((g = t.variable) == null ? void 0 : g.map((r) => ({
|
||||
name: r.key,
|
||||
value: r.value
|
||||
}))) ?? []
|
||||
};
|
||||
n.workspaces.push(p);
|
||||
const g = (r, d = null) => {
|
||||
s.workspaces.push(n);
|
||||
const T = (r, p = null) => {
|
||||
if (typeof r.name == "string" && Array.isArray(r.item)) {
|
||||
const o = {
|
||||
const a = {
|
||||
model: "folder",
|
||||
workspaceId: p.id,
|
||||
id: b("folder"),
|
||||
workspaceId: n.id,
|
||||
id: h("folder"),
|
||||
name: r.name,
|
||||
folderId: d
|
||||
folderId: p
|
||||
};
|
||||
n.folders.push(o);
|
||||
s.folders.push(a);
|
||||
for (const l of r.item)
|
||||
g(l, o.id);
|
||||
T(l, a.id);
|
||||
} else if (typeof r.name == "string" && "request" in r) {
|
||||
const o = i(r.request), l = j(o.body), A = k(o.auth), m = A.authenticationType == null ? u : A, S = {
|
||||
const a = i(r.request), l = j(a.body), w = A(a.auth), d = w.authenticationType == null ? u : w, q = {
|
||||
model: "http_request",
|
||||
id: b("http_request"),
|
||||
workspaceId: p.id,
|
||||
folderId: d,
|
||||
id: h("http_request"),
|
||||
workspaceId: n.id,
|
||||
folderId: p,
|
||||
name: r.name,
|
||||
method: o.method || "GET",
|
||||
url: typeof o.url == "string" ? o.url : i(o.url).raw,
|
||||
method: a.method || "GET",
|
||||
url: typeof a.url == "string" ? a.url : i(a.url).raw,
|
||||
body: l.body,
|
||||
bodyType: l.bodyType,
|
||||
authentication: m.authentication,
|
||||
authenticationType: m.authenticationType,
|
||||
authentication: d.authentication,
|
||||
authenticationType: d.authenticationType,
|
||||
headers: [
|
||||
...l.headers,
|
||||
...m.headers,
|
||||
...f(o.header).map((y) => ({
|
||||
name: y.key,
|
||||
value: y.value,
|
||||
enabled: !y.disabled
|
||||
...d.headers,
|
||||
...b(a.header).map((m) => ({
|
||||
name: m.key,
|
||||
value: m.value,
|
||||
enabled: !m.disabled
|
||||
}))
|
||||
]
|
||||
};
|
||||
n.httpRequests.push(S);
|
||||
s.httpRequests.push(q);
|
||||
} else
|
||||
console.log("Unknown item", r, d);
|
||||
console.log("Unknown item", r, p);
|
||||
};
|
||||
for (const r of a.item)
|
||||
g(r);
|
||||
return { resources: T(n) };
|
||||
for (const r of t.item)
|
||||
T(r);
|
||||
return { resources: f(s) };
|
||||
}
|
||||
function k(e) {
|
||||
function A(e) {
|
||||
const t = i(e);
|
||||
return "basic" in t ? {
|
||||
headers: [],
|
||||
@@ -85,7 +84,7 @@ function k(e) {
|
||||
} : { headers: [], authenticationType: null, authentication: {} };
|
||||
}
|
||||
function j(e) {
|
||||
var a, s, c, u;
|
||||
var o, c, u, s;
|
||||
const t = i(e);
|
||||
return "graphql" in t ? {
|
||||
headers: [
|
||||
@@ -98,7 +97,7 @@ function j(e) {
|
||||
bodyType: "graphql",
|
||||
body: {
|
||||
text: JSON.stringify(
|
||||
{ query: t.graphql.query, variables: q(t.graphql.variables) },
|
||||
{ query: t.graphql.query, variables: k(t.graphql.variables) },
|
||||
null,
|
||||
2
|
||||
)
|
||||
@@ -113,7 +112,7 @@ function j(e) {
|
||||
],
|
||||
bodyType: "application/x-www-form-urlencoded",
|
||||
body: {
|
||||
form: f(t.urlencoded).map((n) => ({
|
||||
form: b(t.urlencoded).map((n) => ({
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
value: n.value ?? ""
|
||||
@@ -129,7 +128,7 @@ function j(e) {
|
||||
],
|
||||
bodyType: "multipart/form-data",
|
||||
body: {
|
||||
form: f(t.formdata).map(
|
||||
form: b(t.formdata).map(
|
||||
(n) => n.src != null ? {
|
||||
enabled: !n.disabled,
|
||||
contentType: n.contentType ?? null,
|
||||
@@ -146,17 +145,17 @@ function j(e) {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: ((s = (a = t.options) == null ? void 0 : a.raw) == null ? void 0 : s.language) === "json" ? "application/json" : "",
|
||||
value: ((c = (o = t.options) == null ? void 0 : o.raw) == null ? void 0 : c.language) === "json" ? "application/json" : "",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: ((u = (c = t.options) == null ? void 0 : c.raw) == null ? void 0 : u.language) === "json" ? "application/json" : "other",
|
||||
bodyType: ((s = (u = t.options) == null ? void 0 : u.raw) == null ? void 0 : s.language) === "json" ? "application/json" : "other",
|
||||
body: {
|
||||
text: t.raw ?? ""
|
||||
}
|
||||
} : { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
function q(e) {
|
||||
function k(e) {
|
||||
try {
|
||||
return i(JSON.parse(e));
|
||||
} catch {
|
||||
@@ -166,19 +165,18 @@ function q(e) {
|
||||
function i(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Object]" ? e : {};
|
||||
}
|
||||
function f(e) {
|
||||
function b(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Array]" ? e : [];
|
||||
}
|
||||
function T(e) {
|
||||
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(T) : typeof e == "object" && e != null ? Object.fromEntries(
|
||||
Object.entries(e).map(([t, a]) => [t, T(a)])
|
||||
function f(e) {
|
||||
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(f) : typeof e == "object" && e != null ? Object.fromEntries(
|
||||
Object.entries(e).map(([t, o]) => [t, f(o)])
|
||||
) : e;
|
||||
}
|
||||
const h = {};
|
||||
function b(e) {
|
||||
return h[e] = (h[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${h[e]}`;
|
||||
const y = {};
|
||||
function h(e) {
|
||||
return y[e] = (y[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${y[e]}`;
|
||||
}
|
||||
export {
|
||||
v as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,18 +1,17 @@
|
||||
function u(r, t) {
|
||||
function u(r) {
|
||||
let e;
|
||||
try {
|
||||
e = JSON.parse(t);
|
||||
e = JSON.parse(r);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!(!s(e) || !("yaakSchema" in e)))
|
||||
if (!(!t(e) || !("yaakSchema" in e)))
|
||||
return "requests" in e.resources && (e.resources.httpRequests = e.resources.requests, delete e.resources.requests), { resources: e.resources };
|
||||
}
|
||||
function s(r) {
|
||||
function t(r) {
|
||||
return Object.prototype.toString.call(r) === "[object Object]";
|
||||
}
|
||||
export {
|
||||
s as isJSObject,
|
||||
t as isJSObject,
|
||||
u as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../plugins/importer-yaak/src/index.ts"],"sourcesContent":["export function pluginHookImport(ctx: any, contents: string) {\n let parsed;\n try {\n parsed = JSON.parse(contents);\n } catch (err) {\n return undefined;\n }\n\n if (!isJSObject(parsed)) {\n return undefined;\n }\n\n const isYaakExport = 'yaakSchema' in parsed;\n if (!isYaakExport) {\n return;\n }\n\n // Migrate v1 to v2 -- changes requests to httpRequests\n if ('requests' in parsed.resources) {\n parsed.resources.httpRequests = parsed.resources.requests;\n delete parsed.resources['requests'];\n }\n\n return { resources: parsed.resources }; // Should already be in the correct format\n}\n\nexport function isJSObject(obj: any) {\n return Object.prototype.toString.call(obj) === '[object Object]';\n}\n"],"names":["pluginHookImport","ctx","contents","parsed","isJSObject","obj"],"mappings":"AAAgB,SAAAA,EAAiBC,GAAUC,GAAkB;AACvD,MAAAC;AACA,MAAA;AACO,IAAAA,IAAA,KAAK,MAAMD,CAAQ;AAAA,UAChB;AACL;AAAA,EACT;AAOA,MALI,GAACE,EAAWD,CAAM,KAKlB,EADiB,gBAAgBA;AAMjC,WAAA,cAAcA,EAAO,cAChBA,EAAA,UAAU,eAAeA,EAAO,UAAU,UAC1C,OAAAA,EAAO,UAAU,WAGnB,EAAE,WAAWA,EAAO;AAC7B;AAEO,SAASC,EAAWC,GAAU;AACnC,SAAO,OAAO,UAAU,SAAS,KAAKA,CAAG,MAAM;AACjD;"}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use log::{debug, info};
|
||||
use log::{debug, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::types::JsonValue;
|
||||
@@ -194,7 +194,10 @@ pub async fn track_event(
|
||||
}
|
||||
|
||||
if let Err(e) = req.send().await {
|
||||
info!("Error sending analytics event: {}", e);
|
||||
warn!(
|
||||
"Error sending analytics event: {} {} {} {:?}",
|
||||
e, event, attributes_json, params,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows how to use swc to transpile TypeScript and JSX/TSX
|
||||
//! modules.
|
||||
//!
|
||||
//! It will only transpile, not typecheck (like Deno's `--no-check` flag).
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
use deno_ast::ParseParams;
|
||||
use deno_ast::{EmitOptions, MediaType, SourceMapOption, TranspileOptions};
|
||||
use deno_core::error::{AnyError, JsError};
|
||||
use deno_core::resolve_path;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::ModuleLoadResponse;
|
||||
use deno_core::ModuleLoader;
|
||||
use deno_core::ModuleSource;
|
||||
use deno_core::ModuleSourceCode;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::ModuleType;
|
||||
use deno_core::RequestedModuleType;
|
||||
use deno_core::ResolutionKind;
|
||||
use deno_core::RuntimeOptions;
|
||||
use deno_core::SourceMapGetter;
|
||||
use deno_core::{resolve_import, v8};
|
||||
use tokio::task::block_in_place;
|
||||
|
||||
use crate::deno_ops::op_yaml_parse;
|
||||
use crate::plugin::PluginCapability;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SourceMapStore(Rc<RefCell<HashMap<String, Vec<u8>>>>);
|
||||
|
||||
impl SourceMapGetter for SourceMapStore {
|
||||
fn get_source_map(&self, specifier: &str) -> Option<Vec<u8>> {
|
||||
self.0.borrow().get(specifier).cloned()
|
||||
}
|
||||
|
||||
fn get_source_line(&self, _file_name: &str, _line_number: usize) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct TypescriptModuleLoader {
|
||||
source_maps: SourceMapStore,
|
||||
}
|
||||
|
||||
impl ModuleLoader for TypescriptModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
_requested_module_type: RequestedModuleType,
|
||||
) -> ModuleLoadResponse {
|
||||
let source_maps = self.source_maps.clone();
|
||||
fn load(
|
||||
source_maps: SourceMapStore,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleSource, AnyError> {
|
||||
let path = module_specifier
|
||||
.to_file_path()
|
||||
.map_err(|_| anyhow!("Only file:// URLs are supported."))?;
|
||||
|
||||
let media_type = MediaType::from_path(&path);
|
||||
let (module_type, should_transpile) = match MediaType::from_path(&path) {
|
||||
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
|
||||
(ModuleType::JavaScript, false)
|
||||
}
|
||||
MediaType::Jsx => (ModuleType::JavaScript, true),
|
||||
MediaType::TypeScript
|
||||
| MediaType::Mts
|
||||
| MediaType::Cts
|
||||
| MediaType::Dts
|
||||
| MediaType::Dmts
|
||||
| MediaType::Dcts
|
||||
| MediaType::Tsx => (ModuleType::JavaScript, true),
|
||||
MediaType::Json => (ModuleType::Json, false),
|
||||
_ => bail!("Unknown extension {:?}", path.extension()),
|
||||
};
|
||||
|
||||
let code = std::fs::read_to_string(&path)?;
|
||||
let code = if should_transpile {
|
||||
let parsed = deno_ast::parse_module(ParseParams {
|
||||
specifier: module_specifier.clone(),
|
||||
text: Arc::from(code),
|
||||
media_type,
|
||||
capture_tokens: false,
|
||||
scope_analysis: false,
|
||||
maybe_syntax: None,
|
||||
})?;
|
||||
let res = parsed.transpile(
|
||||
&TranspileOptions::default(),
|
||||
&EmitOptions {
|
||||
source_map: SourceMapOption::Separate,
|
||||
inline_sources: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
let src = res.into_source();
|
||||
let source_map = src.source_map.unwrap();
|
||||
let source = src.source;
|
||||
source_maps
|
||||
.0
|
||||
.borrow_mut()
|
||||
.insert(module_specifier.to_string(), source_map);
|
||||
String::from_utf8(source).unwrap()
|
||||
} else {
|
||||
code
|
||||
};
|
||||
|
||||
Ok(ModuleSource::new(
|
||||
module_type,
|
||||
ModuleSourceCode::String(code.into()),
|
||||
module_specifier,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
ModuleLoadResponse::Sync(load(source_maps, module_specifier))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_plugin_block(
|
||||
plugin_index_file: &str,
|
||||
fn_name: &str,
|
||||
fn_args: Vec<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
block_in_place(|| {
|
||||
tauri::async_runtime::block_on(run_plugin(plugin_index_file, fn_name, fn_args))
|
||||
})
|
||||
}
|
||||
|
||||
deno_core::extension!(
|
||||
yaak_runtime,
|
||||
ops = [ op_yaml_parse ],
|
||||
esm_entry_point = "ext:yaak_runtime/yaml.js",
|
||||
esm = [dir "src/plugin-runtime", "yaml.js"]
|
||||
);
|
||||
|
||||
async fn run_plugin(
|
||||
plugin_index_file: &str,
|
||||
fn_name: &str,
|
||||
fn_args: Vec<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let mut js_runtime = load_js_runtime()?;
|
||||
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
// Get the exported function we're calling
|
||||
let func_key = v8::String::new(scope, fn_name).unwrap();
|
||||
let func = module_namespace.get(scope, func_key.into()).unwrap();
|
||||
let func = v8::Local::<v8::Function>::try_from(func).unwrap();
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
|
||||
// Create Yaak context object
|
||||
let null = v8::null(tc_scope).into();
|
||||
let name = v8::String::new(tc_scope, "foo").unwrap().into();
|
||||
let value = v8::String::new(tc_scope, "bar").unwrap().into();
|
||||
let yaak_ctx: v8::Local<v8::Value> =
|
||||
v8::Object::with_prototype_and_properties(tc_scope, null, &[name], &[value]).into();
|
||||
|
||||
// Create the function arguments
|
||||
let passed_args = &mut fn_args
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let v: v8::Local<v8::Value> = deno_core::serde_v8::to_v8(tc_scope, a).unwrap();
|
||||
v
|
||||
})
|
||||
.collect::<Vec<v8::Local<v8::Value>>>();
|
||||
|
||||
let all_args = &mut vec![yaak_ctx];
|
||||
all_args.append(passed_args);
|
||||
|
||||
// Call the function
|
||||
let func_res = func.call(tc_scope, module_namespace.into(), all_args);
|
||||
|
||||
// Catch and return any thrown errors
|
||||
if tc_scope.has_caught() {
|
||||
let e = tc_scope.exception().unwrap();
|
||||
let js_error = JsError::from_v8_exception(tc_scope, e);
|
||||
return Err(Error::msg(js_error.stack.unwrap_or_default()));
|
||||
}
|
||||
|
||||
// Handle the result
|
||||
match func_res {
|
||||
None => Ok(serde_json::Value::Null),
|
||||
Some(res) => {
|
||||
if res.is_null() || res.is_undefined() {
|
||||
Ok(serde_json::Value::Null)
|
||||
} else {
|
||||
let value: serde_json::Value = deno_core::serde_v8::from_v8(tc_scope, res).unwrap();
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_plugin_capabilities_block(plugin_index_file: &str) -> Result<Vec<PluginCapability>, Error> {
|
||||
block_in_place(|| tauri::async_runtime::block_on(get_plugin_capabilities(plugin_index_file)))
|
||||
}
|
||||
|
||||
pub async fn get_plugin_capabilities(
|
||||
plugin_index_file: &str,
|
||||
) -> Result<Vec<PluginCapability>, Error> {
|
||||
let mut js_runtime = load_js_runtime()?;
|
||||
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
let property_names =
|
||||
match module_namespace.get_own_property_names(scope, v8::GetPropertyNamesArgs::default()) {
|
||||
None => return Ok(Vec::new()),
|
||||
Some(names) => names,
|
||||
};
|
||||
|
||||
let mut capabilities: Vec<PluginCapability> = Vec::new();
|
||||
for i in 0..property_names.length() {
|
||||
let name = property_names.get_index(scope, i);
|
||||
let name = match name {
|
||||
Some(name) => name,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
match name.to_rust_string_lossy(scope).as_str() {
|
||||
"pluginHookImport" => _ = capabilities.push(PluginCapability::Import),
|
||||
"pluginHookExport" => _ = capabilities.push(PluginCapability::Export),
|
||||
"pluginHookResponseFilter" => _ = capabilities.push(PluginCapability::Filter),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(capabilities)
|
||||
}
|
||||
|
||||
async fn load_main_module(
|
||||
js_runtime: &mut JsRuntime,
|
||||
plugin_index_file: &str,
|
||||
) -> Result<v8::Global<v8::Object>, Error> {
|
||||
let main_module = resolve_path(
|
||||
plugin_index_file,
|
||||
&std::env::current_dir().context("Unable to get CWD")?,
|
||||
)?;
|
||||
|
||||
// Load the main module so we can do stuff with it
|
||||
let mod_id = js_runtime.load_main_es_module(&main_module).await?;
|
||||
let result = js_runtime.mod_evaluate(mod_id);
|
||||
js_runtime.run_event_loop(Default::default()).await?;
|
||||
result.await?;
|
||||
|
||||
let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
|
||||
|
||||
Ok(module_namespace)
|
||||
}
|
||||
|
||||
fn load_js_runtime<'s>() -> Result<JsRuntime, Error> {
|
||||
let source_map_store = SourceMapStore(Rc::new(RefCell::new(HashMap::new())));
|
||||
|
||||
let mut ext_console = deno_console::deno_console::init_ops_and_esm();
|
||||
ext_console.esm_entry_point = Some("ext:deno_console/01_console.js");
|
||||
|
||||
let ext_yaak = yaak_runtime::init_ops_and_esm();
|
||||
|
||||
let js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(TypescriptModuleLoader {
|
||||
source_maps: source_map_store.clone(),
|
||||
})),
|
||||
source_map_getter: Some(Rc::new(source_map_store)),
|
||||
extensions: vec![ext_console, ext_yaak],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// let main_module = resolve_path(
|
||||
// plugin_index_file.to_str().unwrap(),
|
||||
// &std::env::current_dir().context("Unable to get CWD")?,
|
||||
// )?;
|
||||
//
|
||||
// // Load the main module so we can do stuff with it
|
||||
// let mod_id = js_runtime.load_main_es_module(&main_module).await?;
|
||||
// let result = js_runtime.mod_evaluate(mod_id);
|
||||
// js_runtime.run_event_loop(Default::default()).await?;
|
||||
// result.await?;
|
||||
//
|
||||
// let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
|
||||
// let scope = &mut js_runtime.handle_scope();
|
||||
// let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
Ok(js_runtime)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::op2;
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_yaml_parse(#[string] text: String) -> Result<serde_json::Value, AnyError> {
|
||||
let value = serde_yaml::from_str(&text)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub fn op_yaml_stringify(#[serde] value: serde_json::Value) -> Result<String, AnyError> {
|
||||
let value = serde_yaml::to_string(&value)?;
|
||||
Ok(value)
|
||||
}
|
||||
@@ -8,17 +8,15 @@ use std::time::Duration;
|
||||
|
||||
use base64::Engine;
|
||||
use http::header::{ACCEPT, USER_AGENT};
|
||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
||||
use log::{error, info, warn};
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::Method;
|
||||
use reqwest::{multipart, Url};
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
use tauri::{Manager, WebviewWindow};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::watch::Receiver;
|
||||
|
||||
use crate::render::variables_from_environment;
|
||||
use crate::{models, render, response_err};
|
||||
|
||||
pub async fn send_http_request(
|
||||
@@ -34,9 +32,8 @@ pub async fn send_http_request(
|
||||
let workspace = models::get_workspace(window, &request.workspace_id)
|
||||
.await
|
||||
.expect("Failed to get Workspace");
|
||||
let vars = variables_from_environment(&workspace, environment_ref);
|
||||
|
||||
let mut url_string = render::render(&request.url, &vars);
|
||||
let mut url_string = render::render(&request.url, &workspace, environment.as_ref());
|
||||
|
||||
url_string = ensure_proto(&url_string);
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
@@ -48,7 +45,6 @@ pub async fn send_http_request(
|
||||
true => Policy::limited(10), // TODO: Handle redirects natively
|
||||
false => Policy::none(),
|
||||
})
|
||||
.connection_verbose(true)
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
@@ -110,7 +106,7 @@ pub async fn send_http_request(
|
||||
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
|
||||
window,
|
||||
)
|
||||
.await;
|
||||
.await;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,8 +142,8 @@ pub async fn send_http_request(
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = render::render(&h.name, &vars);
|
||||
let value = render::render(&h.value, &vars);
|
||||
let name = render::render(&h.name, &workspace, environment_ref);
|
||||
let value = render::render(&h.value, &workspace, environment_ref);
|
||||
|
||||
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
|
||||
Ok(n) => n,
|
||||
@@ -182,8 +178,8 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &vars);
|
||||
let password = render::render(raw_password, &vars);
|
||||
let username = render::render(raw_username, &workspace, environment_ref);
|
||||
let password = render::render(raw_password, &workspace, environment_ref);
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
@@ -193,7 +189,7 @@ pub async fn send_http_request(
|
||||
);
|
||||
} else if b == "bearer" {
|
||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||
let token = render::render(raw_token, &vars);
|
||||
let token = render::render(raw_token, &workspace, environment_ref);
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
||||
@@ -207,8 +203,8 @@ pub async fn send_http_request(
|
||||
continue;
|
||||
}
|
||||
query_params.push((
|
||||
render::render(&p.name, &vars),
|
||||
render::render(&p.value, &vars),
|
||||
render::render(&p.name, &workspace, environment_ref),
|
||||
render::render(&p.value, &workspace, environment_ref),
|
||||
));
|
||||
}
|
||||
request_builder = request_builder.query(&query_params);
|
||||
@@ -224,7 +220,7 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let body = render::render(raw_text, &vars);
|
||||
let body = render::render(raw_text, &workspace, environment_ref);
|
||||
request_builder = request_builder.body(body);
|
||||
} else if body_type == "application/x-www-form-urlencoded"
|
||||
&& request_body.contains_key("form")
|
||||
@@ -251,7 +247,10 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
form_params.push((render::render(name, &vars), render::render(value, &vars)));
|
||||
form_params.push((
|
||||
render::render(name, &workspace, environment_ref),
|
||||
render::render(value, &workspace, environment_ref),
|
||||
));
|
||||
}
|
||||
}
|
||||
request_builder = request_builder.form(&form_params);
|
||||
@@ -300,9 +299,13 @@ pub async fn send_http_request(
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
let name = render::render(name_raw, &vars);
|
||||
let name = render::render(name_raw, &workspace, environment_ref);
|
||||
let mut part = if file_path.is_empty() {
|
||||
multipart::Part::text(render::render(value_raw, &vars))
|
||||
multipart::Part::text(render::render(
|
||||
value_raw,
|
||||
&workspace,
|
||||
environment_ref,
|
||||
))
|
||||
} else {
|
||||
match fs::read(file_path) {
|
||||
Ok(f) => multipart::Part::bytes(f),
|
||||
@@ -319,7 +322,7 @@ pub async fn send_http_request(
|
||||
.unwrap_or_default();
|
||||
|
||||
if !ct_raw.is_empty() {
|
||||
let content_type = render::render(ct_raw, &vars);
|
||||
let content_type = render::render(ct_raw, &workspace, environment_ref);
|
||||
part = part
|
||||
.mime_str(content_type.as_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
@@ -389,11 +392,11 @@ pub async fn send_http_request(
|
||||
response.url = v.url().to_string();
|
||||
response.remote_addr = v.remote_addr().map(|a| a.to_string());
|
||||
response.version = match v.version() {
|
||||
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
http::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
http::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
http::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
http::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
http::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,64 +6,61 @@ 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::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, LogicalSize, 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 ::grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
|
||||
|
||||
use crate::analytics::{AnalyticsAction, AnalyticsResource};
|
||||
use crate::grpc::metadata_to_map;
|
||||
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,
|
||||
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::{
|
||||
find_plugins, get_plugin, ImportResult, PluginCapability,
|
||||
run_plugin_export_curl, run_plugin_filter, run_plugin_import,
|
||||
};
|
||||
use crate::render::{render_request, variables_from_environment};
|
||||
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 deno;
|
||||
mod deno_ops;
|
||||
mod grpc;
|
||||
mod http_request;
|
||||
mod models;
|
||||
@@ -72,6 +69,8 @@ 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_menu;
|
||||
|
||||
@@ -121,6 +120,7 @@ async fn cmd_dismiss_notification(
|
||||
notification_id: &str,
|
||||
yaak_notifier: State<'_, Mutex<YaakNotifier>>,
|
||||
) -> Result<(), String> {
|
||||
info!("SEEN? {notification_id}");
|
||||
yaak_notifier.lock().await.seen(&app, notification_id).await
|
||||
}
|
||||
|
||||
@@ -134,14 +134,14 @@ async fn cmd_grpc_reflect(
|
||||
let req = get_grpc_request(&window, request_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let uri = safe_uri(req.url.as_str());
|
||||
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
||||
if proto_files.len() > 0 {
|
||||
grpc_handle
|
||||
.lock()
|
||||
.await
|
||||
.services_from_files(
|
||||
&req.id,
|
||||
uri.as_str(),
|
||||
&uri,
|
||||
proto_files
|
||||
.iter()
|
||||
.map(|p| PathBuf::from_str(p).unwrap())
|
||||
@@ -152,7 +152,7 @@ async fn cmd_grpc_reflect(
|
||||
grpc_handle
|
||||
.lock()
|
||||
.await
|
||||
.services_from_reflection(&req.id, uri.as_str())
|
||||
.services_from_reflection(&req.id, &uri)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,6 @@ async fn cmd_grpc_go(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut metadata = HashMap::new();
|
||||
let vars = variables_from_environment(&workspace, environment.as_ref());
|
||||
|
||||
// Add rest of metadata
|
||||
for h in req.clone().metadata.0 {
|
||||
@@ -188,14 +187,15 @@ async fn cmd_grpc_go(
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = render::render(&h.name, &vars);
|
||||
let value = render::render(&h.value, &vars);
|
||||
let name = render::render(&h.name, &workspace, environment.as_ref());
|
||||
let value = render::render(&h.value, &workspace, environment.as_ref());
|
||||
|
||||
metadata.insert(name, value);
|
||||
}
|
||||
|
||||
if let Some(b) = &req.authentication_type {
|
||||
let req = req.clone();
|
||||
let environment_ref = environment.as_ref();
|
||||
let empty_value = &serde_json::to_value("").unwrap();
|
||||
let a = req.authentication.0;
|
||||
|
||||
@@ -210,15 +210,15 @@ async fn cmd_grpc_go(
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &vars);
|
||||
let password = render::render(raw_password, &vars);
|
||||
let username = render::render(raw_username, &workspace, environment_ref);
|
||||
let password = render::render(raw_password, &workspace, environment_ref);
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
|
||||
} else if b == "bearer" {
|
||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||
let token = render::render(raw_token, &vars);
|
||||
let token = render::render(raw_token, &workspace, environment_ref);
|
||||
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,7 @@ async fn cmd_grpc_go(
|
||||
let maybe_in_msg_tx = std::sync::Mutex::new(Some(in_msg_tx.clone()));
|
||||
let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false);
|
||||
|
||||
let uri = safe_uri(&req.url);
|
||||
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
||||
|
||||
let in_msg_stream = tokio_stream::wrappers::ReceiverStream::new(in_msg_rx);
|
||||
|
||||
@@ -269,7 +269,7 @@ async fn cmd_grpc_go(
|
||||
.await
|
||||
.connect(
|
||||
&req.clone().id,
|
||||
uri.as_str(),
|
||||
uri,
|
||||
proto_files
|
||||
.iter()
|
||||
.map(|p| PathBuf::from_str(p).unwrap())
|
||||
@@ -290,10 +290,11 @@ async fn cmd_grpc_go(
|
||||
|
||||
let cb = {
|
||||
let cancelled_rx = cancelled_rx.clone();
|
||||
let environment = environment.clone();
|
||||
let workspace = workspace.clone();
|
||||
let w = w.clone();
|
||||
let base_msg = base_msg.clone();
|
||||
let method_desc = method_desc.clone();
|
||||
let vars = vars.clone();
|
||||
|
||||
move |ev: tauri::Event| {
|
||||
if *cancelled_rx.borrow() {
|
||||
@@ -316,8 +317,9 @@ async fn cmd_grpc_go(
|
||||
Ok(IncomingMsg::Message(raw_msg)) => {
|
||||
let w = w.clone();
|
||||
let base_msg = base_msg.clone();
|
||||
let environment_ref = environment.as_ref();
|
||||
let method_desc = method_desc.clone();
|
||||
let msg = render::render(raw_msg.as_str(), &vars);
|
||||
let msg = render::render(raw_msg.as_str(), &workspace, environment_ref);
|
||||
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
|
||||
{
|
||||
Ok(d_msg) => d_msg,
|
||||
@@ -369,13 +371,14 @@ async fn cmd_grpc_go(
|
||||
let w = w.clone();
|
||||
let base_event = base_msg.clone();
|
||||
let req = req.clone();
|
||||
let vars = vars.clone();
|
||||
let workspace = workspace.clone();
|
||||
let environment = environment.clone();
|
||||
let raw_msg = if req.message.is_empty() {
|
||||
"{}".to_string()
|
||||
} else {
|
||||
req.message
|
||||
};
|
||||
let msg = render::render(&raw_msg, &vars);
|
||||
let msg = render::render(&raw_msg, &workspace, environment.as_ref());
|
||||
|
||||
upsert_grpc_event(
|
||||
&w,
|
||||
@@ -735,11 +738,7 @@ async fn cmd_filter_response(
|
||||
};
|
||||
|
||||
let body = read_to_string(response.body_path.unwrap()).unwrap();
|
||||
let plugin = match get_plugin(&w.app_handle(), plugin_name).map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to get plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
let filter_result = run_plugin_filter(&plugin, filter, &body)
|
||||
let filter_result = plugin::run_plugin_filter(&w.app_handle(), plugin_name, filter, &body)
|
||||
.await
|
||||
.expect("Failed to run filter");
|
||||
Ok(filter_result.filtered)
|
||||
@@ -752,23 +751,26 @@ async fn cmd_import_data(
|
||||
_workspace_id: &str,
|
||||
) -> Result<WorkspaceExportResources, String> {
|
||||
let mut result: Option<ImportResult> = None;
|
||||
let file =
|
||||
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let plugins = vec![
|
||||
"importer-postman",
|
||||
"importer-insomnia",
|
||||
"importer-yaak",
|
||||
"importer-curl",
|
||||
];
|
||||
let file = read_to_string(file_path)
|
||||
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let plugins = find_plugins(w.app_handle(), &PluginCapability::Import)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
for plugin in plugins {
|
||||
let v = run_plugin_import(&plugin, file_contents)
|
||||
for plugin_name in plugins {
|
||||
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if let Some(r) = v {
|
||||
info!("Imported data using {}", plugin.name);
|
||||
info!("Imported data using {}", plugin_name);
|
||||
analytics::track_event(
|
||||
&w.app_handle(),
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::Import,
|
||||
Some(json!({ "plugin": plugin.name })),
|
||||
Some(json!({ "plugin": plugin_name })),
|
||||
)
|
||||
.await;
|
||||
result = Some(r);
|
||||
@@ -808,15 +810,13 @@ async fn cmd_import_data(
|
||||
}
|
||||
};
|
||||
|
||||
info!("Importing resources");
|
||||
for mut v in r.resources.workspaces {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
|
||||
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.workspaces.push(x.clone());
|
||||
info!("Imported workspace: {}", x.name);
|
||||
}
|
||||
info!(
|
||||
"Imported {} workspaces",
|
||||
imported_resources.workspaces.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.environments {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
|
||||
@@ -827,11 +827,8 @@ async fn cmd_import_data(
|
||||
);
|
||||
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.environments.push(x.clone());
|
||||
info!("Imported environment: {}", x.name);
|
||||
}
|
||||
info!(
|
||||
"Imported {} environments",
|
||||
imported_resources.environments.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.folders {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
|
||||
@@ -843,8 +840,8 @@ async fn cmd_import_data(
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.folders.push(x.clone());
|
||||
info!("Imported folder: {}", x.name);
|
||||
}
|
||||
info!("Imported {} folders", imported_resources.folders.len());
|
||||
|
||||
for mut v in r.resources.http_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
|
||||
@@ -858,11 +855,8 @@ async fn cmd_import_data(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
imported_resources.http_requests.push(x.clone());
|
||||
info!("Imported request: {}", x.name);
|
||||
}
|
||||
info!(
|
||||
"Imported {} http_requests",
|
||||
imported_resources.http_requests.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.grpc_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
|
||||
@@ -876,11 +870,8 @@ async fn cmd_import_data(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
imported_resources.grpc_requests.push(x.clone());
|
||||
info!("Imported request: {}", x.name);
|
||||
}
|
||||
info!(
|
||||
"Imported {} grpc_requests",
|
||||
imported_resources.grpc_requests.len()
|
||||
);
|
||||
|
||||
Ok(imported_resources)
|
||||
}
|
||||
@@ -909,16 +900,13 @@ async fn cmd_request_to_curl(
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_curl_to_request(
|
||||
app_handle: AppHandle,
|
||||
app: AppHandle,
|
||||
command: &str,
|
||||
workspace_id: &str,
|
||||
) -> Result<HttpRequest, String> {
|
||||
let plugin = match get_plugin(&app_handle, "importer-curl").map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to find plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let v = run_plugin_import(&plugin, command).await;
|
||||
let v = run_plugin_import(&app, "importer-curl", command)
|
||||
.await
|
||||
.map_err(|e| e.to_string());
|
||||
match v {
|
||||
Ok(Some(r)) => r
|
||||
.resources
|
||||
@@ -966,28 +954,6 @@ async fn cmd_export_data(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_save_response(
|
||||
window: WebviewWindow,
|
||||
response_id: &str,
|
||||
filepath: &str,
|
||||
) -> Result<(), String> {
|
||||
let response = get_http_response(&window, response_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let body_path = match response.body_path {
|
||||
None => {
|
||||
return Err("Response does not have a body".to_string());
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
fs::copy(body_path, filepath).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_send_http_request(
|
||||
window: WebviewWindow,
|
||||
@@ -1068,7 +1034,6 @@ async fn response_err(
|
||||
error: String,
|
||||
w: &WebviewWindow,
|
||||
) -> Result<HttpResponse, String> {
|
||||
warn!("Failed to send request: {}", error);
|
||||
let mut response = response.clone();
|
||||
response.elapsed = -1;
|
||||
response.error = Some(error.clone());
|
||||
@@ -1528,8 +1493,6 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
|
||||
&w,
|
||||
Workspace {
|
||||
name: "Yaak".to_string(),
|
||||
setting_follow_redirects: true,
|
||||
setting_validate_certificates: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1590,6 +1553,11 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_os::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());
|
||||
@@ -1618,9 +1586,7 @@ pub fn run() {
|
||||
.level_for("tokio_util", log::LevelFilter::Info)
|
||||
.level_for("tonic", log::LevelFilter::Info)
|
||||
.level_for("tower", log::LevelFilter::Info)
|
||||
.level_for("tracing", log::LevelFilter::Warn)
|
||||
.level_for("swc_ecma_codegen", log::LevelFilter::Off)
|
||||
.level_for("swc_ecma_transforms_base", log::LevelFilter::Off)
|
||||
.level_for("tracing", log::LevelFilter::Info)
|
||||
.with_colors(ColoredLevelConfig::default())
|
||||
.level(if is_dev() {
|
||||
log::LevelFilter::Trace
|
||||
@@ -1732,7 +1698,6 @@ pub fn run() {
|
||||
cmd_new_window,
|
||||
cmd_request_to_curl,
|
||||
cmd_dismiss_notification,
|
||||
cmd_save_response,
|
||||
cmd_send_ephemeral_request,
|
||||
cmd_send_http_request,
|
||||
cmd_set_key_value,
|
||||
@@ -1934,10 +1899,11 @@ async fn get_update_mode(h: &AppHandle) -> UpdateMode {
|
||||
UpdateMode::new(settings.update_channel.as_str())
|
||||
}
|
||||
|
||||
fn safe_uri(endpoint: &str) -> String {
|
||||
if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
||||
endpoint.into()
|
||||
fn safe_uri(endpoint: &str) -> Result<Uri, InvalidUri> {
|
||||
let uri = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
||||
Uri::from_str(endpoint)?
|
||||
} else {
|
||||
format!("http://{}", endpoint)
|
||||
}
|
||||
Uri::from_str(&format!("http://{}", endpoint))?
|
||||
};
|
||||
Ok(uri)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ pub struct Settings {
|
||||
pub interface_scale: i64,
|
||||
pub editor_font_size: i64,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub open_workspace_new_window: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -892,8 +891,7 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
|
||||
SELECT
|
||||
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,
|
||||
open_workspace_new_window
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
|
||||
FROM settings
|
||||
WHERE id = 'default'
|
||||
"#,
|
||||
@@ -930,9 +928,8 @@ pub async fn update_settings(
|
||||
r#"
|
||||
UPDATE settings SET (
|
||||
theme, appearance, theme_dark, theme_light, update_channel,
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
|
||||
open_workspace_new_window
|
||||
) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
|
||||
) = (?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
|
||||
"#,
|
||||
settings.theme,
|
||||
settings.appearance,
|
||||
@@ -943,7 +940,6 @@ pub async fn update_settings(
|
||||
settings.interface_scale,
|
||||
settings.editor_font_size,
|
||||
settings.editor_soft_wrap,
|
||||
settings.open_workspace_new_window,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use chrono::{Duration, NaiveDateTime, Utc};
|
||||
use http::Method;
|
||||
use log::debug;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct YaakNotifier {
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct YaakNotification {
|
||||
timestamp: DateTime<Utc>,
|
||||
timestamp: NaiveDateTime,
|
||||
id: String,
|
||||
message: String,
|
||||
action: Option<YaakNotificationAction>,
|
||||
@@ -77,7 +77,7 @@ impl YaakNotifier {
|
||||
|
||||
let age = notification
|
||||
.timestamp
|
||||
.signed_duration_since(Utc::now());
|
||||
.signed_duration_since(Utc::now().naive_utc());
|
||||
let seen = get_kv(app).await?;
|
||||
if seen.contains(¬ification.id) || (age > Duration::days(1)) {
|
||||
debug!("Already seen notification {}", notification.id);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
((globalThis) => {
|
||||
const core = Deno.core;
|
||||
globalThis.YAML = {
|
||||
parse: core.ops.op_yaml_parse,
|
||||
stringify: core.ops.op_yaml_stringify,
|
||||
};
|
||||
})(globalThis);
|
||||
@@ -1,24 +1,19 @@
|
||||
use std::{fs, io};
|
||||
use std::rc::Rc;
|
||||
|
||||
use log::error;
|
||||
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::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::deno::{get_plugin_capabilities_block, run_plugin_block};
|
||||
use crate::models::{HttpRequest, WorkspaceExportResources};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PluginError {
|
||||
#[error("directory not found")]
|
||||
DirectoryNotFound(#[from] io::Error),
|
||||
#[error("anyhow error")]
|
||||
V8(#[from] anyhow::Error),
|
||||
// #[error("unknown data store error")]
|
||||
// Unknown,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct FilterResult {
|
||||
pub filtered: String,
|
||||
@@ -29,87 +24,26 @@ pub struct ImportResult {
|
||||
pub resources: WorkspaceExportResources,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
pub enum PluginCapability {
|
||||
Export,
|
||||
Import,
|
||||
Filter,
|
||||
}
|
||||
|
||||
pub struct PluginDef {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub capabilities: Vec<PluginCapability>,
|
||||
}
|
||||
|
||||
pub fn scan_plugins(app_handle: &AppHandle) -> Result<Vec<PluginDef>, PluginError> {
|
||||
let plugins_dir = app_handle
|
||||
.path()
|
||||
.resolve("plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let plugin_entries = fs::read_dir(plugins_dir)?;
|
||||
|
||||
let mut plugins = Vec::new();
|
||||
for entry in plugin_entries {
|
||||
let plugin_dir_entry = match entry {
|
||||
Err(_) => continue,
|
||||
Ok(entry) => entry,
|
||||
};
|
||||
|
||||
let plugin_index_file = plugin_dir_entry.path().join("index.mjs");
|
||||
let capabilities = get_plugin_capabilities_block(&plugin_index_file.to_str().unwrap())?;
|
||||
|
||||
plugins.push(PluginDef {
|
||||
name: plugin_dir_entry.file_name().to_string_lossy().to_string(),
|
||||
path: plugin_index_file.to_string_lossy().to_string(),
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
pub async fn find_plugins(
|
||||
app_handle: &AppHandle,
|
||||
capability: &PluginCapability,
|
||||
) -> Result<Vec<PluginDef>, PluginError> {
|
||||
let plugins = scan_plugins(app_handle)?
|
||||
.into_iter()
|
||||
.filter(|p| p.capabilities.contains(capability))
|
||||
.collect();
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
pub fn get_plugin(app_handle: &AppHandle, name: &str) -> Result<Option<PluginDef>, PluginError> {
|
||||
Ok(scan_plugins(app_handle)?
|
||||
.into_iter()
|
||||
.find(|p| p.name == name))
|
||||
}
|
||||
|
||||
pub async fn run_plugin_filter(
|
||||
plugin: &PluginDef,
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
response_body: &str,
|
||||
filter: &str,
|
||||
) -> Option<FilterResult> {
|
||||
let result = run_plugin_block(
|
||||
&plugin.path,
|
||||
let result_json = run_plugin(
|
||||
app_handle,
|
||||
plugin_name,
|
||||
"pluginHookResponseFilter",
|
||||
vec![
|
||||
serde_json::to_value(response_body).unwrap(),
|
||||
serde_json::to_value(filter).unwrap(),
|
||||
],
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
.expect("Failed to run plugin");
|
||||
&[js_string!(response_body).into(), js_string!(filter).into()],
|
||||
);
|
||||
|
||||
if result.is_null() {
|
||||
error!("Plugin {} failed to run", plugin.name);
|
||||
if result_json.is_null() {
|
||||
error!("Plugin {} failed to run", plugin_name);
|
||||
return None;
|
||||
}
|
||||
|
||||
let resources: FilterResult =
|
||||
serde_json::from_value(result).expect("failed to parse filter plugin result json");
|
||||
serde_json::from_value(result_json).expect("failed to parse filter plugin result json");
|
||||
Some(resources)
|
||||
}
|
||||
|
||||
@@ -117,34 +51,114 @@ pub fn run_plugin_export_curl(
|
||||
app_handle: &AppHandle,
|
||||
request: &HttpRequest,
|
||||
) -> Result<String, String> {
|
||||
let plugin = match get_plugin(app_handle, "exporter-curl").map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to get plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let mut context = Context::default();
|
||||
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
|
||||
let result = run_plugin_block(&plugin.path, "pluginHookExport", vec![request_json])
|
||||
.map_err(|e| e.to_string())?;
|
||||
let result_json = run_plugin(
|
||||
app_handle,
|
||||
"exporter-curl",
|
||||
"pluginHookExport",
|
||||
&[JsValue::from_json(&request_json, &mut context).map_err(|e| e.to_string())?],
|
||||
);
|
||||
|
||||
let export_str: String = serde_json::from_value(result).map_err(|e| e.to_string())?;
|
||||
Ok(export_str)
|
||||
let resources: String = serde_json::from_value(result_json).map_err(|e| e.to_string())?;
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
pub async fn run_plugin_import(
|
||||
plugin: &PluginDef,
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
file_contents: &str,
|
||||
) -> Result<Option<ImportResult>, String> {
|
||||
let result = run_plugin_block(
|
||||
&plugin.path,
|
||||
let result_json = run_plugin(
|
||||
app_handle,
|
||||
plugin_name,
|
||||
"pluginHookImport",
|
||||
vec![serde_json::to_value(file_contents).map_err(|e| e.to_string())?],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
&[js_string!(file_contents).into()],
|
||||
);
|
||||
|
||||
if result.is_null() {
|
||||
if result_json.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let resources: ImportResult = serde_json::from_value(result).map_err(|e| e.to_string())?;
|
||||
let resources: ImportResult = serde_json::from_value(result_json).map_err(|e| e.to_string())?;
|
||||
Ok(Some(resources))
|
||||
}
|
||||
|
||||
fn run_plugin(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
entrypoint: &str,
|
||||
js_args: &[JsValue],
|
||||
) -> serde_json::Value {
|
||||
let plugin_dir = app_handle
|
||||
.path()
|
||||
.resolve("plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource")
|
||||
.join(plugin_name);
|
||||
let plugin_index_file = plugin_dir.join("index.mjs");
|
||||
|
||||
debug!(
|
||||
"Running plugin dir={:?} file={:?}",
|
||||
plugin_dir, plugin_index_file
|
||||
);
|
||||
|
||||
let loader = Rc::new(SimpleModuleLoader::new(plugin_dir).unwrap());
|
||||
let context = &mut Context::builder()
|
||||
.module_loader(loader.clone())
|
||||
.build()
|
||||
.expect("failed to create context");
|
||||
|
||||
add_runtime(context);
|
||||
|
||||
let source = Source::from_filepath(&plugin_index_file).expect("Error opening file");
|
||||
|
||||
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
|
||||
let module = Module::parse(source, None, context).expect("failed to parse module");
|
||||
|
||||
// Insert parsed entrypoint into the module loader
|
||||
loader.insert(plugin_index_file, module.clone());
|
||||
|
||||
let promise_result = module.load_link_evaluate(context);
|
||||
|
||||
// Very important to push forward the job queue after queueing promises.
|
||||
context.run_jobs();
|
||||
|
||||
// Checking if the final promise didn't return an error.
|
||||
match promise_result.state() {
|
||||
PromiseState::Pending => {
|
||||
panic!("Promise was pending");
|
||||
}
|
||||
PromiseState::Fulfilled(v) => {
|
||||
assert_eq!(v, JsValue::undefined())
|
||||
}
|
||||
PromiseState::Rejected(err) => {
|
||||
panic!("Failed to link: {}", err.display());
|
||||
}
|
||||
}
|
||||
|
||||
let namespace = module.namespace(context);
|
||||
|
||||
let result = namespace
|
||||
.get(js_string!(entrypoint), context)
|
||||
.expect("failed to get entrypoint")
|
||||
.as_callable()
|
||||
.cloned()
|
||||
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
|
||||
.expect("Failed to get entrypoint")
|
||||
.call(&JsValue::undefined(), js_args, context)
|
||||
.expect("Failed to call entrypoint");
|
||||
|
||||
match result.is_undefined() {
|
||||
true => json!(null), // to_json doesn't work with undefined (yet)
|
||||
false => result
|
||||
.to_json(context)
|
||||
.expect("failed to convert result to json"),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_runtime(context: &mut Context) {
|
||||
let console = Console::init(context);
|
||||
context
|
||||
.register_global_property(js_string!(Console::NAME), console, Attribute::all())
|
||||
.expect("the console builtin shouldn't exist");
|
||||
}
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use regex::Regex;
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
|
||||
use crate::models::{
|
||||
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
|
||||
};
|
||||
use templates::parse_and_render;
|
||||
use crate::models::{Environment, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace};
|
||||
|
||||
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
|
||||
let r = r.clone();
|
||||
let vars = &variables_from_environment(w, e);
|
||||
|
||||
HttpRequest {
|
||||
url: render(r.url.as_str(), vars),
|
||||
url: render(r.url.as_str(), w, e),
|
||||
url_parameters: Json(
|
||||
r.url_parameters
|
||||
.0
|
||||
.iter()
|
||||
.map(|p| HttpUrlParameter {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
name: render(p.name.as_str(), w, e),
|
||||
value: render(p.value.as_str(), w, e),
|
||||
})
|
||||
.collect::<Vec<HttpUrlParameter>>(),
|
||||
),
|
||||
@@ -30,8 +26,8 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
|
||||
.iter()
|
||||
.map(|p| HttpRequestHeader {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
name: render(p.name.as_str(), w, e),
|
||||
value: render(p.value.as_str(), w, e),
|
||||
})
|
||||
.collect::<Vec<HttpRequestHeader>>(),
|
||||
),
|
||||
@@ -41,11 +37,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
render(v.as_str().unwrap(), w, e)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), JsonValue::from(v))
|
||||
(render(k, w, e), JsonValue::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, JsonValue>>(),
|
||||
),
|
||||
@@ -55,11 +51,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
render(v.as_str().unwrap(), w, e)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), JsonValue::from(v))
|
||||
(render(k, w, e), JsonValue::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, JsonValue>>(),
|
||||
),
|
||||
@@ -67,58 +63,31 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recursively_render_variables<'s>(
|
||||
m: &HashMap<String, String>,
|
||||
render_count: usize,
|
||||
) -> HashMap<String, String> {
|
||||
let mut did_render = false;
|
||||
let mut new_map = m.clone();
|
||||
for (k, v) in m.clone() {
|
||||
let rendered = render(v.as_str(), m);
|
||||
if rendered != v {
|
||||
did_render = true
|
||||
}
|
||||
new_map.insert(k, rendered);
|
||||
}
|
||||
|
||||
if did_render && render_count <= 3 {
|
||||
new_map = recursively_render_variables(&new_map, render_count + 1);
|
||||
}
|
||||
|
||||
new_map
|
||||
}
|
||||
|
||||
pub fn variables_from_environment(
|
||||
workspace: &Workspace,
|
||||
environment: Option<&Environment>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut variables = HashMap::new();
|
||||
variables = add_variable_to_map(variables, &workspace.variables.0);
|
||||
|
||||
if let Some(e) = environment {
|
||||
variables = add_variable_to_map(variables, &e.variables.0);
|
||||
}
|
||||
|
||||
recursively_render_variables(&variables, 0)
|
||||
}
|
||||
|
||||
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
|
||||
parse_and_render(template, vars, None)
|
||||
}
|
||||
|
||||
fn add_variable_to_map(
|
||||
m: HashMap<String, String>,
|
||||
variables: &Vec<EnvironmentVariable>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut map = m.clone();
|
||||
for variable in variables {
|
||||
pub fn render(template: &str, workspace: &Workspace, environment: Option<&Environment>) -> String {
|
||||
let mut map = HashMap::new();
|
||||
let workspace_variables = &workspace.variables.0;
|
||||
for variable in workspace_variables {
|
||||
if !variable.enabled || variable.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let name = variable.name.as_str();
|
||||
let value = variable.value.as_str();
|
||||
map.insert(name.into(), value.into());
|
||||
map.insert(variable.name.as_str(), variable.value.as_str());
|
||||
}
|
||||
|
||||
map
|
||||
if let Some(e) = environment {
|
||||
let environment_variables = &e.variables.0;
|
||||
for variable in environment_variables {
|
||||
if !variable.enabled || variable.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
map.insert(variable.name.as_str(), variable.value.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}")
|
||||
.expect("Failed to create regex")
|
||||
.replace_all(template, |caps: ®ex::Captures| {
|
||||
let key = caps.get(1).unwrap().as_str();
|
||||
map.get(key).unwrap_or(&"")
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use hex_color::HexColor;
|
||||
use log::warn;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::{
|
||||
@@ -26,35 +25,15 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
|
||||
let window_for_theme = window.clone();
|
||||
let id1 = h.listen("yaak_bg_changed", move |ev| {
|
||||
let color_str: String = match serde_json::from_str(ev.payload()) {
|
||||
Ok(color) => color,
|
||||
Err(err) => {
|
||||
warn!("Failed to JSON parse color '{}': {}", ev.payload(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match HexColor::parse_rgb(color_str.trim()) {
|
||||
Ok(color) => {
|
||||
update_window_theme(window_for_theme.clone(), color);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to parse background color '{}': {}", color_str, err)
|
||||
}
|
||||
}
|
||||
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 title: String = match serde_json::from_str(ev.payload()) {
|
||||
Ok(title) => title,
|
||||
Err(err) => {
|
||||
warn!("Failed to parse window title \"{}\": {}", ev.payload(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
update_window_title(window_for_title.clone(), title);
|
||||
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();
|
||||
|
||||
97
src-tauri/src/tauri_plugin_windows_window.rs
Normal file
97
src-tauri/src/tauri_plugin_windows_window.rs
Normal 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);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
@@ -134,11 +134,8 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
|
||||
.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)?,
|
||||
&MenuItemBuilder::with_id("dev.generate_theme_css".to_string(), "Generate Theme CSS")
|
||||
.build(app_handle)?,
|
||||
],
|
||||
)?,
|
||||
],
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"productName": "yaak",
|
||||
"version": "2024.6.5",
|
||||
"identifier": "app.yaak.desktop",
|
||||
"build": {
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"productName": "Yaak",
|
||||
"version": "2024.5.0",
|
||||
"identifier": "app.yaak.desktop",
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"security": {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "templates"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -1,7 +0,0 @@
|
||||
pub mod parser;
|
||||
pub mod renderer;
|
||||
|
||||
pub use parser::*;
|
||||
pub use renderer::*;
|
||||
|
||||
pub fn template_foo() {}
|
||||
@@ -1,413 +0,0 @@
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Val {
|
||||
Str(String),
|
||||
Var(String),
|
||||
Fn { name: String, args: Vec<Val> },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Token {
|
||||
Raw(String),
|
||||
Tag(Val),
|
||||
Eof,
|
||||
}
|
||||
|
||||
// Template Syntax
|
||||
//
|
||||
// ${[ my_var ]}
|
||||
// ${[ my_fn() ]}
|
||||
// ${[ my_fn(my_var) ]}
|
||||
// ${[ my_fn(my_var, "A String") ]}
|
||||
|
||||
// default
|
||||
#[derive(Default)]
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
chars: Vec<char>,
|
||||
pos: usize,
|
||||
curr_text: String,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(text: &str) -> Parser {
|
||||
Parser {
|
||||
chars: text.chars().collect(),
|
||||
..Parser::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Vec<Token> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
while self.pos < self.chars.len() {
|
||||
if self.match_str("${[") {
|
||||
let start_curr = self.pos;
|
||||
if let Some(t) = self.parse_tag() {
|
||||
self.push_token(t);
|
||||
} else {
|
||||
self.pos = start_curr;
|
||||
self.curr_text += "${[";
|
||||
}
|
||||
} else {
|
||||
let ch = self.next_char();
|
||||
self.curr_text.push(ch);
|
||||
}
|
||||
|
||||
if start_pos == self.pos {
|
||||
panic!("Parser stuck!");
|
||||
}
|
||||
}
|
||||
|
||||
self.push_token(Token::Eof);
|
||||
self.tokens.clone()
|
||||
}
|
||||
|
||||
fn parse_tag(&mut self) -> Option<Token> {
|
||||
// Parse up to first identifier
|
||||
// ${[ my_var...
|
||||
self.skip_whitespace();
|
||||
|
||||
let val = match self.parse_value() {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Parse to closing tag
|
||||
// ${[ my_var(a, b, c) ]}
|
||||
self.skip_whitespace();
|
||||
if !self.match_str("]}") {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Token::Tag(val))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn debug_pos(&self, x: &str) {
|
||||
println!(
|
||||
r#"Position: {x} -- [{}] = {} --> "{}" --> {:?}"#,
|
||||
self.pos,
|
||||
self.chars[self.pos],
|
||||
self.chars.iter().collect::<String>(),
|
||||
self.tokens,
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_value(&mut self) -> Option<Val> {
|
||||
if let Some((name, args)) = self.parse_fn() {
|
||||
Some(Val::Fn { name, args })
|
||||
} else if let Some(v) = self.parse_ident() {
|
||||
Some(Val::Var(v))
|
||||
} else if let Some(v) = self.parse_string() {
|
||||
Some(Val::Str(v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fn(&mut self) -> Option<(String, Vec<Val>)> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
let name = match self.parse_ident() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let args = match self.parse_fn_args() {
|
||||
Some(args) => args,
|
||||
None => {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((name, args))
|
||||
}
|
||||
|
||||
fn parse_fn_args(&mut self) -> Option<Vec<Val>> {
|
||||
if !self.match_str("(") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_pos = self.pos;
|
||||
|
||||
let mut args: Vec<Val> = Vec::new();
|
||||
while self.pos < self.chars.len() {
|
||||
self.skip_whitespace();
|
||||
if let Some(v) = self.parse_value() {
|
||||
args.push(v);
|
||||
}
|
||||
|
||||
self.skip_whitespace();
|
||||
if self.match_str(")") {
|
||||
break;
|
||||
}
|
||||
|
||||
self.skip_whitespace();
|
||||
|
||||
// If we don't find a comma, that's bad
|
||||
if !args.is_empty() && !self.match_str(",") {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
|
||||
if start_pos == self.pos {
|
||||
panic!("Parser stuck!");
|
||||
}
|
||||
}
|
||||
|
||||
return Some(args);
|
||||
}
|
||||
|
||||
fn parse_ident(&mut self) -> Option<String> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
let mut text = String::new();
|
||||
while self.pos < self.chars.len() {
|
||||
let ch = self.peek_char();
|
||||
if ch.is_alphanumeric() || ch == '_' {
|
||||
text.push(ch);
|
||||
self.pos += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if start_pos == self.pos {
|
||||
panic!("Parser stuck!");
|
||||
}
|
||||
}
|
||||
|
||||
if text.is_empty() {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(text);
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Option<String> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
let mut text = String::new();
|
||||
if !self.match_str("\"") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut found_closing = false;
|
||||
while self.pos < self.chars.len() {
|
||||
let ch = self.next_char();
|
||||
match ch {
|
||||
'\\' => {
|
||||
text.push(self.next_char());
|
||||
}
|
||||
'"' => {
|
||||
found_closing = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
text.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if start_pos == self.pos {
|
||||
panic!("Parser stuck!");
|
||||
}
|
||||
}
|
||||
|
||||
if !found_closing {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(text);
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while self.pos < self.chars.len() {
|
||||
if self.peek_char().is_whitespace() {
|
||||
self.pos += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_char(&mut self) -> char {
|
||||
let ch = self.peek_char();
|
||||
|
||||
self.pos += 1;
|
||||
ch
|
||||
}
|
||||
|
||||
fn peek_char(&self) -> char {
|
||||
let ch = self.chars[self.pos];
|
||||
ch
|
||||
}
|
||||
|
||||
fn push_token(&mut self, token: Token) {
|
||||
// Push any text we've accumulated
|
||||
if !self.curr_text.is_empty() {
|
||||
let text_token = Token::Raw(self.curr_text.clone());
|
||||
self.tokens.push(text_token);
|
||||
self.curr_text.clear();
|
||||
}
|
||||
|
||||
self.tokens.push(token);
|
||||
}
|
||||
|
||||
fn match_str(&mut self, value: &str) -> bool {
|
||||
if self.pos + value.len() > self.chars.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cmp = self.chars[self.pos..self.pos + value.len()]
|
||||
.iter()
|
||||
.collect::<String>();
|
||||
|
||||
if cmp == value {
|
||||
// We have a match, so advance the current index
|
||||
self.pos += value.len();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn var_simple() {
|
||||
let mut p = Parser::new("${[ foo ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Tag(Val::Var("foo".into())), Token::Eof]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_multiple_names_invalid() {
|
||||
let mut p = Parser::new("${[ foo bar ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Raw("${[ foo bar ]}".into()), Token::Eof]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_string() {
|
||||
let mut p = Parser::new(r#"${[ "foo \"bar\" baz" ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Tag(Val::Str(r#"foo "bar" baz"#.into())), Token::Eof]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_surrounded() {
|
||||
let mut p = Parser::new("Hello ${[ foo ]}!");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Raw("Hello ".to_string()),
|
||||
Token::Tag(Val::Var("foo".into())),
|
||||
Token::Raw("!".to_string()),
|
||||
Token::Eof,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_simple() {
|
||||
let mut p = Parser::new("${[ foo() ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: Vec::new(),
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_ident_arg() {
|
||||
let mut p = Parser::new("${[ foo(bar) ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![Val::Var("bar".into())],
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_ident_args() {
|
||||
let mut p = Parser::new("${[ foo(bar,baz, qux ) ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
Val::Var("bar".into()),
|
||||
Val::Var("baz".into()),
|
||||
Val::Var("qux".into()),
|
||||
],
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_mixed_args() {
|
||||
let mut p = Parser::new(r#"${[ foo(bar,"baz \"hi\"", qux ) ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
Val::Var("bar".into()),
|
||||
Val::Str(r#"baz "hi""#.into()),
|
||||
Val::Var("qux".into()),
|
||||
],
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_nested() {
|
||||
let mut p = Parser::new(r#"${[ outer(inner(foo, "i"), "o") ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "outer".into(),
|
||||
args: vec![
|
||||
Val::Fn {
|
||||
name: "inner".into(),
|
||||
args: vec![Val::Var("foo".into()), Val::Str("i".into()),],
|
||||
},
|
||||
Val::Str("o".into())
|
||||
],
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{Parser, Token, Val};
|
||||
|
||||
type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
|
||||
|
||||
pub fn parse_and_render(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
let mut p = Parser::new(template);
|
||||
let tokens = p.parse();
|
||||
render(tokens, vars, cb)
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
tokens: Vec<Token>,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
let mut doc_str: Vec<String> = Vec::new();
|
||||
|
||||
for t in tokens {
|
||||
match t {
|
||||
Token::Raw(s) => doc_str.push(s),
|
||||
Token::Tag(val) => doc_str.push(render_tag(val, &vars, cb)),
|
||||
Token::Eof => {}
|
||||
}
|
||||
}
|
||||
|
||||
return doc_str.join("");
|
||||
}
|
||||
|
||||
fn render_tag(
|
||||
val: Val,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
match val {
|
||||
Val::Str(s) => s.into(),
|
||||
Val::Var(name) => match vars.get(name.as_str()) {
|
||||
Some(v) => v.to_string(),
|
||||
None => "".into(),
|
||||
},
|
||||
Val::Fn { name, args } => {
|
||||
let empty = "".to_string();
|
||||
let resolved_args = args
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
Val::Str(s) => s.to_string(),
|
||||
Val::Var(i) => vars.get(i.as_str()).unwrap_or(&empty).to_string(),
|
||||
val => render_tag(val.clone(), vars, cb),
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
match cb {
|
||||
Some(cb) => cb(name.as_str(), resolved_args),
|
||||
None => "".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn render_empty() {
|
||||
let template = "";
|
||||
let vars = HashMap::new();
|
||||
let result = "";
|
||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_text_only() {
|
||||
let template = "Hello World!";
|
||||
let vars = HashMap::new();
|
||||
let result = "Hello World!";
|
||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_simple() {
|
||||
let template = "${[ foo ]}";
|
||||
let vars = HashMap::from([("foo".to_string(), "bar".to_string())]);
|
||||
let result = "bar";
|
||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_surrounded() {
|
||||
let template = "hello ${[ word ]} world!";
|
||||
let vars = HashMap::from([("word".to_string(), "cruel".to_string())]);
|
||||
let result = "hello cruel world!";
|
||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_valid_fn() {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ say_hello("John", "Kate") ]}"#;
|
||||
let result = r#"say_hello: ["John", "Kate"]"#;
|
||||
|
||||
fn cb(name: &str, args: Vec<String>) -> String {
|
||||
format!("{name}: {:?}", args)
|
||||
}
|
||||
assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_nested_fn() {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ upper(secret()) ]}"#;
|
||||
let result = r#"ABC"#;
|
||||
fn cb(name: &str, args: Vec<String>) -> String {
|
||||
match name {
|
||||
"secret" => "abc".to_string(),
|
||||
"upper" => args[0].to_string().to_uppercase(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_and_render(template, &vars, Some(cb)),
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +1,28 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import classNames from 'classnames';
|
||||
import { search } from 'fast-fuzzy';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
||||
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDebouncedState } from '../hooks/useDebouncedState';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import type { HotkeyAction } from '../hooks/useHotKey';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { CookieDialog } from './CookieDialog';
|
||||
import { Button } from './core/Button';
|
||||
import { Heading } from './core/Heading';
|
||||
import { HotKey } from './core/HotKey';
|
||||
import { HttpMethodTag } from './core/HttpMethodTag';
|
||||
import { Icon } from './core/Icon';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
|
||||
interface CommandPaletteGroup {
|
||||
key: string;
|
||||
label: ReactNode;
|
||||
items: CommandPaletteItem[];
|
||||
}
|
||||
|
||||
type CommandPaletteItem = {
|
||||
key: string;
|
||||
onSelect: () => void;
|
||||
action?: HotkeyAction;
|
||||
} & ({ searchText: string; label: ReactNode } | { label: string });
|
||||
|
||||
const MAX_PER_GROUP = 8;
|
||||
import { Input } from './core/Input';
|
||||
|
||||
export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
const [command, setCommand] = useDebouncedState<string>('', 150);
|
||||
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
const routes = useAppRoutes();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const active = useActiveWorkspaceId();
|
||||
const workspaces = useWorkspaces();
|
||||
const environments = useEnvironments();
|
||||
const recentEnvironments = useRecentEnvironments();
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
const requests = useRequests();
|
||||
const recentRequests = useRecentRequests();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const createHttpRequest = useCreateHttpRequest();
|
||||
const { activeCookieJar } = useActiveCookieJar();
|
||||
const createGrpcRequest = useCreateGrpcRequest();
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const dialog = useDialog();
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const [, setSidebarHidden] = useSidebarHidden();
|
||||
const [command, setCommand] = useState<string>('');
|
||||
|
||||
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
|
||||
const commands: CommandPaletteItem[] = [
|
||||
{
|
||||
key: 'settings.open',
|
||||
label: 'Open Settings',
|
||||
action: 'settings.show',
|
||||
onSelect: async () => {
|
||||
if (workspaceId == null) return;
|
||||
await invoke('cmd_new_nested_window', {
|
||||
url: routes.paths.workspaceSettings({ workspaceId }),
|
||||
label: 'settings',
|
||||
title: 'Yaak Settings',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'app.create',
|
||||
label: 'Create Workspace',
|
||||
onSelect: createWorkspace.mutate,
|
||||
},
|
||||
{
|
||||
key: 'http_request.create',
|
||||
label: 'Create HTTP Request',
|
||||
onSelect: () => createHttpRequest.mutate({}),
|
||||
},
|
||||
{
|
||||
key: 'cookies.show',
|
||||
label: 'Show Cookies',
|
||||
onSelect: async () => {
|
||||
dialog.show({
|
||||
id: 'cookies',
|
||||
title: 'Manage Cookies',
|
||||
size: 'full',
|
||||
render: () => <CookieDialog cookieJarId={activeCookieJar?.id ?? null} />,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'grpc_request.create',
|
||||
label: 'Create GRPC Request',
|
||||
onSelect: () => createGrpcRequest.mutate({}),
|
||||
},
|
||||
{
|
||||
key: 'environment.edit',
|
||||
label: 'Edit Environment',
|
||||
action: 'environmentEditor.toggle',
|
||||
onSelect: () => {
|
||||
dialog.toggle({
|
||||
id: 'environment-editor',
|
||||
noPadding: true,
|
||||
size: 'lg',
|
||||
className: 'h-[80vh]',
|
||||
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'environment.create',
|
||||
label: 'Create Environment',
|
||||
onSelect: createEnvironment.mutate,
|
||||
},
|
||||
{
|
||||
key: 'sidebar.toggle',
|
||||
label: 'Toggle Sidebar',
|
||||
action: 'sidebar.focus',
|
||||
onSelect: () => setSidebarHidden((h) => !h),
|
||||
},
|
||||
];
|
||||
return commands.sort((a, b) =>
|
||||
('searchText' in a ? a.searchText : a.label).localeCompare(
|
||||
'searchText' in b ? b.searchText : b.label,
|
||||
),
|
||||
);
|
||||
}, [
|
||||
activeCookieJar,
|
||||
activeEnvironment,
|
||||
createEnvironment.mutate,
|
||||
createGrpcRequest,
|
||||
createHttpRequest,
|
||||
createWorkspace.mutate,
|
||||
dialog,
|
||||
routes.paths,
|
||||
setSidebarHidden,
|
||||
workspaceId,
|
||||
]);
|
||||
|
||||
const sortedRequests = useMemo(() => {
|
||||
return [...requests].sort((a, b) => {
|
||||
const aRecentIndex = recentRequests.indexOf(a.id);
|
||||
const bRecentIndex = recentRequests.indexOf(b.id);
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
});
|
||||
}, [recentRequests, requests]);
|
||||
|
||||
const sortedEnvironments = useMemo(() => {
|
||||
return [...environments].sort((a, b) => {
|
||||
const aRecentIndex = recentEnvironments.indexOf(a.id);
|
||||
const bRecentIndex = recentEnvironments.indexOf(b.id);
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
});
|
||||
}, [environments, recentEnvironments]);
|
||||
|
||||
const sortedWorkspaces = useMemo(() => {
|
||||
return [...workspaces].sort((a, b) => {
|
||||
const aRecentIndex = recentWorkspaces.indexOf(a.id);
|
||||
const bRecentIndex = recentWorkspaces.indexOf(b.id);
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
});
|
||||
}, [recentWorkspaces, workspaces]);
|
||||
|
||||
const groups = useMemo<CommandPaletteGroup[]>(() => {
|
||||
const actionsGroup: CommandPaletteGroup = {
|
||||
key: 'actions',
|
||||
label: 'Actions',
|
||||
items: workspaceCommands,
|
||||
};
|
||||
|
||||
const requestGroup: CommandPaletteGroup = {
|
||||
key: 'requests',
|
||||
label: 'Requests',
|
||||
items: [],
|
||||
};
|
||||
|
||||
for (const r of sortedRequests) {
|
||||
if (r.id === activeRequestId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requestGroup.items.push({
|
||||
const items = useMemo<{ label: string; onSelect: () => void; key: string }[]>(() => {
|
||||
const items = [];
|
||||
for (const r of requests) {
|
||||
items.push({
|
||||
key: `switch-request-${r.id}`,
|
||||
searchText: fallbackRequestName(r),
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<HttpMethodTag className="text-fg-subtler" request={r} />
|
||||
<div className="truncate">{fallbackRequestName(r)}</div>
|
||||
</HStack>
|
||||
),
|
||||
label: `Switch Request → ${fallbackRequestName(r)}`,
|
||||
onSelect: () => {
|
||||
return routes.navigate('request', {
|
||||
workspaceId: r.workspaceId,
|
||||
@@ -247,79 +32,25 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const environmentGroup: CommandPaletteGroup = {
|
||||
key: 'environments',
|
||||
label: 'Environments',
|
||||
items: [],
|
||||
};
|
||||
|
||||
for (const e of sortedEnvironments) {
|
||||
if (e.id === activeEnvironment?.id) {
|
||||
continue;
|
||||
}
|
||||
environmentGroup.items.push({
|
||||
key: `switch-environment-${e.id}`,
|
||||
label: e.name,
|
||||
onSelect: () => routes.setEnvironment(e),
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceGroup: CommandPaletteGroup = {
|
||||
key: 'workspaces',
|
||||
label: 'Workspaces',
|
||||
items: [],
|
||||
};
|
||||
|
||||
for (const w of sortedWorkspaces) {
|
||||
if (w.id === active) {
|
||||
continue;
|
||||
}
|
||||
workspaceGroup.items.push({
|
||||
for (const w of workspaces) {
|
||||
items.push({
|
||||
key: `switch-workspace-${w.id}`,
|
||||
label: w.name,
|
||||
onSelect: () => openWorkspace.mutate({ workspaceId: w.id, inNewWindow: false }),
|
||||
label: `Switch Workspace → ${w.name}`,
|
||||
onSelect: async () => {
|
||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||
return routes.navigate('workspace', {
|
||||
workspaceId: w.id,
|
||||
environmentId,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}, [activeEnvironmentId, requests, routes, workspaces]);
|
||||
|
||||
return [actionsGroup, requestGroup, environmentGroup, workspaceGroup];
|
||||
}, [
|
||||
workspaceCommands,
|
||||
sortedRequests,
|
||||
activeRequestId,
|
||||
routes,
|
||||
activeEnvironmentId,
|
||||
sortedEnvironments,
|
||||
activeEnvironment?.id,
|
||||
sortedWorkspaces,
|
||||
active,
|
||||
openWorkspace,
|
||||
]);
|
||||
|
||||
const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedItemKey(null);
|
||||
}, [command]);
|
||||
|
||||
const { filteredGroups, filteredAllItems } = useMemo(() => {
|
||||
const result = command
|
||||
? search(command, allItems, {
|
||||
threshold: 0.5,
|
||||
keySelector: (v) => ('searchText' in v ? v.searchText : v.label),
|
||||
})
|
||||
: allItems;
|
||||
|
||||
const filteredGroups = groups
|
||||
.map((g) => {
|
||||
g.items = result.filter((i) => g.items.includes(i)).slice(0, MAX_PER_GROUP);
|
||||
return g;
|
||||
})
|
||||
.filter((g) => g.items.length > 0);
|
||||
|
||||
const filteredAllItems = filteredGroups.flatMap((g) => g.items);
|
||||
return { filteredAllItems, filteredGroups };
|
||||
}, [allItems, command, groups]);
|
||||
const filteredItems = useMemo(() => {
|
||||
return items.filter((v) => v.label.toLowerCase().includes(command.toLowerCase()));
|
||||
}, [command, items]);
|
||||
|
||||
const handleSelectAndClose = useCallback(
|
||||
(cb: () => void) => {
|
||||
@@ -329,73 +60,44 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const selectedItem = useMemo(() => {
|
||||
let selectedItem = filteredAllItems.find((i) => i.key === selectedItemKey) ?? null;
|
||||
if (selectedItem == null) {
|
||||
selectedItem = filteredAllItems[0] ?? null;
|
||||
}
|
||||
return selectedItem;
|
||||
}, [filteredAllItems, selectedItemKey]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
const index = filteredAllItems.findIndex((v) => v.key === selectedItem?.key);
|
||||
|
||||
if (e.key === 'ArrowDown' || (e.ctrlKey && e.key === 'n')) {
|
||||
const next = filteredAllItems[index + 1] ?? filteredAllItems[0];
|
||||
setSelectedItemKey(next?.key ?? null);
|
||||
} else if (e.key === 'ArrowUp' || (e.ctrlKey && e.key === 'k')) {
|
||||
const prev = filteredAllItems[index - 1] ?? filteredAllItems[filteredAllItems.length - 1];
|
||||
setSelectedItemKey(prev?.key ?? null);
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
setSelectedIndex((prev) => prev + 1);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
setSelectedIndex((prev) => prev - 1);
|
||||
} else if (e.key === 'Enter') {
|
||||
const selected = filteredAllItems[index];
|
||||
setSelectedItemKey(selected?.key ?? null);
|
||||
if (selected) {
|
||||
handleSelectAndClose(selected.onSelect);
|
||||
const item = filteredItems[selectedIndex];
|
||||
if (item) {
|
||||
handleSelectAndClose(item.onSelect);
|
||||
}
|
||||
}
|
||||
},
|
||||
[filteredAllItems, handleSelectAndClose, selectedItem?.key],
|
||||
[filteredItems, handleSelectAndClose, selectedIndex],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-[400px] grid grid-rows-[auto_minmax(0,1fr)] overflow-hidden">
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<div className="px-2 py-2 w-full">
|
||||
<PlainInput
|
||||
<Input
|
||||
hideLabel
|
||||
leftSlot={
|
||||
<div className="h-md w-10 flex justify-center items-center">
|
||||
<Icon icon="search" className="text-fg-subtle" />
|
||||
</div>
|
||||
}
|
||||
name="command"
|
||||
label="Command"
|
||||
placeholder="Search or type a command"
|
||||
className="font-sans !text-base"
|
||||
defaultValue={command}
|
||||
placeholder="Type a command"
|
||||
defaultValue=""
|
||||
onChange={setCommand}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-full px-1.5 overflow-y-auto pb-1">
|
||||
{filteredGroups.map((g) => (
|
||||
<div key={g.key} className="mb-1.5 w-full">
|
||||
<Heading size={2} className="!text-xs uppercase px-1.5 h-sm flex items-center">
|
||||
{g.label}
|
||||
</Heading>
|
||||
{g.items.map((v) => (
|
||||
<CommandPaletteItem
|
||||
active={v.key === selectedItem?.key}
|
||||
key={v.key}
|
||||
onClick={() => handleSelectAndClose(v.onSelect)}
|
||||
rightSlot={
|
||||
v.action && <CommandPaletteAction action={v.action} onAction={v.onSelect} />
|
||||
}
|
||||
>
|
||||
{v.label}
|
||||
</CommandPaletteItem>
|
||||
))}
|
||||
</div>
|
||||
<div className="h-full px-1.5 overflow-y-auto">
|
||||
{filteredItems.map((v, i) => (
|
||||
<CommandPaletteItem
|
||||
active={i === selectedIndex}
|
||||
key={v.key}
|
||||
onClick={() => handleSelectAndClose(v.onSelect)}
|
||||
>
|
||||
{v.label}
|
||||
</CommandPaletteItem>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,39 +108,20 @@ function CommandPaletteItem({
|
||||
children,
|
||||
active,
|
||||
onClick,
|
||||
rightSlot,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
rightSlot?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
<button
|
||||
onClick={onClick}
|
||||
tabIndex={active ? undefined : -1}
|
||||
rightSlot={rightSlot}
|
||||
color="custom"
|
||||
justify="start"
|
||||
className={classNames(
|
||||
'w-full h-sm flex items-center rounded px-1.5',
|
||||
'hover:text-fg',
|
||||
'w-full h-xs flex items-center rounded px-1.5 text-fg-subtle',
|
||||
active && 'bg-background-highlight-secondary text-fg',
|
||||
!active && 'text-fg-subtle',
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{children}</span>
|
||||
</Button>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function CommandPaletteAction({
|
||||
action,
|
||||
onAction,
|
||||
}: {
|
||||
action: HotkeyAction;
|
||||
onAction: () => void;
|
||||
}) {
|
||||
useHotKey(action, onAction);
|
||||
return <HotKey className="ml-auto" action={action} />;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { PairEditorProps } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
import { Separator } from './core/Separator';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
@@ -185,20 +185,18 @@ const EnvironmentEditor = function ({
|
||||
/>
|
||||
</Heading>
|
||||
</HStack>
|
||||
<div className="h-full pr-2 pb-2">
|
||||
<PairOrBulkEditor
|
||||
preferenceName="environment"
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
nameAutocompleteVariables={false}
|
||||
namePlaceholder="VAR_NAME"
|
||||
nameValidate={validateName}
|
||||
valueType={valueVisibility.value ? 'text' : 'password'}
|
||||
valueAutocompleteVariables={true}
|
||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||
pairs={variables}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<PairEditor
|
||||
className="pr-2"
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
nameAutocompleteVariables={false}
|
||||
namePlaceholder="VAR_NAME"
|
||||
nameValidate={validateName}
|
||||
valueType={valueVisibility.value ? 'text' : 'password'}
|
||||
valueAutocompleteVariables={false}
|
||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||
pairs={variables}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ export function ExportDataDialog({
|
||||
const noneSelected = numSelected === 0;
|
||||
return (
|
||||
<VStack space={3} className="w-full mb-3 px-4">
|
||||
<table className="w-full mb-auto min-w-full max-w-full divide-y divide-background-highlight-secondary">
|
||||
<table className="w-full mb-auto min-w-full max-w-full divide-y">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-6 min-w-0 py-2 text-left pl-1">
|
||||
@@ -76,7 +76,7 @@ export function ExportDataDialog({
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-background-highlight-secondary">
|
||||
<tbody className="divide-y">
|
||||
{workspaces.map((w) => (
|
||||
<tr key={w.id}>
|
||||
<td className="min-w-0 py-1 pl-1">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import type { Pair, PairEditorProps } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
|
||||
type Props = {
|
||||
forceUpdateKey: string;
|
||||
@@ -27,8 +27,7 @@ export function FormUrlencodedEditor({ body, forceUpdateKey, onChange }: Props)
|
||||
);
|
||||
|
||||
return (
|
||||
<PairOrBulkEditor
|
||||
preferenceName="form_urlencoded"
|
||||
<PairEditor
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="entry_name"
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -41,6 +42,7 @@ export function GlobalHooks() {
|
||||
|
||||
// Other useful things
|
||||
useSyncThemeToDocument();
|
||||
useGlobalCommands();
|
||||
useCommandPalette();
|
||||
useNotificationToast();
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">
|
||||
<Separator dashed className="pb-1">
|
||||
<Separator variant="primary" className="pb-1">
|
||||
Variables
|
||||
</Separator>
|
||||
<Editor
|
||||
|
||||
@@ -111,7 +111,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
) : messages.length >= 0 ? (
|
||||
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
||||
) : (
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'urlBar.focus']} />
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import { mimeTypes } from '../lib/data/mimetypes';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import type { PairEditorProps } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
|
||||
type Props = {
|
||||
forceUpdateKey: string;
|
||||
@@ -16,8 +16,7 @@ type Props = {
|
||||
|
||||
export function HeadersEditor({ headers, onChange, forceUpdateKey }: Props) {
|
||||
return (
|
||||
<PairOrBulkEditor
|
||||
preferenceName="headers"
|
||||
<PairEditor
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
pairs={headers}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
|
||||
interface Props {
|
||||
hide: () => void;
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export function OpenWorkspaceDialog({ hide, workspace }: Props) {
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const [remember, setRemember] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<VStack space={3}>
|
||||
<p>
|
||||
Where would you like to open <InlineCode>{workspace.name}</InlineCode>?
|
||||
</p>
|
||||
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
||||
<Button
|
||||
className="focus"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
hide();
|
||||
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
This Window
|
||||
</Button>
|
||||
<Button
|
||||
className="focus"
|
||||
color="secondary"
|
||||
rightSlot={<Icon icon="externalLink" />}
|
||||
onClick={() => {
|
||||
hide();
|
||||
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
}
|
||||
}}
|
||||
>
|
||||
New Window
|
||||
</Button>
|
||||
</HStack>
|
||||
{settings && (
|
||||
<HStack justifyContent="end">
|
||||
<Checkbox checked={remember} title="Remember my choice" onChange={setRemember} />
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -47,13 +47,13 @@ export function Overlay({
|
||||
variant === 'default' && 'bg-background-backdrop backdrop-blur-sm',
|
||||
)}
|
||||
/>
|
||||
{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" />
|
||||
)}
|
||||
{children}
|
||||
</motion.div>
|
||||
</FocusTrap>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { usePortal } from '../hooks/usePortal';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
name: string;
|
||||
|
||||
@@ -53,7 +53,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
||||
key: request.id,
|
||||
label: fallbackRequestName(request),
|
||||
// leftSlot: <CountBadge className="!ml-0 px-0 w-5" count={recentRequestItems.length} />,
|
||||
leftSlot: <HttpMethodTag className="text-right" shortNames request={request} />,
|
||||
leftSlot: <HttpMethodTag request={request} />,
|
||||
onSelect: () => {
|
||||
routes.navigate('request', {
|
||||
requestId: request.id,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
||||
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||
import { useSaveResponse } from '../hooks/useSaveResponse';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { pluralize } from '../lib/pluralize';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
@@ -26,49 +25,30 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||
const saveResponse = useSaveResponse(activeResponse);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
key: 'save',
|
||||
label: 'Save to File',
|
||||
onSelect: saveResponse.mutate,
|
||||
leftSlot: <Icon icon="save" />,
|
||||
hidden: responses.length === 0,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
key: 'clear-single',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
label: 'Clear Response',
|
||||
onSelect: deleteResponse.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
key: 'unpin',
|
||||
label: 'Unpin Response',
|
||||
onSelect: () => onPinnedResponseId(activeResponse.id),
|
||||
leftSlot: <Icon icon="unpin" />,
|
||||
hidden: latestResponseId === activeResponse.id,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{ type: 'separator', label: 'History' },
|
||||
{
|
||||
key: 'clear-all',
|
||||
label: `Delete ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
hidden: responses.length <= 1,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: 'separator', label: 'History' },
|
||||
...responses.slice(0, 20).map((r: HttpResponse) => ({
|
||||
key: r.id,
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<StatusTag className="text-sm" response={r} />
|
||||
<span className="text-fg-subtle">→</span>{' '}
|
||||
<span>→</span>{' '}
|
||||
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
|
||||
</HStack>
|
||||
),
|
||||
|
||||
@@ -262,15 +262,13 @@ export const RequestPane = memo(function RequestPane({
|
||||
options:
|
||||
requests.length > 0
|
||||
? [
|
||||
...requests
|
||||
.filter((r) => r.id !== activeRequestId)
|
||||
.map(
|
||||
(r) =>
|
||||
({
|
||||
type: 'constant',
|
||||
label: r.url,
|
||||
} as GenericCompletionOption),
|
||||
),
|
||||
...requests.map(
|
||||
(r) =>
|
||||
({
|
||||
type: 'constant',
|
||||
label: r.url,
|
||||
} as GenericCompletionOption),
|
||||
),
|
||||
]
|
||||
: [
|
||||
{ label: 'http://', type: 'constant' },
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { AudioViewer } from './responseViewers/AudioViewer';
|
||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { PdfViewer } from './responseViewers/PdfViewer';
|
||||
import { TextViewer } from './responseViewers/TextViewer';
|
||||
import { VideoViewer } from './responseViewers/VideoViewer';
|
||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||
@@ -84,7 +83,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
>
|
||||
{activeResponse == null ? (
|
||||
<HotKeyList
|
||||
hotkeys={['http_request.send', 'http_request.create', 'sidebar.focus', 'urlBar.focus']}
|
||||
hotkeys={['http_request.send', 'http_request.create', 'sidebar.toggle', 'urlBar.focus']}
|
||||
/>
|
||||
) : isResponseLoading(activeResponse) ? (
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
@@ -159,12 +158,12 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
<AudioViewer response={activeResponse} />
|
||||
) : contentType?.startsWith('video') ? (
|
||||
<VideoViewer response={activeResponse} />
|
||||
) : contentType?.match(/pdf/) ? (
|
||||
<PdfViewer response={activeResponse} />
|
||||
) : contentType?.match(/csv|tab-separated/) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
||||
<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
|
||||
className="-mr-2" // Pull to the right
|
||||
|
||||
@@ -50,33 +50,6 @@ export function SettingsGeneral() {
|
||||
onClick={() => checkForUpdates.mutateAsync()}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
name="openWorkspace"
|
||||
label="Open Workspace"
|
||||
labelPosition="left"
|
||||
size="sm"
|
||||
value={
|
||||
settings.openWorkspaceNewWindow === true
|
||||
? 'new'
|
||||
: settings.openWorkspaceNewWindow === false
|
||||
? 'current'
|
||||
: 'ask'
|
||||
}
|
||||
onChange={(v) => {
|
||||
if (v === 'current') {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
} else if (v === 'new') {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
} else {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: null });
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Always Ask', value: 'ask' },
|
||||
{ label: 'Current Window', value: 'current' },
|
||||
{ label: 'New Window', value: 'new' },
|
||||
]}
|
||||
/>
|
||||
<Separator className="my-4" />
|
||||
|
||||
<Heading size={2}>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function Sidebar({ className }: Props) {
|
||||
[collapsed.value],
|
||||
);
|
||||
|
||||
const { tree, treeParentMap, selectableRequests } = useMemo<{
|
||||
const { tree, treeParentMap, selectableRequests, selectedRequest } = useMemo<{
|
||||
tree: TreeNode | null;
|
||||
treeParentMap: Record<string, TreeNode>;
|
||||
selectedRequest: HttpRequest | GrpcRequest | null;
|
||||
@@ -159,6 +159,8 @@ export function Sidebar({ className }: Props) {
|
||||
return { tree, treeParentMap, selectableRequests, selectedRequest };
|
||||
}, [activeWorkspace, selectedId, requests, folders, collapsed.value]);
|
||||
|
||||
const deleteSelectedRequest = useDeleteRequest(selectedRequest);
|
||||
|
||||
const focusActiveRequest = useCallback(
|
||||
(
|
||||
args: {
|
||||
@@ -190,7 +192,7 @@ export function Sidebar({ className }: Props) {
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (id: string, opts: { noFocus?: boolean } = {}) => {
|
||||
async (id: string) => {
|
||||
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const node = children.find((m) => m.item.id === id) ?? null;
|
||||
@@ -210,7 +212,7 @@ export function Sidebar({ className }: Props) {
|
||||
});
|
||||
setSelectedId(id);
|
||||
setSelectedTree(tree);
|
||||
if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } });
|
||||
focusActiveRequest({ forced: { id, tree } });
|
||||
}
|
||||
},
|
||||
[treeParentMap, collapsed, routes, activeEnvironmentId, focusActiveRequest],
|
||||
@@ -228,6 +230,21 @@ export function Sidebar({ className }: Props) {
|
||||
|
||||
const handleBlur = useCallback(() => setHasFocus(false), []);
|
||||
|
||||
const handleDeleteKey = useCallback(
|
||||
async (e: KeyboardEvent) => {
|
||||
if (!hasFocus) return;
|
||||
e.preventDefault();
|
||||
|
||||
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||
if (selected == null) return;
|
||||
await deleteSelectedRequest.mutateAsync();
|
||||
},
|
||||
[hasFocus, selectableRequests, deleteSelectedRequest, selectedId],
|
||||
);
|
||||
|
||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||
useKeyPressEvent('Delete', handleDeleteKey);
|
||||
|
||||
useHotKey('sidebar.focus', async () => {
|
||||
// Hide the sidebar if it's already focused
|
||||
if (!hidden && hasFocus) {
|
||||
@@ -603,7 +620,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
) {
|
||||
const activeRequest = useActiveRequest();
|
||||
const deleteFolder = useDeleteFolder(itemId);
|
||||
const deleteRequest = useDeleteRequest(itemId);
|
||||
const deleteRequest = useDeleteRequest(activeRequest ?? null);
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||
const copyAsCurl = useCopyAsCurl(itemId);
|
||||
@@ -822,7 +839,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="flex items-end gap-2 min-w-0">
|
||||
{itemPrefix}
|
||||
{editing ? (
|
||||
<input
|
||||
|
||||
@@ -31,6 +31,7 @@ export function SidebarActions() {
|
||||
className="pointer-events-auto"
|
||||
size="sm"
|
||||
title="Show sidebar"
|
||||
hotkeyAction="sidebar.toggle"
|
||||
icon={hidden ? 'leftPanelHidden' : 'leftPanelVisible'}
|
||||
/>
|
||||
<CreateDropdown hotKeyAction="http_request.create">
|
||||
|
||||
@@ -67,7 +67,7 @@ export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
<ToastContext.Provider value={state}>
|
||||
{children}
|
||||
<Portal name="toasts">
|
||||
<div className="absolute right-0 bottom-0 z-10">
|
||||
<div className="absolute right-0 bottom-0">
|
||||
<AnimatePresence>
|
||||
{toasts.map((props: PrivateToastEntry) => (
|
||||
<ToastInstance key={props.id} {...props} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
|
||||
type Props = {
|
||||
forceUpdateKey: string;
|
||||
@@ -9,8 +9,7 @@ type Props = {
|
||||
|
||||
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange }: Props) {
|
||||
return (
|
||||
<PairOrBulkEditor
|
||||
preferenceName="url_parameters"
|
||||
<PairEditor
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="param_name"
|
||||
|
||||
@@ -186,7 +186,7 @@ export default function Workspace() {
|
||||
</div>
|
||||
) : activeRequest == null ? (
|
||||
<HotKeyList
|
||||
hotkeys={['http_request.create', 'sidebar.focus', 'settings.show']}
|
||||
hotkeys={['http_request.create', 'sidebar.toggle', 'settings.show']}
|
||||
bottomSlot={
|
||||
<HStack space={1} justifyContent="center" className="mt-3">
|
||||
<Button variant="border" size="sm" onClick={() => importData.mutate()}>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useCommand } from '../hooks/useCommands';
|
||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { getRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { RadioDropdownItem } from './core/RadioDropdown';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
|
||||
|
||||
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown' | 'leftSlot'>;
|
||||
|
||||
@@ -30,25 +30,86 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const createWorkspace = useCommand('workspace.create');
|
||||
const dialog = useDialog();
|
||||
const prompt = usePrompt();
|
||||
const settings = useSettings();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
const routes = useAppRoutes();
|
||||
|
||||
const { workspaceItems, extraItems } = useMemo<{
|
||||
workspaceItems: RadioDropdownItem[];
|
||||
extraItems: DropdownItem[];
|
||||
}>(() => {
|
||||
const workspaceItems: RadioDropdownItem[] = workspaces.map((w) => ({
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
|
||||
key: w.id,
|
||||
label: w.name,
|
||||
value: w.id,
|
||||
leftSlot: w.id === activeWorkspaceId ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: async () => {
|
||||
dialog.show({
|
||||
id: 'open-workspace',
|
||||
size: 'sm',
|
||||
title: 'Open Workspace',
|
||||
description: (
|
||||
<>
|
||||
Where would you like to open <InlineCode>{w.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
render: ({ hide }) => {
|
||||
return (
|
||||
<HStack space={2} justifyContent="start" className="mt-4 mb-6 flex-row-reverse">
|
||||
<Button
|
||||
className="focus"
|
||||
color="primary"
|
||||
onClick={async () => {
|
||||
hide();
|
||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||
const requestId = (await getRecentRequests(w.id))[0];
|
||||
if (requestId != null) {
|
||||
routes.navigate('request', { workspaceId: w.id, environmentId, requestId });
|
||||
} else {
|
||||
routes.navigate('workspace', { workspaceId: w.id, environmentId });
|
||||
}
|
||||
}}
|
||||
>
|
||||
This Window
|
||||
</Button>
|
||||
<Button
|
||||
className="focus"
|
||||
color="secondary"
|
||||
rightSlot={<Icon icon="externalLink" />}
|
||||
onClick={async () => {
|
||||
hide();
|
||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||
const requestId = (await getRecentRequests(w.id))[0];
|
||||
const path =
|
||||
requestId != null
|
||||
? routes.paths.request({
|
||||
workspaceId: w.id,
|
||||
environmentId,
|
||||
requestId,
|
||||
})
|
||||
: routes.paths.workspace({ workspaceId: w.id, environmentId });
|
||||
await invoke('cmd_new_window', { url: path });
|
||||
}}
|
||||
>
|
||||
New Window
|
||||
</Button>
|
||||
</HStack>
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
const extraItems: DropdownItem[] = [
|
||||
const activeWorkspaceItems: DropdownItem[] =
|
||||
workspaces.length <= 1
|
||||
? []
|
||||
: [
|
||||
...workspaceItems,
|
||||
{
|
||||
type: 'separator',
|
||||
label: activeWorkspace?.name,
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
...activeWorkspaceItems,
|
||||
{
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
@@ -82,50 +143,23 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: 'create-workspace',
|
||||
label: 'New Workspace',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: createWorkspace.mutate,
|
||||
onSelect: () => createWorkspace.mutate({}),
|
||||
},
|
||||
];
|
||||
|
||||
return { workspaceItems, extraItems };
|
||||
}, [
|
||||
activeWorkspace?.name,
|
||||
activeWorkspaceId,
|
||||
createWorkspace,
|
||||
deleteWorkspace.mutate,
|
||||
dialog,
|
||||
prompt,
|
||||
routes,
|
||||
updateWorkspace,
|
||||
workspaces,
|
||||
]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (workspaceId: string | null) => {
|
||||
if (workspaceId == null) return;
|
||||
|
||||
if (typeof openWorkspaceNewWindow === 'boolean') {
|
||||
openWorkspace.mutate({ workspaceId, inNewWindow: openWorkspaceNewWindow });
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = await getWorkspace(workspaceId);
|
||||
if (workspace == null) return;
|
||||
|
||||
dialog.show({
|
||||
id: 'open-workspace',
|
||||
size: 'sm',
|
||||
title: 'Open Workspace',
|
||||
render: ({ hide }) => <OpenWorkspaceDialog workspace={workspace} hide={hide} />,
|
||||
});
|
||||
},
|
||||
[dialog, openWorkspace, openWorkspaceNewWindow],
|
||||
);
|
||||
|
||||
return (
|
||||
<RadioDropdown
|
||||
items={workspaceItems}
|
||||
extraItems={extraItems}
|
||||
onChange={handleChange}
|
||||
value={activeWorkspaceId}
|
||||
>
|
||||
<Dropdown items={items}>
|
||||
<Button
|
||||
size="sm"
|
||||
className={classNames(
|
||||
@@ -137,6 +171,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
>
|
||||
{activeWorkspace?.name ?? 'Workspace'}
|
||||
</Button>
|
||||
</RadioDropdown>
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Editor } from './Editor';
|
||||
import type { PairEditorProps } from './PairEditor';
|
||||
|
||||
type Props = PairEditorProps;
|
||||
|
||||
export function BulkPairEditor({ pairs, onChange, namePlaceholder, valuePlaceholder }: Props) {
|
||||
const pairsText = useMemo(() => {
|
||||
return pairs
|
||||
.filter((p) => !(p.name.trim() === '' && p.value.trim() === ''))
|
||||
.map((p) => `${p.name}: ${p.value}`)
|
||||
.join('\n');
|
||||
}, [pairs]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(text: string) => {
|
||||
const pairs = text
|
||||
.split('\n')
|
||||
.filter((l: string) => l.trim())
|
||||
.map(lineToPair);
|
||||
onChange(pairs);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
|
||||
defaultValue={pairsText}
|
||||
contentType="pairs"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function lineToPair(l: string): PairEditorProps['pairs'][0] {
|
||||
const [name, ...values] = l.split(':');
|
||||
const pair: PairEditorProps['pairs'][0] = {
|
||||
name: (name ?? '').trim(),
|
||||
value: values.join(':').trim(),
|
||||
};
|
||||
return pair;
|
||||
}
|
||||
@@ -18,7 +18,6 @@ export interface DialogProps {
|
||||
hideX?: boolean;
|
||||
noPadding?: boolean;
|
||||
noScroll?: boolean;
|
||||
vAlign?: 'top' | 'center';
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
@@ -32,7 +31,6 @@ export function Dialog({
|
||||
hideX,
|
||||
noPadding,
|
||||
noScroll,
|
||||
vAlign = 'center',
|
||||
}: DialogProps) {
|
||||
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
|
||||
const descriptionId = useMemo(
|
||||
@@ -52,14 +50,13 @@ export function Dialog({
|
||||
|
||||
return (
|
||||
<Overlay open={open} onClose={onClose} portalName="dialog">
|
||||
<div
|
||||
className={classNames(
|
||||
'x-theme-dialog absolute inset-0 flex flex-col items-center pointer-events-none',
|
||||
vAlign === 'top' && 'justify-start',
|
||||
vAlign === 'center' && 'justify-center',
|
||||
)}
|
||||
>
|
||||
<div role="dialog" aria-labelledby={titleId} aria-describedby={descriptionId}>
|
||||
<div className="x-theme-dialog absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={descriptionId}
|
||||
className="pointer-events-auto"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ top: 5, scale: 0.97 }}
|
||||
animate={{ top: 0, scale: 1 }}
|
||||
@@ -69,12 +66,12 @@ export function Dialog({
|
||||
'relative bg-background pointer-events-auto',
|
||||
'rounded-lg',
|
||||
'border border-background-highlight-secondary shadow-lg shadow-[rgba(0,0,0,0.1)]',
|
||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-4rem)]',
|
||||
size === 'sm' && 'w-[28rem] max-h-[80vh]',
|
||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
||||
size === 'sm' && 'w-[25rem] max-h-[80vh]',
|
||||
size === 'md' && 'w-[45rem] max-h-[80vh]',
|
||||
size === 'lg' && 'w-[65rem] max-h-[80vh]',
|
||||
size === 'full' && 'w-[100vw] h-[100vh]',
|
||||
size === 'dynamic' && 'min-w-[20rem] max-w-[100vw] w-full mt-8',
|
||||
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
|
||||
)}
|
||||
>
|
||||
{title ? (
|
||||
|
||||
@@ -35,7 +35,6 @@ import { HStack, VStack } from './Stacks';
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
label?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemDefault = {
|
||||
@@ -274,7 +273,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
let nextIndex = (currIndex ?? 0) - 1;
|
||||
const maxTries = items.length;
|
||||
for (let i = 0; i < maxTries; i++) {
|
||||
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
|
||||
if (items[nextIndex]?.type === 'separator') {
|
||||
nextIndex--;
|
||||
} else if (nextIndex < 0) {
|
||||
nextIndex = items.length - 1;
|
||||
@@ -291,7 +290,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
let nextIndex = (currIndex ?? -1) + 1;
|
||||
const maxTries = items.length;
|
||||
for (let i = 0; i < maxTries; i++) {
|
||||
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
|
||||
if (items[nextIndex]?.type === 'separator') {
|
||||
nextIndex++;
|
||||
} else if (nextIndex >= items.length) {
|
||||
nextIndex = 0;
|
||||
@@ -374,7 +373,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
right: onRight ? docRect.width - triggerShape.right : undefined,
|
||||
left: !onRight ? triggerShape.left : undefined,
|
||||
minWidth: fullWidth ? triggerWidth : undefined,
|
||||
maxWidth: '40rem',
|
||||
};
|
||||
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
|
||||
const triangleStyles = onRight
|
||||
@@ -457,9 +455,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
<span className="text-fg-subtler text-center px-2 py-1">No matches</span>
|
||||
)}
|
||||
{filteredItems.map((item, i) => {
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
if (item.type === 'separator') {
|
||||
return (
|
||||
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
|
||||
@@ -467,6 +462,9 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
</Separator>
|
||||
);
|
||||
}
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
focused={i === selectedIndex}
|
||||
@@ -539,12 +537,20 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
'h-xs', // More compact
|
||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap',
|
||||
'focus:bg-background-highlight focus:text-fg rounded',
|
||||
item.variant === 'danger' && '!text-fg-danger',
|
||||
item.variant === 'notify' && '!text-fg-primary',
|
||||
item.variant === 'default' && 'text-fg-subtle',
|
||||
item.variant === 'danger' && 'text-fg-danger',
|
||||
item.variant === 'notify' && 'text-fg-primary',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={classNames('truncate')}>{item.label}</div>
|
||||
<div
|
||||
className={classNames(
|
||||
// Add padding on right when no right slot, for some visual balance
|
||||
!item.rightSlot && 'pr-4',
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
// Use ref so we can update the handler without re-initializing the editor
|
||||
const handleChange = useRef<EditorProps['onChange']>(onChange);
|
||||
useEffect(() => {
|
||||
handleChange.current = onChange ? onChange : onChange;
|
||||
handleChange.current = onChange;
|
||||
}, [onChange]);
|
||||
|
||||
// Use ref so we can update the handler without re-initializing the editor
|
||||
@@ -332,20 +332,6 @@ function getExtensions({
|
||||
|
||||
return [
|
||||
...baseExtensions, // Must be first
|
||||
EditorView.domEventHandlers({
|
||||
focus: () => {
|
||||
onFocus.current?.();
|
||||
},
|
||||
blur: () => {
|
||||
onBlur.current?.();
|
||||
},
|
||||
keydown: (e) => {
|
||||
onKeyDown.current?.(e);
|
||||
},
|
||||
paste: (e) => {
|
||||
onPaste.current?.(e.clipboardData?.getData('text/plain') ?? '');
|
||||
},
|
||||
}),
|
||||
tooltips({ parent }),
|
||||
keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
|
||||
...(singleLine ? [singleLineExt()] : []),
|
||||
@@ -363,6 +349,21 @@ function getExtensions({
|
||||
onChange.current?.(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
|
||||
EditorView.domEventHandlers({
|
||||
focus: () => {
|
||||
onFocus.current?.();
|
||||
},
|
||||
blur: () => {
|
||||
onBlur.current?.();
|
||||
},
|
||||
keydown: (e) => {
|
||||
onKeyDown.current?.(e);
|
||||
},
|
||||
paste: (e) => {
|
||||
onPaste.current?.(e.clipboardData?.getData('text/plain') ?? '');
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { Environment, Workspace } from '../../../lib/models';
|
||||
import type { EditorProps } from './index';
|
||||
import { pairs } from './pairs/extension';
|
||||
import { text } from './text/extension';
|
||||
import { twig } from './twig/extension';
|
||||
import { url } from './url/extension';
|
||||
@@ -72,7 +71,6 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||
'application/xml': xml(),
|
||||
'text/xml': xml(),
|
||||
url: url(),
|
||||
pairs: pairs(),
|
||||
};
|
||||
|
||||
export function getLanguageExtension({
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||
import { parser } from './pairs';
|
||||
|
||||
const urlLanguage = LRLanguage.define({
|
||||
parser,
|
||||
languageData: {},
|
||||
});
|
||||
|
||||
export function pairs() {
|
||||
return new LanguageSupport(urlLanguage, []);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { styleTags, tags as t } from '@lezer/highlight';
|
||||
|
||||
export const highlight = styleTags({
|
||||
Sep: t.bracket,
|
||||
Key: t.attributeName,
|
||||
Value: t.string,
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
@top pairs { (Key? Sep Value)* }
|
||||
|
||||
@tokens {
|
||||
Sep { ":" }
|
||||
Key { ![:]+ }
|
||||
Value { ![\n]+ }
|
||||
}
|
||||
|
||||
@external propSource highlight from "./highlight"
|
||||
@@ -1,19 +0,0 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!QQQOPOOOYOQO'#CaO_OPO'#CaQQOPOOOOOO,58{,58{OdOQO,58{OOOO-E6_-E6_OOOO1G.g1G.g",
|
||||
stateData: "i~OQQORPO~OSSO~ORTO~OSVO~O",
|
||||
goto: "]UPPPPPVQRORUR",
|
||||
nodeNames: "⚠ pairs Key Sep Value",
|
||||
maxTerm: 6,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "#oRRVOYhYZ!UZ![h![!]#[!];'Sh;'S;=`#U<%lOhRoVQPSQOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!UQ!rSSQOY!mZ;'S!m;'S;=`#O<%lO!mQ#RP;=`<%l!mR#XP;=`<%lhR#cSRPSQOY!mZ;'S!m;'S;=`#O<%lO!m",
|
||||
tokenizers: [0, 1],
|
||||
topRules: {"pairs":[0,1]},
|
||||
tokenPrec: 0
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user