mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 22:27:51 +01:00
Compare commits
21 Commits
v2024.6.0-
...
v2024.6.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33763b6d2f | ||
|
|
641fe86cf7 | ||
|
|
65e7c804d7 | ||
|
|
23ec8bee8f | ||
|
|
8aeeaa2e09 | ||
|
|
57f01d249e | ||
|
|
6c5a914db6 | ||
|
|
155e51aa74 | ||
|
|
012a984456 | ||
|
|
25800202f2 | ||
|
|
a058064f1f | ||
|
|
9f40804532 | ||
|
|
26cc467858 | ||
|
|
be1cf7bf65 | ||
|
|
ea4f104ca7 | ||
|
|
32a28a3170 | ||
|
|
6215914212 | ||
|
|
a2dbd7f849 | ||
|
|
5bb9815f4b | ||
|
|
7cd8ac3b21 | ||
|
|
456d3aaf52 |
Binary file not shown.
696
package-lock.json
generated
696
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "yaak-app",
|
||||
"name": "yaak",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -52,6 +52,7 @@
|
||||
"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",
|
||||
@@ -64,6 +65,7 @@
|
||||
"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",
|
||||
@@ -103,6 +105,7 @@
|
||||
"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"
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/exporter-curl'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-jsonpath'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-xpath'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-curl'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-insomnia'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-postman'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
||||
},
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
outDir: resolve(__dirname, 'build'),
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-yaak'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<?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>
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
</plist>
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
const o = `\\
|
||||
`;
|
||||
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)) {
|
||||
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)) {
|
||||
if (e.file) {
|
||||
let c = `${e.name}=@${e.file}`;
|
||||
c += e.contentType ? `;type=${e.contentType}` : "", t.push(a, c);
|
||||
let d = `${e.name}=@${e.file}`;
|
||||
d += e.contentType ? `;type=${e.contentType}` : "", n.push(a, d);
|
||||
} else
|
||||
t.push(a, i(`${e.name}=${e.value}`));
|
||||
t.push(o);
|
||||
n.push(a, i(`${e.name}=${e.value}`));
|
||||
n.push(o);
|
||||
}
|
||||
} else
|
||||
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(
|
||||
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(
|
||||
"--user",
|
||||
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(" ");
|
||||
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(" ");
|
||||
}
|
||||
function i(n) {
|
||||
return `'${n.replace(/'/g, "\\'")}'`;
|
||||
function i(p) {
|
||||
return `'${p.replace(/'/g, "\\'")}'`;
|
||||
}
|
||||
function p(n) {
|
||||
return n.enabled !== !1 && !!n.name;
|
||||
function h(p) {
|
||||
return p.enabled !== !1 && !!p.name;
|
||||
}
|
||||
export {
|
||||
d as pluginHookExport
|
||||
y as pluginHookExport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
1
src-tauri/plugins/exporter-curl/index.mjs.map
Normal file
1
src-tauri/plugins/exporter-curl/index.mjs.map
Normal file
@@ -0,0 +1 @@
|
||||
{"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(le) {
|
||||
return le && le.__esModule && Object.prototype.hasOwnProperty.call(le, "default") ? le.default : le;
|
||||
function Nt(ce) {
|
||||
return ce && ce.__esModule && Object.prototype.hasOwnProperty.call(ce, "default") ? ce.default : ce;
|
||||
}
|
||||
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.');
|
||||
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.');
|
||||
}
|
||||
var Ve = { exports: {} };
|
||||
/*! jsonpath 1.1.1 */
|
||||
(function(le, Ne) {
|
||||
(function(ce, Ne) {
|
||||
(function(T) {
|
||||
le.exports = T();
|
||||
ce.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(ce()), _("]") || O(","));
|
||||
_(",") ? (N(), e.push(null)) : (e.push(le()), _("]") || 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 = ce(), 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 = le(), 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 = ce(), n.markEnd(n.createProperty("init", t, l), x);
|
||||
return t = De(), O(":"), l = le(), 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(ce()), !_(")")); )
|
||||
for (; r < h && (e.push(le()), !_(")")); )
|
||||
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 = ce(), p.allowIn = t, O(":"), l = ce(), e = n.createConditionalExpression(e, u, l), n.markEnd(e, C)), e;
|
||||
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;
|
||||
}
|
||||
function ce() {
|
||||
function le() {
|
||||
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 = ce(), 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 = le(), l = n.markEnd(n.createAssignmentExpression(e.value, t, u), C)), l;
|
||||
}
|
||||
function te() {
|
||||
var e, t = i;
|
||||
if (e = ce(), _(",")) {
|
||||
if (e = le(), _(",")) {
|
||||
for (e = n.createSequenceExpression([e]); r < h && _(","); )
|
||||
N(), e.expressions.push(ce());
|
||||
N(), e.expressions.push(le());
|
||||
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 = ce()) : _("=") && (N(), t = ce()), n.markEnd(n.createVariableDeclarator(u, t), 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);
|
||||
}
|
||||
function Te(e) {
|
||||
var t = [];
|
||||
@@ -3176,16 +3176,17 @@ Expecting ` + se.join(", ") + ", got '" + (this.terminals_[w] || w) + "'" : ie =
|
||||
})(Ve);
|
||||
var Pt = Ve.exports;
|
||||
const Lt = /* @__PURE__ */ Nt(Pt);
|
||||
function Rt(le, Ne) {
|
||||
let T;
|
||||
function Rt(ce, Ne, T) {
|
||||
let L;
|
||||
try {
|
||||
T = JSON.parse(Ne);
|
||||
L = JSON.parse(T);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const L = Lt.query(T, le);
|
||||
return { filtered: JSON.stringify(L, null, 2) };
|
||||
const B = Lt.query(L, Ne);
|
||||
return { filtered: JSON.stringify(B, null, 2) };
|
||||
}
|
||||
export {
|
||||
Rt as pluginHookResponseFilter
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
1
src-tauri/plugins/filter-jsonpath/index.mjs.map
Normal file
1
src-tauri/plugins/filter-jsonpath/index.mjs.map
Normal file
File diff suppressed because one or more lines are too long
@@ -6116,10 +6116,11 @@ Tr.__DOMHandler = Dr;
|
||||
Tr.normalizeLineEndings = Rt;
|
||||
Tr.DOMParser = _t;
|
||||
var Cu = Tr.DOMParser;
|
||||
function yu(t, u) {
|
||||
const n = new Cu().parseFromString(u, "text/xml");
|
||||
return { filtered: `${Qt.select(t, n)}` };
|
||||
function yu(t, u, n) {
|
||||
const s = new Cu().parseFromString(n, "text/xml");
|
||||
return { filtered: `${Qt.select(u, s)}` };
|
||||
}
|
||||
export {
|
||||
yu as pluginHookResponseFilter
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
|
||||
1
src-tauri/plugins/filter-xpath/index.mjs.map
Normal file
1
src-tauri/plugins/filter-xpath/index.mjs.map
Normal file
File diff suppressed because one or more lines are too long
298
src-tauri/plugins/importer-curl/index.mjs
Normal file
298
src-tauri/plugins/importer-curl/index.mjs
Normal file
@@ -0,0 +1,298 @@
|
||||
var j = "(?:" + [
|
||||
"\\|\\|",
|
||||
"\\&\\&",
|
||||
";;",
|
||||
"\\|\\&",
|
||||
"\\<\\(",
|
||||
"\\<\\<\\<",
|
||||
">>",
|
||||
">\\&",
|
||||
"<\\&",
|
||||
"[&;()|<>]"
|
||||
].join("|") + ")", D = new RegExp("^" + j + "$"), _ = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, q = "'", G = '"', U = "$", R = "", z = 4294967296;
|
||||
for (var L = 0; L < 4; L++)
|
||||
R += (z * Math.random()).toString(16);
|
||||
var J = new RegExp("^" + R);
|
||||
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;
|
||||
}
|
||||
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 K(n, s, t) {
|
||||
t || (t = {});
|
||||
var r = t.escape || "\\", l = "(\\" + r + `['"` + _ + `]|[^\\s'"` + _ + "])+", h = new RegExp([
|
||||
"(" + j + ")",
|
||||
// control chars
|
||||
"(" + l + "|" + M + "|" + Q + ")+"
|
||||
].join("|"), "g"), p = X(n, h);
|
||||
if (p.length === 0)
|
||||
return [];
|
||||
s || (s = {});
|
||||
var g = !1;
|
||||
return p.map(function(d) {
|
||||
var e = d[0];
|
||||
if (!e || g)
|
||||
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;
|
||||
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);
|
||||
}
|
||||
return F(s, "", f);
|
||||
}
|
||||
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;
|
||||
else {
|
||||
if (D.test(u))
|
||||
return { op: e };
|
||||
if (V.test(u)) {
|
||||
g = !0;
|
||||
var b = { comment: n.slice(d.index + a + 1) };
|
||||
return m.length ? [m, b] : [b];
|
||||
} else
|
||||
u === r ? T = !0 : u === U ? m += $() : m += u;
|
||||
}
|
||||
}
|
||||
return O ? { op: "glob", pattern: m } : m;
|
||||
}).reduce(function(d, e) {
|
||||
return typeof e > "u" ? d : d.concat(e);
|
||||
}, []);
|
||||
}
|
||||
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;
|
||||
}));
|
||||
}, []);
|
||||
}, Z = Y;
|
||||
const ae = "curl", se = "cURL", ie = "cURL command line tool", H = ["d", "data", "data-raw", "data-urlencode", "data-binary", "data-ascii"], ee = [
|
||||
["url"],
|
||||
// Specify the URL explicitly
|
||||
["user", "u"],
|
||||
// Authentication
|
||||
["digest"],
|
||||
// Apply auth as digest
|
||||
["header", "H"],
|
||||
["cookie", "b"],
|
||||
["get", "G"],
|
||||
// Put the post data in the URL
|
||||
["d", "data"],
|
||||
// Add url encoded data
|
||||
["data-raw"],
|
||||
["data-urlencode"],
|
||||
["data-binary"],
|
||||
["data-ascii"],
|
||||
["form", "F"],
|
||||
// Add multipart data
|
||||
["request", "X"],
|
||||
// Request method
|
||||
H
|
||||
].flatMap((n) => n);
|
||||
function oe(n, s) {
|
||||
if (!s.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);
|
||||
continue;
|
||||
}
|
||||
if ("comment" in e)
|
||||
continue;
|
||||
const { op: c } = e;
|
||||
if (c === ";") {
|
||||
t.push(l), l = [];
|
||||
continue;
|
||||
}
|
||||
if (c != null && c.startsWith("$")) {
|
||||
const T = c.slice(2, c.length - 1).replace(/\\'/g, "'");
|
||||
l.push(T);
|
||||
continue;
|
||||
}
|
||||
c === "glob" && l.push(e.pattern);
|
||||
}
|
||||
t.push(l);
|
||||
const g = {
|
||||
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]
|
||||
}
|
||||
};
|
||||
}
|
||||
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))
|
||||
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);
|
||||
} else
|
||||
o && r.push(o);
|
||||
}
|
||||
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(/:(.*)$/);
|
||||
return E ? {
|
||||
name: (o ?? "").trim(),
|
||||
value: E.trim(),
|
||||
enabled: !0
|
||||
} : {
|
||||
name: (o ?? "").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({
|
||||
name: "Cookie",
|
||||
value: $,
|
||||
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 = {
|
||||
name: E,
|
||||
enabled: !0
|
||||
};
|
||||
return v.indexOf("@") === 0 ? y.file = v.slice(1) : y.value = v, 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 || "")
|
||||
}))
|
||||
}, a.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({
|
||||
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"), {
|
||||
id: N("http_request"),
|
||||
model: "http_request",
|
||||
workspaceId: s,
|
||||
name: "",
|
||||
urlParameters: l,
|
||||
url: h,
|
||||
method: P,
|
||||
headers: a,
|
||||
authentication: O,
|
||||
authenticationType: m,
|
||||
body: w,
|
||||
bodyType: I,
|
||||
folderId: null,
|
||||
sortPriority: 0
|
||||
};
|
||||
}
|
||||
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")
|
||||
continue;
|
||||
const [h, p] = l.split("=");
|
||||
l.startsWith("@") ? s.push({
|
||||
name: h ?? "",
|
||||
value: "",
|
||||
filePath: l.slice(1),
|
||||
enabled: !0
|
||||
}) : s.push({
|
||||
name: h ?? "",
|
||||
value: t === "data-urlencode" ? encodeURIComponent(p ?? "") : p ?? "",
|
||||
enabled: !0
|
||||
});
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}, A = (n, s, t) => {
|
||||
for (const r of t)
|
||||
if (n[r] && n[r].length)
|
||||
return n[r][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 k = {};
|
||||
function N(n) {
|
||||
return k[n] = (k[n] ?? -1) + 1, `GENERATE_ID::${n.toUpperCase()}_${k[n]}`;
|
||||
}
|
||||
export {
|
||||
ie as description,
|
||||
ae as id,
|
||||
te as importCommand,
|
||||
se as name,
|
||||
oe as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
1
src-tauri/plugins/importer-curl/index.mjs.map
Normal file
1
src-tauri/plugins/importer-curl/index.mjs.map
Normal file
File diff suppressed because one or more lines are too long
184
src-tauri/plugins/importer-insomnia/index.mjs
Normal file
184
src-tauri/plugins/importer-insomnia/index.mjs
Normal file
@@ -0,0 +1,184 @@
|
||||
function O(e, o) {
|
||||
let n;
|
||||
try {
|
||||
n = JSON.parse(o);
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
n = n ?? YAML.parse(o);
|
||||
} catch (a) {
|
||||
console.log("FAILED", a);
|
||||
}
|
||||
if (!f(n) || !Array.isArray(n.resources))
|
||||
return;
|
||||
const t = {
|
||||
workspaces: [],
|
||||
httpRequests: [],
|
||||
grpcRequests: [],
|
||||
environments: [],
|
||||
folders: []
|
||||
}, i = n.resources.filter(_);
|
||||
for (const a of i) {
|
||||
const s = n.resources.find(
|
||||
(l) => y(l) && l.parentId === a._id
|
||||
);
|
||||
t.workspaces.push({
|
||||
id: p(a._id),
|
||||
createdAt: new Date(i.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(i.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
model: "workspace",
|
||||
name: a.name,
|
||||
variables: s ? h(s.data) : []
|
||||
});
|
||||
const d = n.resources.filter(
|
||||
(l) => y(l) && l.parentId === (s == null ? void 0 : s._id)
|
||||
);
|
||||
t.environments.push(
|
||||
...d.map((l) => b(l, a._id))
|
||||
);
|
||||
const c = (l) => {
|
||||
const w = n.resources.filter((u) => u.parentId === l);
|
||||
let r = 0;
|
||||
for (const u of w)
|
||||
D(u) ? (t.folders.push(I(u, a._id)), c(u._id)) : v(u) ? t.httpRequests.push(
|
||||
g(u, a._id, r++)
|
||||
) : q(u) && t.grpcRequests.push(
|
||||
S(u, a._id, r++)
|
||||
);
|
||||
};
|
||||
c(a._id);
|
||||
}
|
||||
return t.httpRequests = t.httpRequests.filter(Boolean), t.grpcRequests = t.grpcRequests.filter(Boolean), t.environments = t.environments.filter(Boolean), t.workspaces = t.workspaces.filter(Boolean), { resources: t };
|
||||
}
|
||||
function b(e, o) {
|
||||
return {
|
||||
id: p(e._id),
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
workspaceId: p(o),
|
||||
model: "environment",
|
||||
name: e.name,
|
||||
variables: Object.entries(e.data).map(([n, t]) => ({
|
||||
enabled: !0,
|
||||
name: n,
|
||||
value: `${t}`
|
||||
}))
|
||||
};
|
||||
}
|
||||
function I(e, o) {
|
||||
return {
|
||||
id: p(e._id),
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
folderId: e.parentId === o ? null : p(e.parentId),
|
||||
workspaceId: p(o),
|
||||
model: "folder",
|
||||
name: e.name
|
||||
};
|
||||
}
|
||||
function S(e, o, n = 0) {
|
||||
var s;
|
||||
const t = e.protoMethodName.split("/").filter((d) => d !== ""), i = t[0] ?? null, a = t[1] ?? null;
|
||||
return {
|
||||
id: p(e._id),
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
workspaceId: p(o),
|
||||
folderId: e.parentId === o ? null : p(e.parentId),
|
||||
model: "grpc_request",
|
||||
sortPriority: n,
|
||||
name: e.name,
|
||||
url: m(e.url),
|
||||
service: i,
|
||||
method: a,
|
||||
message: ((s = e.body) == null ? void 0 : s.text) ?? "",
|
||||
metadata: (e.metadata ?? []).map((d) => ({
|
||||
enabled: !d.disabled,
|
||||
name: d.name ?? "",
|
||||
value: d.value ?? ""
|
||||
})).filter(({ name: d, value: c }) => d !== "" || c !== "")
|
||||
};
|
||||
}
|
||||
function g(e, o, n = 0) {
|
||||
var d, c, l, w;
|
||||
let t = null, i = {};
|
||||
e.body.mimeType === "application/octet-stream" ? (t = "binary", i = { filePath: e.body.fileName ?? "" }) : ((d = e.body) == null ? void 0 : d.mimeType) === "application/x-www-form-urlencoded" ? (t = "application/x-www-form-urlencoded", i = {
|
||||
form: (e.body.params ?? []).map((r) => ({
|
||||
enabled: !r.disabled,
|
||||
name: r.name ?? "",
|
||||
value: r.value ?? ""
|
||||
}))
|
||||
}) : ((c = e.body) == null ? void 0 : c.mimeType) === "multipart/form-data" ? (t = "multipart/form-data", i = {
|
||||
form: (e.body.params ?? []).map((r) => ({
|
||||
enabled: !r.disabled,
|
||||
name: r.name ?? "",
|
||||
value: r.value ?? "",
|
||||
file: r.fileName ?? null
|
||||
}))
|
||||
}) : ((l = e.body) == null ? void 0 : l.mimeType) === "application/graphql" ? (t = "graphql", i = { text: m(e.body.text ?? "") }) : ((w = e.body) == null ? void 0 : w.mimeType) === "application/json" && (t = "application/json", i = { text: m(e.body.text ?? "") });
|
||||
let a = null, s = {};
|
||||
return e.authentication.type === "bearer" ? (a = "bearer", s = {
|
||||
token: m(e.authentication.token)
|
||||
}) : e.authentication.type === "basic" && (a = "basic", s = {
|
||||
username: m(e.authentication.username),
|
||||
password: m(e.authentication.password)
|
||||
}), {
|
||||
id: p(e._id),
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
workspaceId: p(o),
|
||||
folderId: e.parentId === o ? null : p(e.parentId),
|
||||
model: "http_request",
|
||||
sortPriority: n,
|
||||
name: e.name,
|
||||
url: m(e.url),
|
||||
body: i,
|
||||
bodyType: t,
|
||||
authentication: s,
|
||||
authenticationType: a,
|
||||
method: e.method,
|
||||
headers: (e.headers ?? []).map((r) => ({
|
||||
enabled: !r.disabled,
|
||||
name: r.name ?? "",
|
||||
value: r.value ?? ""
|
||||
})).filter(({ name: r, value: u }) => r !== "" || u !== "")
|
||||
};
|
||||
}
|
||||
function h(e) {
|
||||
return Object.entries(e).map(([o, n]) => ({
|
||||
enabled: !0,
|
||||
name: o,
|
||||
value: `${n}`
|
||||
}));
|
||||
}
|
||||
function m(e) {
|
||||
return A(e) ? e.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : e;
|
||||
}
|
||||
function _(e) {
|
||||
return f(e) && e._type === "workspace";
|
||||
}
|
||||
function D(e) {
|
||||
return f(e) && e._type === "request_group";
|
||||
}
|
||||
function v(e) {
|
||||
return f(e) && e._type === "request";
|
||||
}
|
||||
function q(e) {
|
||||
return f(e) && e._type === "grpc_request";
|
||||
}
|
||||
function y(e) {
|
||||
return f(e) && e._type === "environment";
|
||||
}
|
||||
function f(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Object]";
|
||||
}
|
||||
function A(e) {
|
||||
return Object.prototype.toString.call(e) === "[object String]";
|
||||
}
|
||||
function p(e) {
|
||||
return e.startsWith("GENERATE_ID::") ? e : `GENERATE_ID::${e}`;
|
||||
}
|
||||
export {
|
||||
O as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
1
src-tauri/plugins/importer-insomnia/index.mjs.map
Normal file
1
src-tauri/plugins/importer-insomnia/index.mjs.map
Normal file
File diff suppressed because one or more lines are too long
184
src-tauri/plugins/importer-postman/index.mjs
Normal file
184
src-tauri/plugins/importer-postman/index.mjs
Normal file
@@ -0,0 +1,184 @@
|
||||
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)
|
||||
return;
|
||||
const s = i(a.info);
|
||||
if (!O.includes(s.schema) || !Array.isArray(a.item))
|
||||
return;
|
||||
const u = k(a.auth), n = {
|
||||
workspaces: [],
|
||||
environments: [],
|
||||
httpRequests: [],
|
||||
folders: []
|
||||
}, p = {
|
||||
model: "workspace",
|
||||
id: b("workspace"),
|
||||
name: s.name || "Postman Import",
|
||||
description: s.description || "",
|
||||
variables: ((w = a.variable) == null ? void 0 : w.map((r) => ({
|
||||
name: r.key,
|
||||
value: r.value
|
||||
}))) ?? []
|
||||
};
|
||||
n.workspaces.push(p);
|
||||
const g = (r, d = null) => {
|
||||
if (typeof r.name == "string" && Array.isArray(r.item)) {
|
||||
const o = {
|
||||
model: "folder",
|
||||
workspaceId: p.id,
|
||||
id: b("folder"),
|
||||
name: r.name,
|
||||
folderId: d
|
||||
};
|
||||
n.folders.push(o);
|
||||
for (const l of r.item)
|
||||
g(l, o.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 = {
|
||||
model: "http_request",
|
||||
id: b("http_request"),
|
||||
workspaceId: p.id,
|
||||
folderId: d,
|
||||
name: r.name,
|
||||
method: o.method || "GET",
|
||||
url: typeof o.url == "string" ? o.url : i(o.url).raw,
|
||||
body: l.body,
|
||||
bodyType: l.bodyType,
|
||||
authentication: m.authentication,
|
||||
authenticationType: m.authenticationType,
|
||||
headers: [
|
||||
...l.headers,
|
||||
...m.headers,
|
||||
...f(o.header).map((y) => ({
|
||||
name: y.key,
|
||||
value: y.value,
|
||||
enabled: !y.disabled
|
||||
}))
|
||||
]
|
||||
};
|
||||
n.httpRequests.push(S);
|
||||
} else
|
||||
console.log("Unknown item", r, d);
|
||||
};
|
||||
for (const r of a.item)
|
||||
g(r);
|
||||
return { resources: T(n) };
|
||||
}
|
||||
function k(e) {
|
||||
const t = i(e);
|
||||
return "basic" in t ? {
|
||||
headers: [],
|
||||
authenticationType: "basic",
|
||||
authentication: {
|
||||
username: t.basic.username || "",
|
||||
password: t.basic.password || ""
|
||||
}
|
||||
} : "bearer" in t ? {
|
||||
headers: [],
|
||||
authenticationType: "bearer",
|
||||
authentication: {
|
||||
token: t.bearer.token || ""
|
||||
}
|
||||
} : { headers: [], authenticationType: null, authentication: {} };
|
||||
}
|
||||
function j(e) {
|
||||
var a, s, c, u;
|
||||
const t = i(e);
|
||||
return "graphql" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "application/json",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "graphql",
|
||||
body: {
|
||||
text: JSON.stringify(
|
||||
{ query: t.graphql.query, variables: q(t.graphql.variables) },
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
} : "urlencoded" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "application/x-www-form-urlencoded",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "application/x-www-form-urlencoded",
|
||||
body: {
|
||||
form: f(t.urlencoded).map((n) => ({
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
value: n.value ?? ""
|
||||
}))
|
||||
}
|
||||
} : "formdata" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "multipart/form-data",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "multipart/form-data",
|
||||
body: {
|
||||
form: f(t.formdata).map(
|
||||
(n) => n.src != null ? {
|
||||
enabled: !n.disabled,
|
||||
contentType: n.contentType ?? null,
|
||||
name: n.key ?? "",
|
||||
file: n.src ?? ""
|
||||
} : {
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
value: n.value ?? ""
|
||||
}
|
||||
)
|
||||
}
|
||||
} : "raw" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: ((s = (a = t.options) == null ? void 0 : a.raw) == null ? void 0 : s.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",
|
||||
body: {
|
||||
text: t.raw ?? ""
|
||||
}
|
||||
} : { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
function q(e) {
|
||||
try {
|
||||
return i(JSON.parse(e));
|
||||
} catch {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function i(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Object]" ? e : {};
|
||||
}
|
||||
function f(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)])
|
||||
) : e;
|
||||
}
|
||||
const h = {};
|
||||
function b(e) {
|
||||
return h[e] = (h[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${h[e]}`;
|
||||
}
|
||||
export {
|
||||
v as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
1
src-tauri/plugins/importer-postman/index.mjs.map
Normal file
1
src-tauri/plugins/importer-postman/index.mjs.map
Normal file
File diff suppressed because one or more lines are too long
18
src-tauri/plugins/importer-yaak/index.mjs
Normal file
18
src-tauri/plugins/importer-yaak/index.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
function u(r, t) {
|
||||
let e;
|
||||
try {
|
||||
e = JSON.parse(t);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!(!s(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) {
|
||||
return Object.prototype.toString.call(r) === "[object Object]";
|
||||
}
|
||||
export {
|
||||
s as isJSObject,
|
||||
u as pluginHookImport
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
1
src-tauri/plugins/importer-yaak/index.mjs.map
Normal file
1
src-tauri/plugins/importer-yaak/index.mjs.map
Normal file
@@ -0,0 +1 @@
|
||||
{"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;"}
|
||||
@@ -735,7 +735,7 @@ async fn cmd_filter_response(
|
||||
};
|
||||
|
||||
let body = read_to_string(response.body_path.unwrap()).unwrap();
|
||||
let filter_result = plugin::run_plugin_filter(plugin_name, 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)
|
||||
@@ -758,7 +758,7 @@ async fn cmd_import_data(
|
||||
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
for plugin_name in plugins {
|
||||
let v = run_plugin_import(plugin_name, file_contents)
|
||||
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if let Some(r) = v {
|
||||
@@ -903,12 +903,12 @@ async fn cmd_request_to_curl(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rendered = render_request(&request, &workspace, environment.as_ref());
|
||||
Ok(run_plugin_export_curl(&rendered)?)
|
||||
Ok(run_plugin_export_curl(&app, &rendered)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_curl_to_request(command: &str, workspace_id: &str) -> Result<HttpRequest, String> {
|
||||
let v = run_plugin_import("importer-curl", command)
|
||||
async fn cmd_curl_to_request(app_handle: AppHandle, command: &str, workspace_id: &str) -> Result<HttpRequest, String> {
|
||||
let v = run_plugin_import(&app_handle, "importer-curl", command)
|
||||
.await
|
||||
.map_err(|e| e.to_string());
|
||||
match v {
|
||||
@@ -958,6 +958,28 @@ 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,
|
||||
@@ -1702,6 +1724,7 @@ 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,
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::path;
|
||||
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use crate::deno::run_plugin_deno_block;
|
||||
use crate::models::{HttpRequest, WorkspaceExportResources};
|
||||
@@ -17,12 +19,17 @@ pub struct ImportResult {
|
||||
}
|
||||
|
||||
pub async fn run_plugin_filter(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
response_body: &str,
|
||||
filter: &str,
|
||||
) -> Option<FilterResult> {
|
||||
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
|
||||
let plugin_index_file = plugin_dir.join(plugin_name).join("build/index.mjs");
|
||||
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");
|
||||
|
||||
let result = run_plugin_deno_block(
|
||||
plugin_index_file.to_str().unwrap(),
|
||||
@@ -45,9 +52,16 @@ pub async fn run_plugin_filter(
|
||||
Some(resources)
|
||||
}
|
||||
|
||||
pub fn run_plugin_export_curl(request: &HttpRequest) -> Result<String, String> {
|
||||
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
|
||||
let plugin_index_file = plugin_dir.join("exporter-curl").join("build/index.mjs");
|
||||
pub fn run_plugin_export_curl(
|
||||
app_handle: &AppHandle,
|
||||
request: &HttpRequest,
|
||||
) -> Result<String, String> {
|
||||
let plugin_dir = app_handle
|
||||
.path()
|
||||
.resolve("plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource")
|
||||
.join("exporter-curl");
|
||||
let plugin_index_file = plugin_dir.join("index.mjs");
|
||||
|
||||
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
|
||||
let result = run_plugin_deno_block(
|
||||
@@ -62,11 +76,16 @@ pub fn run_plugin_export_curl(request: &HttpRequest) -> Result<String, String> {
|
||||
}
|
||||
|
||||
pub async fn run_plugin_import(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
file_contents: &str,
|
||||
) -> Result<Option<ImportResult>, String> {
|
||||
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
|
||||
let plugin_index_file = plugin_dir.join(plugin_name).join("build/index.mjs");
|
||||
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");
|
||||
|
||||
let result = run_plugin_deno_block(
|
||||
plugin_index_file.to_str().unwrap(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use hex_color::HexColor;
|
||||
use log::warn;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::{
|
||||
@@ -25,15 +26,35 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
|
||||
let window_for_theme = window.clone();
|
||||
let id1 = h.listen("yaak_bg_changed", move |ev| {
|
||||
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
|
||||
let color = HexColor::parse_rgb(payload).unwrap();
|
||||
update_window_theme(window_for_theme.clone(), color);
|
||||
let 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 window_for_title = window.clone();
|
||||
let id2 = h.listen("yaak_title_changed", move |ev| {
|
||||
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
|
||||
update_window_title(window_for_title.clone(), payload.to_string());
|
||||
let 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 h = h.clone();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "yaak",
|
||||
"version": "2024.6.0-beta.1",
|
||||
"version": "2024.6.4",
|
||||
"identifier": "app.yaak.desktop",
|
||||
"build": {
|
||||
"beforeBuildCommand": "npm run build",
|
||||
@@ -27,6 +27,7 @@
|
||||
"desktop": {
|
||||
"schemes": [
|
||||
"yaak"
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Val {
|
||||
Str(String),
|
||||
Ident(String),
|
||||
Var(String),
|
||||
Fn { name: String, args: Vec<Val> },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Token {
|
||||
Raw(String),
|
||||
Var { name: String },
|
||||
Fn { name: String, args: Vec<Val> },
|
||||
Tag(Val),
|
||||
Eof,
|
||||
}
|
||||
|
||||
@@ -66,17 +66,10 @@ impl Parser {
|
||||
// Parse up to first identifier
|
||||
// ${[ my_var...
|
||||
self.skip_whitespace();
|
||||
let name = match self.parse_ident() {
|
||||
None => return None,
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
// Parse fn args if they exist
|
||||
// ${[ my_var(a, b, c)
|
||||
let args = if self.match_str("(") {
|
||||
self.parse_fn_args()
|
||||
} else {
|
||||
None
|
||||
let val = match self.parse_value() {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Parse to closing tag
|
||||
@@ -86,29 +79,65 @@ impl Parser {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(match args {
|
||||
Some(a) => Token::Fn { args: a, name },
|
||||
None => Token::Var { name },
|
||||
})
|
||||
Some(Token::Tag(val))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn debug_pos(&self, x: &str) {
|
||||
println!(
|
||||
r#"Position: {x} -- [{}] = {} --> "{}"#,
|
||||
r#"Position: {x} -- [{}] = {} --> "{}" --> {:?}"#,
|
||||
self.pos,
|
||||
self.chars[self.pos],
|
||||
self.chars.iter().collect::<String>()
|
||||
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_ident_or_string() {
|
||||
if let Some(v) = self.parse_value() {
|
||||
args.push(v);
|
||||
}
|
||||
|
||||
@@ -121,6 +150,7 @@ impl Parser {
|
||||
|
||||
// If we don't find a comma, that's bad
|
||||
if !args.is_empty() && !self.match_str(",") {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -132,16 +162,6 @@ impl Parser {
|
||||
return Some(args);
|
||||
}
|
||||
|
||||
fn parse_ident_or_string(&mut self) -> Option<Val> {
|
||||
if let Some(i) = self.parse_ident() {
|
||||
Some(Val::Ident(i))
|
||||
} else if let Some(s) = self.parse_string() {
|
||||
Some(Val::Str(s))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ident(&mut self) -> Option<String> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
@@ -161,6 +181,7 @@ impl Parser {
|
||||
}
|
||||
|
||||
if text.is_empty() {
|
||||
self.pos = start_pos;
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -169,6 +190,7 @@ impl Parser {
|
||||
|
||||
fn parse_string(&mut self) -> Option<String> {
|
||||
let start_pos = self.pos;
|
||||
|
||||
let mut text = String::new();
|
||||
if !self.match_str("\"") {
|
||||
return None;
|
||||
@@ -264,7 +286,7 @@ mod tests {
|
||||
let mut p = Parser::new("${[ foo ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Var { name: "foo".into() }, Token::Eof]
|
||||
vec![Token::Tag(Val::Var("foo".into())), Token::Eof]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -282,7 +304,7 @@ mod tests {
|
||||
let mut p = Parser::new(r#"${[ "foo \"bar\" baz" ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Raw(r#"${[ "foo \"bar\" baz" ]}"#.into()), Token::Eof]
|
||||
vec![Token::Tag(Val::Str(r#"foo "bar" baz"#.into())), Token::Eof]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -293,7 +315,7 @@ mod tests {
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Raw("Hello ".to_string()),
|
||||
Token::Var { name: "foo".into() },
|
||||
Token::Tag(Val::Var("foo".into())),
|
||||
Token::Raw("!".to_string()),
|
||||
Token::Eof,
|
||||
]
|
||||
@@ -306,10 +328,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Fn {
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: Vec::new(),
|
||||
},
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -321,10 +343,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Fn {
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![Val::Ident("bar".into())],
|
||||
},
|
||||
args: vec![Val::Var("bar".into())],
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -336,14 +358,14 @@ mod tests {
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Fn {
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
Val::Ident("bar".into()),
|
||||
Val::Ident("baz".into()),
|
||||
Val::Ident("qux".into()),
|
||||
Val::Var("bar".into()),
|
||||
Val::Var("baz".into()),
|
||||
Val::Var("qux".into()),
|
||||
],
|
||||
},
|
||||
}),
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -355,14 +377,35 @@ mod tests {
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![
|
||||
Token::Fn {
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
Val::Ident("bar".into()),
|
||||
Val::Var("bar".into()),
|
||||
Val::Str(r#"baz "hi""#.into()),
|
||||
Val::Ident("qux".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,7 +1,8 @@
|
||||
use crate::{Parser, Token, Val};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type TemplateCallback = fn(name: &str, args: Vec<&str>) -> String;
|
||||
use crate::{Parser, Token, Val};
|
||||
|
||||
type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
|
||||
|
||||
pub fn parse_and_render(
|
||||
template: &str,
|
||||
@@ -23,26 +24,7 @@ pub fn render(
|
||||
for t in tokens {
|
||||
match t {
|
||||
Token::Raw(s) => doc_str.push(s),
|
||||
Token::Var { name } => {
|
||||
if let Some(v) = vars.get(name.as_str()) {
|
||||
doc_str.push(v.to_string());
|
||||
}
|
||||
}
|
||||
Token::Fn { name, args } => {
|
||||
let empty = &"";
|
||||
let resolved_args = args
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
Val::Str(s) => s.as_str(),
|
||||
Val::Ident(i) => vars.get(i.as_str()).unwrap_or(empty),
|
||||
})
|
||||
.collect();
|
||||
let val = match cb {
|
||||
Some(cb) => cb(name.as_str(), resolved_args),
|
||||
None => "".into(),
|
||||
};
|
||||
doc_str.push(val);
|
||||
}
|
||||
Token::Tag(val) => doc_str.push(render_tag(val, vars.clone(), cb)),
|
||||
Token::Eof => {}
|
||||
}
|
||||
}
|
||||
@@ -50,11 +32,41 @@ pub fn render(
|
||||
return doc_str.join("");
|
||||
}
|
||||
|
||||
fn render_tag<'s>(
|
||||
val: Val,
|
||||
vars: HashMap<&'s str, &'s str>,
|
||||
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 = &"";
|
||||
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.clone(), cb),
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
match cb {
|
||||
Some(cb) => cb(name.as_str(), resolved_args),
|
||||
None => "".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn render_empty() {
|
||||
let template = "";
|
||||
@@ -92,8 +104,26 @@ mod tests {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ say_hello("John", "Kate") ]}"#;
|
||||
let result = r#"say_hello: ["John", "Kate"]"#;
|
||||
let cb: fn(&str, Vec<&str>) -> String =
|
||||
|name: &str, args: Vec<&str>| format!("{name}: {:?}", args);
|
||||
|
||||
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,21 +1,40 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import classNames from 'classnames';
|
||||
import { search } from 'fast-fuzzy';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
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 { 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;
|
||||
@@ -26,22 +45,120 @@ interface CommandPaletteGroup {
|
||||
type CommandPaletteItem = {
|
||||
key: string;
|
||||
onSelect: () => void;
|
||||
action?: HotkeyAction;
|
||||
} & ({ searchText: string; label: ReactNode } | { label: string });
|
||||
|
||||
const MAX_PER_GROUP = 4;
|
||||
const MAX_PER_GROUP = 8;
|
||||
|
||||
export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
const [command, setCommand] = useDebouncedState<string>('', 150);
|
||||
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
||||
const routes = useAppRoutes();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const active = useActiveWorkspaceId();
|
||||
const workspaces = useWorkspaces();
|
||||
const environments = useEnvironments();
|
||||
const recentEnvironments = useRecentEnvironments();
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
const requests = useRequests();
|
||||
const recentRequests = useRecentRequests();
|
||||
const [command, setCommand] = useState<string>('');
|
||||
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 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) => {
|
||||
@@ -60,6 +177,23 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
});
|
||||
}, [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);
|
||||
@@ -78,6 +212,12 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
}, [recentWorkspaces, workspaces]);
|
||||
|
||||
const groups = useMemo<CommandPaletteGroup[]>(() => {
|
||||
const actionsGroup: CommandPaletteGroup = {
|
||||
key: 'actions',
|
||||
label: 'Actions',
|
||||
items: workspaceCommands,
|
||||
};
|
||||
|
||||
const requestGroup: CommandPaletteGroup = {
|
||||
key: 'requests',
|
||||
label: 'Requests',
|
||||
@@ -91,10 +231,10 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
|
||||
requestGroup.items.push({
|
||||
key: `switch-request-${r.id}`,
|
||||
searchText: `${r.method} ${r.name}`,
|
||||
searchText: fallbackRequestName(r),
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<HttpMethodTag className="text-fg-subtler" shortNames request={r} />
|
||||
<HttpMethodTag className="text-fg-subtler" request={r} />
|
||||
<div className="truncate">{fallbackRequestName(r)}</div>
|
||||
</HStack>
|
||||
),
|
||||
@@ -108,6 +248,23 @@ 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',
|
||||
@@ -115,7 +272,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
};
|
||||
|
||||
for (const w of sortedWorkspaces) {
|
||||
if (w.id === activeWorkspaceId) {
|
||||
if (w.id === active) {
|
||||
continue;
|
||||
}
|
||||
workspaceGroup.items.push({
|
||||
@@ -125,30 +282,44 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
});
|
||||
}
|
||||
|
||||
return [requestGroup, workspaceGroup];
|
||||
return [actionsGroup, requestGroup, environmentGroup, workspaceGroup];
|
||||
}, [
|
||||
activeEnvironmentId,
|
||||
activeRequestId,
|
||||
activeWorkspaceId,
|
||||
openWorkspace,
|
||||
routes,
|
||||
workspaceCommands,
|
||||
sortedRequests,
|
||||
activeRequestId,
|
||||
routes,
|
||||
activeEnvironmentId,
|
||||
sortedEnvironments,
|
||||
activeEnvironment?.id,
|
||||
sortedWorkspaces,
|
||||
active,
|
||||
openWorkspace,
|
||||
]);
|
||||
|
||||
const filteredGroups = useMemo(
|
||||
() =>
|
||||
groups
|
||||
.map((g) => {
|
||||
g.items = g.items.filter((v) => {
|
||||
const s = 'searchText' in v ? v.searchText : v.label;
|
||||
return s.toLowerCase().includes(command.toLowerCase());
|
||||
});
|
||||
return g;
|
||||
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),
|
||||
})
|
||||
.filter((g) => g.items.length > 0),
|
||||
[command, groups],
|
||||
);
|
||||
: 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 handleSelectAndClose = useCallback(
|
||||
(cb: () => void) => {
|
||||
@@ -158,38 +329,37 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const { allItems, selectedItem } = useMemo(() => {
|
||||
const allItems = filteredGroups.flatMap((g) => g.items);
|
||||
let selectedItem = allItems.find((i) => i.key === selectedItemKey) ?? null;
|
||||
const selectedItem = useMemo(() => {
|
||||
let selectedItem = filteredAllItems.find((i) => i.key === selectedItemKey) ?? null;
|
||||
if (selectedItem == null) {
|
||||
selectedItem = allItems[0] ?? null;
|
||||
selectedItem = filteredAllItems[0] ?? null;
|
||||
}
|
||||
return { selectedItem, allItems };
|
||||
}, [filteredGroups, selectedItemKey]);
|
||||
return selectedItem;
|
||||
}, [filteredAllItems, selectedItemKey]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
const index = allItems.findIndex((v) => v.key === selectedItem?.key);
|
||||
const index = filteredAllItems.findIndex((v) => v.key === selectedItem?.key);
|
||||
|
||||
if (e.key === 'ArrowDown' || (e.ctrlKey && e.key === 'n')) {
|
||||
const next = allItems[index + 1];
|
||||
const next = filteredAllItems[index + 1] ?? filteredAllItems[0];
|
||||
setSelectedItemKey(next?.key ?? null);
|
||||
} else if (e.key === 'ArrowUp' || (e.ctrlKey && e.key === 'k')) {
|
||||
const prev = allItems[index - 1];
|
||||
const prev = filteredAllItems[index - 1] ?? filteredAllItems[filteredAllItems.length - 1];
|
||||
setSelectedItemKey(prev?.key ?? null);
|
||||
} else if (e.key === 'Enter') {
|
||||
const selected = allItems[index];
|
||||
const selected = filteredAllItems[index];
|
||||
setSelectedItemKey(selected?.key ?? null);
|
||||
if (selected) {
|
||||
handleSelectAndClose(selected.onSelect);
|
||||
}
|
||||
}
|
||||
},
|
||||
[allItems, handleSelectAndClose, selectedItem?.key],
|
||||
[filteredAllItems, handleSelectAndClose, selectedItem?.key],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full max-h-[20rem] w-[400px] grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<div className="h-full w-[400px] grid grid-rows-[auto_minmax(0,1fr)] overflow-hidden">
|
||||
<div className="px-2 py-2 w-full">
|
||||
<PlainInput
|
||||
hideLabel
|
||||
@@ -209,15 +379,18 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
</div>
|
||||
<div className="h-full px-1.5 overflow-y-auto pb-1">
|
||||
{filteredGroups.map((g) => (
|
||||
<div key={g.key} className="mb-1.5">
|
||||
<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.slice(0, MAX_PER_GROUP).map((v) => (
|
||||
{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>
|
||||
@@ -233,15 +406,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',
|
||||
@@ -250,6 +428,17 @@ function CommandPaletteItem({
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{children}</span>
|
||||
</button>
|
||||
</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 { PairEditor } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { Separator } from './core/Separator';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
@@ -185,18 +185,20 @@ const EnvironmentEditor = function ({
|
||||
/>
|
||||
</Heading>
|
||||
</HStack>
|
||||
<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}
|
||||
/>
|
||||
<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={false}
|
||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||
pairs={variables}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<table className="w-full mb-auto min-w-full max-w-full divide-y divide-background-highlight-secondary">
|
||||
<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">
|
||||
<tbody className="divide-y divide-background-highlight-secondary">
|
||||
{workspaces.map((w) => (
|
||||
<tr key={w.id}>
|
||||
<td className="min-w-0 py-1 pl-1">
|
||||
|
||||
@@ -6,7 +6,6 @@ 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';
|
||||
@@ -42,7 +41,6 @@ export function GlobalHooks() {
|
||||
|
||||
// Other useful things
|
||||
useSyncThemeToDocument();
|
||||
useGlobalCommands();
|
||||
useCommandPalette();
|
||||
useNotificationToast();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -25,30 +26,49 @@ 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: 'Clear Response',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
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: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
label: `Delete ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
hidden: responses.length <= 1,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{ type: 'separator', label: 'History' },
|
||||
{ type: 'separator' },
|
||||
...responses.slice(0, 20).map((r: HttpResponse) => ({
|
||||
key: r.id,
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<StatusTag className="text-sm" response={r} />
|
||||
<span>→</span>{' '}
|
||||
<span className="text-fg-subtle">→</span>{' '}
|
||||
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
|
||||
</HStack>
|
||||
),
|
||||
|
||||
@@ -262,13 +262,15 @@ export const RequestPane = memo(function RequestPane({
|
||||
options:
|
||||
requests.length > 0
|
||||
? [
|
||||
...requests.map(
|
||||
(r) =>
|
||||
({
|
||||
type: 'constant',
|
||||
label: r.url,
|
||||
} as GenericCompletionOption),
|
||||
),
|
||||
...requests
|
||||
.filter((r) => r.id !== activeRequestId)
|
||||
.map(
|
||||
(r) =>
|
||||
({
|
||||
type: 'constant',
|
||||
label: r.url,
|
||||
} as GenericCompletionOption),
|
||||
),
|
||||
]
|
||||
: [
|
||||
{ label: 'http://', type: 'constant' },
|
||||
|
||||
@@ -23,6 +23,7 @@ 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';
|
||||
@@ -158,12 +159,12 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
<AudioViewer response={activeResponse} />
|
||||
) : contentType?.startsWith('video') ? (
|
||||
<VideoViewer 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(/pdf/) ? (
|
||||
<PdfViewer response={activeResponse} />
|
||||
) : contentType?.match(/csv|tab-separated/) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||
<WebPageViewer response={activeResponse} />
|
||||
) : (
|
||||
<TextViewer
|
||||
className="-mr-2" // Pull to the right
|
||||
|
||||
@@ -159,20 +159,6 @@ export function Sidebar({ className }: Props) {
|
||||
return { tree, treeParentMap, selectableRequests, selectedRequest };
|
||||
}, [activeWorkspace, selectedId, requests, folders, collapsed.value]);
|
||||
|
||||
const jumpToRequest = async (index: number) => {
|
||||
const r = selectableRequests[index];
|
||||
if (r != null) await handleSelect(r.id);
|
||||
};
|
||||
|
||||
useHotKey('sidebar.jump_1', () => jumpToRequest(0));
|
||||
useHotKey('sidebar.jump_2', () => jumpToRequest(1));
|
||||
useHotKey('sidebar.jump_3', () => jumpToRequest(2));
|
||||
useHotKey('sidebar.jump_4', () => jumpToRequest(3));
|
||||
useHotKey('sidebar.jump_5', () => jumpToRequest(4));
|
||||
useHotKey('sidebar.jump_6', () => jumpToRequest(5));
|
||||
useHotKey('sidebar.jump_7', () => jumpToRequest(6));
|
||||
useHotKey('sidebar.jump_8', () => jumpToRequest(7));
|
||||
|
||||
const focusActiveRequest = useCallback(
|
||||
(
|
||||
args: {
|
||||
@@ -204,7 +190,7 @@ export function Sidebar({ className }: Props) {
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (id: string) => {
|
||||
async (id: string, opts: { noFocus?: boolean } = {}) => {
|
||||
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const node = children.find((m) => m.item.id === id) ?? null;
|
||||
@@ -224,7 +210,7 @@ export function Sidebar({ className }: Props) {
|
||||
});
|
||||
setSelectedId(id);
|
||||
setSelectedTree(tree);
|
||||
focusActiveRequest({ forced: { id, tree } });
|
||||
if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } });
|
||||
}
|
||||
},
|
||||
[treeParentMap, collapsed, routes, activeEnvironmentId, focusActiveRequest],
|
||||
@@ -617,7 +603,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
) {
|
||||
const activeRequest = useActiveRequest();
|
||||
const deleteFolder = useDeleteFolder(itemId);
|
||||
const deleteRequest = useDeleteRequest(activeRequest ?? null);
|
||||
const deleteRequest = useDeleteRequest(itemId);
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||
const copyAsCurl = useCopyAsCurl(itemId);
|
||||
|
||||
@@ -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">
|
||||
<div className="absolute right-0 bottom-0 z-10">
|
||||
<AnimatePresence>
|
||||
{toasts.map((props: PrivateToastEntry) => (
|
||||
<ToastInstance key={props.id} {...props} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCommand } from '../hooks/useCommands';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
@@ -28,7 +28,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||
const createWorkspace = useCommand('workspace.create');
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const dialog = useDialog();
|
||||
const prompt = usePrompt();
|
||||
const settings = useSettings();
|
||||
@@ -101,7 +101,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: 'create-workspace',
|
||||
label: 'New Workspace',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: () => createWorkspace.mutate({}),
|
||||
onSelect: createWorkspace.mutate,
|
||||
},
|
||||
];
|
||||
}, [
|
||||
|
||||
@@ -2,12 +2,7 @@ import { useCallback, useMemo } from 'react';
|
||||
import { Editor } from './Editor';
|
||||
import type { PairEditorProps } from './PairEditor';
|
||||
|
||||
type Props = Pick<
|
||||
PairEditorProps,
|
||||
'onChange' | 'pairs' | 'namePlaceholder' | 'valuePlaceholder'
|
||||
> & {
|
||||
foo?: string;
|
||||
};
|
||||
type Props = PairEditorProps;
|
||||
|
||||
export function BulkPairEditor({ pairs, onChange, namePlaceholder, valuePlaceholder }: Props) {
|
||||
const pairsText = useMemo(() => {
|
||||
@@ -30,6 +25,8 @@ export function BulkPairEditor({ pairs, onChange, namePlaceholder, valuePlacehol
|
||||
|
||||
return (
|
||||
<Editor
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
|
||||
defaultValue={pairsText}
|
||||
contentType="pairs"
|
||||
|
||||
@@ -54,17 +54,12 @@ export function Dialog({
|
||||
<Overlay open={open} onClose={onClose} portalName="dialog">
|
||||
<div
|
||||
className={classNames(
|
||||
'x-theme-dialog absolute inset-0 flex flex-col items-center pointer-events-none my-5',
|
||||
'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}
|
||||
className="pointer-events-auto"
|
||||
>
|
||||
<div role="dialog" aria-labelledby={titleId} aria-describedby={descriptionId}>
|
||||
<motion.div
|
||||
initial={{ top: 5, scale: 0.97 }}
|
||||
animate={{ top: 0, scale: 1 }}
|
||||
@@ -74,12 +69,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-6rem)]',
|
||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-4rem)]',
|
||||
size === 'sm' && 'w-[28rem] 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-[80vw] max-h-[80vh]',
|
||||
size === 'dynamic' && 'min-w-[20rem] max-w-[100vw] w-full mt-8',
|
||||
)}
|
||||
>
|
||||
{title ? (
|
||||
|
||||
@@ -35,6 +35,7 @@ import { HStack, VStack } from './Stacks';
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
label?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemDefault = {
|
||||
@@ -273,7 +274,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]?.type === 'separator') {
|
||||
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
|
||||
nextIndex--;
|
||||
} else if (nextIndex < 0) {
|
||||
nextIndex = items.length - 1;
|
||||
@@ -290,7 +291,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]?.type === 'separator') {
|
||||
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
|
||||
nextIndex++;
|
||||
} else if (nextIndex >= items.length) {
|
||||
nextIndex = 0;
|
||||
@@ -373,7 +374,7 @@ 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: '25rem',
|
||||
maxWidth: '40rem',
|
||||
};
|
||||
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
|
||||
const triangleStyles = onRight
|
||||
@@ -456,6 +457,9 @@ 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')}>
|
||||
@@ -463,9 +467,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
</Separator>
|
||||
);
|
||||
}
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
focused={i === selectedIndex}
|
||||
@@ -538,9 +539,8 @@ 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 === 'default' && 'text-fg-subtle',
|
||||
item.variant === 'danger' && 'text-fg-danger',
|
||||
item.variant === 'notify' && 'text-fg-primary',
|
||||
item.variant === 'danger' && '!text-fg-danger',
|
||||
item.variant === 'notify' && '!text-fg-primary',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { useHotKeyLabel } from '../../hooks/useHotKey';
|
||||
|
||||
interface Props {
|
||||
action: HotkeyAction;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function HotKeyLabel({ action }: Props) {
|
||||
export function HotKeyLabel({ action, className }: Props) {
|
||||
const label = useHotKeyLabel(action);
|
||||
return <span className="text-fg-subtle whitespace-nowrap">{label}</span>;
|
||||
return <span className={classNames(className, 'text-fg-subtle whitespace-nowrap')}>{label}</span>;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React, { Fragment } from 'react';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { HotKey } from './HotKey';
|
||||
import { HotKeyLabel } from './HotKeyLabel';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
|
||||
interface Props {
|
||||
hotkeys: HotkeyAction[];
|
||||
bottomSlot?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const HotKeyList = ({ hotkeys, bottomSlot }: Props) => {
|
||||
export const HotKeyList = ({ hotkeys, bottomSlot, className }: Props) => {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<VStack space={2}>
|
||||
<div className={classNames(className, 'h-full flex items-center justify-center')}>
|
||||
<div className="px-4 grid gap-2 grid-cols-[auto_auto]">
|
||||
{hotkeys.map((hotkey) => (
|
||||
<HStack key={hotkey} className="grid grid-cols-2">
|
||||
<HotKeyLabel action={hotkey} />
|
||||
<HotKey className="ml-auto" action={hotkey} />
|
||||
</HStack>
|
||||
<Fragment key={hotkey}>
|
||||
<HotKeyLabel className="truncate" action={hotkey} />
|
||||
<HotKey className="ml-4" action={hotkey} />
|
||||
</Fragment>
|
||||
))}
|
||||
{bottomSlot}
|
||||
</VStack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -54,13 +54,15 @@ const icons = {
|
||||
plusCircle: lucide.PlusCircleIcon,
|
||||
question: lucide.ShieldQuestionIcon,
|
||||
refresh: lucide.RefreshCwIcon,
|
||||
save: lucide.SaveIcon,
|
||||
search: lucide.SearchIcon,
|
||||
sendHorizontal: lucide.SendHorizonalIcon,
|
||||
settings2: lucide.Settings2Icon,
|
||||
settings: lucide.SettingsIcon,
|
||||
sparkles: lucide.SparklesIcon,
|
||||
sun: lucide.SunIcon,
|
||||
trash: lucide.TrashIcon,
|
||||
trash: lucide.Trash2Icon,
|
||||
unpin: lucide.PinOffIcon,
|
||||
update: lucide.RefreshCcwIcon,
|
||||
upload: lucide.UploadIcon,
|
||||
x: lucide.XIcon,
|
||||
|
||||
@@ -6,7 +6,7 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
||||
<code
|
||||
className={classNames(
|
||||
className,
|
||||
'font-mono text-shrink bg-background-highlight-secondary border border-background-highlight',
|
||||
'font-mono text-shrink bg-background-highlight-secondary border border-background-highlight-secondary',
|
||||
'px-1.5 py-0.5 rounded text-fg shadow-inner',
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="border"
|
||||
title={useBulk ? 'Bulk edit' : 'Regular Edit'}
|
||||
title={useBulk ? 'Enable form edit' : 'Enable bulk edit'}
|
||||
className={classNames(
|
||||
'transition-opacity opacity-0 group-hover:opacity-80 hover:!opacity-100 shadow',
|
||||
'bg-background text-fg-subtle hover:text-fg group-hover/wrapper:opacity-100',
|
||||
|
||||
27
src-web/components/responseViewers/BinaryViewer.tsx
Normal file
27
src-web/components/responseViewers/BinaryViewer.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useSaveResponse } from '../../hooks/useSaveResponse';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
import { getContentTypeHeader } from '../../lib/models';
|
||||
import { Banner } from '../core/Banner';
|
||||
import { Button } from '../core/Button';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function BinaryViewer({ response }: Props) {
|
||||
const saveResponse = useSaveResponse(response);
|
||||
const contentType = getContentTypeHeader(response.headers) ?? 'unknown';
|
||||
return (
|
||||
<Banner color="primary" className="h-full flex flex-col gap-3">
|
||||
<p>
|
||||
Content type <InlineCode>{contentType}</InlineCode> cannot be previewed
|
||||
</p>
|
||||
<div>
|
||||
<Button variant="border" size="sm" onClick={() => saveResponse.mutate()}>
|
||||
Save to File
|
||||
</Button>
|
||||
</div>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
3
src-web/components/responseViewers/PdfViewer.css
Normal file
3
src-web/components/responseViewers/PdfViewer.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.react-pdf__Document * {
|
||||
@apply select-text;
|
||||
}
|
||||
61
src-web/components/responseViewers/PdfViewer.tsx
Normal file
61
src-web/components/responseViewers/PdfViewer.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import 'react-pdf/dist/Page/TextLayer.css';
|
||||
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import { useDebouncedState } from '../../hooks/useDebouncedState';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
import './PdfViewer.css';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
const options = {
|
||||
cMapUrl: '/cmaps/',
|
||||
standardFontDataUrl: '/standard_fonts/',
|
||||
};
|
||||
|
||||
export function PdfViewer({ response }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerWidth, setContainerWidth] = useDebouncedState<number>(0, 100);
|
||||
const [numPages, setNumPages] = useState<number>();
|
||||
|
||||
useResizeObserver(containerRef.current ?? null, (v) => {
|
||||
setContainerWidth(v.contentRect.width);
|
||||
});
|
||||
|
||||
const onDocumentLoadSuccess = ({ numPages: nextNumPages }: PDFDocumentProxy): void => {
|
||||
setNumPages(nextNumPages);
|
||||
};
|
||||
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
}
|
||||
|
||||
const src = convertFileSrc(response.bodyPath);
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full overflow-y-auto">
|
||||
<Document
|
||||
file={src}
|
||||
options={options}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
externalLinkTarget="_blank"
|
||||
externalLinkRel="noopener noreferrer"
|
||||
>
|
||||
{Array.from(new Array(numPages), (_, index) => (
|
||||
<Page
|
||||
className="mb-6 select-all"
|
||||
renderTextLayer
|
||||
renderAnnotationLayer
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={containerWidth}
|
||||
/>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import { Editor } from '../core/Editor';
|
||||
import { hyperlink } from '../core/Editor/hyperlink/extension';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { Input } from '../core/Input';
|
||||
import { EmptyStateText } from '../EmptyStateText';
|
||||
import { BinaryViewer } from './BinaryViewer';
|
||||
|
||||
const extraExtensions = [hyperlink];
|
||||
|
||||
@@ -35,21 +37,14 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
);
|
||||
|
||||
const contentType = useContentTypeFromHeaders(response.headers);
|
||||
const rawBody = useResponseBodyText(response) ?? '';
|
||||
const rawBody = useResponseBodyText(response) ?? null;
|
||||
const isSearching = filterText != null;
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
? tryFormatJson(rawBody)
|
||||
: pretty && contentType?.includes('xml')
|
||||
? tryFormatXml(rawBody)
|
||||
: rawBody;
|
||||
|
||||
const filteredResponse = useFilterResponse({
|
||||
filter: debouncedFilterText ?? '',
|
||||
responseId: response.id,
|
||||
});
|
||||
|
||||
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (isSearching) {
|
||||
setFilterText(null);
|
||||
@@ -104,6 +99,22 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
return result;
|
||||
}, [canFilter, filterText, isJson, isSearching, response.id, setFilterText, toggleSearch]);
|
||||
|
||||
if (rawBody == null) {
|
||||
return <BinaryViewer response={response} />;
|
||||
}
|
||||
|
||||
if ((response.contentLength ?? 0) > 2 * 1000 * 1000) {
|
||||
return <EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>;
|
||||
}
|
||||
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
? tryFormatJson(rawBody)
|
||||
: pretty && contentType?.includes('xml')
|
||||
? tryFormatXml(rawBody)
|
||||
: rawBody;
|
||||
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
readOnly
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import { useAlert } from './useAlert';
|
||||
import { useAppInfo } from './useAppInfo';
|
||||
|
||||
export function useCheckForUpdates() {
|
||||
const alert = useAlert();
|
||||
const appInfo = useAppInfo();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const hasUpdate: boolean = await minPromiseMillis(invoke('cmd_check_for_updates'), 500);
|
||||
if (!hasUpdate) {
|
||||
alert({
|
||||
id: 'no-updates',
|
||||
title: 'No Updates',
|
||||
body: 'You are currently up to date',
|
||||
title: 'No Update Available',
|
||||
body: (
|
||||
<>
|
||||
You are currently on the latest version <InlineCode>{appInfo?.version}</InlineCode>
|
||||
</>
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -18,11 +18,13 @@ export function useClipboardText() {
|
||||
const setText = useCallback(
|
||||
(text: string) => {
|
||||
writeText(text).catch(console.error);
|
||||
toast.show({
|
||||
id: 'copied',
|
||||
variant: 'copied',
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
if (text != '') {
|
||||
toast.show({
|
||||
id: 'copied',
|
||||
variant: 'copied',
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
}
|
||||
setValue(text);
|
||||
},
|
||||
[setValue, toast],
|
||||
|
||||
@@ -9,6 +9,7 @@ export function useCommandPalette() {
|
||||
id: 'command_palette',
|
||||
size: 'dynamic',
|
||||
hideX: true,
|
||||
className: '!max-h-[min(30rem,calc(100vh-4rem))]',
|
||||
vAlign: 'top',
|
||||
noPadding: true,
|
||||
noScroll: true,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { UseMutationOptions } from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import type { TrackAction, TrackResource } from '../lib/analytics';
|
||||
import type { Workspace } from '../lib/models';
|
||||
|
||||
interface CommandInstance<T, V> extends UseMutationOptions<V, unknown, T> {
|
||||
track?: [TrackResource, TrackAction];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type Commands = {
|
||||
'workspace.create': CommandInstance<Partial<Pick<Workspace, 'name'>>, Workspace>;
|
||||
};
|
||||
|
||||
const useCommandState = createGlobalState<Commands>();
|
||||
|
||||
export function useRegisterCommand<K extends keyof Commands>(action: K, command: Commands[K]) {
|
||||
const [, setState] = useCommandState();
|
||||
|
||||
useEffect(() => {
|
||||
setState((commands) => {
|
||||
return { ...commands, [action]: command };
|
||||
});
|
||||
|
||||
// Remove action when it goes out of scope
|
||||
return () => {
|
||||
setState((commands) => {
|
||||
return { ...commands, [action]: undefined };
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [action]);
|
||||
}
|
||||
|
||||
export function useCommand<K extends keyof Commands>(action: K) {
|
||||
const [commands] = useCommandState();
|
||||
const cmd = commands[action];
|
||||
return useMutation({ ...cmd });
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function useCreateHttpRequest() {
|
||||
const routes = useAppRoutes();
|
||||
|
||||
return useMutation<HttpRequest, unknown, Partial<HttpRequest>>({
|
||||
mutationFn: (patch) => {
|
||||
mutationFn: (patch = {}) => {
|
||||
if (workspaceId === null) {
|
||||
throw new Error("Cannot create request when there's no active workspace");
|
||||
}
|
||||
@@ -28,7 +28,6 @@ export function useCreateHttpRequest() {
|
||||
}
|
||||
}
|
||||
patch.folderId = patch.folderId || activeRequest?.folderId;
|
||||
console.log('PATCH', patch);
|
||||
return invoke('cmd_create_http_request', { request: { workspaceId, ...patch } });
|
||||
},
|
||||
onSettled: () => trackEvent('http_request', 'create'),
|
||||
|
||||
27
src-web/hooks/useCreateWorkspace.ts
Normal file
27
src-web/hooks/useCreateWorkspace.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { usePrompt } from './usePrompt';
|
||||
|
||||
export function useCreateWorkspace() {
|
||||
const routes = useAppRoutes();
|
||||
const prompt = usePrompt();
|
||||
return useMutation<Workspace, void, void>({
|
||||
mutationFn: async () => {
|
||||
const name = await prompt({
|
||||
id: 'new-workspace',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: 'My Workspace',
|
||||
title: 'New Workspace',
|
||||
confirmLabel: 'Create',
|
||||
placeholder: 'My Workspace',
|
||||
});
|
||||
return invoke('cmd_create_workspace', { name });
|
||||
},
|
||||
onSuccess: async (workspace) => {
|
||||
routes.navigate('workspace', { workspaceId: workspace.id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { GrpcRequest, HttpRequest } from '../lib/models';
|
||||
import { useDeleteAnyGrpcRequest } from './useDeleteAnyGrpcRequest';
|
||||
import { useDeleteAnyHttpRequest } from './useDeleteAnyHttpRequest';
|
||||
|
||||
export function useDeleteRequest(request: HttpRequest | GrpcRequest | null) {
|
||||
export function useDeleteRequest(id: string | null) {
|
||||
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
|
||||
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
|
||||
|
||||
return useMutation<void, string>({
|
||||
mutationFn: async () => {
|
||||
if (request?.model === 'http_request') {
|
||||
await deleteAnyHttpRequest.mutateAsync(request.id);
|
||||
} else if (request?.model === 'grpc_request') {
|
||||
await deleteAnyGrpcRequest.mutateAsync(request.id);
|
||||
} else {
|
||||
// Request is null
|
||||
}
|
||||
if (id == null) return;
|
||||
// We don't know what type it is based on the ID, so just try deleting both
|
||||
deleteAnyHttpRequest.mutate(id);
|
||||
deleteAnyGrpcRequest.mutate(id);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { useRegisterCommand } from './useCommands';
|
||||
import { usePrompt } from './usePrompt';
|
||||
|
||||
export function useGlobalCommands() {
|
||||
const prompt = usePrompt();
|
||||
const routes = useAppRoutes();
|
||||
|
||||
useRegisterCommand('workspace.create', {
|
||||
name: 'New Workspace',
|
||||
track: ['workspace', 'create'],
|
||||
onSuccess: async (workspace) => {
|
||||
routes.navigate('workspace', { workspaceId: workspace.id });
|
||||
},
|
||||
mutationFn: async ({ name: patchName }) => {
|
||||
const name =
|
||||
patchName ??
|
||||
(await prompt({
|
||||
id: 'new-workspace',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: 'My Workspace',
|
||||
title: 'New Workspace',
|
||||
confirmLabel: 'Create',
|
||||
placeholder: 'My Workspace',
|
||||
}));
|
||||
return invoke('cmd_create_workspace', { name });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -18,14 +18,6 @@ export type HotkeyAction =
|
||||
| 'request_switcher.toggle'
|
||||
| 'settings.show'
|
||||
| 'sidebar.focus'
|
||||
| 'sidebar.jump_1'
|
||||
| 'sidebar.jump_2'
|
||||
| 'sidebar.jump_3'
|
||||
| 'sidebar.jump_4'
|
||||
| 'sidebar.jump_5'
|
||||
| 'sidebar.jump_6'
|
||||
| 'sidebar.jump_7'
|
||||
| 'sidebar.jump_8'
|
||||
| 'urlBar.focus'
|
||||
| 'command_palette.toggle'
|
||||
| 'app.zoom_in'
|
||||
@@ -44,14 +36,6 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'request_switcher.toggle': ['CmdCtrl+p'],
|
||||
'settings.show': ['CmdCtrl+,'],
|
||||
'sidebar.focus': ['CmdCtrl+b'],
|
||||
'sidebar.jump_1': ['CmdCtrl+1'],
|
||||
'sidebar.jump_2': ['CmdCtrl+2'],
|
||||
'sidebar.jump_3': ['CmdCtrl+3'],
|
||||
'sidebar.jump_4': ['CmdCtrl+4'],
|
||||
'sidebar.jump_5': ['CmdCtrl+5'],
|
||||
'sidebar.jump_6': ['CmdCtrl+6'],
|
||||
'sidebar.jump_7': ['CmdCtrl+7'],
|
||||
'sidebar.jump_8': ['CmdCtrl+8'],
|
||||
'urlBar.focus': ['CmdCtrl+l'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'app.zoom_in': ['CmdCtrl+='],
|
||||
@@ -71,14 +55,6 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'request_switcher.toggle': 'Toggle Request Switcher',
|
||||
'settings.show': 'Open Settings',
|
||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||
'sidebar.jump_1': 'Jump to request 1',
|
||||
'sidebar.jump_2': 'Jump to request 2',
|
||||
'sidebar.jump_3': 'Jump to request 3',
|
||||
'sidebar.jump_4': 'Jump to request 4',
|
||||
'sidebar.jump_5': 'Jump to request 5',
|
||||
'sidebar.jump_6': 'Jump to request 6',
|
||||
'sidebar.jump_7': 'Jump to request 7',
|
||||
'sidebar.jump_8': 'Jump to request 8',
|
||||
'urlBar.focus': 'Focus URL',
|
||||
'command_palette.toggle': 'Toggle Command Palette',
|
||||
'app.zoom_in': 'Zoom In',
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useLatestHttpResponse } from './useLatestHttpResponse';
|
||||
|
||||
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
||||
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
||||
const { set: setPinnedResponseId, value: pinnedResponseId } = useKeyValue<string | null>({
|
||||
const { set, value: pinnedResponseId } = useKeyValue<string | null>({
|
||||
// Key on latest response instead of activeRequest because responses change out of band of active request
|
||||
key: ['pinned_http_response_id', latestResponse?.id ?? 'n/a'],
|
||||
fallback: null,
|
||||
@@ -15,5 +15,13 @@ export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
||||
const activeResponse: HttpResponse | null =
|
||||
responses.find((r) => r.id === pinnedResponseId) ?? latestResponse;
|
||||
|
||||
const setPinnedResponseId = async (id: string) => {
|
||||
if (pinnedResponseId === id) {
|
||||
await set(null);
|
||||
} else {
|
||||
await set(id);
|
||||
}
|
||||
};
|
||||
|
||||
return { activeResponse, setPinnedResponseId, pinnedResponseId, responses } as const;
|
||||
}
|
||||
|
||||
37
src-web/hooks/useSaveResponse.tsx
Normal file
37
src-web/hooks/useSaveResponse.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import mime from 'mime';
|
||||
import slugify from 'slugify';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { useToast } from '../components/ToastContext';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { getContentTypeHeader } from '../lib/models';
|
||||
import { getHttpRequest } from '../lib/store';
|
||||
|
||||
export function useSaveResponse(response: HttpResponse) {
|
||||
const toast = useToast();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const request = await getHttpRequest(response.requestId);
|
||||
if (request == null) return null;
|
||||
|
||||
const contentType = getContentTypeHeader(response.headers) ?? 'unknown';
|
||||
const ext = mime.getExtension(contentType);
|
||||
const slug = slugify(request.name, { lower: true });
|
||||
const filepath = await save({
|
||||
defaultPath: ext ? `${slug}.${ext}` : slug,
|
||||
title: 'Save Response',
|
||||
});
|
||||
await invoke('cmd_save_response', { responseId: response.id, filepath });
|
||||
toast.show({
|
||||
message: (
|
||||
<>
|
||||
Response saved to <InlineCode>{filepath}</InlineCode>
|
||||
</>
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -220,3 +220,7 @@ export function modelsEq(a: Model, b: Model) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getContentTypeHeader(headers: HttpHeader[]): string | null {
|
||||
return headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? null;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
@apply select-none cursor-default;
|
||||
}
|
||||
|
||||
.select-all * {
|
||||
/*@apply select-all;*/
|
||||
}
|
||||
|
||||
a,
|
||||
a[href] * {
|
||||
@apply cursor-pointer !important;
|
||||
|
||||
@@ -5,6 +5,12 @@ import { createRoot } from 'react-dom/client';
|
||||
import { attachConsole } from 'tauri-plugin-log-api';
|
||||
import { App } from './components/App';
|
||||
import './main.css';
|
||||
import { pdfjs } from 'react-pdf';
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
||||
const osType = await type();
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { internalIpV4 } from 'internal-ip';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { defineConfig, normalizePath } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const cMapsDir = normalizePath(
|
||||
path.join(path.dirname(require.resolve('pdfjs-dist/package.json')), 'cmaps'),
|
||||
);
|
||||
const standardFontsDir = normalizePath(
|
||||
path.join(path.dirname(require.resolve('pdfjs-dist/package.json')), 'standard_fonts'),
|
||||
);
|
||||
|
||||
const mobile = !!/android|ios/.exec(process.env.TAURI_ENV_PLATFORM ?? '');
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [svgr(), react(), topLevelAwait()],
|
||||
plugins: [
|
||||
svgr(),
|
||||
react(),
|
||||
topLevelAwait(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{ src: cMapsDir, dest: '' },
|
||||
{ src: standardFontsDir, dest: '' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 1420,
|
||||
|
||||
Reference in New Issue
Block a user