implement timestamp editing (awkwardly)
This commit is contained in:
parent
68ca114d5e
commit
275129933d
9 changed files with 115 additions and 22 deletions
10
deno.lock
generated
10
deno.lock
generated
|
|
@ -9,9 +9,11 @@
|
|||
"jsr:@std/path@^1.1.1": "1.1.1",
|
||||
"jsr:@std/uuid@*": "1.0.9",
|
||||
"jsr:@std/uuid@^1.0.9": "1.0.9",
|
||||
"npm:@date-fns/utc@^2.1.1": "2.1.1",
|
||||
"npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0",
|
||||
"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_sass-embedded@1.91.0",
|
||||
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
|
||||
"npm:date-fns@^4.1.0": "4.1.0",
|
||||
"npm:sass-embedded@^1.91.0": "1.91.0",
|
||||
"npm:svelte-check@^4.3.1": "4.3.1_svelte@5.38.1__acorn@8.15.0_typescript@5.8.3",
|
||||
"npm:svelte-language-server@~0.17.19": "0.17.19_prettier@3.3.3_svelte@4.2.20_typescript@5.9.2",
|
||||
|
|
@ -65,6 +67,9 @@
|
|||
"@bufbuild/protobuf@2.7.0": {
|
||||
"integrity": "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA=="
|
||||
},
|
||||
"@date-fns/utc@2.1.1": {
|
||||
"integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="
|
||||
},
|
||||
"@deno/vite-plugin@1.0.5_vite@7.1.2__picomatch@4.0.3": {
|
||||
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
|
||||
"dependencies": [
|
||||
|
|
@ -687,6 +692,9 @@
|
|||
"source-map-js"
|
||||
]
|
||||
},
|
||||
"date-fns@4.1.0": {
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="
|
||||
},
|
||||
"debug@4.4.1": {
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dependencies": [
|
||||
|
|
@ -1351,9 +1359,11 @@
|
|||
],
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@date-fns/utc@^2.1.1",
|
||||
"npm:@deno/vite-plugin@^1.0.5",
|
||||
"npm:@sveltejs/vite-plugin-svelte@^6.1.1",
|
||||
"npm:@tsconfig/svelte@^5.0.4",
|
||||
"npm:date-fns@^4.1.0",
|
||||
"npm:sass-embedded@^1.91.0",
|
||||
"npm:svelte-check@^4.3.1",
|
||||
"npm:svelte-language-server@~0.17.19",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use interim_pgtypes::pg_attribute::PgAttribute;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
|
||||
pub const RFC_3339_S: &str = "%Y-%m-%dT%H:%M:%S";
|
||||
pub const RFC_3339_S: &str = "yyyy-MM-dd'T'HH:mm:ssXXX";
|
||||
|
||||
/// Struct defining how a field's is displayed and how it accepts input in UI.
|
||||
#[derive(Clone, Debug, Deserialize, EnumIter, EnumString, PartialEq, Serialize, strum::Display)]
|
||||
|
|
@ -18,6 +18,7 @@ pub enum Presentation {
|
|||
},
|
||||
Timestamp {
|
||||
format: String,
|
||||
utc: bool,
|
||||
},
|
||||
Uuid {},
|
||||
}
|
||||
|
|
@ -50,6 +51,7 @@ impl Presentation {
|
|||
}),
|
||||
"timestamp" => Some(Self::Timestamp {
|
||||
format: RFC_3339_S.to_owned(),
|
||||
utc: true,
|
||||
}),
|
||||
"uuid" => Some(Self::Uuid {}),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ impl TryFrom<PresentationForm> for Presentation {
|
|||
} else {
|
||||
form_value.timestamp_format
|
||||
},
|
||||
utc: true,
|
||||
},
|
||||
Presentation::Uuid { .. } => Presentation::Uuid {},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-fns/utc": "^2.1.1",
|
||||
"@deno/vite-plugin": "^1.0.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"sass-embedded": "^1.91.0",
|
||||
"svelte-language-server": "^0.17.19",
|
||||
"uuid": "^11.1.0",
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ $table-border-color: #ccc;
|
|||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&--timestamp {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&--uuid {
|
||||
font-family: globals.$font-family-mono;
|
||||
overflow: hidden;
|
||||
|
|
@ -331,6 +338,17 @@ $table-border-color: #ccc;
|
|||
padding: 0.75rem 0.5rem;
|
||||
}
|
||||
|
||||
&__timestamp-inputs {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
grid-area: value-control;
|
||||
justify-content: start;
|
||||
|
||||
input {
|
||||
@include globals.reset_input;
|
||||
}
|
||||
}
|
||||
|
||||
&__helpers {
|
||||
grid-area: helpers;
|
||||
overflow: auto;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
HTMLButtonElement | undefined
|
||||
>();
|
||||
let type_selector_popover_element = $state<HTMLDivElement | undefined>();
|
||||
let date_input_element = $state<HTMLInputElement | undefined>();
|
||||
let text_input_element = $state<HTMLInputElement | undefined>();
|
||||
let blur_timeout = $state<number | undefined>();
|
||||
|
||||
|
|
@ -65,7 +66,16 @@
|
|||
});
|
||||
|
||||
export function focus() {
|
||||
if (
|
||||
field_info.field.presentation.t === "Dropdown" ||
|
||||
field_info.field.presentation.t === "Numeric" ||
|
||||
field_info.field.presentation.t === "Text" ||
|
||||
field_info.field.presentation.t === "Uuid"
|
||||
) {
|
||||
text_input_element?.focus();
|
||||
} else if (field_info.field.presentation.t === "Timestamp") {
|
||||
date_input_element?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function handle_blur(ev: FocusEvent) {
|
||||
|
|
@ -204,16 +214,38 @@
|
|||
type="text"
|
||||
/>
|
||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
||||
<div class="datum-editor__timestamp-inputs">
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
bind:this={date_input_element}
|
||||
oninput={({ currentTarget }) => {
|
||||
if (!editor_state) {
|
||||
console.warn("date input oninput() preconditions not met");
|
||||
return;
|
||||
}
|
||||
editor_state.date_value = currentTarget.value;
|
||||
editor_state.is_null = false;
|
||||
handle_input();
|
||||
}}
|
||||
value={editor_state.date_value}
|
||||
type="date"
|
||||
/>
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
oninput={({ currentTarget }) => {
|
||||
if (!editor_state) {
|
||||
console.warn("time input oninput() preconditions not met");
|
||||
return;
|
||||
}
|
||||
editor_state.time_value = currentTarget.value;
|
||||
editor_state.is_null = false;
|
||||
handle_input();
|
||||
}}
|
||||
value={editor_state.time_value}
|
||||
step="1"
|
||||
type="time"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="datum-editor__helpers" tabindex="-1">
|
||||
{#if field_info.field.presentation.t === "Dropdown"}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as uuid from "uuid";
|
||||
import { format as format_date, parse as parse_date } from "date-fns";
|
||||
import { utc } from "@date-fns/utc";
|
||||
|
||||
import { type Datum, parse_datum_from_text } from "./datum.svelte.ts";
|
||||
import {
|
||||
|
|
@ -36,14 +37,11 @@ export function editor_state_from_datum(value: Datum): EditorState {
|
|||
return {
|
||||
...DEFAULT_EDITOR_STATE,
|
||||
date_value: value.c
|
||||
? `${value.c.getFullYear()}-${
|
||||
value.c.getMonth() + 1
|
||||
}-${value.c.getDate()}`
|
||||
// FIXME: time zones
|
||||
? format_date(value.c, "yyyy-MM-dd")
|
||||
: "",
|
||||
is_null: value.c === undefined,
|
||||
time_value: value.c
|
||||
? `${value.c.getHours()}:${value.c.getMinutes()}`
|
||||
: "",
|
||||
time_value: value.c ? format_date(value.c, "HH:mm:ss") : "",
|
||||
};
|
||||
} else if (value.t === "Uuid") {
|
||||
return {
|
||||
|
|
@ -76,8 +74,22 @@ export function datum_from_editor_state(
|
|||
return parsed;
|
||||
}
|
||||
if (presentation.t === "Timestamp") {
|
||||
// FIXME
|
||||
throw new Error("not yet implemented");
|
||||
if (value.is_null) {
|
||||
return get_empty_datum_for(presentation);
|
||||
}
|
||||
if (value.date_value === "" || value.time_value === "") {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = parse_date(
|
||||
`${value.date_value}T${value.time_value}`,
|
||||
"yyyy-MM-dd'T'HH:mm:ss",
|
||||
new Date(),
|
||||
{ in: utc },
|
||||
);
|
||||
if (Number.isNaN(parsed)) {
|
||||
return undefined;
|
||||
}
|
||||
return { t: "Timestamp", c: parsed };
|
||||
}
|
||||
type _ = Assert<typeof presentation extends never ? true : false>;
|
||||
throw new Error("this should be unreachable");
|
||||
|
|
|
|||
|
|
@ -81,7 +81,9 @@ export type PresentationText = z.infer<typeof presentation_text_schema>;
|
|||
|
||||
const presentation_timestamp_schema = z.object({
|
||||
t: z.literal("Timestamp"),
|
||||
c: z.unknown(),
|
||||
c: z.object({
|
||||
format: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type PresentationTimestamp = z.infer<
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { utc } from "@date-fns/utc";
|
||||
import { format as format_date } from "date-fns";
|
||||
|
||||
import { type Coords } from "./coords.svelte";
|
||||
import { type Datum } from "./datum.svelte";
|
||||
import { type FieldInfo } from "./field.svelte";
|
||||
|
|
@ -135,6 +138,17 @@
|
|||
{value.c}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if field.field.presentation.t === "Timestamp"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--timestamp"
|
||||
class:lens-cell__content--null={value.c === undefined}
|
||||
>
|
||||
{#if value.c === undefined}
|
||||
<i class={["ti", null_value_class]}></i>
|
||||
{:else}
|
||||
{format_date(value.c, field.field.presentation.c.format, { in: utc })}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="lens-cell__content lens-cell__content--unknown">
|
||||
<div>UNKNOWN</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue