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/path@^1.1.1": "1.1.1",
|
||||||
"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:@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:@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:@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:@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: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-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",
|
"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": {
|
"@bufbuild/protobuf@2.7.0": {
|
||||||
"integrity": "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA=="
|
"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": {
|
"@deno/vite-plugin@1.0.5_vite@7.1.2__picomatch@4.0.3": {
|
||||||
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
|
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -687,6 +692,9 @@
|
||||||
"source-map-js"
|
"source-map-js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"date-fns@4.1.0": {
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="
|
||||||
|
},
|
||||||
"debug@4.4.1": {
|
"debug@4.4.1": {
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1351,9 +1359,11 @@
|
||||||
],
|
],
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
"npm:@date-fns/utc@^2.1.1",
|
||||||
"npm:@deno/vite-plugin@^1.0.5",
|
"npm:@deno/vite-plugin@^1.0.5",
|
||||||
"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:date-fns@^4.1.0",
|
||||||
"npm:sass-embedded@^1.91.0",
|
"npm:sass-embedded@^1.91.0",
|
||||||
"npm:svelte-check@^4.3.1",
|
"npm:svelte-check@^4.3.1",
|
||||||
"npm:svelte-language-server@~0.17.19",
|
"npm:svelte-language-server@~0.17.19",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use interim_pgtypes::pg_attribute::PgAttribute;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumString};
|
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.
|
/// 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)]
|
#[derive(Clone, Debug, Deserialize, EnumIter, EnumString, PartialEq, Serialize, strum::Display)]
|
||||||
|
|
@ -18,6 +18,7 @@ pub enum Presentation {
|
||||||
},
|
},
|
||||||
Timestamp {
|
Timestamp {
|
||||||
format: String,
|
format: String,
|
||||||
|
utc: bool,
|
||||||
},
|
},
|
||||||
Uuid {},
|
Uuid {},
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +51,7 @@ impl Presentation {
|
||||||
}),
|
}),
|
||||||
"timestamp" => Some(Self::Timestamp {
|
"timestamp" => Some(Self::Timestamp {
|
||||||
format: RFC_3339_S.to_owned(),
|
format: RFC_3339_S.to_owned(),
|
||||||
|
utc: true,
|
||||||
}),
|
}),
|
||||||
"uuid" => Some(Self::Uuid {}),
|
"uuid" => Some(Self::Uuid {}),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ impl TryFrom<PresentationForm> for Presentation {
|
||||||
} else {
|
} else {
|
||||||
form_value.timestamp_format
|
form_value.timestamp_format
|
||||||
},
|
},
|
||||||
|
utc: true,
|
||||||
},
|
},
|
||||||
Presentation::Uuid { .. } => Presentation::Uuid {},
|
Presentation::Uuid { .. } => Presentation::Uuid {},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@date-fns/utc": "^2.1.1",
|
||||||
"@deno/vite-plugin": "^1.0.5",
|
"@deno/vite-plugin": "^1.0.5",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"sass-embedded": "^1.91.0",
|
"sass-embedded": "^1.91.0",
|
||||||
"svelte-language-server": "^0.17.19",
|
"svelte-language-server": "^0.17.19",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,13 @@ $table-border-color: #ccc;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--timestamp {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
&--uuid {
|
&--uuid {
|
||||||
font-family: globals.$font-family-mono;
|
font-family: globals.$font-family-mono;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -331,6 +338,17 @@ $table-border-color: #ccc;
|
||||||
padding: 0.75rem 0.5rem;
|
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 {
|
&__helpers {
|
||||||
grid-area: helpers;
|
grid-area: helpers;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
HTMLButtonElement | undefined
|
HTMLButtonElement | undefined
|
||||||
>();
|
>();
|
||||||
let type_selector_popover_element = $state<HTMLDivElement | undefined>();
|
let type_selector_popover_element = $state<HTMLDivElement | undefined>();
|
||||||
|
let date_input_element = $state<HTMLInputElement | undefined>();
|
||||||
let text_input_element = $state<HTMLInputElement | undefined>();
|
let text_input_element = $state<HTMLInputElement | undefined>();
|
||||||
let blur_timeout = $state<number | undefined>();
|
let blur_timeout = $state<number | undefined>();
|
||||||
|
|
||||||
|
|
@ -65,7 +66,16 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
export function focus() {
|
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();
|
text_input_element?.focus();
|
||||||
|
} else if (field_info.field.presentation.t === "Timestamp") {
|
||||||
|
date_input_element?.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_blur(ev: FocusEvent) {
|
function handle_blur(ev: FocusEvent) {
|
||||||
|
|
@ -204,16 +214,38 @@
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
{:else if field_info.field.presentation.t === "Timestamp"}
|
||||||
|
<div class="datum-editor__timestamp-inputs">
|
||||||
<input
|
<input
|
||||||
{...interactive_handlers}
|
{...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}
|
value={editor_state.date_value}
|
||||||
type="date"
|
type="date"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
{...interactive_handlers}
|
{...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}
|
value={editor_state.time_value}
|
||||||
|
step="1"
|
||||||
type="time"
|
type="time"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="datum-editor__helpers" tabindex="-1">
|
<div class="datum-editor__helpers" tabindex="-1">
|
||||||
{#if field_info.field.presentation.t === "Dropdown"}
|
{#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 { type Datum, parse_datum_from_text } from "./datum.svelte.ts";
|
||||||
import {
|
import {
|
||||||
|
|
@ -36,14 +37,11 @@ export function editor_state_from_datum(value: Datum): EditorState {
|
||||||
return {
|
return {
|
||||||
...DEFAULT_EDITOR_STATE,
|
...DEFAULT_EDITOR_STATE,
|
||||||
date_value: value.c
|
date_value: value.c
|
||||||
? `${value.c.getFullYear()}-${
|
// FIXME: time zones
|
||||||
value.c.getMonth() + 1
|
? format_date(value.c, "yyyy-MM-dd")
|
||||||
}-${value.c.getDate()}`
|
|
||||||
: "",
|
: "",
|
||||||
is_null: value.c === undefined,
|
is_null: value.c === undefined,
|
||||||
time_value: value.c
|
time_value: value.c ? format_date(value.c, "HH:mm:ss") : "",
|
||||||
? `${value.c.getHours()}:${value.c.getMinutes()}`
|
|
||||||
: "",
|
|
||||||
};
|
};
|
||||||
} else if (value.t === "Uuid") {
|
} else if (value.t === "Uuid") {
|
||||||
return {
|
return {
|
||||||
|
|
@ -76,8 +74,22 @@ export function datum_from_editor_state(
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
if (presentation.t === "Timestamp") {
|
if (presentation.t === "Timestamp") {
|
||||||
// FIXME
|
if (value.is_null) {
|
||||||
throw new Error("not yet implemented");
|
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>;
|
type _ = Assert<typeof presentation extends never ? true : false>;
|
||||||
throw new Error("this should be unreachable");
|
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({
|
const presentation_timestamp_schema = z.object({
|
||||||
t: z.literal("Timestamp"),
|
t: z.literal("Timestamp"),
|
||||||
c: z.unknown(),
|
c: z.object({
|
||||||
|
format: z.string(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type PresentationTimestamp = z.infer<
|
export type PresentationTimestamp = z.infer<
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<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 Coords } from "./coords.svelte";
|
||||||
import { type Datum } from "./datum.svelte";
|
import { type Datum } from "./datum.svelte";
|
||||||
import { type FieldInfo } from "./field.svelte";
|
import { type FieldInfo } from "./field.svelte";
|
||||||
|
|
@ -135,6 +138,17 @@
|
||||||
{value.c}
|
{value.c}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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}
|
{:else}
|
||||||
<div class="lens-cell__content lens-cell__content--unknown">
|
<div class="lens-cell__content lens-cell__content--unknown">
|
||||||
<div>UNKNOWN</div>
|
<div>UNKNOWN</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue