insert rows from viewer
|
|
@ -5,7 +5,7 @@
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@1",
|
"@std/assert": "jsr:@std/assert@1",
|
||||||
"@std/path": "jsr:@std/path@^1.1.1",
|
"@std/path": "jsr:@std/path@^1.1.1",
|
||||||
"@std/uuid": "jsr:@std/uuid@^1.0.9",
|
"@std/uuid": "jsr:@std/uuid@^1.0.9"
|
||||||
},
|
},
|
||||||
"unstable": ["fmt-component"]
|
"unstable": ["fmt-component"]
|
||||||
}
|
}
|
||||||
18
svelte/deno.lock → deno.lock
generated
|
|
@ -10,7 +10,6 @@
|
||||||
"jsr:@std/uuid@*": "1.0.9",
|
"jsr:@std/uuid@*": "1.0.9",
|
||||||
"jsr:@std/uuid@^1.0.9": "1.0.9",
|
"jsr:@std/uuid@^1.0.9": "1.0.9",
|
||||||
"npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@7.1.2__picomatch@4.0.3",
|
"npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@7.1.2__picomatch@4.0.3",
|
||||||
"npm:@jsr/std__uuid@^1.0.9": "1.0.9",
|
|
||||||
"npm:@sveltejs/vite-plugin-svelte@^6.1.1": "6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3",
|
"npm:@sveltejs/vite-plugin-svelte@^6.1.1": "6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3",
|
||||||
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
|
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
|
||||||
"npm:svelte-check@^4.3.1": "4.3.1_svelte@5.38.1__acorn@8.15.0_typescript@5.8.3",
|
"npm:svelte-check@^4.3.1": "4.3.1_svelte@5.38.1__acorn@8.15.0_typescript@5.8.3",
|
||||||
|
|
@ -370,22 +369,6 @@
|
||||||
"@jridgewell/sourcemap-codec"
|
"@jridgewell/sourcemap-codec"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@jsr/std__bytes@1.0.6": {
|
|
||||||
"integrity": "sha512-St6yKggjFGhxS52IFLJWvkchRFbAKg2Xh8UxA4S1EGz7GJ2Ui+ssDDldj/w2c8vCxvl6qgR0HaYbKeFJNqujmA==",
|
|
||||||
"tarball": "https://npm.jsr.io/~/11/@jsr/std__bytes/1.0.6.tgz"
|
|
||||||
},
|
|
||||||
"@jsr/std__crypto@1.0.5": {
|
|
||||||
"integrity": "sha512-iqFCkjeGeQccLgmxH9m1d7abjZcFMW0XrYZu1itNz8vVHzH9crObalonjVQaVDdKHCrNNOklMN1t0u3k46dirA==",
|
|
||||||
"tarball": "https://npm.jsr.io/~/11/@jsr/std__crypto/1.0.5.tgz"
|
|
||||||
},
|
|
||||||
"@jsr/std__uuid@1.0.9": {
|
|
||||||
"integrity": "sha512-hyTTOsmUTnLuah/OMnkYfZ8fSqYbB113idHZfIYlhN26PdpMGTqTUZkFCA1Q6oBhW8d8N82RG0PnRohQ0peqhA==",
|
|
||||||
"dependencies": [
|
|
||||||
"@jsr/std__bytes",
|
|
||||||
"@jsr/std__crypto"
|
|
||||||
],
|
|
||||||
"tarball": "https://npm.jsr.io/~/11/@jsr/std__uuid/1.0.9.tgz"
|
|
||||||
},
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.46.2": {
|
"@rollup/rollup-android-arm-eabi@4.46.2": {
|
||||||
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
|
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
|
||||||
"os": ["android"],
|
"os": ["android"],
|
||||||
|
|
@ -996,7 +979,6 @@
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"npm:@deno/vite-plugin@^1.0.5",
|
"npm:@deno/vite-plugin@^1.0.5",
|
||||||
"npm:@jsr/std__uuid@^1.0.9",
|
|
||||||
"npm:@sveltejs/vite-plugin-svelte@^6.1.1",
|
"npm:@sveltejs/vite-plugin-svelte@^6.1.1",
|
||||||
"npm:@tsconfig/svelte@^5.0.4",
|
"npm:@tsconfig/svelte@^5.0.4",
|
||||||
"npm:svelte-check@^4.3.1",
|
"npm:svelte-check@^4.3.1",
|
||||||
|
|
@ -143,16 +143,6 @@ impl FieldType {
|
||||||
Self::Unknown => None,
|
Self::Unknown => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_for_insert(&self) -> Result<Encodable, FieldTypeUnknownError> {
|
|
||||||
match self {
|
|
||||||
Self::InterimUser {} => Ok(Encodable::Text(None)),
|
|
||||||
Self::Text {} => Ok(Encodable::Text(None)),
|
|
||||||
Self::Timestamp { .. } => Ok(Encodable::Timestamp(None)),
|
|
||||||
Self::Uuid { .. } => Ok(Encodable::Uuid(None)),
|
|
||||||
Self::Unknown => Err(FieldTypeUnknownError {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error)]
|
#[derive(Clone, Debug, Error)]
|
||||||
|
|
@ -235,7 +225,7 @@ pub enum Encodable {
|
||||||
|
|
||||||
impl Encodable {
|
impl Encodable {
|
||||||
pub fn bind_onto<'a>(
|
pub fn bind_onto<'a>(
|
||||||
&'a self,
|
self,
|
||||||
query: sqlx::query::Query<'a, Postgres, <sqlx::Postgres as sqlx::Database>::Arguments<'a>>,
|
query: sqlx::query::Query<'a, Postgres, <sqlx::Postgres as sqlx::Database>::Arguments<'a>>,
|
||||||
) -> sqlx::query::Query<'a, Postgres, <sqlx::Postgres as sqlx::Database>::Arguments<'a>> {
|
) -> sqlx::query::Query<'a, Postgres, <sqlx::Postgres as sqlx::Database>::Arguments<'a>> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -255,4 +245,11 @@ impl Encodable {
|
||||||
let deserialized: Tagged = serde_json::from_value(serialized).unwrap();
|
let deserialized: Tagged = serde_json::from_value(serialized).unwrap();
|
||||||
deserialized.c
|
deserialized.c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Text(None) | Self::Timestamp(None) | Self::Uuid(None) => true,
|
||||||
|
Self::Text(_) | Self::Timestamp(_) | Self::Uuid(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
interim-server/src/field.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
use interim_models::field::Field;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct FieldInfo {
|
||||||
|
pub field: Field,
|
||||||
|
pub has_default: bool,
|
||||||
|
pub not_null: bool,
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ mod auth;
|
||||||
mod base_pooler;
|
mod base_pooler;
|
||||||
mod base_user_perms;
|
mod base_user_perms;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod field;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
mod navbar;
|
mod navbar;
|
||||||
mod navigator;
|
mod navigator;
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,10 @@ pub fn new_router(state: AppState) -> Router<()> {
|
||||||
"/d/{base_id}/r/{class_oid}/l/{lens_id}/update-value",
|
"/d/{base_id}/r/{class_oid}/l/{lens_id}/update-value",
|
||||||
post(routes::lenses::update_value_page_post),
|
post(routes::lenses::update_value_page_post),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/d/{base_id}/r/{class_oid}/l/{lens_id}/insert",
|
||||||
|
post(routes::lens_insert::insert_page_post),
|
||||||
|
)
|
||||||
.route_with_tsr(
|
.route_with_tsr(
|
||||||
"/d/{base_id}/r/{class_oid}/l/{lens_id}/viewer/",
|
"/d/{base_id}/r/{class_oid}/l/{lens_id}/viewer/",
|
||||||
get(routes::lenses::viewer_page),
|
get(routes::lenses::viewer_page),
|
||||||
|
|
|
||||||
95
interim-server/src/routes/lens_insert.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
response::Response,
|
||||||
|
};
|
||||||
|
use axum_extra::extract::Form;
|
||||||
|
use interim_models::{field::Encodable, lens::Lens};
|
||||||
|
use interim_pgtypes::{escape_identifier, pg_class::PgClass};
|
||||||
|
use sqlx::{postgres::types::Oid, query};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app_error::AppError,
|
||||||
|
app_state::AppDbConn,
|
||||||
|
base_pooler::{BasePooler, RoleAssignment},
|
||||||
|
navigator::Navigator,
|
||||||
|
user::CurrentUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LensPagePath;
|
||||||
|
|
||||||
|
pub async fn insert_page_post(
|
||||||
|
State(mut base_pooler): State<BasePooler>,
|
||||||
|
navigator: Navigator,
|
||||||
|
AppDbConn(mut app_db): AppDbConn,
|
||||||
|
CurrentUser(current_user): CurrentUser,
|
||||||
|
Path(LensPagePath {
|
||||||
|
base_id,
|
||||||
|
class_oid,
|
||||||
|
lens_id,
|
||||||
|
}): Path<LensPagePath>,
|
||||||
|
Form(body): Form<HashMap<String, Vec<String>>>,
|
||||||
|
) -> Result<Response, AppError> {
|
||||||
|
// FIXME auth, csrf
|
||||||
|
|
||||||
|
let lens = Lens::with_id(lens_id).fetch_one(&mut app_db).await?;
|
||||||
|
let mut base_client = base_pooler
|
||||||
|
.acquire_for(base_id, RoleAssignment::User(current_user.id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let rel = PgClass::with_oid(Oid(class_oid))
|
||||||
|
.fetch_one(&mut base_client)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let col_names: Vec<String> = body.keys().cloned().collect();
|
||||||
|
let col_list_sql = col_names
|
||||||
|
.iter()
|
||||||
|
.map(|value| escape_identifier(value))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let n_rows = body.values().map(|value| value.len()).max().unwrap_or(0);
|
||||||
|
if n_rows > 0 {
|
||||||
|
let mut param_index = 1;
|
||||||
|
let mut params: Vec<Encodable> = vec![];
|
||||||
|
let mut row_list: Vec<String> = vec![];
|
||||||
|
for i in 0..n_rows {
|
||||||
|
let mut param_slots: Vec<String> = vec![];
|
||||||
|
for col in col_names.iter() {
|
||||||
|
let maybe_value: Option<Encodable> = body
|
||||||
|
.get(col)
|
||||||
|
.and_then(|col_values| col_values.get(i))
|
||||||
|
.map(|value_raw| serde_json::from_str(value_raw))
|
||||||
|
.transpose()?;
|
||||||
|
if let Some(value) = maybe_value.filter(|value| !value.is_none()) {
|
||||||
|
params.push(value);
|
||||||
|
param_slots.push(format!("${param_index}"));
|
||||||
|
param_index += 1;
|
||||||
|
} else {
|
||||||
|
param_slots.push("default".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row_list.push(format!("({})", param_slots.join(", ")));
|
||||||
|
}
|
||||||
|
let row_list_sql = row_list.join(",\n");
|
||||||
|
|
||||||
|
let query_sql = &format!(
|
||||||
|
r#"
|
||||||
|
insert into {0}.{1}
|
||||||
|
({col_list_sql})
|
||||||
|
values
|
||||||
|
{row_list_sql}
|
||||||
|
"#,
|
||||||
|
escape_identifier(&rel.regnamespace),
|
||||||
|
escape_identifier(&rel.relname)
|
||||||
|
);
|
||||||
|
let mut q = query(query_sql);
|
||||||
|
for param in params {
|
||||||
|
q = param.bind_onto(q);
|
||||||
|
}
|
||||||
|
q.execute(base_client.get_conn()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(navigator.lens_page(&lens).redirect_to())
|
||||||
|
}
|
||||||
|
|
@ -25,12 +25,15 @@ use crate::{
|
||||||
app_error::{AppError, bad_request},
|
app_error::{AppError, bad_request},
|
||||||
app_state::AppDbConn,
|
app_state::AppDbConn,
|
||||||
base_pooler::{BasePooler, RoleAssignment},
|
base_pooler::{BasePooler, RoleAssignment},
|
||||||
|
field::FieldInfo,
|
||||||
navbar::{NavLocation, Navbar, RelLocation},
|
navbar::{NavLocation, Navbar, RelLocation},
|
||||||
navigator::Navigator,
|
navigator::Navigator,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
user::CurrentUser,
|
user::CurrentUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::LensPagePath;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LensesPagePath {
|
pub struct LensesPagePath {
|
||||||
base_id: Uuid,
|
base_id: Uuid,
|
||||||
|
|
@ -138,13 +141,6 @@ pub async fn add_lens_page_post(
|
||||||
Ok(navigator.lens_page(&lens).redirect_to())
|
Ok(navigator.lens_page(&lens).redirect_to())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct LensPagePath {
|
|
||||||
base_id: Uuid,
|
|
||||||
class_oid: u32,
|
|
||||||
lens_id: Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lens_page(
|
pub async fn lens_page(
|
||||||
State(settings): State<Settings>,
|
State(settings): State<Settings>,
|
||||||
State(mut base_pooler): State<BasePooler>,
|
State(mut base_pooler): State<BasePooler>,
|
||||||
|
|
@ -163,63 +159,15 @@ pub async fn lens_page(
|
||||||
let mut base_client = base_pooler
|
let mut base_client = base_pooler
|
||||||
.acquire_for(lens.base_id, RoleAssignment::User(current_user.id))
|
.acquire_for(lens.base_id, RoleAssignment::User(current_user.id))
|
||||||
.await?;
|
.await?;
|
||||||
let rel = PgClass::with_oid(lens.class_oid)
|
|
||||||
.fetch_one(&mut base_client)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let attrs = PgAttribute::all_for_rel(lens.class_oid)
|
|
||||||
.fetch_all(&mut base_client)
|
|
||||||
.await?;
|
|
||||||
let fields = Field::belonging_to_lens(lens.id)
|
|
||||||
.fetch_all(&mut app_db)
|
|
||||||
.await?;
|
|
||||||
let pkey_attrs = PgAttribute::pkeys_for_rel(lens.class_oid)
|
|
||||||
.fetch_all(&mut base_client)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
const FRONTEND_ROW_LIMIT: i64 = 1000;
|
|
||||||
let rows: Vec<PgRow> = query(&format!(
|
|
||||||
"select {0} from {1}.{2} limit $1",
|
|
||||||
pkey_attrs
|
|
||||||
.iter()
|
|
||||||
.chain(attrs.iter())
|
|
||||||
.map(|attr| escape_identifier(&attr.attname))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
escape_identifier(&rel.regnamespace),
|
|
||||||
escape_identifier(&rel.relname),
|
|
||||||
))
|
|
||||||
.bind(FRONTEND_ROW_LIMIT)
|
|
||||||
.fetch_all(base_client.get_conn())
|
|
||||||
.await?;
|
|
||||||
let pkeys: Vec<HashMap<String, Encodable>> = rows
|
|
||||||
.iter()
|
|
||||||
.map(|row| {
|
|
||||||
let mut pkey_values: HashMap<String, Encodable> = HashMap::new();
|
|
||||||
for attr in pkey_attrs.clone() {
|
|
||||||
let field = Field::default_from_attr(&attr);
|
|
||||||
pkey_values.insert(field.name.clone(), field.get_value_encodable(row).unwrap());
|
|
||||||
}
|
|
||||||
pkey_values
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "lens0_2.html")]
|
#[template(path = "lens0_2.html")]
|
||||||
struct ResponseTemplate {
|
struct ResponseTemplate {
|
||||||
fields: Vec<Field>,
|
|
||||||
all_columns: Vec<PgAttribute>,
|
|
||||||
rows: Vec<PgRow>,
|
|
||||||
pkeys: Vec<HashMap<String, Encodable>>,
|
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
navbar: Navbar,
|
navbar: Navbar,
|
||||||
}
|
}
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
ResponseTemplate {
|
ResponseTemplate {
|
||||||
all_columns: attrs,
|
|
||||||
fields,
|
|
||||||
pkeys,
|
|
||||||
rows,
|
|
||||||
navbar: Navbar::builder()
|
navbar: Navbar::builder()
|
||||||
.root_path(settings.root_path.clone())
|
.root_path(settings.root_path.clone())
|
||||||
.base(base.clone())
|
.base(base.clone())
|
||||||
|
|
@ -262,13 +210,27 @@ pub async fn get_data_page_get(
|
||||||
let attrs = PgAttribute::all_for_rel(lens.class_oid)
|
let attrs = PgAttribute::all_for_rel(lens.class_oid)
|
||||||
.fetch_all(&mut base_client)
|
.fetch_all(&mut base_client)
|
||||||
.await?;
|
.await?;
|
||||||
let fields = Field::belonging_to_lens(lens.id)
|
|
||||||
.fetch_all(&mut app_db)
|
|
||||||
.await?;
|
|
||||||
let pkey_attrs = PgAttribute::pkeys_for_rel(lens.class_oid)
|
let pkey_attrs = PgAttribute::pkeys_for_rel(lens.class_oid)
|
||||||
.fetch_all(&mut base_client)
|
.fetch_all(&mut base_client)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let fields: Vec<FieldInfo> = {
|
||||||
|
let fields: Vec<Field> = Field::belonging_to_lens(lens.id)
|
||||||
|
.fetch_all(&mut app_db)
|
||||||
|
.await?;
|
||||||
|
let mut field_info: Vec<FieldInfo> = Vec::with_capacity(fields.len());
|
||||||
|
for field in fields {
|
||||||
|
if let Some(attr) = attrs.iter().find(|attr| attr.attname == field.name) {
|
||||||
|
field_info.push(FieldInfo {
|
||||||
|
field,
|
||||||
|
has_default: attr.atthasdef,
|
||||||
|
not_null: attr.attnotnull.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field_info
|
||||||
|
};
|
||||||
|
|
||||||
const FRONTEND_ROW_LIMIT: i64 = 1000;
|
const FRONTEND_ROW_LIMIT: i64 = 1000;
|
||||||
let rows: Vec<PgRow> = query(&format!(
|
let rows: Vec<PgRow> = query(&format!(
|
||||||
"select {0} from {1}.{2} limit $1",
|
"select {0} from {1}.{2} limit $1",
|
||||||
|
|
@ -291,7 +253,7 @@ pub async fn get_data_page_get(
|
||||||
data: Vec<Encodable>,
|
data: Vec<Encodable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data: Vec<DataRow> = vec![];
|
let mut data_rows: Vec<DataRow> = vec![];
|
||||||
let mut pkeys: Vec<String> = vec![];
|
let mut pkeys: Vec<String> = vec![];
|
||||||
for row in rows.iter() {
|
for row in rows.iter() {
|
||||||
let mut pkey_values: HashMap<String, Encodable> = HashMap::new();
|
let mut pkey_values: HashMap<String, Encodable> = HashMap::new();
|
||||||
|
|
@ -303,9 +265,9 @@ pub async fn get_data_page_get(
|
||||||
pkeys.push(pkey.clone());
|
pkeys.push(pkey.clone());
|
||||||
let mut row_data: Vec<Encodable> = vec![];
|
let mut row_data: Vec<Encodable> = vec![];
|
||||||
for field in fields.iter() {
|
for field in fields.iter() {
|
||||||
row_data.push(field.get_value_encodable(row)?);
|
row_data.push(field.field.get_value_encodable(row)?);
|
||||||
}
|
}
|
||||||
data.push(DataRow {
|
data_rows.push(DataRow {
|
||||||
pkey,
|
pkey,
|
||||||
data: row_data,
|
data: row_data,
|
||||||
});
|
});
|
||||||
|
|
@ -313,14 +275,14 @@ pub async fn get_data_page_get(
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ResponseBody {
|
struct ResponseBody {
|
||||||
|
rows: Vec<DataRow>,
|
||||||
|
fields: Vec<FieldInfo>,
|
||||||
pkeys: Vec<String>,
|
pkeys: Vec<String>,
|
||||||
data: Vec<DataRow>,
|
|
||||||
fields: Vec<Field>,
|
|
||||||
}
|
}
|
||||||
Ok(Json(ResponseBody {
|
Ok(Json(ResponseBody {
|
||||||
|
rows: data_rows,
|
||||||
fields,
|
fields,
|
||||||
pkeys,
|
pkeys,
|
||||||
data,
|
|
||||||
})
|
})
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
@ -472,6 +434,7 @@ pub async fn update_value_page_post(
|
||||||
body.pkeys
|
body.pkeys
|
||||||
.get(&pkey_attrs.first().unwrap().attname)
|
.get(&pkey_attrs.first().unwrap().attname)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.clone()
|
||||||
.bind_onto(body.value.bind_onto(query(&format!(
|
.bind_onto(body.value.bind_onto(query(&format!(
|
||||||
r#"update {0}.{1} set {2} = $1 where {3} = $2"#,
|
r#"update {0}.{1} set {2} = $1 where {3} = $2"#,
|
||||||
escape_identifier(&rel.regnamespace),
|
escape_identifier(&rel.regnamespace),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,14 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod bases;
|
pub mod bases;
|
||||||
|
pub mod lens_insert;
|
||||||
pub mod lenses;
|
pub mod lenses;
|
||||||
pub mod relations;
|
pub mod relations;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LensPagePath {
|
||||||
|
base_id: Uuid,
|
||||||
|
class_oid: u32,
|
||||||
|
lens_id: Uuid,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@deno/vite-plugin": "^1.0.5",
|
"@deno/vite-plugin": "^1.0.5",
|
||||||
"@jsr/std__uuid": "^1.0.9",
|
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
||||||
"svelte-language-server": "^0.17.19",
|
"svelte-language-server": "^0.17.19",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
|
|
@ -43,12 +43,6 @@ $table-border-color: #ccc;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header-actions {
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
grid-area: main;
|
grid-area: main;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
@ -69,25 +63,32 @@ $table-border-color: #ccc;
|
||||||
&--insertable {
|
&--insertable {
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__inserter {
|
||||||
|
align-items: stretch;
|
||||||
|
display: flex;
|
||||||
|
grid-area: inserter;
|
||||||
|
justify-items: flex-start;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lens-cell {
|
||||||
|
&__container {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
.lens-table__cell-content {
|
|
||||||
outline: 3px solid #37f;
|
outline: 3px solid #37f;
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&__cell-content {
|
&__content {
|
||||||
|
flex: 1;
|
||||||
font-family: globals.$font-family-data;
|
font-family: globals.$font-family-data;
|
||||||
|
|
||||||
&--null {
|
|
||||||
color: color.scale(#000, $lightness: 50%, $space: hsl);
|
|
||||||
font-style: oblique;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--text {
|
&--text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
@ -102,22 +103,60 @@ $table-border-color: #ccc;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--null {
|
||||||
|
color: color.scale(#000, $lightness: 50%, $space: hsl);
|
||||||
|
font-family: globals.$font-family-data;
|
||||||
|
font-style: oblique;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inserter {
|
&__notice {
|
||||||
grid-area: inserter;
|
align-items: center;
|
||||||
margin-bottom: 2rem;
|
color: color.scale(#000, $lightness: 50%, $space: hsl);
|
||||||
|
display: flex;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lens-inserter {
|
||||||
|
&__rows {
|
||||||
.lens-table__cell {
|
.lens-table__cell {
|
||||||
border: dashed 1px $table-border-color;
|
border: dashed 1px $table-border-color;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lens-table__row:first-child .lens-table__cell {
|
.lens-table__row:first-child .lens-table__cell {
|
||||||
border-top: dashed 1px $table-border-color;
|
border-top: dashed 1px $table-border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__submit {
|
||||||
|
@include globals.reset-button;
|
||||||
|
align-items: center;
|
||||||
|
border: dashed 1px globals.$button-primary-background;
|
||||||
|
border-bottom-right-radius: globals.$border-radius-rounded-sm;
|
||||||
|
border-top-right-radius: globals.$border-radius-rounded-sm;
|
||||||
|
color: globals.$button-primary-background;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lens-editor {
|
.lens-editor {
|
||||||
|
|
@ -128,7 +167,7 @@ $table-border-color: #ccc;
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
@include globals.reset_input;
|
@include globals.reset_input;
|
||||||
padding: 0.5rem;
|
padding: 0.75rem 0.5rem;
|
||||||
font-family: globals.$font-family-data;
|
font-family: globals.$font-family-data;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
@jsr:registry=https://npm.jsr.io
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
|
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 877 B |
|
Before Width: | Height: | Size: 674 B After Width: | Height: | Size: 674 B |
|
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 490 B |
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 847 B |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 988 B After Width: | Height: | Size: 988 B |
|
Before Width: | Height: | Size: 1,023 B After Width: | Height: | Size: 1,023 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
|
Before Width: | Height: | Size: 560 B After Width: | Height: | Size: 560 B |
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 556 B |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 670 B After Width: | Height: | Size: 670 B |
|
Before Width: | Height: | Size: 988 B After Width: | Height: | Size: 988 B |
|
Before Width: | Height: | Size: 1,011 B After Width: | Height: | Size: 1,011 B |
|
Before Width: | Height: | Size: 560 B After Width: | Height: | Size: 560 B |
|
Before Width: | Height: | Size: 838 B After Width: | Height: | Size: 838 B |
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
|
Before Width: | Height: | Size: 883 B After Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
|
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 658 B |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 592 B |
|
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 700 B After Width: | Height: | Size: 700 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 853 B After Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 687 B |
|
Before Width: | Height: | Size: 698 B After Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 691 B |
|
Before Width: | Height: | Size: 697 B After Width: | Height: | Size: 697 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 954 B |
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 1,011 B After Width: | Height: | Size: 1,011 B |
|
Before Width: | Height: | Size: 454 B After Width: | Height: | Size: 454 B |
|
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
|
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
|
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 741 B After Width: | Height: | Size: 741 B |
|
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 991 B |
|
Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 966 B |
|
Before Width: | Height: | Size: 622 B After Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 536 B |
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 622 B After Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 620 B |
|
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 408 B |
|
Before Width: | Height: | Size: 743 B After Width: | Height: | Size: 743 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |