Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timezone Control in the GUIDE #820

Merged
merged 61 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
9a8bc6e
Allow user to specify a timezone to use in the GUIDE
garrettmflynn Jun 3, 2024
5b826f5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
4dc776f
Generate timezone list
garrettmflynn Jun 3, 2024
ad1cf97
Merge branch 'main' into timezone
CodyCBakerPhD Jun 3, 2024
303e275
Provide datetime values with a timezone offset specified. Render diff…
garrettmflynn Jun 3, 2024
ad1e9c9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
31b5fec
More uniform time handling
garrettmflynn Jun 3, 2024
977950f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
f521e57
Extract timezone offset using timezone awareness. Remove timezone awa…
garrettmflynn Jun 3, 2024
3681429
Merge branch 'timezone' of https://github.com/NeurodataWithoutBorders…
garrettmflynn Jun 3, 2024
4c69fab
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
a195173
Set timezone when converting data
garrettmflynn Jun 3, 2024
7af0222
Add timestamp awareness to forms and conversion process
garrettmflynn Jun 3, 2024
3e74935
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
fae3596
Ensure default values are carried in the preform
garrettmflynn Jun 3, 2024
864c2da
Merge branch 'timezone' of https://github.com/NeurodataWithoutBorders…
garrettmflynn Jun 3, 2024
9b7eb71
Merge branch 'main' into timezone
CodyCBakerPhD Jun 4, 2024
5beaafe
Merge branch 'main' into timezone
CodyCBakerPhD Jun 4, 2024
389c113
Use keywords, categories, and labels to improve the rendering of time…
garrettmflynn Jun 4, 2024
d026cd8
Merge branch 'timezone' of https://github.com/NeurodataWithoutBorders…
garrettmflynn Jun 4, 2024
74856c9
Require timezone and move workflow value resolution into the Dashboar…
garrettmflynn Jun 4, 2024
f868b1e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
ca3ea2d
Update Dashboard.js
garrettmflynn Jun 4, 2024
720fe30
Merge branch 'timezone' of https://github.com/NeurodataWithoutBorders…
garrettmflynn Jun 4, 2024
d0d49c1
Add non-category timezone information to the end
garrettmflynn Jun 4, 2024
ed416e1
Show long and short name
garrettmflynn Jun 4, 2024
7042094
Instead of always including UTC, add it if the timezone detector has …
garrettmflynn Jun 4, 2024
fa29aa4
Properly resolve promises
garrettmflynn Jun 4, 2024
b619ce3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
f02a800
Bring dashboard changes directly from the backend configuration PR
garrettmflynn Jun 4, 2024
bf96a46
Update Dashboard.js
garrettmflynn Jun 4, 2024
28bc090
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
90fa005
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
aad5e9a
Merge branch 'main' into timezone
CodyCBakerPhD Jun 4, 2024
b82875d
Rely on Python for timezone information
garrettmflynn Jun 4, 2024
c9370ad
Update GuidedMetadata.js
garrettmflynn Jun 4, 2024
75e2f79
Merge branch 'main' into timezone
garrettmflynn Jun 4, 2024
c8a69fc
Update GuidedSourceData.js
garrettmflynn Jun 4, 2024
6476daf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
b3fc871
cleanup modules
CodyCBakerPhD Jun 4, 2024
6d03f2e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
48d8e76
add call to management helpers
CodyCBakerPhD Jun 4, 2024
051bbd5
Merge remote-tracking branch 'origin/timezone' into timezone
CodyCBakerPhD Jun 4, 2024
b440644
remove placeholder
CodyCBakerPhD Jun 4, 2024
5f948c9
object does not mutate
CodyCBakerPhD Jun 4, 2024
eaf29a1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
8e1b835
adjust fetch calls
CodyCBakerPhD Jun 4, 2024
987e1b2
fix namespace for docs
CodyCBakerPhD Jun 4, 2024
b75e55d
debug import
CodyCBakerPhD Jun 4, 2024
6bd7823
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
d3ba6d1
fix import and parse datetime object
CodyCBakerPhD Jun 4, 2024
d2bec56
Merge remote-tracking branch 'origin/timezone' into timezone
CodyCBakerPhD Jun 4, 2024
3180bc3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
10aa18b
Sort by category
garrettmflynn Jun 4, 2024
0cc837c
Update timezone.schema.ts
garrettmflynn Jun 4, 2024
37d75d6
Add current timezone if filtered out (e.g. in Actions)
garrettmflynn Jun 5, 2024
1006ac0
Merge branch 'main' into timezone
CodyCBakerPhD Jun 5, 2024
518dd96
Fix workflow loading. Add localstorage data handling back on the web
garrettmflynn Jun 5, 2024
aaaeef7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2024
4f3056f
Fix preform page. Add multi-session workflow to show other pages
garrettmflynn Jun 5, 2024
8473378
Merge branch 'timezone' of https://github.com/NeurodataWithoutBorders…
garrettmflynn Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions environments/environment-Linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ dependencies:
- pytest-cov == 4.1.0
- scikit-learn == 1.4.0
- tqdm_publisher >= 0.0.1
- tzlocal >= 5.2
CodyCBakerPhD marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions environments/environment-MAC-apple-silicon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ dependencies:
- pytest-cov == 4.1.0
- scikit-learn == 1.4.0
- tqdm_publisher >= 0.0.1
- tzlocal >= 5.2
1 change: 1 addition & 0 deletions environments/environment-MAC-intel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dependencies:
- pytest-cov == 4.1.0
- scikit-learn == 1.4.0
- tqdm_publisher >= 0.0.1
- tzlocal >= 5.2
1 change: 1 addition & 0 deletions environments/environment-Windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dependencies:
- pytest-cov == 4.1.0
- scikit-learn == 1.4.0
- tqdm_publisher >= 0.0.1
- tzlocal >= 5.2
68 changes: 53 additions & 15 deletions src/electron/frontend/core/components/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,26 @@ export class Dashboard extends LitElement {

this.page.set(toPass, false);

// Constrain based on workflow configuration
const workflowConfig = page.workflow ?? (page.workflow = {});
const workflowValues = page.info.globalState?.project?.workflow ?? {};

// Define the value for each workflow value
Object.entries(workflowValues).forEach(([key, value]) => {
const config = workflowConfig[key] ?? (workflowConfig[key] = {});
config.value = value;
});

// Toggle elements based on workflow configuration
Object.entries(workflowConfig).forEach(([key, config]) => {
const { value, elements } = config;
if (elements) {
if (value) elements.forEach((el) => el.removeAttribute("hidden"));
else elements.forEach((el) => el.setAttribute("hidden", true));
}
});

// Ensure that all states are synced to the proper state for this page (e.g. conversions have been run)
this.page
.checkSyncState()
.then(async () => {
Expand All @@ -254,25 +274,34 @@ export class Dashboard extends LitElement {
? `<h4 style="margin-bottom: 0px;">${projectName}</h4><small>Conversion Pipeline</small>`
: projectName;

this.updateSections({ sidebar: false, main: true });

if (this.#transitionPromise.value) this.#transitionPromise.trigger(page); // This ensures calls to page.to() can be properly awaited until the next page is ready

const { skipped } = this.subSidebar.sections[info.section]?.pages?.[info.id] ?? {};

if (skipped) {
if (isStorybook) return; // Do not skip on storybook

// Run skip functions
Object.entries(page.workflow).forEach(([key, state]) => {
if (typeof state.skip === "function") state.skip();
});

// Skip right over the page if configured as such
if (previous && previous.info.previous === this.page) await this.page.onTransition(-1);
else await this.page.onTransition(1);
const backwards = previous && previous.info.previous === this.page;

return (
Promise.all(
Object.entries(page.workflow).map(async ([_, state]) => {
if (typeof state.skip === "function" && !backwards) return await state.skip(); // Run skip functions
})
)

// Skip right over the page if configured as such
.then(async () => {
if (backwards) await this.main.onTransition(-1);
else await this.main.onTransition(1);
})
);
}

page.requestUpdate(); // Re-render the page on each load

// Update main to render page
this.updateSections({ sidebar: false, main: true });
})

.catch((e) => {
const previousId = previous?.info?.id ?? -1;
this.main.onTransition(previousId); // Revert back to previous page
Expand All @@ -283,6 +312,9 @@ export class Dashboard extends LitElement {
: `<h4 style="margin: 0">Fallback to previous page after error occurred</h4><small>${e}</small>`,
"error"
);
})
.finally(() => {
if (this.#transitionPromise.value) this.#transitionPromise.trigger(this.main.page); // This ensures calls to page.to() can be properly awaited until the next page is ready
});
}

Expand Down Expand Up @@ -342,9 +374,15 @@ export class Dashboard extends LitElement {
if (!active) active = this.activePage; // default to active page

this.main.onTransition = async (transition) => {
const promise = (this.#transitionPromise.value = new Promise(
(resolve) => (this.#transitionPromise.trigger = resolve)
));
const promise =
this.#transitionPromise.value ??
(this.#transitionPromise.value = new Promise(
(resolve) =>
(this.#transitionPromise.trigger = (value) => {
delete this.#transitionPromise.value;
resolve(value);
})
));

if (typeof transition === "number") {
const info = this.page.info;
Expand Down
57 changes: 40 additions & 17 deletions src/electron/frontend/core/components/DateTimeSelector.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
import { LitElement, css } from "lit";
import { getTimezoneOffset, formatTimezoneOffset } from "../../../../schemas/timezone.schema";

const convertToDateTimeLocalString = (date) => {
// Function to format the GMT offset
export function extractISOString(date = new Date(), { offset = false, timezone = undefined } = {}) {
if (typeof date === "string") date = new Date();

// Extract the GMT offset
const offsetMs = getTimezoneOffset(date, timezone);
const gmtOffset = formatTimezoneOffset(offsetMs);

// Format the date back to the original format with GMT offset
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
return `${year}-${month}-${day}T${hours}:${minutes}`;
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-indexed
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");

// Recreate the ISO string with the GMT offset
const formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
return offset ? formattedDate + gmtOffset : formattedDate;
}

export const renderDateTime = (value) => {
if (typeof value === "string") return extractISOString(new Date(value));
else if (value instanceof Date) return extractISOString(value);
return value;
};

export const resolveDateTime = renderDateTime;

export class DateTimeSelector extends LitElement {
static get styles() {
return css`
Expand All @@ -20,31 +41,33 @@ export class DateTimeSelector extends LitElement {
}

get value() {
return this.input.value;
const date = new Date(this.input.value);
const resolved = resolveDateTime(date);

console.log(this.input.value, resolved);
// return this.input.value;
return resolved;
}

set value(newValue) {
if (newValue) this.input.value = newValue;
else {
const d = new Date();
d.setHours(0, 0, 0, 0);
this.input.value = convertToDateTimeLocalString(d);
}
const date = newValue ? new Date(newValue) : new Date();
if (!newValue) date.setHours(0, 0, 0, 0);
this.input.value = resolveDateTime(date);
}
get min() {
return this.input.min;
}

set min(newValue) {
this.input.min = newValue;
set min(value) {
this.input.min = value;
}

get max() {
return this.input.max;
}

set max(newValue) {
this.input.max = newValue;
set max(value) {
this.input.max = value;
}

constructor({ value, min, max } = {}) {
Expand Down
11 changes: 6 additions & 5 deletions src/electron/frontend/core/components/JSONSchemaForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { resolveProperties } from "./pages/guided-mode/data/utils";
import { JSONSchemaInput, getEditableItems } from "./JSONSchemaInput";
import { InspectorListItem } from "./preview/inspector/InspectorList";

import { Validator } from "jsonschema";
import { successHue, warningHue, errorHue } from "./globals";
import { Button } from "./Button";

const encode = (str) => {
try {
document.querySelector(`#${str}`);
Expand Down Expand Up @@ -65,10 +69,6 @@ const additionalPropPattern = "additional";

const templateNaNMessage = `<br/><small>Type <b>NaN</b> to represent an unknown value.</small>`;

import { Validator } from "jsonschema";
import { successHue, warningHue, errorHue } from "./globals";
import { Button } from "./Button";

var validator = new Validator();

const isObject = (item) => {
Expand Down Expand Up @@ -902,14 +902,15 @@ export class JSONSchemaForm extends LitElement {
if (!parent) parent = this.#get(path, this.resolved);
if (!schema) schema = this.getSchema(localPath);

const value = parent[name];
let value = parent[name];

const skipValidation = this.validateEmptyValues === null && value === undefined;

const validateArgs = input.pattern || skipValidation ? [] : [value, schema];

// Run validation functions
const jsonSchemaErrors = validateArgs.length === 2 ? this.validateSchema(...validateArgs, name) : [];

const valid = skipValidation ? true : await this.validateOnChange(name, parent, pathToValidate, value);

if (valid === null) return null; // Skip validation / data change if the value is null
Expand Down
16 changes: 3 additions & 13 deletions src/electron/frontend/core/components/JSONSchemaInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,10 @@ import tippy from "tippy.js";
import { merge } from "./pages/utils";
import { OptionalSection } from "./OptionalSection";
import { InspectorListItem } from "./preview/inspector/InspectorList";
import { renderDateTime, resolveDateTime } from "./DateTimeSelector";

const isDevelopment = !!import.meta.env;

const dateTimeRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/;

function resolveDateTime(value) {
if (typeof value === "string") {
const match = value.match(dateTimeRegex);
if (match) return `${match[1]}-${match[2]}-${match[3]}T${match[4]}:${match[5]}`;
return value;
}

return value;
}

export function createTable(fullPath, { onUpdate, onThrow, overrides = {} }) {
const name = fullPath.slice(-1)[0];
const path = fullPath.slice(0, -1);
Expand Down Expand Up @@ -1226,7 +1215,7 @@ export class JSONSchemaInput extends LitElement {
? "datetime-local"
: schema.format ?? (schema.type === "string" ? "text" : schema.type);

const value = isDateTime ? resolveDateTime(this.value) : this.value;
const value = isDateTime ? renderDateTime(this.value) : this.value;

const { minimum, maximum, exclusiveMax, exclusiveMin } = schema;
const min = exclusiveMin ?? minimum;
Expand All @@ -1249,6 +1238,7 @@ export class JSONSchemaInput extends LitElement {

if (isInteger) value = newValue = parseInt(value);
else if (isNumber) value = newValue = parseFloat(value);
else if (isDateTime) value = newValue = resolveDateTime(value);

if (isNumber) {
if ("min" in schema && newValue < schema.min) newValue = schema.min;
Expand Down
18 changes: 0 additions & 18 deletions src/electron/frontend/core/components/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,6 @@ export class Main extends LitElement {
page.onTransition = this.onTransition;
page.updatePages = this.updatePages;

// Constrain based on workflow configuration
const workflowConfig = page.workflow ?? (page.workflow = {});
const workflowValues = page.info.globalState?.project?.workflow ?? {};

Object.entries(workflowConfig).forEach(([key, state]) => {
workflowConfig[key].value = workflowValues[key];

const value = workflowValues[key];

if (state.elements) {
const elements = state.elements;
if (value) elements.forEach((el) => el.removeAttribute("hidden"));
else elements.forEach((el) => el.setAttribute("hidden", true));
}
});

page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations

if (this.content)
this.toRender = toRender.page ? toRender : { page }; // Ensure re-render in either case
else this.#queue.push(page);
Expand Down
1 change: 1 addition & 0 deletions src/electron/frontend/core/components/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class Search extends LitElement {
}

#close = () => {
console.log("CLOSING", this.getSelectedOption());
if (this.listMode === "input" && this.getAttribute("interacted") === "true") {
this.setAttribute("interacted", false);
this.#onSelect(this.getSelectedOption());
Expand Down
Loading
Loading