import { invoke } from "@tauri-apps/api/core"; import type { Workspace, WorkspaceEntryBase, RequestBody, WorkspaceEntry, WorkspaceEnvironment, EnvVariable, RequestUrl, RequestHeader, RequestPathParam, } from "./types"; import { getSetting, setSetting } from "./settings.svelte"; export type WorkspaceState = { /** * Currently selected workspace. */ workspace: Workspace | null; /** * Currently selected workspace entry. */ entry: WorkspaceEntry | null; /** * Workspace root entries. */ roots: number[]; /** * Workspace entry parent => children mappings. */ children: Record; /** * All workspace entries. */ indexes: Record; /** * Currently selected workspace environments. */ environments: WorkspaceEnvironment[]; /** * Currently selected environment. */ environment: WorkspaceEnvironment | null; }; export const state: WorkspaceState = $state({ workspace: null, entry: null, roots: [], children: {}, indexes: {}, environments: [], environment: null, }); const index = (entry: WorkspaceEntry) => { console.log("indexing", entry); state.indexes[entry.id] = entry; if (entry.parent_id !== null) { if (state.children[entry.parent_id]) { state.children[entry.parent_id].push(entry.id); } else { state.children[entry.parent_id] = [entry.id]; } } else { state.roots.push(entry.id); } }; function reset() { state.children = {}; state.indexes = {}; state.roots = []; state.entry = null; state.environment = null; state.environments = []; } export async function selectEnvironment(id: number | null) { if (id === null) { state.environment = null; return; } state.environment = state.environments.find((e) => e.id === id) ?? null; let env = await getSetting("lastEnvironment"); if (env) { env[state.workspace!!.id] = id; } else { env = { [state.workspace!!.id]: id }; } setSetting("lastEnvironment", env); console.debug("selected environment:", state.environment?.name); } export function selectWorkspace(ws: Workspace) { console.debug("selecting workspace:", ws.name); state.workspace = ws; } export async function selectEntry(id: number) { state.entry = state.indexes[id]; console.log("selected entry:", $state.snapshot(state.entry)); if (state.entry.parent_id !== null) { let parent = state.indexes[state.entry.parent_id]; while (parent) { parent.open = true; if (parent.parent_id === null) { break; } parent = state.indexes[parent.parent_id]; } } if (state.entry.type === "Request") { parseUrl(state.entry!!.url) .then(() => console.debug("working URL:", $state.snapshot(state.entry.workingUrl)), ) .catch((e) => { console.error("error parsing URL", e); }); expandUrl() .then(() => console.debug( "expanded URL:", $state.snapshot(state.entry.expandedUrl), ), ) .catch((e) => { console.error("error expanding URL", e); }); } } // COMMANDS export async function createWorkspace(name: string): Promise { return invoke("create_workspace", { name }); } export async function listWorkspaces(): Promise { return invoke("list_workspaces"); } export async function loadWorkspace(ws: Workspace) { reset(); state.workspace = ws; const entries = await invoke( "list_workspace_entries", { id: state.workspace.id, }, ); for (const entry of entries) { if (entry.type === "Request") { index({ ...entry.data.entry, method: entry.data.method, url: entry.data.url, headers: entry.data.headers, body: entry.data.body, path: entry.data.path_params, }); } else { index(entry.data); } } await loadEnvironments(state.workspace.id); } export function createRequest(parentId?: number) { if (state.workspace == null) { console.warn("create workspace request called with no active workspace"); return; } const data = { Request: { name: "", workspace_id: state.workspace.id, parent_id: parentId, method: "GET", url: "", }, }; invoke("create_workspace_entry", { data, }).then((entry) => { index({ ...entry, method: data.Request.method, url: data.Request.url, body: null, headers: [], path: [], }); console.log("request created:", entry); }); } export function createCollection(parentId?: number) { if (state.workspace == null) { console.warn("create workspace request called with no active workspace"); return; } const data = { Collection: { name: "", workspace_id: state.workspace.id, parent_id: parentId, }, }; invoke("create_workspace_entry", { data, }).then((entry) => { index(entry); console.log("collection created:", entry); }); } export async function loadEnvironments(workspaceId: number) { state.environments = await invoke( "list_environments", { workspaceId }, ); const lastEnv = (await getSetting("lastEnvironment"))?.[workspaceId]; if (lastEnv) { selectEnvironment(lastEnv); } } export async function createEnvironment(workspaceId: number, name: string) { console.debug("creating environment in", workspaceId); const env = await invoke("create_env", { workspaceId, name, }); state.environment = env; state.environments.push(state.environment); } export async function updateEnvironment() { if (!state.environment) { console.warn("attempted to persist null env"); return; } console.debug("updating environment", state.environment); await invoke("update_env", { id: state.environment.id, name: state.environment.name, }); } export async function sendRequest(): Promise { const res = await invoke("send_request", { reqId: state.entry!!.id, envId: state.environment?.id, }); console.debug(res); return res; } export async function updateEntryName(name: string) { if (!state.entry) { console.warn("attempted to persist null entry"); return; } console.debug(state.entry.id, "updating entry name to", name); const data = state.entry.type === "Request" ? { Request: { base: { name, }, }, } : { Collection: { name } }; await invoke("update_workspace_entry", { entryId: state.entry.id, data, }); } export async function parseUrl(url: string) { console.debug("parsing", $state.snapshot(url)); state.entry!!.workingUrl = await invoke("parse_url", { url, envId: state.environment?.id, }); } /** * Update a request's URL string. If `usePathparams` is true, path entries * from state.entry.path will be used to replace those at the same position and should * be set to true whenever this is called from an input field of a destructured URL. */ export async function updateUrl(u: string, usePathParams: boolean) { const [url, params] = await invoke("update_url", { entryId: state.entry!!.id, usePathParams, url: u, pathParams: state.entry.path, }); state.entry!!.url = u; state.entry.path = params; state.entry.workingUrl = url; expandUrl(); console.debug("updated", $state.snapshot(state.entry)); } export async function expandUrl() { state.entry!!.expandedUrl = await invoke("expand_url", { entryId: state.entry!!.id, envId: state.environment?.id, url: state.entry!!.url, }); } export async function insertEnvVariable( workspaceId: number, envId: number, name: string = "", value: string = "", secret: boolean = false, ) { const v = await invoke("insert_env_var", { workspaceId, envId, name, value, secret, }); state.environment?.variables.push(v); } export async function updateEnvVariable(v: EnvVariable) { if (v.name.length === 0 && v.value.length === 0) { console.debug("deleting var:", v); return deleteEnvVariable(v.id); } console.debug("updating var:", v); return invoke("update_env_var", { id: v.id, name: v.name, value: v.value, secret: v.secret, }); } export async function deleteEnvVariable(id: number) { await invoke("delete_env_var", { id }); state.environment!!.variables = state.environment!!.variables.filter( (v) => v.id !== id, ); } export async function insertHeader() { const header = await invoke("insert_header", { entryId: state.entry!!.id, insert: { name: "", value: "" }, }); state.entry!!.headers.push(header); } export async function updateHeader(id: number, name: string, value: string) { await invoke("update_header", { update: { id, name, value }, }); const header = state.entry!!.headers.find((header) => header.id === id); header.name = name; header.value = value; } export async function deleteHeader(id: number) { await invoke("delete_header", { headerId: id, }); state.entry!!.headers = state.entry!!.headers.filter( (header) => header.id !== id, ); } export async function deleteBody() { if (state.entry!!.body === null) { console.warn("attempted to delete null body", $state.snapshot(state.entry)); return; } await invoke("update_request_body", { id: state.entry!!.body.id, body: { Null: null }, }); state.entry.body = null; console.debug("Deleted request body"); } export async function updateBodyContent(body: string, ct: string) { if (state.entry!!.body !== null) { await invoke("update_request_body", { id: state.entry!!.body.id, body: { Value: { ty: ct, content: body, }, }, }); state.entry.body.body = body; state.entry.body.content_type = ct; } else { const b = await invoke("insert_request_body", { entryId: state.entry!!.id, body: { ty: ct, content: body, }, }); state.entry!!.body = b; } console.debug("Updated body content to", $state.snapshot(state.entry!!.body)); } type WorkspaceEntryResponse = | { type: "Collection"; data: WorkspaceEntryBase; } | { type: "Request"; data: WorkspaceRequestResponse; }; type WorkspaceRequestResponse = { entry: WorkspaceEntryBase; method: string; url: string; body: RequestBody | null; headers: RequestHeader[]; path_params: RequestPathParam[]; };