From cd396994678badb22530c255037d002c0c9464a2 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Fri, 17 Mar 2023 08:36:21 -0700 Subject: [PATCH] Flatten migrations, kvs lib, fix tabs --- index.html | 37 +- src-tauri/migrations/20230225181302_init.sql | 74 ++- .../20230301163450_add-response-error.sql | 1 - .../20230314160400_add-body-type.sql | 1 - .../migrations/20230316052652_key-values.sql | 12 - .../20230316062901_model-fields.sql | 3 - src-tauri/sqlx-data.json | 426 ++++++++---------- src-tauri/src/main.rs | 2 +- src-tauri/src/models.rs | 19 +- src-web/components/App.tsx | 5 +- src-web/components/RequestPane.tsx | 6 +- src-web/components/Sidebar.tsx | 14 +- src-web/components/core/Tabs/Tabs.tsx | 4 +- src-web/hooks/useIsResponseLoading.ts | 2 +- src-web/hooks/useKeyValue.ts | 39 ++ src-web/hooks/useKeyValues.ts | 57 --- src-web/hooks/useResponseViewMode.ts | 4 +- src-web/hooks/useTheme.ts | 10 +- src-web/hooks/useWorkspaces.ts | 1 + src-web/lib/clamp.ts | 3 + src-web/lib/keyValueStore.ts | 62 +++ src-web/lib/models.ts | 6 +- src-web/main.tsx | 10 +- 23 files changed, 406 insertions(+), 392 deletions(-) delete mode 100644 src-tauri/migrations/20230301163450_add-response-error.sql delete mode 100644 src-tauri/migrations/20230314160400_add-body-type.sql delete mode 100644 src-tauri/migrations/20230316052652_key-values.sql delete mode 100644 src-tauri/migrations/20230316062901_model-fields.sql create mode 100644 src-web/hooks/useKeyValue.ts delete mode 100644 src-web/hooks/useKeyValues.ts create mode 100644 src-web/lib/clamp.ts create mode 100644 src-web/lib/keyValueStore.ts diff --git a/index.html b/index.html index 934cca9b..8f77583a 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,28 @@ - - - - - Yaak App - - + + + + + Yaak App + + + + + +
+
+
+ + diff --git a/src-tauri/migrations/20230225181302_init.sql b/src-tauri/migrations/20230225181302_init.sql index 15f8d1e9..52fa79e7 100644 --- a/src-tauri/migrations/20230225181302_init.sql +++ b/src-tauri/migrations/20230225181302_init.sql @@ -1,39 +1,65 @@ +CREATE TABLE key_values +( + model TEXT DEFAULT 'key_value' NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted_at DATETIME, + namespace TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (namespace, key) +); + CREATE TABLE workspaces ( - id TEXT NOT NULL PRIMARY KEY, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + id TEXT NOT NULL + PRIMARY KEY, + model TEXT DEFAULT 'workspace' NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, deleted_at DATETIME, - name TEXT NOT NULL, - description TEXT NOT NULL + name TEXT NOT NULL, + description TEXT NOT NULL ); CREATE TABLE http_requests ( - id TEXT NOT NULL PRIMARY KEY, - workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + id TEXT NOT NULL + PRIMARY KEY, + model TEXT DEFAULT 'http_request' NOT NULL, + workspace_id TEXT NOT NULL + REFERENCES workspaces + ON DELETE CASCADE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, deleted_at DATETIME, - name TEXT NOT NULL, - url TEXT NOT NULL, - method TEXT NOT NULL, - headers TEXT NOT NULL, - body TEXT + name TEXT NOT NULL, + url TEXT NOT NULL, + method TEXT NOT NULL, + headers TEXT NOT NULL, + body TEXT, + body_type TEXT ); CREATE TABLE http_responses ( - id TEXT NOT NULL PRIMARY KEY, - request_id TEXT NOT NULL REFERENCES http_requests (id) ON DELETE CASCADE, - workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + id TEXT NOT NULL + PRIMARY KEY, + model TEXT DEFAULT 'http_response' NOT NULL, + request_id TEXT NOT NULL + REFERENCES http_requests + ON DELETE CASCADE, + workspace_id TEXT NOT NULL + REFERENCES workspaces + ON DELETE CASCADE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, deleted_at DATETIME, - elapsed INTEGER NOT NULL, - status INTEGER NOT NULL, + elapsed INTEGER NOT NULL, + status INTEGER NOT NULL, status_reason TEXT, - url TEXT NOT NULL, - body TEXT NOT NULL, - headers TEXT NOT NULL + url TEXT NOT NULL, + body TEXT NOT NULL, + headers TEXT NOT NULL, + error TEXT ); diff --git a/src-tauri/migrations/20230301163450_add-response-error.sql b/src-tauri/migrations/20230301163450_add-response-error.sql deleted file mode 100644 index 578d876b..00000000 --- a/src-tauri/migrations/20230301163450_add-response-error.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE http_responses ADD COLUMN error TEXT NULL; diff --git a/src-tauri/migrations/20230314160400_add-body-type.sql b/src-tauri/migrations/20230314160400_add-body-type.sql deleted file mode 100644 index 77d27a1b..00000000 --- a/src-tauri/migrations/20230314160400_add-body-type.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE http_requests ADD COLUMN body_type TEXT NULL; diff --git a/src-tauri/migrations/20230316052652_key-values.sql b/src-tauri/migrations/20230316052652_key-values.sql deleted file mode 100644 index 54943962..00000000 --- a/src-tauri/migrations/20230316052652_key-values.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE key_values -( - model TEXT NOT NULL DEFAULT 'key_value', - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at DATETIME, - namespace TEXT NOT NULL, - key TEXT NOT NULL, - value TEXT NOT NULL, - - PRIMARY KEY (namespace, key) -); diff --git a/src-tauri/migrations/20230316062901_model-fields.sql b/src-tauri/migrations/20230316062901_model-fields.sql deleted file mode 100644 index 14ef6f90..00000000 --- a/src-tauri/migrations/20230316062901_model-fields.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE http_responses ADD COLUMN model TEXT DEFAULT 'http_response'; -ALTER TABLE http_requests ADD COLUMN model TEXT DEFAULT 'http_request'; -ALTER TABLE workspaces ADD COLUMN model TEXT DEFAULT 'workspace'; diff --git a/src-tauri/sqlx-data.json b/src-tauri/sqlx-data.json index b82264b2..7b0bf4fc 100644 --- a/src-tauri/sqlx-data.json +++ b/src-tauri/sqlx-data.json @@ -1,6 +1,6 @@ { "db": "SQLite", - "032c7a3861630c499f3e9785687be303aa95c023548e170572ad36eba90ecf84": { + "06aaf8f4a17566f1d25da2a60f0baf4b5fc28c3cf0c001a84e25edf9eab3c7e3": { "describe": { "columns": [ { @@ -18,24 +18,19 @@ "ordinal": 2, "type_info": "Datetime" }, - { - "name": "deleted_at", - "ordinal": 3, - "type_info": "Datetime" - }, { "name": "namespace", - "ordinal": 4, + "ordinal": 3, "type_info": "Text" }, { "name": "key", - "ordinal": 5, + "ordinal": 4, "type_info": "Text" }, { "name": "value", - "ordinal": 6, + "ordinal": 5, "type_info": "Text" } ], @@ -43,7 +38,6 @@ false, false, false, - true, false, false, false @@ -52,7 +46,7 @@ "Right": 2 } }, - "query": "\n SELECT model, created_at, updated_at, deleted_at, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n " + "query": "\n SELECT model, created_at, updated_at, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n " }, "07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": { "describe": { @@ -84,7 +78,7 @@ }, "query": "\n DELETE FROM http_requests\n WHERE id = ?\n " }, - "8423faeb5ce376acffe4c24804e0332c334edf929d9cea0086ee00c108e833fb": { + "68b7b17a25d415ce90b33aef16418d668f7ff6275b383bf21c16cafb38cca342": { "describe": { "columns": [ { @@ -103,9 +97,9 @@ "type_info": "Text" }, { - "name": "request_id", + "name": "created_at", "ordinal": 3, - "type_info": "Text" + "type_info": "Datetime" }, { "name": "updated_at", @@ -113,48 +107,33 @@ "type_info": "Datetime" }, { - "name": "deleted_at", + "name": "name", "ordinal": 5, - "type_info": "Datetime" + "type_info": "Text" }, { - "name": "created_at", + "name": "url", "ordinal": 6, - "type_info": "Datetime" + "type_info": "Text" }, { - "name": "status", + "name": "method", "ordinal": 7, - "type_info": "Int64" - }, - { - "name": "status_reason", - "ordinal": 8, "type_info": "Text" }, { "name": "body", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "body_type", "ordinal": 9, "type_info": "Text" }, { - "name": "elapsed", + "name": "headers!: sqlx::types::Json>", "ordinal": 10, - "type_info": "Int64" - }, - { - "name": "url", - "ordinal": 11, - "type_info": "Text" - }, - { - "name": "error", - "ordinal": 12, - "type_info": "Text" - }, - { - "name": "headers!: sqlx::types::Json>", - "ordinal": 13, "type_info": "Text" } ], @@ -164,13 +143,10 @@ false, false, false, - true, + false, false, false, true, - false, - false, - false, true, false ], @@ -178,7 +154,7 @@ "Right": 1 } }, - "query": "\n SELECT id, model, workspace_id, request_id, updated_at, deleted_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n " + "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " }, "913f3c3a46b1834c4cd8367aed9d5a9659a1d775d8771e9f5bf9a5aa41197356": { "describe": { @@ -190,60 +166,6 @@ }, "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n url = excluded.url\n " }, - "9fcbf8317858427b2e75b29ce64deeed1cc77ef22a35463ab65eee89eb25fa02": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "model", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "description", - "ordinal": 6, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n WHERE id = ?\n " - }, "a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": { "describe": { "columns": [], @@ -254,7 +176,7 @@ }, "query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n " }, - "bca6dd1eec0cdf4de57323f7b88e8578ebe44b6c9606e38cbaacc3e0094b434d": { + "caf3f21bf291dfbd36446592066e96c1f83abe96f6ea9211a3e049eb9c58a8c8": { "describe": { "columns": [ { @@ -267,54 +189,24 @@ "ordinal": 1, "type_info": "Text" }, - { - "name": "workspace_id", - "ordinal": 2, - "type_info": "Text" - }, { "name": "created_at", - "ordinal": 3, + "ordinal": 2, "type_info": "Datetime" }, { "name": "updated_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 5, + "ordinal": 3, "type_info": "Datetime" }, { "name": "name", - "ordinal": 6, + "ordinal": 4, "type_info": "Text" }, { - "name": "url", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "method", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "body", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "body_type", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "headers!: sqlx::types::Json>", - "ordinal": 11, + "name": "description", + "ordinal": 5, "type_info": "Text" } ], @@ -324,31 +216,63 @@ false, false, false, - true, - false, - false, - false, - true, - true, false ], "parameters": { "Right": 1 } }, - "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n " + "query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces WHERE id = ?\n " }, - "d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": { + "cea4cae52f16ec78aca9a47b17117422d4f165e5a3b308c70fd1a180382475ea": { "describe": { - "columns": [], - "nullable": [], + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "model", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 2, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 5, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false + ], "parameters": { - "Right": 3 + "Right": 0 } }, - "query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n " + "query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces\n " }, - "d9b614f9f0de223474464e0a30e663f6f4467f85d6f6abaacd8b76d9289ba38f": { + "d5ad6d5f82fe837fa9215bd4619ec18a7c95b3088d4fbf9825f2d1d28069d1ce": { "describe": { "columns": [ { @@ -377,48 +301,43 @@ "type_info": "Datetime" }, { - "name": "deleted_at", + "name": "created_at", "ordinal": 5, "type_info": "Datetime" }, - { - "name": "created_at", - "ordinal": 6, - "type_info": "Datetime" - }, { "name": "status", - "ordinal": 7, + "ordinal": 6, "type_info": "Int64" }, { "name": "status_reason", - "ordinal": 8, + "ordinal": 7, "type_info": "Text" }, { "name": "body", - "ordinal": 9, + "ordinal": 8, "type_info": "Text" }, { "name": "elapsed", - "ordinal": 10, + "ordinal": 9, "type_info": "Int64" }, { "name": "url", - "ordinal": 11, + "ordinal": 10, "type_info": "Text" }, { "name": "error", - "ordinal": 12, + "ordinal": 11, "type_info": "Text" }, { "name": "headers!: sqlx::types::Json>", - "ordinal": 13, + "ordinal": 12, "type_info": "Text" } ], @@ -428,7 +347,6 @@ false, false, false, - true, false, false, true, @@ -442,73 +360,19 @@ "Right": 1 } }, - "query": "\n SELECT id, model, workspace_id, request_id, updated_at, deleted_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE id = ?\n " + "query": "\n SELECT id, model, workspace_id, request_id, updated_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n " }, - "e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": { + "d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 9 + "Right": 3 } }, - "query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n " + "query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n " }, - "eb7b10c632c871122d9e50cacba1f52604c366d888f014ac7f0a2642dbd27839": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "model", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "description", - "ordinal": 6, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - false, - false - ], - "parameters": { - "Right": 0 - } - }, - "query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n " - }, - "eda13351c05e67885a71a1b9fc7f2d0641d94ca5f4be824d4a0edad5f1ec657e": { + "d9ea350bc21ac2f51f6dcb9713328ec330f0e12105da70bf5a6eff9601e32a85": { "describe": { "columns": [ { @@ -536,39 +400,34 @@ "ordinal": 4, "type_info": "Datetime" }, - { - "name": "deleted_at", - "ordinal": 5, - "type_info": "Datetime" - }, { "name": "name", - "ordinal": 6, + "ordinal": 5, "type_info": "Text" }, { "name": "url", - "ordinal": 7, + "ordinal": 6, "type_info": "Text" }, { "name": "method", - "ordinal": 8, + "ordinal": 7, "type_info": "Text" }, { "name": "body", - "ordinal": 9, + "ordinal": 8, "type_info": "Text" }, { "name": "body_type", - "ordinal": 10, + "ordinal": 9, "type_info": "Text" }, { "name": "headers!: sqlx::types::Json>", - "ordinal": 11, + "ordinal": 10, "type_info": "Text" } ], @@ -578,7 +437,6 @@ false, false, false, - true, false, false, false, @@ -590,7 +448,107 @@ "Right": 1 } }, - "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " + "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n " + }, + "e3ade0a69348d512e47e964bded9d7d890b92fdc1e01c6c22fa5e91f943639f2": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "model", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "workspace_id", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "request_id", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "updated_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "created_at", + "ordinal": 5, + "type_info": "Datetime" + }, + { + "name": "status", + "ordinal": 6, + "type_info": "Int64" + }, + { + "name": "status_reason", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "body", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "elapsed", + "ordinal": 9, + "type_info": "Int64" + }, + { + "name": "url", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "error", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 12, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE id = ?\n " + }, + "e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 9 + } + }, + "query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n " }, "f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": { "describe": { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d1bb0b6c..f0cff84d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -354,7 +354,7 @@ async fn workspaces( .await .expect("Failed to find workspaces"); if workspaces.is_empty() { - let workspace = models::create_workspace("Default", "This is the default workspace", pool) + let workspace = models::create_workspace("My Project", "This is the default workspace", pool) .await .expect("Failed to create workspace"); Ok(vec![workspace]) diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 33fd6583..4c5ed8bc 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -11,7 +11,6 @@ pub struct Workspace { pub model: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub deleted_at: Option, pub name: String, pub description: String, } @@ -30,7 +29,6 @@ pub struct HttpRequest { pub model: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub deleted_at: Option, pub workspace_id: String, pub name: String, pub url: String, @@ -56,7 +54,6 @@ pub struct HttpResponse { pub request_id: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub deleted_at: Option, pub error: Option, pub url: String, pub elapsed: i64, @@ -72,7 +69,6 @@ pub struct KeyValue { pub model: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub deleted_at: Option, pub namespace: String, pub key: String, pub value: String, @@ -106,7 +102,7 @@ pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool) -> O sqlx::query_as!( KeyValue, r#" - SELECT model, created_at, updated_at, deleted_at, namespace, key, value + SELECT model, created_at, updated_at, namespace, key, value FROM key_values WHERE namespace = ? AND key = ? "#, @@ -122,7 +118,7 @@ pub async fn find_workspaces(pool: &Pool) -> Result, sqlx sqlx::query_as!( Workspace, r#" - SELECT id, model, created_at, updated_at, deleted_at, name, description + SELECT id, model, created_at, updated_at, name, description FROM workspaces "#, ) @@ -134,9 +130,8 @@ pub async fn get_workspace(id: &str, pool: &Pool) -> Result) -> Result) -> Result>" FROM http_responses @@ -380,7 +373,7 @@ pub async fn find_responses( sqlx::query_as!( HttpResponse, r#" - SELECT id, model, workspace_id, request_id, updated_at, deleted_at, + SELECT id, model, workspace_id, request_id, updated_at, created_at, status, status_reason, body, elapsed, url, error, headers AS "headers!: sqlx::types::Json>" FROM http_responses diff --git a/src-web/components/App.tsx b/src-web/components/App.tsx index af0dbe9c..dd7f2c98 100644 --- a/src-web/components/App.tsx +++ b/src-web/components/App.tsx @@ -5,10 +5,11 @@ import { listen } from '@tauri-apps/api/event'; import { MotionConfig } from 'framer-motion'; import { HelmetProvider } from 'react-helmet-async'; import { matchPath } from 'react-router-dom'; -import { keyValueQueryKey } from '../hooks/useKeyValues'; +import { keyValueQueryKey } from '../hooks/useKeyValue'; import { requestsQueryKey } from '../hooks/useRequests'; import { responsesQueryKey } from '../hooks/useResponses'; import { DEFAULT_FONT_SIZE } from '../lib/constants'; +import { extractKeyValue } from '../lib/keyValueStore'; import type { HttpRequest, HttpResponse, KeyValue } from '../lib/models'; import { convertDates } from '../lib/models'; import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter'; @@ -16,7 +17,7 @@ import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter'; const queryClient = new QueryClient(); await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => { - queryClient.setQueryData(keyValueQueryKey(keyValue), keyValue); + queryClient.setQueryData(keyValueQueryKey(keyValue), extractKeyValue(keyValue)); }); await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => { diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index 9fe6ed3c..e7d39653 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -1,7 +1,8 @@ import classnames from 'classnames'; +import { act } from 'react-dom/test-utils'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useIsResponseLoading } from '../hooks/useIsResponseLoading'; -import { useKeyValues } from '../hooks/useKeyValues'; +import { useKeyValue } from '../hooks/useKeyValue'; import { useSendRequest } from '../hooks/useSendRequest'; import { useUpdateRequest } from '../hooks/useUpdateRequest'; import { tryFormatJson } from '../lib/formatters'; @@ -21,7 +22,7 @@ export function RequestPane({ fullHeight, className }: Props) { const updateRequest = useUpdateRequest(activeRequest); const sendRequest = useSendRequest(activeRequest); const responseLoading = useIsResponseLoading(); - const activeTab = useKeyValues({ + const activeTab = useKeyValue({ key: ['active_request_body_tab', activeRequest?.id ?? 'n/a'], initialValue: 'body', }); @@ -61,7 +62,6 @@ export function RequestPane({ fullHeight, className }: Props) { { value: 'auth', label: 'Auth' }, ]} className="mt-2" - defaultValue="body" label="Request body" > diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 2cc0a912..eb9550a9 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -3,10 +3,11 @@ import React, { useRef, useState } from 'react'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useCreateRequest } from '../hooks/useCreateRequest'; import { useDeleteRequest } from '../hooks/useDeleteRequest'; -import { useKeyValues } from '../hooks/useKeyValues'; +import { useKeyValue } from '../hooks/useKeyValue'; import { useRequests } from '../hooks/useRequests'; import { useTheme } from '../hooks/useTheme'; import { useUpdateRequest } from '../hooks/useUpdateRequest'; +import { clamp } from '../lib/clamp'; import type { HttpRequest } from '../lib/models'; import { Button } from './core/Button'; import { Dropdown, DropdownMenuTrigger } from './core/Dropdown'; @@ -19,11 +20,12 @@ interface Props { className?: string; } -const MIN_WIDTH = 130; +const MIN_WIDTH = 110; +const MAX_WIDTH = 500; export function Sidebar({ className }: Props) { const [isDragging, setIsDragging] = useState(false); - const width = useKeyValues({ key: 'sidebar_width', initialValue: 200 }); + const width = useKeyValue({ key: 'sidebar_width', initialValue: 200 }); const requests = useRequests(); const activeRequest = useActiveRequest(); const createRequest = useCreateRequest({ navigateAfter: true }); @@ -43,7 +45,7 @@ export function Sidebar({ className }: Props) { const startWidth = width.value; moveState.current = { move: (e: MouseEvent) => { - const newWidth = Math.max(MIN_WIDTH, startWidth + (e.clientX - mouseStartX)); + const newWidth = clamp(startWidth + (e.clientX - mouseStartX), MIN_WIDTH, MAX_WIDTH); width.set(newWidth); }, up: () => { @@ -73,8 +75,8 @@ export function Sidebar({ className }: Props) { >
diff --git a/src-web/components/core/Tabs/Tabs.tsx b/src-web/components/core/Tabs/Tabs.tsx index f61078dc..d3212261 100644 --- a/src-web/components/core/Tabs/Tabs.tsx +++ b/src-web/components/core/Tabs/Tabs.tsx @@ -10,7 +10,6 @@ import { HStack } from '../Stacks'; import './Tabs.css'; interface Props { - defaultValue?: string; label: string; onChangeValue: (value: string) => void; value: string; @@ -31,7 +30,6 @@ interface Props { export function Tabs({ value, onChangeValue, - defaultValue, label, children, tabs, @@ -40,7 +38,7 @@ export function Tabs({ }: Props) { return ( diff --git a/src-web/hooks/useIsResponseLoading.ts b/src-web/hooks/useIsResponseLoading.ts index bca79c5c..dbbc69b6 100644 --- a/src-web/hooks/useIsResponseLoading.ts +++ b/src-web/hooks/useIsResponseLoading.ts @@ -4,5 +4,5 @@ export function useIsResponseLoading(): boolean { const responses = useResponses(); const response = responses[responses.length - 1]; if (!response) return false; - return !(response.body || response.error); + return !(response.body || response.status || response.error); } diff --git a/src-web/hooks/useKeyValue.ts b/src-web/hooks/useKeyValue.ts new file mode 100644 index 00000000..9ebf5885 --- /dev/null +++ b/src-web/hooks/useKeyValue.ts @@ -0,0 +1,39 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore'; + +const DEFAULT_NAMESPACE = 'app'; + +export function keyValueQueryKey({ + namespace = DEFAULT_NAMESPACE, + key, +}: { + namespace?: string; + key: string | string[]; +}) { + return ['key_value', { namespace, key: buildKeyValueKey(key) }]; +} + +export function useKeyValue({ + namespace = DEFAULT_NAMESPACE, + key, + initialValue, +}: { + namespace?: string; + key: string | string[]; + initialValue: T; +}) { + const query = useQuery({ + initialData: initialValue, + queryKey: keyValueQueryKey({ namespace, key }), + queryFn: async () => getKeyValue({ namespace, key, fallback: initialValue }), + }); + + const mutate = useMutation({ + mutationFn: (value) => setKeyValue({ namespace, key, value }), + }); + + return { + value: query.data, + set: (value: T) => mutate.mutate(value), + }; +} diff --git a/src-web/hooks/useKeyValues.ts b/src-web/hooks/useKeyValues.ts deleted file mode 100644 index 86e7294a..00000000 --- a/src-web/hooks/useKeyValues.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useMutation, useQuery } from '@tanstack/react-query'; -import { invoke } from '@tauri-apps/api'; -import type { KeyValue } from '../lib/models'; - -const DEFAULT_NAMESPACE = 'app'; - -export function keyValueQueryKey({ - namespace = DEFAULT_NAMESPACE, - key, -}: { - namespace?: string; - key: string | string[]; -}) { - return ['key_value', { namespace, key: buildKey(key) }]; -} - -export function useKeyValues({ - namespace = DEFAULT_NAMESPACE, - key, - initialValue, -}: { - namespace?: string; - key: string | string[]; - initialValue: T; -}) { - const query = useQuery({ - initialData: null, - queryKey: keyValueQueryKey({ namespace, key }), - queryFn: async () => invoke('get_key_value', { namespace, key: buildKey(key) }), - }); - - const mutate = useMutation({ - mutationFn: (value) => { - return invoke('set_key_value', { - namespace, - key: buildKey(key), - value: JSON.stringify(value), - }); - }, - }); - - let value: T; - try { - value = JSON.parse(query.data?.value ?? JSON.stringify(initialValue)); - } catch (e) { - value = initialValue; - } - return { - value, - set: (value: T) => mutate.mutate(value), - }; -} - -function buildKey(key: string | string[]): string { - if (typeof key === 'string') return key; - return key.join('::'); -} diff --git a/src-web/hooks/useResponseViewMode.ts b/src-web/hooks/useResponseViewMode.ts index 3c434d30..6a03369f 100644 --- a/src-web/hooks/useResponseViewMode.ts +++ b/src-web/hooks/useResponseViewMode.ts @@ -1,7 +1,7 @@ -import { useKeyValues } from './useKeyValues'; +import { useKeyValue } from './useKeyValue'; export function useResponseViewMode(requestId?: string): [string, () => void] { - const v = useKeyValues({ + const v = useKeyValue({ namespace: 'app', key: ['response_view_mode', requestId ?? 'n/a'], initialValue: 'pretty', diff --git a/src-web/hooks/useTheme.ts b/src-web/hooks/useTheme.ts index 8d96fd42..de47c89e 100644 --- a/src-web/hooks/useTheme.ts +++ b/src-web/hooks/useTheme.ts @@ -1,3 +1,4 @@ +import { app } from '@tauri-apps/api'; import { useEffect } from 'react'; import type { Appearance } from '../lib/theme/window'; import { @@ -5,10 +6,13 @@ import { setAppearance, subscribeToPreferredAppearanceChange, } from '../lib/theme/window'; -import { useKeyValues } from './useKeyValues'; +import { useKeyValue } from './useKeyValue'; export function useTheme() { - const appearanceKv = useKeyValues({ key: 'appearance', initialValue: getAppearance() }); + const appearanceKv = useKeyValue({ + key: 'appearance', + initialValue: getAppearance(), + }); const themeChange = (appearance: Appearance) => { appearanceKv.set(appearance); @@ -22,7 +26,7 @@ export function useTheme() { useEffect(() => subscribeToPreferredAppearanceChange(themeChange), []); // Sync appearance when k/v changes - useEffect(() => setAppearance(appearanceKv.value as Appearance), [appearanceKv.value]); + useEffect(() => setAppearance(appearanceKv.value), [appearanceKv.value]); return { appearance: appearanceKv.value, diff --git a/src-web/hooks/useWorkspaces.ts b/src-web/hooks/useWorkspaces.ts index bb882b28..051541a7 100644 --- a/src-web/hooks/useWorkspaces.ts +++ b/src-web/hooks/useWorkspaces.ts @@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query'; export function useWorkspaces() { return ( useQuery(['workspaces'], async () => { + console.log('INVOKING WORKSPACES'); const workspaces = (await invoke('workspaces')) as Workspace[]; return workspaces.map(convertDates); }).data ?? [] diff --git a/src-web/lib/clamp.ts b/src-web/lib/clamp.ts new file mode 100644 index 00000000..0210f95e --- /dev/null +++ b/src-web/lib/clamp.ts @@ -0,0 +1,3 @@ +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} diff --git a/src-web/lib/keyValueStore.ts b/src-web/lib/keyValueStore.ts new file mode 100644 index 00000000..329089b4 --- /dev/null +++ b/src-web/lib/keyValueStore.ts @@ -0,0 +1,62 @@ +import { invoke } from '@tauri-apps/api'; +import type { KeyValue } from './models'; + +const DEFAULT_NAMESPACE = 'app'; + +type KeyValueValue = string | number | boolean; + +export async function setKeyValue({ + namespace = DEFAULT_NAMESPACE, + key, + value, +}: { + namespace?: string; + key: string | string[]; + value: T; +}): Promise { + await invoke('set_key_value', { + namespace, + key: buildKeyValueKey(key), + value: JSON.stringify(value), + }); + return value; +} + +export async function getKeyValue({ + namespace = DEFAULT_NAMESPACE, + key, + fallback, +}: { + namespace?: string; + key: string | string[]; + fallback: T; +}) { + const kv = (await invoke('get_key_value', { + namespace, + key: buildKeyValueKey(key), + })) as KeyValue | null; + return extractKeyValueOrFallback(kv, fallback); +} + +export function extractKeyValue(kv: KeyValue | null): T | undefined { + if (kv === null) return undefined; + try { + return JSON.parse(kv.value) as T; + } catch (err) { + return undefined; + } +} + +export function extractKeyValueOrFallback( + kv: KeyValue | null, + fallback: T, +): T { + const v = extractKeyValue(kv); + if (v === undefined) return fallback; + return v; +} + +export function buildKeyValueKey(key: string | string[]): string { + if (typeof key === 'string') return key; + return key.join('::'); +} diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index 30b77f46..4d361938 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -3,7 +3,6 @@ export interface BaseModel { readonly workspaceId: string; readonly createdAt: Date; readonly updatedAt: Date; - readonly deletedAt: Date | null; } export interface Workspace extends BaseModel { @@ -46,14 +45,11 @@ export interface HttpResponse extends BaseModel { readonly headers: HttpHeader[]; } -export function convertDates>( - m: T, -): T { +export function convertDates>(m: T): T { return { ...m, createdAt: convertDate(m.createdAt), updatedAt: convertDate(m.updatedAt), - deletedAt: m.deletedAt ? convertDate(m.deletedAt) : null, }; } diff --git a/src-web/main.tsx b/src-web/main.tsx index 87f9b0d7..d6d8f379 100644 --- a/src-web/main.tsx +++ b/src-web/main.tsx @@ -1,17 +1,11 @@ -import { invoke } from '@tauri-apps/api'; import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './components/App'; -import type { KeyValue } from './lib/models'; -import type { Appearance } from './lib/theme/window'; +import { getKeyValue } from './lib/keyValueStore'; import { getPreferredAppearance, setAppearance } from './lib/theme/window'; import './main.css'; -const appearance: KeyValue = await invoke('get_key_value', { - namespace: 'app', - key: 'appearance', -}); -setAppearance((appearance?.value ?? getPreferredAppearance()) as Appearance); +setAppearance(await getKeyValue({ key: 'appearance', fallback: getPreferredAppearance() })); // root holds our app's root DOM Element: ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(