Compare commits
No commits in common. "647ce5fc526e8c3d6c27586d76a97d3c4086e289" and "1b4cbe85176a63eb1b2d0a35e39b94a7af573faa" have entirely different histories.
647ce5fc52
...
1b4cbe8517
7 changed files with 50 additions and 138 deletions
|
|
@ -11,7 +11,7 @@ use phono_backends::{
|
|||
use phono_models::{
|
||||
accessors::{Accessor, Actor, portal::PortalAccessor},
|
||||
datum::Datum,
|
||||
expression::{PgExpressionAny, QueryFragment},
|
||||
expression::QueryFragment,
|
||||
field::Field,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -19,14 +19,11 @@ use sqlx::{
|
|||
postgres::{PgRow, types::Oid},
|
||||
query,
|
||||
};
|
||||
use tracing::debug;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{
|
||||
app::AppDbConn,
|
||||
errors::AppError,
|
||||
extractors::ValidatedForm,
|
||||
field_info::TableFieldInfo,
|
||||
user::CurrentUser,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
|
|
@ -39,16 +36,10 @@ pub(super) struct PathParams {
|
|||
workspace_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub(super) struct FormBody {
|
||||
subfilter: Option<String>,
|
||||
}
|
||||
|
||||
const FRONTEND_ROW_LIMIT: i64 = 1000;
|
||||
|
||||
/// HTTP GET handler for an API endpoint returning a JSON encoding of portal
|
||||
/// data to display in a table or similar form. If the `subfilter` URL parameter
|
||||
/// is specified, it is `&&`-ed with the portal's stored filter.
|
||||
/// data to display in a table or similar form.
|
||||
///
|
||||
/// Only queries up to the first [`FRONTEND_ROW_LIMIT`] rows.
|
||||
pub(super) async fn get(
|
||||
|
|
@ -60,7 +51,6 @@ pub(super) async fn get(
|
|||
rel_oid,
|
||||
workspace_id,
|
||||
}): Path<PathParams>,
|
||||
ValidatedForm(form): ValidatedForm<FormBody>,
|
||||
) -> Result<Response, AppError> {
|
||||
let mut workspace_client = workspace_pooler
|
||||
.acquire_for(workspace_id, RoleAssignment::User(user.id))
|
||||
|
|
@ -119,31 +109,10 @@ pub(super) async fn get(
|
|||
.join(", "),
|
||||
rel.get_identifier(),
|
||||
));
|
||||
if let Some(filter_expr) = portal.table_filter.0.clone() {
|
||||
if let Some(filter_expr) = portal.table_filter.0 {
|
||||
sql_fragment.push(QueryFragment::from_sql(" where "));
|
||||
sql_fragment.push(filter_expr.into_query_fragment());
|
||||
}
|
||||
if let Some(subfilter_expr) = form.subfilter.and_then(|value| {
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
serde_json::from_str::<Option<PgExpressionAny>>(&value)
|
||||
// Ignore invalid input. A user likely pasted incorrectly
|
||||
// or made a typo.
|
||||
.inspect_err(|_| debug!("ignoring invalid subfilter expression"))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}) {
|
||||
sql_fragment.push(QueryFragment::from_sql(
|
||||
if portal.table_filter.0.is_some() {
|
||||
" and "
|
||||
} else {
|
||||
" where "
|
||||
},
|
||||
));
|
||||
sql_fragment.push(subfilter_expr.into_query_fragment());
|
||||
}
|
||||
sql_fragment.push(QueryFragment::from_sql(" order by _id limit "));
|
||||
sql_fragment.push(QueryFragment::from_param(Datum::Numeric(Some(
|
||||
FRONTEND_ROW_LIMIT.into(),
|
||||
|
|
|
|||
|
|
@ -12,12 +12,10 @@ use phono_models::{
|
|||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::types::Oid;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{
|
||||
app::AppDbConn,
|
||||
errors::AppError,
|
||||
extractors::ValidatedForm,
|
||||
navigator::Navigator,
|
||||
settings::Settings,
|
||||
user::CurrentUser,
|
||||
|
|
@ -32,12 +30,6 @@ pub(super) struct PathParams {
|
|||
workspace_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub(super) struct FormBody {
|
||||
#[serde(default)]
|
||||
subfilter: String,
|
||||
}
|
||||
|
||||
/// HTTP GET handler for the table viewer page of a [`Portal`]. This handler
|
||||
/// performs some relatively simple queries pertaining to table structure, but
|
||||
/// the bulk of the query logic resides in the [`super::get_data_handler`]
|
||||
|
|
@ -53,9 +45,6 @@ pub(super) async fn get(
|
|||
rel_oid,
|
||||
workspace_id,
|
||||
}): Path<PathParams>,
|
||||
ValidatedForm(FormBody {
|
||||
subfilter: subfilter_str,
|
||||
}): ValidatedForm<FormBody>,
|
||||
) -> Result<Response, AppError> {
|
||||
let mut workspace_client = pooler
|
||||
.acquire_for(workspace_id, RoleAssignment::User(user.id))
|
||||
|
|
@ -100,7 +89,6 @@ pub(super) async fn get(
|
|||
attr_names: Vec<String>,
|
||||
filter: Option<PgExpressionAny>,
|
||||
settings: Settings,
|
||||
subfilter_str: String,
|
||||
navbar: WorkspaceNav,
|
||||
}
|
||||
Ok(Html(
|
||||
|
|
@ -118,7 +106,6 @@ pub(super) async fn get(
|
|||
Some(RelLocation::Portal(portal.id)),
|
||||
))
|
||||
.build()?,
|
||||
subfilter_str,
|
||||
settings,
|
||||
}
|
||||
.render()?,
|
||||
|
|
|
|||
|
|
@ -26,12 +26,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<main class="page-grid__main">
|
||||
<table-viewer
|
||||
columns="{{ columns | json }}"
|
||||
{%- if subfilter_str != "" %}
|
||||
subfilter="{{ subfilter_str }}"
|
||||
{% endif -%}
|
||||
></table-viewer>
|
||||
<table-viewer columns="{{ columns | json }}"></table-viewer>
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
:root {
|
||||
--accent-color: #fc0;
|
||||
|
||||
--default-border-color: #aaa;
|
||||
--default-border-color: #ccc;
|
||||
--default-border-radius--rounded: 8px;
|
||||
--default-border-radius--rounded-sm: 4px;
|
||||
--default-font-family:
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
--button-background--primary: var(--accent-color);
|
||||
--button-background--secondary: #fff;
|
||||
--button-border-color--primary: oklch(from var(--button-background--primary) calc(l * 0.9) c h);
|
||||
--button-border-color--secondary: oklch(from var(--button-background--secondary) calc(l * 0.8) c h);
|
||||
--button-border-color--secondary: oklch(from var(--button-background--secondary) calc(l * 0.85) c h);
|
||||
--button-border-radius: var(--default-border-radius--rounded);
|
||||
--button-color--primary: #000;
|
||||
--button-color--secondary: #000;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
@import "./expression-editor.css";
|
||||
|
||||
:root {
|
||||
--table-header-border-color: var(--default-border-color);
|
||||
--table-cell-border-color: oklch(from var(--default-border-color) calc(l * 1.15) c h);
|
||||
}
|
||||
|
||||
/* ======== Toolbar ======== */
|
||||
|
||||
.filter-menu {
|
||||
|
|
@ -29,7 +24,6 @@
|
|||
grid-area: table;
|
||||
grid-template:
|
||||
'headers' max-content
|
||||
'subfilter-indicator' max-content
|
||||
'main' 1fr
|
||||
'inserter' max-content;
|
||||
height: 100%;
|
||||
|
|
@ -55,7 +49,7 @@
|
|||
.field-adder__header-lookalike {
|
||||
align-items: center;
|
||||
background: #0001;
|
||||
border: solid 1px var(--table-header-border-color);
|
||||
border: solid 1px #ccc;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
|
|
@ -75,10 +69,7 @@
|
|||
}
|
||||
|
||||
.field-header__label {
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.field-header__type-indicator {
|
||||
|
|
@ -131,7 +122,7 @@
|
|||
|
||||
.field-adder__completions {
|
||||
align-items: stretch;
|
||||
border-right: solid 1px var(--table-header-border-color);
|
||||
border-right: solid 1px var(--default-border-color);
|
||||
grid-area: completions;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -158,13 +149,6 @@
|
|||
padding: var(--default-padding);
|
||||
}
|
||||
|
||||
/* ======== Subfilter Indicator ======== */
|
||||
|
||||
.subfilter-indicator {
|
||||
grid-area: subfilter-indicator;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* ======== Table Body ======== */
|
||||
|
||||
.table-viewer__main {
|
||||
|
|
@ -176,17 +160,11 @@
|
|||
align-items: stretch;
|
||||
display: flex;
|
||||
height: 2.25rem;
|
||||
|
||||
&:first-child {
|
||||
.table-viewer__cell {
|
||||
border-top: solid 1px var(--table-cell-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-viewer__cell {
|
||||
align-items: stretch;
|
||||
border: solid 1px var(--table-cell-border-color);
|
||||
border: solid 1px var(--default-border-color);
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
|
|
@ -267,10 +245,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-viewer__cell-indicator {
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-viewer__notice {
|
||||
|
|
|
|||
|
|
@ -156,12 +156,6 @@
|
|||
<div>UNKNOWN</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if value.t === "Text" && /^[a-z+.-]+:\/\/\S+$/i.test(value.c ?? "")}
|
||||
<a class="table-viewer__cell-indicator" href={value.c}
|
||||
><i class="ti ti-external-link"><div class="sr-only">Open link</div></i
|
||||
></a
|
||||
>
|
||||
{/if}
|
||||
{#if invalid_value}
|
||||
<div class="table-viewer__cell-notice">
|
||||
<i class="ti ti-alert-circle"></i>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
customElement={{
|
||||
props: {
|
||||
columns: { type: "Array" },
|
||||
subfilter: { type: "String" },
|
||||
},
|
||||
shadow: "none",
|
||||
tag: "table-viewer",
|
||||
|
|
@ -35,10 +34,9 @@
|
|||
name: string;
|
||||
regtype: string;
|
||||
}[];
|
||||
subfilter?: string;
|
||||
};
|
||||
|
||||
let { columns = [], subfilter = "null" }: Props = $props();
|
||||
let { columns = [] }: Props = $props();
|
||||
|
||||
type CellDelta = {
|
||||
// Assumes that primary keys are immutable and that rows are only added or
|
||||
|
|
@ -535,9 +533,7 @@
|
|||
),
|
||||
fields: z.array(field_info_schema),
|
||||
});
|
||||
const resp = await fetch(
|
||||
`get-data?subfilter=${encodeURIComponent(subfilter)}`,
|
||||
);
|
||||
const resp = await fetch("get-data");
|
||||
const body = get_data_response_schema.parse(await resp.json());
|
||||
lazy_data = {
|
||||
fields: body.fields,
|
||||
|
|
@ -638,11 +634,6 @@
|
|||
<FieldAdder {columns}></FieldAdder>
|
||||
</div>
|
||||
</div>
|
||||
{#if subfilter && subfilter !== "null"}
|
||||
<div class="subfilter-indicator">
|
||||
<a href="?">…</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="table-viewer__main">
|
||||
{@render table_region({
|
||||
region: "main",
|
||||
|
|
@ -663,7 +654,8 @@
|
|||
},
|
||||
})}
|
||||
</div>
|
||||
<form method="post" action="insert" class="table-viewer__inserter">
|
||||
<form method="post" action="insert">
|
||||
<div class="table-viewer__inserter">
|
||||
<h3 class="table-viewer__inserter-help">
|
||||
Insert rows — press "shift + enter" to jump here or add a row
|
||||
</h3>
|
||||
|
|
@ -702,6 +694,7 @@
|
|||
<i class="ti ti-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#each inserter_rows as row}
|
||||
{#each lazy_data.fields as field, field_index}
|
||||
<input
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue