From 15087f2d5a2f07c7a87fd0ae3e765e9f63d6ea0e Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 28 Oct 2023 11:29:29 -0700 Subject: [PATCH] Variables under Environment, and render all props --- .../migrations/20231028161007_variables.sql | 2 + src-tauri/sqlx-data.json | 196 +++++++++--------- src-tauri/src/main.rs | 62 +++--- src-tauri/src/models.rs | 50 +++-- src-tauri/src/render.rs | 29 +-- src-web/components/BasicAuth.tsx | 2 + src-web/components/BearerAuth.tsx | 1 + .../components/EnvironmentActionsDropdown.tsx | 101 ++------- src-web/components/EnvironmentEditDialog.tsx | 37 ++-- src-web/components/HeaderEditor.tsx | 2 + src-web/components/core/Editor/Editor.css | 2 + src-web/components/core/Editor/Editor.tsx | 1 - src-web/components/core/Editor/placeholder.ts | 2 +- .../components/core/Editor/twig/extension.ts | 12 +- src-web/components/core/PairEditor.tsx | 11 +- src-web/hooks/useVariables.ts | 18 ++ src-web/lib/models.ts | 10 +- 17 files changed, 263 insertions(+), 275 deletions(-) create mode 100644 src-tauri/migrations/20231028161007_variables.sql create mode 100644 src-web/hooks/useVariables.ts diff --git a/src-tauri/migrations/20231028161007_variables.sql b/src-tauri/migrations/20231028161007_variables.sql new file mode 100644 index 00000000..f8caa9d7 --- /dev/null +++ b/src-tauri/migrations/20231028161007_variables.sql @@ -0,0 +1,2 @@ +ALTER TABLE environments DROP COLUMN data; +ALTER TABLE environments ADD COLUMN variables DEFAULT '[]' NOT NULL; diff --git a/src-tauri/sqlx-data.json b/src-tauri/sqlx-data.json index 780530e1..a7caf6c5 100644 --- a/src-tauri/sqlx-data.json +++ b/src-tauri/sqlx-data.json @@ -160,6 +160,16 @@ }, "query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n " }, + "3ec4710d28a7f38608c96798d971217ac97788bcb639089d0c5750c0d339bc9a": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 3 + } + }, + "query": "\n UPDATE environments\n SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n " + }, "448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": { "describe": { "columns": [], @@ -282,6 +292,60 @@ }, "query": "\n UPDATE http_responses SET (\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n error,\n headers,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n " }, + "689bcc92b914f50c14921faa796c07a256deb84c832fc3d90200b393fb159417": { + "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": "created_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "variables!: sqlx::types::Json>", + "ordinal": 6, + "type_info": "Null" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n variables AS \"variables!: sqlx::types::Json>\"\n FROM environments\n WHERE id = ?\n " + }, "6f0cb5a6d1e8dbc8cdfcc3c7e7944b2c83c22cb795b9d6b98fe067dabec9680b": { "describe": { "columns": [ @@ -378,16 +442,6 @@ }, "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 authentication AS \"authentication!: Json>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " }, - "6f12b56113b09966b472431b6cb95c354bea51b4dfb22a96517655c0fca0ab05": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 3 - } - }, - "query": "\n UPDATE environments\n SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n " - }, "84be2b954870ab181738656ecd4d03fca2ff21012947014c79626abfce8e999b": { "describe": { "columns": [], @@ -398,6 +452,16 @@ }, "query": "\n DELETE FROM workspaces\n WHERE id = ?\n " }, + "86e32d6a6fadf35436f19b577a659c203a8d143cb3a8d6122951c5bf54a0888d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 4 + } + }, + "query": "\n INSERT INTO environments (id, workspace_id, name, variables)\n VALUES (?, ?, ?, ?)\n " + }, "8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c": { "describe": { "columns": [], @@ -408,7 +472,27 @@ }, "query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n " }, - "986763e31599881f287ef378002fc35d8e983af10a30a9aa4ade606dacf83260": { + "aeb0712785a9964d516dc8939bc54aa8206ad852e608b362d014b67a0f21b0ed": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 1 + } + }, + "query": "\n DELETE FROM environments\n WHERE id = ?\n " + }, + "b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 11 + } + }, + "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\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 authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n sort_priority = excluded.sort_priority\n " + }, + "ba2b34a77723f24f86e4c3c45274dbfec6ca130e16e592f948844c037bdc0593": { "describe": { "columns": [ { @@ -442,9 +526,9 @@ "type_info": "Text" }, { - "name": "data!: Json>", + "name": "variables!: sqlx::types::Json>", "ordinal": 6, - "type_info": "Text" + "type_info": "Null" } ], "nullable": [ @@ -460,37 +544,7 @@ "Right": 1 } }, - "query": "\n SELECT id, workspace_id, model, created_at, updated_at, name,\n data AS \"data!: Json>\"\n FROM environments\n WHERE workspace_id = ?\n " - }, - "ab7294b681f1202ef06aaa26885147ead2db6ac740023793cda1e1c92665d996": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 4 - } - }, - "query": "\n INSERT INTO environments (\n id,\n workspace_id,\n name,\n data\n )\n VALUES (?, ?, ?, ?)\n " - }, - "aeb0712785a9964d516dc8939bc54aa8206ad852e608b362d014b67a0f21b0ed": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 1 - } - }, - "query": "\n DELETE FROM environments\n WHERE id = ?\n " - }, - "b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 11 - } - }, - "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\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 authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n sort_priority = excluded.sort_priority\n " + "query": "\n SELECT id, workspace_id, model, created_at, updated_at, name,\n variables AS \"variables!: sqlx::types::Json>\"\n FROM environments\n WHERE workspace_id = ?\n " }, "c23c61b05a4c9e04ab0c1fc2c579d6f2a82a37aeed8addf9861b4985f2a5422e": { "describe": { @@ -815,59 +869,5 @@ } }, "query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n " - }, - "fb89f653780b3f3ab0dd0bb2af30c8d3945203819cb9df7bdd331df56a6ae690": { - "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": "created_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "data!: Json>", - "ordinal": 6, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n data AS \"data!: Json>\"\n FROM environments\n WHERE id = ?\n " } } \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1a84fd5e..447ada91 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -16,7 +16,7 @@ use reqwest::redirect::Policy; use serde::Serialize; use sqlx::migrate::Migrator; use sqlx::sqlite::SqlitePoolOptions; -use sqlx::types::{Json, JsonValue}; +use sqlx::types::Json; use sqlx::{Pool, Sqlite}; use std::collections::HashMap; use std::env::current_dir; @@ -81,14 +81,10 @@ async fn actually_send_ephemeral_request( pool: &Pool, ) -> Result { let start = std::time::Instant::now(); - let environment = models::get_environment(environment_id, pool).await.ok(); + let environment_ref = environment.as_ref(); - // TODO: Use active environment - let mut url_string = match environment { - Some(e) => render::render(&request.url, e.clone()), - None => request.url.to_string(), - }; + let mut url_string = render::render(&request.url, environment.as_ref()); if !url_string.starts_with("http://") && !url_string.starts_with("https://") { url_string = format!("http://{}", url_string); @@ -110,57 +106,54 @@ async fn actually_send_ephemeral_request( if h.name.is_empty() && h.value.is_empty() { continue; } + if !h.enabled { continue; } - let header_name = match HeaderName::from_bytes(h.name.as_bytes()) { + + let name = render::render(&h.name, environment_ref); + let value = render::render(&h.value, environment_ref); + + let header_name = match HeaderName::from_bytes(name.as_bytes()) { Ok(n) => n, Err(e) => { eprintln!("Failed to create header name: {}", e); continue; } }; - let header_value = match HeaderValue::from_str(h.value.as_str()) { + let header_value = match HeaderValue::from_str(value.as_str()) { Ok(n) => n, Err(e) => { eprintln!("Failed to create header value: {}", e); continue; } }; + headers.insert(header_name, header_value); } if let Some(b) = &request.authentication_type { let empty_value = &serde_json::to_value("").unwrap(); + let a = request.authentication.0; + if b == "basic" { - let a = request.authentication.0; - let auth = format!( - "{}:{}", - a.get("username") - .unwrap_or(empty_value) - .as_str() - .unwrap_or(""), - a.get("password") - .unwrap_or(empty_value) - .as_str() - .unwrap_or(""), - ); + let raw_username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or(""); + let raw_password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or(""); + let username = render::render(raw_username, environment_ref); + let password = render::render(raw_password, environment_ref); + + let auth = format!( "{username}:{password}"); let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth); headers.insert( "Authorization", HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(), ); } else if b == "bearer" { - let token = request - .authentication - .0 - .get("token") - .unwrap_or(empty_value) - .as_str() - .unwrap_or(""); + let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or(""); + let token = render::render(raw_token, environment_ref); headers.insert( "Authorization", - HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), + HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), ); } } @@ -170,7 +163,10 @@ async fn actually_send_ephemeral_request( let builder = client.request(m, url_string.to_string()).headers(headers); let sendable_req_result = match (request.body, request.body_type) { - (Some(b), Some(_)) => builder.body(b).build(), + (Some(raw_body), Some(_)) => { + let body = render::render(&raw_body, environment_ref); + builder.body(body).build() + }, _ => builder.build(), }; @@ -341,8 +337,8 @@ async fn create_environment( db_instance: State<'_, Mutex>>, ) -> Result { let pool = &*db_instance.lock().await; - let data: HashMap = HashMap::new(); - let created_environment = models::create_environment(workspace_id, name, data, pool) + let variables = Vec::new(); + let created_environment = models::create_environment(workspace_id, name, variables, pool) .await .expect("Failed to create environment"); @@ -418,7 +414,7 @@ async fn update_environment( let updated_environment = models::update_environment( environment.id.as_str(), environment.name.as_str(), - environment.data.0, + environment.variables.0, pool, ) .await diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 15108a15..52fe3dd7 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -27,7 +27,30 @@ pub struct Environment { pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub name: String, - pub data: Json>, + pub variables: Json>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EnvironmentVariable { + #[serde(default)] + pub enabled: bool, + pub name: String, + pub value: String, +} + +#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Variable { + pub id: String, + pub workspace_id: String, + pub environment_id: String, + pub model: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub name: String, + pub value: String, + pub sort_priority: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -213,7 +236,7 @@ pub async fn find_environments( Environment, r#" SELECT id, workspace_id, model, created_at, updated_at, name, - data AS "data!: Json>" + variables AS "variables!: sqlx::types::Json>" FROM environments WHERE workspace_id = ? "#, @@ -226,26 +249,21 @@ pub async fn find_environments( pub async fn create_environment( workspace_id: &str, name: &str, - data: HashMap, + variables: Vec, pool: &Pool, ) -> Result { let id = generate_id(Some("en")); - let data_json = Json(data); let trimmed_name = name.trim(); + let variables_json = Json(variables); sqlx::query!( r#" - INSERT INTO environments ( - id, - workspace_id, - name, - data - ) + INSERT INTO environments (id, workspace_id, name, variables) VALUES (?, ?, ?, ?) "#, id, workspace_id, trimmed_name, - data_json, + variables_json, ) .execute(pool) .await?; @@ -270,18 +288,18 @@ pub async fn delete_environment(id: &str, pool: &Pool) -> Result, + variables: Vec, pool: &Pool, ) -> Result { - let json_data = Json(data); + let variables_json = Json(variables); sqlx::query!( r#" UPDATE environments - SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP) + SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP) WHERE id = ?; "#, name, - json_data, + variables_json, id, ) .execute(pool) @@ -300,7 +318,7 @@ pub async fn get_environment(id: &str, pool: &Pool) -> Result>" + variables AS "variables!: sqlx::types::Json>" FROM environments WHERE id = ? "#, diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index 61cb36ac..07e6631a 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -1,23 +1,26 @@ +use crate::models::Environment; +use std::collections::HashMap; use tauri::regex::Regex; -use crate::models::Environment; +pub fn render(template: &str, environment: Option<&Environment>) -> String { + match environment { + Some(environment) => render_with_environment(template, environment), + None => template.to_string(), + } +} + +fn render_with_environment(template: &str, environment: &Environment) -> String { + let mut map = HashMap::new(); + let variables = &environment.variables.0; + for variable in variables { + map.insert(variable.name.as_str(), variable.value.as_str()); + } -pub fn render(template: &str, environment: Environment) -> String { - let variables = environment.data; Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}") .expect("Failed to create regex") .replace(template, |caps: &tauri::regex::Captures| { let key = caps.get(1).unwrap().as_str(); - match variables.get(key) { - Some(v) => { - if v.is_string() { - v.as_str().expect("Should be string").to_string() - } else { - v.to_string() - } - } - None => "".to_string(), - } + map.get(key).unwrap_or(&"") }) .to_string() } diff --git a/src-web/components/BasicAuth.tsx b/src-web/components/BasicAuth.tsx index d74e4258..ed1bd61c 100644 --- a/src-web/components/BasicAuth.tsx +++ b/src-web/components/BasicAuth.tsx @@ -14,6 +14,7 @@ export function BasicAuth({ requestId, authentication }: Props) { return ( { - const environmentItems: DropdownItem[] = environments.map( - (e) => ({ - key: e.id, - label: e.name, - rightSlot: e.id === activeEnvironment?.id ? : undefined, - onSelect: async () => { - routes.setEnvironment(e); - }, - }), - [activeEnvironment?.id], - ); - - return [ - ...environmentItems, - ...((environmentItems.length > 0 - ? [{ type: 'separator', label: activeEnvironment?.name }] - : []) as DropdownItem[]), - ...((activeEnvironment != null - ? [ - { - key: 'rename', - label: 'Rename', - leftSlot: , - onSelect: async () => { - const name = await prompt({ - title: 'Rename Environment', - description: ( - <> - Enter a new name for {activeEnvironment?.name} - - ), - name: 'name', - label: 'Name', - defaultValue: activeEnvironment?.name, - }); - updateEnvironment.mutate({ name }); - }, - }, - { - key: 'delete', - label: 'Delete', - leftSlot: , - onSelect: deleteEnvironment.mutate, - variant: 'danger', - }, - { type: 'separator' }, - ] - : []) as DropdownItem[]), + const items: DropdownItem[] = useMemo( + () => [ + ...environments.map( + (e) => ({ + key: e.id, + label: e.name, + rightSlot: e.id === activeEnvironment?.id ? : undefined, + onSelect: async () => { + routes.setEnvironment(e); + }, + }), + [activeEnvironment?.id], + ), + { type: 'separator', label: 'Environments' }, ...((environments.length > 0 ? [ { @@ -95,32 +51,9 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo }, ] : []) as DropdownItem[]), - { - key: 'create-environment', - label: 'Create Environment', - leftSlot: , - onSelect: async () => { - const name = await prompt({ - name: 'name', - label: 'Name', - defaultValue: 'My Environment', - description: 'Enter a name for the new environment', - title: 'Create Environment', - }); - createEnvironment.mutate({ name }); - }, - }, - ]; - }, [ - activeEnvironment, - createEnvironment, - deleteEnvironment, - dialog, - environments, - prompt, - routes, - updateEnvironment, - ]); + ], + [activeEnvironment, dialog, environments, routes], + ); return ( diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index 65edadeb..b547bdeb 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -1,16 +1,17 @@ import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useEnvironments } from '../hooks/useEnvironments'; import { usePrompt } from '../hooks/usePrompt'; -import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment'; import type { Environment } from '../lib/models'; import { Button } from './core/Button'; -import { Editor } from './core/Editor'; import classNames from 'classnames'; import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; -import { Link } from 'react-router-dom'; import { useAppRoutes } from '../hooks/useAppRoutes'; +import { PairEditor } from './core/PairEditor'; +import type { PairEditorProps } from './core/PairEditor'; +import { useCallback } from 'react'; +import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment'; -export const EnvironmentEditDialog = function() { +export const EnvironmentEditDialog = function () { const routes = useAppRoutes(); const prompt = usePrompt(); const environments = useEnvironments(); @@ -58,21 +59,21 @@ export const EnvironmentEditDialog = function() { ); }; -const EnvironmentEditor = function({ environment }: { environment: Environment }) { +const EnvironmentEditor = function ({ environment }: { environment: Environment }) { const updateEnvironment = useUpdateEnvironment(environment.id); + const handleChange = useCallback( + (variables) => { + updateEnvironment.mutate({ variables }); + }, + [updateEnvironment], + ); return ( - { - try { - updateEnvironment.mutate({ data: JSON.parse(data) }); - } catch (err) { - // That's okay - } - }} - /> +
+ +
); }; diff --git a/src-web/components/HeaderEditor.tsx b/src-web/components/HeaderEditor.tsx index 03ac462e..c277bbb9 100644 --- a/src-web/components/HeaderEditor.tsx +++ b/src-web/components/HeaderEditor.tsx @@ -63,5 +63,7 @@ const validateHttpHeader = (v: string) => { return true; } + const hi = v.replace(/\$\{\[\s*[^\]\s]+\s*]}/gi, 'fo'); + console.log('V', v, '-->', hi); return v.match(/^[a-zA-Z0-9-_]+$/) !== null; }; diff --git a/src-web/components/core/Editor/Editor.css b/src-web/components/core/Editor/Editor.css index 3e248070..f5f62db4 100644 --- a/src-web/components/core/Editor/Editor.css +++ b/src-web/components/core/Editor/Editor.css @@ -54,6 +54,8 @@ /* Bring above on hover */ @apply hover:z-10 relative; + + -webkit-text-security: none; } } diff --git a/src-web/components/core/Editor/Editor.tsx b/src-web/components/core/Editor/Editor.tsx index 5c2a3ade..b3420ccf 100644 --- a/src-web/components/core/Editor/Editor.tsx +++ b/src-web/components/core/Editor/Editor.tsx @@ -12,7 +12,6 @@ import './Editor.css'; import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions'; import type { GenericCompletionConfig } from './genericCompletion'; import { singleLineExt } from './singleLine'; -import { useEnvironments } from '../../../hooks/useEnvironments'; import { useActiveEnvironment } from '../../../hooks/useActiveEnvironment'; // Export some things so all the code-split parts are in this file diff --git a/src-web/components/core/Editor/placeholder.ts b/src-web/components/core/Editor/placeholder.ts index c79839a9..0d4e4a97 100644 --- a/src-web/components/core/Editor/placeholder.ts +++ b/src-web/components/core/Editor/placeholder.ts @@ -48,7 +48,7 @@ const placeholderMatcher = new BetterMatchDecorator({ if (groupMatch == null) { // Should never happen, but make TS happy console.warn('Group match was empty', match); - return Decoration.replace({});; + return Decoration.replace({}); } return Decoration.replace({ diff --git a/src-web/components/core/Editor/twig/extension.ts b/src-web/components/core/Editor/twig/extension.ts index 56949767..f335b264 100644 --- a/src-web/components/core/Editor/twig/extension.ts +++ b/src-web/components/core/Editor/twig/extension.ts @@ -9,11 +9,13 @@ import { twigCompletion } from './completion'; import { parser as twigParser } from './twig'; import type { Environment } from '../../../../lib/models'; -export function twig(base: LanguageSupport, environment: Environment | null, autocomplete?: GenericCompletionConfig) { - // TODO: fill variables here - const data = environment?.data ?? {}; - const options = Object.keys(data).map(key => ({ name: key })); - const completions = twigCompletion({ options }); +export function twig( + base: LanguageSupport, + environment: Environment | null, + autocomplete?: GenericCompletionConfig, +) { + const variables = environment?.variables ?? []; + const completions = twigCompletion({ options: variables }); const language = mixLanguage(base); const completion = language.data.of({ autocomplete: completions }); diff --git a/src-web/components/core/PairEditor.tsx b/src-web/components/core/PairEditor.tsx index 5de1bdc2..b5f5704b 100644 --- a/src-web/components/core/PairEditor.tsx +++ b/src-web/components/core/PairEditor.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { XYCoord } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd'; import { v4 as uuid } from 'uuid'; @@ -24,7 +24,8 @@ export type PairEditorProps = { valueValidate?: InputProps['validate']; }; -type Pair = { +export type Pair = { + id?: string; enabled?: boolean; name: string; value: string; @@ -342,6 +343,8 @@ const FormRow = memo(function FormRow({ ); }); -const newPairContainer = (pair?: Pair): PairContainer => { - return { pair: pair ?? { name: '', value: '', enabled: true }, id: uuid() }; +const newPairContainer = (initialPair?: Pair): PairContainer => { + const id = initialPair?.id ?? uuid(); + const pair = initialPair ?? { name: '', value: '', enabled: true }; + return { id, pair }; }; diff --git a/src-web/hooks/useVariables.ts b/src-web/hooks/useVariables.ts new file mode 100644 index 00000000..67c39ac8 --- /dev/null +++ b/src-web/hooks/useVariables.ts @@ -0,0 +1,18 @@ +import { useQuery } from '@tanstack/react-query'; +import { invoke } from '@tauri-apps/api'; +import type { Variable } from '../lib/models'; + +export function variablesQueryKey({ environmentId }: { environmentId: string }) { + return ['variables', { environmentId }]; +} + +export function useVariables({ environmentId }: { environmentId: string }) { + return ( + useQuery({ + queryKey: variablesQueryKey({ environmentId }), + queryFn: async () => { + return (await invoke('list_variables', { environmentId })) as Variable[]; + }, + }).data ?? [] + ); +} diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index 638f07cd..db0bb0f9 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -22,7 +22,7 @@ export interface Workspace extends BaseModel { description: string; } -export interface HttpHeader { +export interface EnvironmentVariable { name: string; value: string; enabled?: boolean; @@ -32,7 +32,13 @@ export interface Environment extends BaseModel { readonly workspaceId: string; readonly model: 'environment'; name: string; - data: Record; + variables: EnvironmentVariable[]; +} + +export interface HttpHeader { + name: string; + value: string; + enabled?: boolean; } export interface HttpRequest extends BaseModel {