Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 SW.Bitween.Web/ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@fontsource-variable/instrument-sans": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@headlessui/react": "^2.2.10",
"@monaco-editor/loader": "^1.7.0",
"@monaco-editor/react": "^4.7.0",
"@tailwindcss/vite": "^4.3.2",
"@tanstack/react-query": "^5.101.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { lazy, Suspense, useEffect, useRef } from "react";
import loader from "@monaco-editor/loader";
import {
useMappingEditorDispatch,
useMappingEditorState,
Expand All @@ -9,6 +10,10 @@ import {
} from "../../lib/mapping/MappingEditorContext";
import { generateScriban, parseScriban } from "../../lib/mapping/scribanGenerator";

// Pinned to an exact version so the CSP entries in Startup.cs (which allow only
// this path, not all of cdn.jsdelivr.net) stay valid — bump both together.
loader.config({ paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs" } });

// Monaco is heavy — lazy-load it so it stays out of the main bundle.
const MonacoEditor = lazy(() => import("@monaco-editor/react"));

Expand Down
2 changes: 1 addition & 1 deletion SW.Bitween.Web/ClientApp/src/components/ui/basics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function Button({
<button
type="button"
disabled={disabled || busy}
className={`inline-flex items-center justify-center gap-1.5 rounded-lg font-medium transition-colors cursor-pointer disabled:cursor-not-allowed ${
className={`inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-lg font-medium transition-colors cursor-pointer disabled:cursor-not-allowed ${
size === "sm" ? "h-8 px-3 text-[13px]" : "h-9.5 px-4 text-sm"
} ${buttonStyles[variant]} ${className}`}
{...rest}
Expand Down
28 changes: 19 additions & 9 deletions SW.Bitween.Web/ClientApp/src/lib/dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@ export const timeAgo = (iso: string): string => {
return formatDate(iso);
};

/** "in 34m", "in 2h" for things scheduled in the future. */
/** "in 34m", "in 2h" for the future; "overdue by 3d" once a schedule has been missed a while. */
export const timeUntil = (iso: string): string => {
const seconds = (new Date(iso).getTime() - Date.now()) / 1000;
if (seconds <= 0) return "any moment";
if (seconds < 90) return "in under a minute";
const minutes = seconds / 60;
if (minutes < 60) return `in ${Math.round(minutes)}m`;
const hours = minutes / 60;
if (hours < 24) return `in ${Math.round(hours)}h`;
return `in ${Math.round(hours / 24)}d`;
const d = asDate(iso);
if (!d) return "—";
const seconds = (d.getTime() - Date.now()) / 1000;
if (seconds > 0) {
if (seconds < 90) return "in under a minute";
const minutes = seconds / 60;
if (minutes < 60) return `in ${Math.round(minutes)}m`;
const hours = minutes / 60;
if (hours < 24) return `in ${Math.round(hours)}h`;
return `in ${Math.round(hours / 24)}d`;
}
const overdue = -seconds;
if (overdue < 30) return "any moment";
const minutes = overdue / 60;
if (minutes < 90) return `overdue by ${Math.max(1, Math.round(minutes))}m`;
const hours = overdue / 3600;
if (hours < 48) return `overdue by ${Math.round(hours)}h`;
return `overdue by ${Math.round(overdue / 86400)}d`;
};

const timeFormatter = new Intl.DateTimeFormat("en", {
Expand Down
2 changes: 1 addition & 1 deletion SW.Bitween.Web/ClientApp/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"

"@monaco-editor/loader@^1.5.0":
"@monaco-editor/loader@^1.5.0", "@monaco-editor/loader@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.7.0.tgz#967aaa4601b19e913627688dfe8159d57549e793"
integrity sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==
Expand Down
9 changes: 7 additions & 2 deletions SW.Bitween.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,13 @@ public void ConfigureServices(IServiceCollection services)
/// <summary>Mirrors the policy the legacy UI enforces at nginx, minus its nginx-only bits.</summary>
private const string ContentSecurityPolicy =
"default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self' 'unsafe-inline'; " +
// The Scriban mapping editor lazy-loads Monaco, which fetches its script,
// stylesheet and language workers from jsdelivr. Pinned to the exact
// version path ManualEditor.tsx configures the loader with — not the
// whole jsdelivr origin — so this must move in lockstep with that pin.
"script-src 'self' https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/; " +
"worker-src 'self' blob: https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/; " +
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/; " +
"img-src 'self' data:; " +
"connect-src 'self' https://login.microsoftonline.com; " +
"frame-src https://login.microsoftonline.com; " +
Expand Down
Loading