{
+const generateSource = ({ id }: Screen) => {
const screensUrl = document
.querySelector("meta[name=screens-url]")
?.getAttribute("content");
const queryParams = "requestor=screenplay";
- return `${screensUrl}/v2/screen${
- isPending ? "/pending/" : "/"
- }${id}/simulation?${queryParams}`;
+ return `${screensUrl}/v2/screen/${id}/simulation?${queryParams}`;
};
export default ScreenSimulation;
diff --git a/assets/js/components/Dashboard/Sidebar.tsx b/assets/js/components/Dashboard/Sidebar.tsx
index bcce07655..91273c7f6 100644
--- a/assets/js/components/Dashboard/Sidebar.tsx
+++ b/assets/js/components/Dashboard/Sidebar.tsx
@@ -9,8 +9,6 @@ import {
VolumeUpFill,
Lightning,
LightningFill,
- Signpost,
- SignpostFill,
ArrowDownShort,
Icon,
} from "react-bootstrap-icons";
@@ -19,8 +17,7 @@ import TLogoBlack from "Images/t-logo-black.svg";
import cx from "classnames";
import * as sidebarStyles from "Styles/sidebar.module.scss";
import * as predictionSuppressionStyles from "Styles/prediction-suppression.module.scss";
-
-import { isEmergencyAdmin, isScreensAdmin } from "Utils/auth";
+import { isEmergencyAdmin } from "Utils/auth";
import { usePredictionSuppressionState } from "Hooks/useScreenplayContext";
const SidebarLink = ({
@@ -122,11 +119,6 @@ const Sidebar = () => {
Emergency Takeover
)}
- {isScreensAdmin() && (
-
- Configure
-
- )}
);
};
diff --git a/assets/js/components/Tables/Rows/AssociateAlertRow.tsx b/assets/js/components/Tables/Rows/AssociateAlertRow.tsx
index ef7922539..602787701 100644
--- a/assets/js/components/Tables/Rows/AssociateAlertRow.tsx
+++ b/assets/js/components/Tables/Rows/AssociateAlertRow.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { Button } from "react-bootstrap";
-import moment from "moment";
+import moment from "moment-timezone";
import { Alert } from "Models/alert";
import { getAlertEarliestStartLatestEnd } from "../../../util";
import * as messageTableStyles from "Styles/message-table.module.scss";
diff --git a/assets/js/components/Tables/Rows/PaMessageRow.tsx b/assets/js/components/Tables/Rows/PaMessageRow.tsx
index 70abaf191..2ade139f6 100644
--- a/assets/js/components/Tables/Rows/PaMessageRow.tsx
+++ b/assets/js/components/Tables/Rows/PaMessageRow.tsx
@@ -2,7 +2,7 @@ import React, { ComponentType } from "react";
import KebabMenu from "Components/KebabMenu";
import { updateExistingPaMessage } from "Utils/api";
import type { PaMessage, PaMessageChange } from "Models/pa_message";
-import moment from "moment";
+import moment from "moment-timezone";
import cx from "classnames";
import { useNavigate } from "react-router-dom";
import { Dropdown, FormCheck } from "react-bootstrap";
diff --git a/assets/js/hooks/useScreenplayContext.tsx b/assets/js/hooks/useScreenplayContext.tsx
index f04020b6d..f0ffecc9a 100644
--- a/assets/js/hooks/useScreenplayContext.tsx
+++ b/assets/js/hooks/useScreenplayContext.tsx
@@ -6,7 +6,6 @@ import { DirectionID } from "../models/direction_id";
import { ScreensByAlert } from "../models/screensByAlert";
import { LineStop } from "../models/line_stop";
import { SuppressedPrediction } from "../models/suppressed_prediction";
-import { ConfigValidationErrors } from "../models/configValidationErrors";
import {
PLACES_PAGE_MODES_AND_LINES,
ALERTS_PAGE_MODES_AND_LINES,
@@ -138,40 +137,6 @@ const AlertsListStateContainer = ({ children }: Props) => {
);
};
-interface ConfigValidationState {
- newScreenValidationErrors: ConfigValidationErrors;
- pendingScreenValidationErrors: ConfigValidationErrors;
- setValidationErrors: (
- arg0: ConfigValidationErrors,
- arg1: ConfigValidationErrors,
- ) => void;
-}
-
-const [useConfigValidationState, ConfigValidationStateProvider] =
- createGenericContext
();
-
-const ConfigValidationStateContainer = ({ children }: Props) => {
- const [newScreenValidationErrors, setNewScreenValidationErrors] =
- useState({});
- const [pendingScreenValidationErrors, setPendingScreenValidationErrors] =
- useState({});
-
- return (
- {
- setNewScreenValidationErrors(newErrors);
- setPendingScreenValidationErrors(pendingErrors);
- },
- }}
- >
- {children}
-
- );
-};
-
interface PlacesListState {
sortDirection: DirectionID;
modeLineFilterValue: FilterValue;
@@ -280,11 +245,9 @@ const ScreenplayProvider = ({ children }: Props) => {
-
-
- {children}
-
-
+
+ {children}
+
@@ -292,13 +255,12 @@ const ScreenplayProvider = ({ children }: Props) => {
};
// Types & Interfaces
-export { FilterValue, DirectionID };
+export { DirectionID };
// Values
export {
useScreenplayState,
usePlacesListState,
- useConfigValidationState,
useAlertsListState,
usePredictionSuppressionState,
ScreenplayProvider,
diff --git a/assets/js/models/configValidationErrors.ts b/assets/js/models/configValidationErrors.ts
deleted file mode 100644
index e86580044..000000000
--- a/assets/js/models/configValidationErrors.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface ValidationErrorsForScreen {
- isDuplicateScreenId: boolean;
- missingFields: string[];
-}
-
-export interface ConfigValidationErrors {
- [place_id: string]: ValidationErrorsForScreen[];
-}
diff --git a/assets/js/models/screen_configuration.ts b/assets/js/models/screen_configuration.ts
deleted file mode 100644
index bd812cbf5..000000000
--- a/assets/js/models/screen_configuration.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { DirectionID } from "./direction_id";
-
-interface Destination {
- route_id: string;
- direction_id?: DirectionID;
-}
-
-interface GlEinkAppParams {
- header: Destination;
- platform_location?: "front" | "back" | null;
-}
-
-export interface GLScreenConfiguration {
- app_id: "gl_eink_v2";
- screen_id?: string;
- new_id?: string;
- app_params: GlEinkAppParams;
- is_live?: boolean;
- is_deleted?: boolean;
-}
-
-export type ScreenConfiguration = GLScreenConfiguration;
-// | More configuration shapes to be added here as we support them
diff --git a/assets/js/util.ts b/assets/js/util.ts
index ffd187efc..10ae3f26f 100644
--- a/assets/js/util.ts
+++ b/assets/js/util.ts
@@ -5,7 +5,7 @@ import { Alert, ActivePeriod } from "Models/alert";
import { Place } from "Models/place";
import { Screen } from "Models/screen";
import { ScreensByAlert } from "Models/screensByAlert";
-import moment from "moment";
+import moment from "moment-timezone";
import fp from "lodash/fp";
import { StaticTemplate } from "Models/static_template";
@@ -78,38 +78,6 @@ export const translateRouteID = (id: string) => {
}
};
-export const getModeFromAffectedList = (affectedList: string[]) => {
- if (
- [
- "red",
- "orange",
- "blue",
- "green",
- "green-b",
- "green-c",
- "green-d",
- "green-e",
- ].some((line) => affectedList.includes(line))
- ) {
- return "subway";
- } else {
- return affectedList[0];
- }
-};
-
-export const convertArrayToListString = (array: string[]) => {
- if (array.length === 1) {
- return array[0];
- } else if (array.length === 2) {
- return `${array[0]} and ${array[1]}`;
- } else {
- return (
- array.slice(0, array.length - 1).join(", ") +
- `and ${array[array.length - 1]}`
- );
- }
-};
-
export const matchStation = (
stationId: string,
stationScreenOrientationList: StationsByLine,
@@ -260,14 +228,6 @@ export const capitalizeTerminalStops = (
return isTerminalStop ? stationName.toUpperCase() : stationName;
};
-export const capitalize = (str: string) => {
- if (str.length <= 1) {
- return str.toUpperCase();
- } else {
- return str.charAt(0).toUpperCase() + str.slice(1);
- }
-};
-
export const getAlertEarliestStartLatestEnd = (
activePeriods: ActivePeriod[],
) => {
@@ -289,7 +249,7 @@ export const getAlertEarliestStartLatestEnd = (
return [start, end];
};
-export const allRouteIdsAtPlaces = (places: Place[]) => {
+const allRouteIdsAtPlaces = (places: Place[]) => {
return fp.uniq(
places.flatMap((place) => place.screens.flatMap(getRouteIdsForSign)),
);
diff --git a/assets/js/utils/api.ts b/assets/js/utils/api.ts
index ec208ba48..b371d5124 100644
--- a/assets/js/utils/api.ts
+++ b/assets/js/utils/api.ts
@@ -1,9 +1,7 @@
import fp from "lodash/fp";
import { Alert } from "../models/alert";
import { Place } from "../models/place";
-import { ScreenConfiguration } from "../models/screen_configuration";
import { ScreensByAlert } from "../models/screensByAlert";
-import { PlaceIdsAndNewScreens } from "../components/Dashboard/PermanentConfiguration/Workflows/GlEink/ConfigureScreensPage";
import getCsrfToken from "../csrf";
import type { PaMessageChange } from "Models/pa_message";
import { SuppressedPrediction } from "Models/suppressed_prediction";
@@ -50,7 +48,7 @@ interface AlertsResponse {
screens_by_alert: ScreensByAlert;
}
-export const _fetchAlerts = async (): Promise => {
+const _fetchAlerts = async (): Promise => {
const response = await fetch("/api/alerts");
if (response.status === 200) {
return response.json();
@@ -59,14 +57,13 @@ export const _fetchAlerts = async (): Promise => {
}
};
-export const _fetchActiveAndFutureAlerts =
- async (): Promise => {
- const response = await fetch("/api/alerts/non_access_alerts");
- if (!response.ok) {
- throw response;
- }
- return response.json();
- };
+const _fetchActiveAndFutureAlerts = async (): Promise => {
+ const response = await fetch("/api/alerts/non_access_alerts");
+ if (!response.ok) {
+ throw response;
+ }
+ return response.json();
+};
export const fetchActiveAndFutureAlerts = withErrorHandling(
_fetchActiveAndFutureAlerts,
@@ -79,118 +76,9 @@ export const fetchAlerts = withErrorHandling(_fetchAlerts, {
customMessage: `Failed to load alerts. ${REFRESH_PAGE_ERROR_MESSAGE}`,
});
-///////////
-// Screens
-///////////
-export interface ExistingScreens {
- [place_id: string]: ExistingScreensAtPlace;
-}
-
-export interface ExistingScreensAtPlace {
- live_screens?: { [screen_id: string]: ScreenConfiguration };
- pending_screens: { [screen_id: string]: ScreenConfiguration };
-}
-
-const _fetchExistingScreens = async (
- appId: string,
- placeIds: string[],
-): Promise<{ places_and_screens: ExistingScreens; version_id: string }> => {
- const response = await fetch(
- `/config/existing-screens/${appId}?place_ids=${placeIds.join(",")}`,
- );
-
- if (!response.ok) {
- throw response;
- }
-
- return response.json();
-};
-
-export const fetchExistingScreens = withErrorHandling(_fetchExistingScreens, {
- customMessage: `Failed to load existing screens. ${REFRESH_PAGE_ERROR_MESSAGE}`,
-});
-export interface PendingAndLiveScreensResponse {
- places_and_screens: PendingAndLiveScreens;
- etag: string;
- version_id: string;
- last_modified_ms: number | null;
-}
-
-// Very similar to the `ExistingScreens` interface, except:
-// 1. key is a string that combines place and app ID, and
-// 2. place and app IDs are added to each `ExistingScreensAtPlace` object, so that we don't have to parse them from the combined string.
-export interface PendingAndLiveScreens {
- [placeAndAppID: string]: ExistingScreensAtPlace & {
- place_id: string;
- app_id: string;
- };
-}
-
-const _fetchExistingScreensAtPlacesWithPendingScreens =
- async (): Promise => {
- const response = await fetch(
- "/config/existing-screens-at-places-with-pending-screens",
- );
- if (!response.ok) {
- throw response;
- }
- const etag = response.headers.get("etag") as string;
- const data = (await response.json()) as Omit<
- PendingAndLiveScreensResponse,
- "etag"
- >;
- return { ...data, etag };
- };
-
-export const fetchExistingScreensAtPlacesWithPendingScreens = withErrorHandling(
- _fetchExistingScreensAtPlacesWithPendingScreens,
- {
- customMessage: `Failed to load pending screens data. ${REFRESH_PAGE_ERROR_MESSAGE}`,
- },
-);
-
-export const putPendingScreens = async (
- placesAndScreens: PlaceIdsAndNewScreens,
- screenType: "gl_eink_v2" | null,
- version_id: string,
-): Promise => {
- return fetch("/config/put", {
- ...getPostBodyAndHeaders({
- places_and_screens: placesAndScreens,
- screen_type: screenType,
- version_id: version_id,
- }),
- credentials: "include",
- });
-};
-
-interface PublishScreensForPlaceResponse {
- message: string;
- new_config?: Place[];
-}
-
-export const publishScreensForPlace = async (
- placeId: string,
- appId: string,
- hiddenFromScreenplayIds: string[],
- etag: string,
-): Promise<{ status: number; message: string; newConfig: Place[] }> => {
- const bodyData = {
- hidden_from_screenplay_ids: hiddenFromScreenplayIds,
- };
- const response = await fetch(`/config/publish/${placeId}/${appId}`, {
- ...getPostBodyAndHeaders(bodyData, { "if-match": etag }),
- credentials: "include",
- });
-
- const json: PublishScreensForPlaceResponse = await response.json();
-
- return {
- status: response.status,
- message: json.message,
- newConfig: json.new_config ?? [],
- };
-};
+///////////////
+// PA Messages
+///////////////
export const createNewPaMessage = async (
message: PaMessageChange,
@@ -237,6 +125,10 @@ export const updateExistingPaMessage = async (
};
};
+/////////////////////////
+// Suppressed Predictions
+/////////////////////////
+
const fetchOk = async (
url: string,
options: Omit & { body: any },
diff --git a/assets/js/utils/auth.ts b/assets/js/utils/auth.ts
index 026d7e242..b51067742 100644
--- a/assets/js/utils/auth.ts
+++ b/assets/js/utils/auth.ts
@@ -3,5 +3,4 @@ const hasRoleMeta = (name: string): boolean =>
export const isEmergencyAdmin = (): boolean => hasRoleMeta("emergency-admin");
export const isPaMessageAdmin = (): boolean => hasRoleMeta("pa-message-admin");
-export const isScreensAdmin = (): boolean => hasRoleMeta("screens-admin");
export const isSuppressionAdmin = () => hasRoleMeta("suppression-admin");
diff --git a/assets/js/utils/emergencyMessages.ts b/assets/js/utils/emergencyMessages.ts
index 307f20e18..675fc0720 100644
--- a/assets/js/utils/emergencyMessages.ts
+++ b/assets/js/utils/emergencyMessages.ts
@@ -21,7 +21,7 @@ export interface CannedMessage {
};
}
-export interface CustomMessage {
+interface CustomMessage {
type: "custom";
text: {
indoor: string;
diff --git a/assets/js/utils/errorHandler.tsx b/assets/js/utils/errorHandler.tsx
index 4b8e7797e..2c81be588 100644
--- a/assets/js/utils/errorHandler.tsx
+++ b/assets/js/utils/errorHandler.tsx
@@ -85,7 +85,7 @@ export const withErrorHandling = (
/**
* Surfaces the error to the user through the error modal.
*/
-export const displayErrorModal = (
+const displayErrorModal = (
error: Error | Response,
options: ErrorHandlingOptions = {},
) => {
diff --git a/assets/package-lock.json b/assets/package-lock.json
index e5e01298b..ebf26557b 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -23,10 +23,9 @@
"react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-dom": "^18.3.1",
+ "react-router": "^6.22.3",
"react-router-dom": "^6.3.0",
- "react-search-autocomplete": "^8.5.2",
"react-tooltip": "^4.2.21",
- "regenerator-runtime": "^0.14.1",
"swr": "^2.2.5"
},
"devDependencies": {
@@ -35,10 +34,8 @@
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.23",
- "@types/phoenix": "^1.6.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
- "@types/react-test-renderer": "^18.3.0",
"esbuild": "^0.27.7",
"esbuild-sass-plugin": "^3.3.1",
"eslint": "^9.39.2",
@@ -47,6 +44,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"jest": "^29.7.0",
"jest-fixed-jsdom": "^0.0.11",
+ "knip": "^6.17.1",
"prettier": "3.2.5",
"ts-jest": "^29.1.5",
"typescript": "^5.9.3",
@@ -69,6 +67,7 @@
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -82,6 +81,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "dev": true,
"dependencies": {
"@babel/highlight": "^7.24.7",
"picocolors": "^1.0.0"
@@ -94,6 +94,7 @@
"version": "7.24.9",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz",
"integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -102,6 +103,7 @@
"version": "7.24.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
"integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
+ "dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7",
@@ -131,6 +133,7 @@
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
+ "dev": true,
"dependencies": {
"@babel/types": "^7.25.0",
"@jridgewell/gen-mapping": "^0.3.5",
@@ -141,21 +144,11 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz",
- "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==",
- "dependencies": {
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-compilation-targets": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz",
"integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==",
+ "dev": true,
"dependencies": {
"@babel/compat-data": "^7.24.8",
"@babel/helper-validator-option": "^7.24.8",
@@ -171,6 +164,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
+ "dev": true,
"dependencies": {
"@babel/types": "^7.24.7"
},
@@ -182,6 +176,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "dev": true,
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
@@ -194,6 +189,7 @@
"version": "7.24.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz",
"integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==",
+ "dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-module-imports": "^7.24.7",
@@ -212,6 +208,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -220,6 +217,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
"integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+ "dev": true,
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
@@ -232,6 +230,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
+ "dev": true,
"dependencies": {
"@babel/types": "^7.24.7"
},
@@ -243,6 +242,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -251,6 +251,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -259,6 +260,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -267,6 +269,7 @@
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
"integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
+ "dev": true,
"dependencies": {
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.0"
@@ -279,6 +282,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
@@ -293,6 +297,7 @@
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+ "dev": true,
"dependencies": {
"@babel/types": "^7.25.2"
},
@@ -360,6 +365,7 @@
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.24.1",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
@@ -481,6 +487,7 @@
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.25.0",
@@ -494,6 +501,7 @@
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
+ "dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
@@ -511,6 +519,7 @@
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+ "dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
@@ -533,32 +542,39 @@
"license": "(Apache-2.0 AND BSD-3-Clause)",
"peer": true
},
- "node_modules/@emotion/is-prop-valid": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
- "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==",
+ "node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@emotion/memoize": "^0.9.0"
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
}
},
- "node_modules/@emotion/memoize": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
- "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
- "license": "MIT"
- },
- "node_modules/@emotion/stylis": {
- "version": "0.8.5",
- "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
- "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==",
- "license": "MIT"
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
},
- "node_modules/@emotion/unitless": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
- "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
- "license": "MIT"
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
@@ -1967,6 +1983,7 @@
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@@ -1979,6 +1996,7 @@
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1986,23 +2004,691 @@
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-parser/binding-android-arm-eabi": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz",
+ "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-android-arm64": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz",
+ "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-darwin-arm64": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz",
+ "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-darwin-x64": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz",
+ "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-freebsd-x64": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz",
+ "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz",
+ "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-arm-musleabihf": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz",
+ "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-arm64-gnu": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz",
+ "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-arm64-musl": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz",
+ "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-ppc64-gnu": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz",
+ "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-riscv64-gnu": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz",
+ "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-riscv64-musl": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz",
+ "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-s390x-gnu": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz",
+ "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-x64-gnu": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz",
+ "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-linux-x64-musl": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz",
+ "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-openharmony-arm64": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz",
+ "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-wasm32-wasi": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz",
+ "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.5"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-win32-arm64-msvc": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz",
+ "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-win32-ia32-msvc": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz",
+ "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-parser/binding-win32-x64-msvc": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.137.0.tgz",
+ "integrity": "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
+ "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@oxc-resolver/binding-android-arm-eabi": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz",
+ "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-android-arm64": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz",
+ "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-darwin-arm64": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz",
+ "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-darwin-x64": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz",
+ "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-freebsd-x64": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz",
+ "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz",
+ "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz",
+ "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz",
+ "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm64-musl": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz",
+ "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz",
+ "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz",
+ "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz",
+ "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz",
+ "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-x64-gnu": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz",
+ "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-x64-musl": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz",
+ "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-openharmony-arm64": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz",
+ "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-wasm32-wasi": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz",
+ "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.0",
+ "@emnapi/runtime": "1.11.0",
+ "@napi-rs/wasm-runtime": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz",
+ "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
+ "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
+ "dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
+ "tslib": "^2.4.0"
}
},
+ "node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz",
+ "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-win32-x64-msvc": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz",
+ "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
@@ -2788,6 +3474,17 @@
"node": ">= 10"
}
},
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -2942,13 +3639,6 @@
"undici-types": "~5.26.4"
}
},
- "node_modules/@types/phoenix": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
- "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/pluralize": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.33.tgz",
@@ -2978,16 +3668,6 @@
"@types/react": "*"
}
},
- "node_modules/@types/react-test-renderer": {
- "version": "18.3.0",
- "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.0.tgz",
- "integrity": "sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/react": "*"
- }
- },
"node_modules/@types/react-transition-group": {
"version": "4.4.10",
"license": "MIT",
@@ -3390,6 +4070,7 @@
},
"node_modules/ansi-styles": {
"version": "3.2.1",
+ "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
@@ -3726,22 +4407,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/babel-plugin-styled-components": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz",
- "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-module-imports": "^7.22.5",
- "@babel/plugin-syntax-jsx": "^7.22.5",
- "lodash": "^4.17.21",
- "picomatch": "^2.3.1"
- },
- "peerDependencies": {
- "styled-components": ">= 2"
- }
- },
"node_modules/babel-preset-current-node-syntax": {
"version": "1.0.1",
"dev": true,
@@ -3788,6 +4453,7 @@
"version": "2.9.11",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
+ "dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
@@ -3839,6 +4505,7 @@
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3966,19 +4633,11 @@
"node": ">=6"
}
},
- "node_modules/camelize": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
- "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/caniuse-lite": {
"version": "1.0.30001762",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
"integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3997,6 +4656,7 @@
},
"node_modules/chalk": {
"version": "2.4.2",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
@@ -4089,6 +4749,7 @@
},
"node_modules/color-convert": {
"version": "1.9.3",
+ "dev": true,
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
@@ -4096,6 +4757,7 @@
},
"node_modules/color-name": {
"version": "1.1.3",
+ "dev": true,
"license": "MIT"
},
"node_modules/colorjs.io": {
@@ -4126,6 +4788,7 @@
},
"node_modules/convert-source-map": {
"version": "2.0.0",
+ "dev": true,
"license": "MIT"
},
"node_modules/create-jest": {
@@ -4227,26 +4890,6 @@
"node": ">= 8"
}
},
- "node_modules/css-color-keywords": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
- "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
- "license": "ISC",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/css-to-react-native": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
- "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
- "license": "MIT",
- "dependencies": {
- "camelize": "^1.0.0",
- "css-color-keywords": "^1.0.0",
- "postcss-value-parser": "^4.0.2"
- }
- },
"node_modules/css.escape": {
"version": "1.5.1",
"dev": true,
@@ -4377,6 +5020,7 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4548,6 +5192,7 @@
"version": "1.5.267",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
"license": "ISC"
},
"node_modules/emittery": {
@@ -4819,6 +5464,7 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -4826,6 +5472,7 @@
},
"node_modules/escape-string-regexp": {
"version": "1.0.5",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.0"
@@ -5402,6 +6049,16 @@
"bser": "2.1.1"
}
},
+ "node_modules/fd-package-json": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz",
+ "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "walk-up-path": "^4.0.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -5489,6 +6146,22 @@
"node": ">= 6"
}
},
+ "node_modules/formatly": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz",
+ "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fd-package-json": "^2.0.0"
+ },
+ "bin": {
+ "formatly": "bin/index.mjs"
+ },
+ "engines": {
+ "node": ">=18.3.0"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"dev": true,
@@ -5543,13 +6216,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/fuse.js": {
- "version": "6.6.2",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -5562,6 +6228,7 @@
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -5651,6 +6318,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
"node_modules/glob": {
"version": "7.2.3",
"dev": true,
@@ -5703,6 +6383,7 @@
},
"node_modules/globals": {
"version": "11.12.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -5758,6 +6439,7 @@
},
"node_modules/has-flag": {
"version": "3.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -8384,6 +9066,16 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"license": "MIT"
@@ -8448,6 +9140,7 @@
},
"node_modules/jsesc": {
"version": "2.5.2",
+ "dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
@@ -8479,6 +9172,7 @@
},
"node_modules/json5": {
"version": "2.2.3",
+ "dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
@@ -8518,6 +9212,89 @@
"node": ">=6"
}
},
+ "node_modules/knip": {
+ "version": "6.25.0",
+ "resolved": "https://registry.npmjs.org/knip/-/knip-6.25.0.tgz",
+ "integrity": "sha512-Q3n41VjOOB/aqsbxb8kallAcFKrUz3b2S5fD5pTODljVpP01t+rvAgy2x3j0Cq8yEpRRHNdar1vHuqFfGuIakQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/webpro"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/knip"
+ }
+ ],
+ "license": "ISC",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "formatly": "^0.3.0",
+ "get-tsconfig": "4.14.0",
+ "jiti": "^2.7.0",
+ "oxc-parser": "^0.137.0",
+ "oxc-resolver": "11.21.3",
+ "picomatch": "^4.0.4",
+ "smol-toml": "^1.6.1",
+ "strip-json-comments": "5.0.3",
+ "tinyglobby": "^0.2.17",
+ "unbash": "^4.0.1",
+ "yaml": "^2.9.0",
+ "zod": "^4.1.11"
+ },
+ "bin": {
+ "knip": "bin/knip.js",
+ "knip-bun": "bin/knip-bun.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/knip/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/knip/node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/knip/node_modules/strip-json-comments": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
+ "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.22",
"dev": true,
@@ -8598,6 +9375,7 @@
},
"node_modules/lru-cache": {
"version": "5.1.1",
+ "dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
@@ -8730,6 +9508,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/natural-compare": {
@@ -8754,6 +9533,7 @@
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/normalize-path": {
@@ -8939,6 +9719,75 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/oxc-parser": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.137.0.tgz",
+ "integrity": "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "^0.137.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxc-parser/binding-android-arm-eabi": "0.137.0",
+ "@oxc-parser/binding-android-arm64": "0.137.0",
+ "@oxc-parser/binding-darwin-arm64": "0.137.0",
+ "@oxc-parser/binding-darwin-x64": "0.137.0",
+ "@oxc-parser/binding-freebsd-x64": "0.137.0",
+ "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0",
+ "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0",
+ "@oxc-parser/binding-linux-arm64-gnu": "0.137.0",
+ "@oxc-parser/binding-linux-arm64-musl": "0.137.0",
+ "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0",
+ "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0",
+ "@oxc-parser/binding-linux-riscv64-musl": "0.137.0",
+ "@oxc-parser/binding-linux-s390x-gnu": "0.137.0",
+ "@oxc-parser/binding-linux-x64-gnu": "0.137.0",
+ "@oxc-parser/binding-linux-x64-musl": "0.137.0",
+ "@oxc-parser/binding-openharmony-arm64": "0.137.0",
+ "@oxc-parser/binding-wasm32-wasi": "0.137.0",
+ "@oxc-parser/binding-win32-arm64-msvc": "0.137.0",
+ "@oxc-parser/binding-win32-ia32-msvc": "0.137.0",
+ "@oxc-parser/binding-win32-x64-msvc": "0.137.0"
+ }
+ },
+ "node_modules/oxc-resolver": {
+ "version": "11.21.3",
+ "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz",
+ "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxc-resolver/binding-android-arm-eabi": "11.21.3",
+ "@oxc-resolver/binding-android-arm64": "11.21.3",
+ "@oxc-resolver/binding-darwin-arm64": "11.21.3",
+ "@oxc-resolver/binding-darwin-x64": "11.21.3",
+ "@oxc-resolver/binding-freebsd-x64": "11.21.3",
+ "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3",
+ "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3",
+ "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3",
+ "@oxc-resolver/binding-linux-arm64-musl": "11.21.3",
+ "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3",
+ "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3",
+ "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3",
+ "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3",
+ "@oxc-resolver/binding-linux-x64-gnu": "11.21.3",
+ "@oxc-resolver/binding-linux-x64-musl": "11.21.3",
+ "@oxc-resolver/binding-openharmony-arm64": "11.21.3",
+ "@oxc-resolver/binding-wasm32-wasi": "11.21.3",
+ "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3",
+ "@oxc-resolver/binding-win32-x64-msvc": "11.21.3"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"dev": true,
@@ -9044,10 +9893,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -9094,10 +9945,6 @@
"node": ">= 0.4"
}
},
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "license": "MIT"
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"dev": true,
@@ -9331,18 +10178,6 @@
"react-dom": ">=16.8"
}
},
- "node_modules/react-search-autocomplete": {
- "version": "8.5.2",
- "license": "MIT",
- "dependencies": {
- "fuse.js": "^6.5.3",
- "styled-components": "^5.3.3"
- },
- "peerDependencies": {
- "react": "^17.0.2 || ^16.0.2 || ^18.0.0",
- "react-dom": "^17.0.2 || ^16.0.2 || ^18.0.0"
- }
- },
"node_modules/react-tooltip": {
"version": "4.5.1",
"license": "MIT",
@@ -9498,6 +10333,16 @@
"node": ">=8"
}
},
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
"node_modules/resolve.exports": {
"version": "2.0.2",
"dev": true,
@@ -10027,6 +10872,7 @@
},
"node_modules/semver": {
"version": "6.3.1",
+ "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -10077,12 +10923,6 @@
"node": ">= 0.4"
}
},
- "node_modules/shallowequal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
- "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
- "license": "MIT"
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"dev": true,
@@ -10196,6 +11036,19 @@
"node": ">=8"
}
},
+ "node_modules/smol-toml": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz",
+ "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/cyyynthia"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"dev": true,
@@ -10450,38 +11303,9 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/styled-components": {
- "version": "5.3.11",
- "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
- "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.0.0",
- "@babel/traverse": "^7.4.5",
- "@emotion/is-prop-valid": "^1.1.0",
- "@emotion/stylis": "^0.8.4",
- "@emotion/unitless": "^0.7.4",
- "babel-plugin-styled-components": ">= 1.12.0",
- "css-to-react-native": "^3.0.0",
- "hoist-non-react-statics": "^3.0.0",
- "shallowequal": "^1.1.0",
- "supports-color": "^5.5.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/styled-components"
- },
- "peerDependencies": {
- "react": ">= 16.8.0",
- "react-dom": ">= 16.8.0",
- "react-is": ">= 16.8.0"
- }
- },
"node_modules/supports-color": {
"version": "5.5.0",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
@@ -10579,14 +11403,14 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -10614,9 +11438,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10633,6 +11457,7 @@
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -10922,6 +11747,16 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
+ "node_modules/unbash": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.2.tgz",
+ "integrity": "sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -10973,6 +11808,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -11067,6 +11903,16 @@
"node": ">=14"
}
},
+ "node_modules/walk-up-path": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz",
+ "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"dev": true,
@@ -11344,8 +12190,25 @@
},
"node_modules/yallist": {
"version": "3.1.1",
+ "dev": true,
"license": "ISC"
},
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
"node_modules/yargs": {
"version": "17.7.2",
"dev": true,
diff --git a/assets/package.json b/assets/package.json
index df2e6daae..0d4987ad8 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -10,7 +10,8 @@
"format:check": "prettier --check \"{.,**}/*.{js,mjs,json,ts,tsx,css,scss}\"",
"typecheck": "tsc --noEmit",
"test": "jest",
- "check": "npm run typecheck && npm run lint:check && npm run format:check"
+ "unused": "knip --tags=-knipignore",
+ "check": "npm run typecheck && npm run lint:check && npm run format:check && npm run unused"
},
"dependencies": {
"@fullstory/browser": "^2.0.8",
@@ -30,10 +31,9 @@
"react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-dom": "^18.3.1",
+ "react-router": "^6.22.3",
"react-router-dom": "^6.3.0",
- "react-search-autocomplete": "^8.5.2",
"react-tooltip": "^4.2.21",
- "regenerator-runtime": "^0.14.1",
"swr": "^2.2.5"
},
"devDependencies": {
@@ -42,10 +42,8 @@
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.23",
- "@types/phoenix": "^1.6.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
- "@types/react-test-renderer": "^18.3.0",
"esbuild": "^0.27.7",
"esbuild-sass-plugin": "^3.3.1",
"eslint": "^9.39.2",
@@ -54,6 +52,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"jest": "^29.7.0",
"jest-fixed-jsdom": "^0.0.11",
+ "knip": "^6.17.1",
"prettier": "3.2.5",
"ts-jest": "^29.1.5",
"typescript": "^5.9.3",
diff --git a/config/config.exs b/config/config.exs
index 3794e21d6..13a8e3ad2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -29,7 +29,6 @@ config :screenplay, ScreenplayWeb.Endpoint,
config :screenplay,
config_fetcher: Screenplay.Places.S3Fetch,
screens_config_fetcher: Screenplay.ScreensConfig.Fetch.S3,
- pending_screens_config_fetcher: Screenplay.PendingScreensConfig.Fetch.S3,
http_client: HTTPoison,
config_s3_bucket: "mbta-ctd-config",
record_sentry: false,
diff --git a/config/dev.exs b/config/dev.exs
index 01d063c64..54e63a67c 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -70,9 +70,7 @@ config :screenplay,
sftp_client_module: Screenplay.Outfront.FakeSFTPClient,
config_fetcher: Screenplay.Places.LocalFetch,
screens_config_fetcher: Screenplay.ScreensConfig.Fetch.Local,
- pending_screens_config_fetcher: Screenplay.PendingScreensConfig.Fetch.Local,
local_screens_config_file_spec: "../screens/priv/local.json",
- local_pending_screens_config_file_spec: "../screens/priv/local_pending.json",
local_place_descriptions_file_spec: {:priv, "place_descriptions.json"},
local_paess_labels_file_spec: {:priv, "paess_labels.json"},
api_v3_key: System.get_env("API_V3_KEY"),
@@ -88,7 +86,6 @@ config :ueberauth, Ueberauth,
client_id: "dev-client",
roles: [
"screenplay-emergency-admin",
- "screens-admin",
"pa-message-admin",
"suppression-admin"
]
diff --git a/config/test.exs b/config/test.exs
index 8197ed335..72ea6162e 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -15,9 +15,7 @@ config :screenplay,
local_place_descriptions_file_spec: {:test, "place_descriptions.json"},
local_paess_labels_file_spec: {:test, "paess_labels.json"},
screens_config_fetcher: Screenplay.ScreensConfig.Fetch.Local,
- pending_screens_config_fetcher: Screenplay.PendingScreensConfig.Fetch.Local,
local_screens_config_file_spec: {:test, "screens_config.json"},
- local_pending_screens_config_file_spec: {:test, "pending_config.json"},
api_v3_url: "https://fake-mbta-api",
api_key: "test_api_key",
sftp_client_module: Screenplay.Outfront.FakeSFTPClient,
@@ -36,7 +34,6 @@ config :ueberauth, Ueberauth,
auto_redirect: true,
roles: [
"screenplay-emergency-admin",
- "screens-admin",
"pa-message-admin",
"suppression-admin"
]
diff --git a/lib/screenplay/emergency_takeover_tool/screens_config.ex b/lib/screenplay/emergency_takeover_tool/config_updater.ex
similarity index 56%
rename from lib/screenplay/emergency_takeover_tool/screens_config.ex
rename to lib/screenplay/emergency_takeover_tool/config_updater.ex
index a5a8d351b..01208e544 100644
--- a/lib/screenplay/emergency_takeover_tool/screens_config.ex
+++ b/lib/screenplay/emergency_takeover_tool/config_updater.ex
@@ -1,22 +1,111 @@
-defmodule Screenplay.EmergencyTakeoverTool.ScreensConfig do
+defmodule Screenplay.EmergencyTakeoverTool.ConfigUpdater do
@moduledoc """
Module responsible for building the EmergencyTakeover struct used in Screens configurations
of active Emergency Takeover Alerts.
"""
+ require Logger
alias Screenplay.EmergencyTakeoverTool.CannedMessages
alias Screenplay.EmergencyTakeoverTool.EmergencyTakeover, as: EmergencyTakeoverContext
- alias ScreensConfig.{EmergencyMessagingLocation, EmergencyTakeover, Screen}
+ alias Screenplay.ScreensConfig.Fetch, as: PublishedScreensFetch
+ alias ScreensConfig.{Config, EmergencyMessagingLocation, EmergencyTakeover, Screen}
@image_store Application.compile_env!(:screenplay, :image_store_module)
+ def add_emergency_takeover_configs(alert_id, showtime_screen_ids, message) do
+ with {published_config, _published_version_id} <- get_current_published_config(),
+ {:ok, published_config_deserialized} <- Jason.decode(published_config) do
+ %Config{screens: published_screens, devops: devops} =
+ published_config_deserialized |> Config.from_json()
+
+ updated_screens =
+ update_screens_with_emergency_takeover(
+ published_screens,
+ showtime_screen_ids,
+ alert_id,
+ message
+ )
+
+ %Config{screens: updated_screens, devops: devops}
+ |> publish_new_config()
+ else
+ _error ->
+ {:error, "Could not fetch published screens config"}
+ end
+ end
+
+ defp update_screens_with_emergency_takeover(screens, screen_ids, alert_id, message) do
+ for {id, screen} <- screens,
+ into: %{} do
+ if id in screen_ids do
+ case screen do
+ %Screen{app_params: %{emergency_messaging_location: eml}} when not is_nil(eml) ->
+ emergency_takeover = build_emergency_takeover(message, alert_id, screen.app_id, eml)
+
+ {id,
+ put_in(
+ screen,
+ [Access.key!(:app_params), Access.key!(:emergency_takeover)],
+ emergency_takeover
+ )}
+
+ _ ->
+ Logger.error("Tried to takeover #{id} without an emergency_messaging_location")
+
+ {id, screen}
+ end
+ else
+ {id, screen}
+ end
+ end
+ end
+
+ def clear_emergency_takeover_configs(showtime_screen_ids) do
+ with {published_config, _published_version_id} <- get_current_published_config(),
+ {:ok, published_config_deserialized} <- Jason.decode(published_config) do
+ %Config{screens: published_screens, devops: devops} =
+ published_config_deserialized |> Config.from_json()
+
+ updated_screens = clear_screens_emergency_takeover(published_screens, showtime_screen_ids)
+
+ %Config{screens: updated_screens, devops: devops}
+ |> publish_new_config()
+ else
+ _error ->
+ {:error, "Could not fetch published screens config"}
+ end
+ end
+
+ defp clear_screens_emergency_takeover(screens, screen_ids) do
+ for {id, screen} <- screens, into: %{} do
+ if id in screen_ids do
+ {id, put_in(screen, [Access.key!(:app_params), Access.key!(:emergency_takeover)], nil)}
+ else
+ {id, screen}
+ end
+ end
+ end
+
+ defp get_current_published_config do
+ case PublishedScreensFetch.fetch_config() do
+ {:ok, config, version_id} -> {config, version_id}
+ error -> error
+ end
+ end
+
+ def publish_new_config(new_config) do
+ with(:ok <- PublishedScreensFetch.put_config(new_config)) do
+ PublishedScreensFetch.commit()
+ end
+ end
+
@spec build_emergency_takeover(
EmergencyTakeoverContext.message(),
String.t(),
Screen.app_id(),
EmergencyMessagingLocation.t()
) :: EmergencyTakeover.t()
- def build_emergency_takeover(message, alert_id, app_id, eml) do
+ defp build_emergency_takeover(message, alert_id, app_id, eml) do
%EmergencyTakeover{
audio_asset_path: audio_path(message, eml),
text_for_audio: text_for_audio(message, eml),
diff --git a/lib/screenplay/pending_screens_config/fetch.ex b/lib/screenplay/pending_screens_config/fetch.ex
deleted file mode 100644
index fcd47d929..000000000
--- a/lib/screenplay/pending_screens_config/fetch.ex
+++ /dev/null
@@ -1,39 +0,0 @@
-defmodule Screenplay.PendingScreensConfig.Fetch do
- @moduledoc """
- Defines a behaviour for, and delegates to, a module that provides access to
- the pending screens config file.
- """
- alias ScreensConfig.PendingConfig
-
- defmodule Metadata do
- @moduledoc false
-
- @type t :: %__MODULE__{
- etag: String.t(),
- version_id: String.t(),
- last_modified: DateTime.t()
- }
-
- @enforce_keys [:etag, :version_id, :last_modified]
- defstruct @enforce_keys
- end
-
- @type fetch_result ::
- {:ok, json :: String.t(), metadata :: Metadata.t()}
- | :error
-
- @callback fetch_config() :: fetch_result
- @callback put_config(PendingConfig.t()) :: :ok | :error
- @callback commit() :: :ok
- @callback revert(String.t()) :: :ok
-
- # The module adopting this behaviour that we use for the current environment.
- @config_fetcher Application.compile_env(:screenplay, :pending_screens_config_fetcher)
-
- # These delegates let other modules call functions from the appropriate Fetch module
- # without having to know which it is.
- defdelegate fetch_config(), to: @config_fetcher
- defdelegate put_config(config), to: @config_fetcher
- defdelegate commit(), to: @config_fetcher
- defdelegate revert(version), to: @config_fetcher
-end
diff --git a/lib/screenplay/pending_screens_config/fetch/local.ex b/lib/screenplay/pending_screens_config/fetch/local.ex
deleted file mode 100644
index bdc94583c..000000000
--- a/lib/screenplay/pending_screens_config/fetch/local.ex
+++ /dev/null
@@ -1,79 +0,0 @@
-defmodule Screenplay.PendingScreensConfig.Fetch.Local do
- @moduledoc """
- Functions to work with a local copy of the pending screens config.
- """
-
- alias Screenplay.PendingScreensConfig.Fetch
- alias ScreensConfig.PendingConfig
-
- @behaviour Fetch
-
- @impl true
- def fetch_config do
- path = local_config_path()
-
- with {:ok, last_modified} <- get_last_modified(path),
- {:ok, contents} <- do_fetch(path) do
- last_modified_as_string = DateTime.to_iso8601(last_modified)
-
- metadata = %Fetch.Metadata{
- etag: last_modified_as_string,
- version_id: last_modified_as_string,
- last_modified: last_modified
- }
-
- {:ok, contents, metadata}
- end
- end
-
- @impl true
- # sobelow_skip ["Traversal.FileModule"]
- def put_config(config) do
- json = config |> PendingConfig.to_json() |> Jason.encode!(pretty: true)
-
- File.copy!(local_config_path(), local_config_path() <> ".temp")
-
- case File.write(local_config_path(), json) do
- :ok -> :ok
- {:error, _} -> :error
- end
- end
-
- @impl true
- # sobelow_skip ["Traversal.FileModule"]
- def commit do
- File.rm!(local_config_path() <> ".temp")
- end
-
- @impl true
- # sobelow_skip ["Traversal.FileModule"]
- def revert(_) do
- File.copy!(local_config_path() <> ".temp", local_config_path())
- File.rm!(local_config_path() <> ".temp")
- end
-
- defp local_config_path do
- case Application.get_env(:screenplay, :local_pending_screens_config_file_spec) do
- {:test, file_name} -> Path.join(~w[#{File.cwd!()} test fixtures #{file_name}])
- path -> path
- end
- end
-
- # sobelow_skip ["Traversal.FileModule"]
- defp do_fetch(path) do
- case File.read(path) do
- {:ok, contents} -> {:ok, contents}
- _ -> :error
- end
- end
-
- defp get_last_modified(path) do
- case File.stat(path, time: :posix) do
- {:ok, %File.Stat{mtime: mtime}} ->
- {:ok, DateTime.from_unix!(mtime)}
-
- {:error, _} ->
- :error
- end
- end
-end
diff --git a/lib/screenplay/pending_screens_config/fetch/s3.ex b/lib/screenplay/pending_screens_config/fetch/s3.ex
deleted file mode 100644
index 688e64e23..000000000
--- a/lib/screenplay/pending_screens_config/fetch/s3.ex
+++ /dev/null
@@ -1,80 +0,0 @@
-defmodule Screenplay.PendingScreensConfig.Fetch.S3 do
- @moduledoc """
- Functions to work with an S3-hosted copy of the pending screens config.
- """
-
- require Logger
-
- alias Screenplay.PendingScreensConfig.Fetch
- alias ScreensConfig.PendingConfig
-
- @behaviour Fetch
-
- @impl true
- def fetch_config do
- bucket = Application.get_env(:screenplay, :config_s3_bucket)
- path = config_path_for_environment()
-
- get_operation = ExAws.S3.get_object(bucket, path)
-
- case ExAws.request(get_operation) do
- {:ok, %{body: body, headers: headers, status_code: 200}} ->
- metadata = get_metadata(headers)
- {:ok, body, metadata}
-
- {:error, err} ->
- _ = Logger.info("s3_pending_screens_config_fetch_error #{inspect(err)}")
- :error
- end
- end
-
- @impl true
- def commit, do: :ok
-
- @impl true
- def revert(version) do
- bucket = Application.get_env(:screenplay, :config_s3_bucket)
- path = config_path_for_environment()
-
- get_operation = ExAws.S3.get_object(bucket, path, version_id: version)
- %{body: body, status_code: 200} = ExAws.request!(get_operation)
-
- put_operation = ExAws.S3.put_object(bucket, path, body)
- ExAws.request!(put_operation)
- end
-
- @impl true
- def put_config(config) do
- json = config |> PendingConfig.to_json() |> Jason.encode!(pretty: true)
- bucket = Application.get_env(:screenplay, :config_s3_bucket)
- path = config_path_for_environment()
- put_operation = ExAws.S3.put_object(bucket, path, json)
-
- case ExAws.request(put_operation) do
- {:ok, %{status_code: 200}} -> :ok
- _ -> :error
- end
- end
-
- defp get_metadata(headers) do
- headers = Map.new(headers)
-
- etag = Map.get(headers, "ETag")
- version_id = Map.get(headers, "x-amz-version-id")
-
- last_modified =
- headers
- |> Map.get("Last-Modified")
- |> Timex.parse("{RFC1123}")
- |> case do
- {:ok, dt} -> dt
- {:error, _} -> nil
- end
-
- %Fetch.Metadata{etag: etag, version_id: version_id, last_modified: last_modified}
- end
-
- defp config_path_for_environment do
- "screens/pending-screens-#{Application.get_env(:screenplay, :environment_name)}.json"
- end
-end
diff --git a/lib/screenplay/permanent_config.ex b/lib/screenplay/permanent_config.ex
deleted file mode 100644
index 91ed396f0..000000000
--- a/lib/screenplay/permanent_config.ex
+++ /dev/null
@@ -1,547 +0,0 @@
-defmodule Screenplay.PermanentConfig do
- @moduledoc false
-
- # Suppress dialyzer warning until more app_ids are implemented.
- @dialyzer [{:nowarn_function, get_route_id: 3}, {:nowarn_function, json_to_struct: 4}]
-
- require Logger
-
- alias Screenplay.EmergencyTakeoverTool.ScreensConfig, as: EmergencyTakeoverConfig
- alias Screenplay.PendingScreensConfig.Fetch, as: PendingScreensFetch
- alias Screenplay.Places
- alias Screenplay.Places.Fetch
- alias Screenplay.Places.Place.ShowtimeScreen
- alias Screenplay.RoutePatterns.RoutePattern
- alias Screenplay.ScreensConfig, as: ScreensConfigStore
- alias Screenplay.ScreensConfig.Fetch, as: PublishedScreensFetch
- alias ScreensConfig.{Alerts, Config, Departures, Footer, LineMap, PendingConfig, Screen}
- alias ScreensConfig.Departures.{Query, Section}
- alias ScreensConfig.Header.Destination
- alias ScreensConfig.Screen.GlEink
-
- @type screen_type :: :gl_eink_v2
-
- @spec get_existing_screens_at_places_with_pending_screens() :: %{
- places_and_screens: %{
- (place_id_app_id_pair :: String.t()) => %{
- place_id: String.t(),
- app_id: atom(),
- live_screens: %{Config.screen_id() => Screen.t()},
- pending_screens: %{Config.screen_id() => Screen.t()}
- }
- },
- etag: String.t(),
- version_id: String.t(),
- last_modified_ms: integer
- }
- def get_existing_screens_at_places_with_pending_screens do
- {pending_screens_config, metadata} =
- case PendingScreensFetch.fetch_config() do
- {:ok, config, metadata} ->
- %PendingConfig{screens: pending_screens} =
- config
- |> Jason.decode!()
- |> PendingConfig.from_json()
-
- {pending_screens, metadata}
-
- _ ->
- raise(
- "Could not fetch pending screens config in existing_screens_at_places_with_pending_screens/2"
- )
- end
-
- existing =
- pending_screens_config
- |> Enum.group_by(fn {_, screen} -> {screen_to_place_id(screen), screen.app_id} end)
- |> Map.new(fn {{place_id, app_id}, pending_screens_at_place} ->
- live_screens_of_same_type_at_place =
- ScreensConfigStore.screens()
- |> Enum.filter(fn {_id, screen} ->
- screen.app_id == app_id and screen_to_place_id(screen) == place_id
- end)
- |> Enum.map(fn {id, screen} -> {id, Screen.to_json(screen)} end)
- |> Map.new()
-
- pending_screens_at_place =
- for {id, screen} <- pending_screens_at_place,
- into: %{},
- do: {id, Screen.to_json(screen)}
-
- live_and_pending = %{
- live_screens: live_screens_of_same_type_at_place,
- pending_screens: pending_screens_at_place,
- place_id: place_id,
- app_id: app_id
- }
-
- json_key = "#{place_id}/#{app_id}"
-
- {json_key, live_and_pending}
- end)
-
- %{
- places_and_screens: existing,
- etag: metadata.etag,
- version_id: metadata.version_id,
- last_modified_ms: DateTime.to_unix(metadata.last_modified, :millisecond)
- }
- end
-
- @spec put_pending_screens(map(), screen_type(), Fetch.version_id()) ::
- {:error,
- :version_mismatch
- | :config_not_fetched
- | :config_not_written
- | {:duplicate_screen_ids, list()}}
- | :ok
- def put_pending_screens(places_and_screens, screen_type, version_id) do
- with {:ok, config_string} <- get_current_pending_config(version_id),
- {:ok, deserialized} <- Jason.decode(config_string),
- {published_config, _published_version_id} <- get_current_published_config(),
- {:ok, published_config_deserialized} <- Jason.decode(published_config),
- {:ok, existing_screens} <-
- check_for_duplicate_screen_ids(
- deserialized,
- places_and_screens,
- published_config_deserialized
- ) do
- new_pending_screens_config =
- Enum.reduce(
- places_and_screens,
- existing_screens,
- get_config_reducer(screen_type)
- )
-
- case PendingScreensFetch.put_config(%PendingConfig{screens: new_pending_screens_config}) do
- :ok -> :ok
- :error -> {:error, :config_not_written}
- end
- else
- error ->
- error
- end
- end
-
- def publish_pending_screens(place_id, app_id, hidden_from_screenplay_ids) do
- {pending_config, pending_version_id} = get_current_pending_config()
-
- %PendingConfig{screens: pending_screens} =
- pending_config |> Jason.decode!() |> PendingConfig.from_json()
-
- {published_config, published_version_id} = get_current_published_config()
- places_config = Places.get()
-
- {screens_to_publish, new_pending_screens} =
- pending_screens
- |> Enum.filter(fn {_screen_id, screen} -> screen.app_id == app_id end)
- |> Enum.map(fn
- {screen_id, %Screen{} = screen} ->
- if screen_id in hidden_from_screenplay_ids do
- {screen_id, %{screen | hidden_from_screenplay: true}}
- else
- {screen_id, screen}
- end
- end)
- |> Enum.split_with(&place_has_screen(&1, place_id))
-
- new_pending_screens_config =
- %PendingConfig{screens: Enum.into(new_pending_screens, %{})}
-
- new_published_screens_config = get_new_published_screens(published_config, screens_to_publish)
-
- screenplay_screens_to_add =
- Enum.reject(screens_to_publish, fn {_, screen} -> screen.hidden_from_screenplay end)
-
- new_places_config =
- get_new_places_config(places_config, screenplay_screens_to_add)
-
- with :ok <- PendingScreensFetch.put_config(new_pending_screens_config),
- :ok <- PublishedScreensFetch.put_config(new_published_screens_config) do
- PendingScreensFetch.commit()
- PublishedScreensFetch.commit()
- Places.update(new_places_config)
- else
- _ ->
- PendingScreensFetch.revert(pending_version_id)
- PublishedScreensFetch.revert(published_version_id)
- :error
- end
- end
-
- defp check_for_duplicate_screen_ids(
- deserialized,
- places_and_screens,
- published_config_deserialized
- ) do
- %PendingConfig{screens: existing_screens} = PendingConfig.from_json(deserialized)
- %Config{screens: published_screens} = Config.from_json(published_config_deserialized)
-
- {new_screen_ids, changed_screen_ids, deleted_screen_ids} =
- Enum.flat_map(places_and_screens, fn {_place_id,
- %{
- "new_pending_screens" => new_screens,
- "updated_pending_screens" => updated_pending_screens
- }} ->
- new_screens ++ updated_pending_screens
- end)
- |> Enum.reduce({[], [], []}, fn screen,
- {new_screen_ids, changed_screen_ids, deleted_screen_ids} =
- acc ->
- cond do
- screen["is_deleted"] ->
- {new_screen_ids, changed_screen_ids, [screen["screen_id"] | deleted_screen_ids]}
-
- # new screen
- screen["new_id"] != nil and screen["screen_id"] == nil ->
- {[screen["new_id"] | new_screen_ids], changed_screen_ids, deleted_screen_ids}
-
- # screen is existing but had its ID changed.
- screen["new_id"] != nil and screen["screen_id"] != nil ->
- {[screen["new_id"] | new_screen_ids], [screen["screen_id"] | changed_screen_ids],
- deleted_screen_ids}
-
- # existing screen is being updated but did not change IDs
- true ->
- acc
- end
- end)
-
- all_screen_ids =
- (Map.keys(existing_screens) -- deleted_screen_ids -- changed_screen_ids) ++
- new_screen_ids ++ Map.keys(published_screens)
-
- duplicate_screen_ids =
- all_screen_ids
- |> Enum.group_by(& &1)
- |> Enum.filter(fn {_string, occurrences} -> length(occurrences) > 1 end)
- |> Enum.map(&elem(&1, 0))
-
- if Enum.empty?(duplicate_screen_ids),
- do: {:ok, existing_screens},
- else: {:error, {:duplicate_screen_ids, duplicate_screen_ids}}
- end
-
- # Config reducers do 3 things:
- # 1. Make necessary data requests for retrieving stop related data for screen configurations
- # 2. Update any existing pending configurations that were changed.
- # 3. Add new pending screen configurations
- # Return on each reducer should be all pending configurations, including configurations that were not modified:
- # %{ screen_id => Screen.t() }
- defp get_config_reducer(:gl_eink_v2), do: &gl_eink_config_reducer/2
-
- defp get_config_reducer(app_id),
- do: raise("get_config_reducer/1 not implemented for app_id: #{app_id}")
-
- defp gl_eink_config_reducer(place_and_screens, acc) do
- {place_id,
- %{
- "updated_pending_screens" => updated_pending_screens,
- "new_pending_screens" => new_pending_screens
- }} =
- place_and_screens
-
- updated_pending_screens =
- Enum.reject(updated_pending_screens, &is_nil(&1))
-
- route_id = get_route_id(:gl_eink_v2, updated_pending_screens, new_pending_screens)
-
- # Need to fetch platform IDs because these screens are direction specific
- platform_ids = route_pattern_mod().fetch_platform_ids_for_route_at_stop(place_id, route_id)
-
- # Update/remove existing pending configs.
- new_config =
- update_existing_pending_screens(place_id, platform_ids, updated_pending_screens, acc)
-
- # Add new pending screens
- add_new_pending_screens(place_id, platform_ids, new_pending_screens, new_config)
- end
-
- defp get_current_pending_config do
- case PendingScreensFetch.fetch_config() do
- {:ok, config, metadata} -> {config, metadata.version_id}
- error -> error
- end
- end
-
- defp get_current_pending_config(version_id) do
- # Get config directly from source so we have an up-to-date version_id
- case PendingScreensFetch.fetch_config() do
- {:ok, config, %{version_id: ^version_id}} ->
- {:ok, config}
-
- :error ->
- {:error, :config_not_fetched}
-
- _ ->
- {:error, :version_mismatch}
- end
- end
-
- defp json_to_struct(screen, :gl_eink_v2, parent_station_id, platform_ids) do
- %{
- "app_params" => %{
- "header" => %{"route_id" => route_id, "direction_id" => direction_id},
- "platform_location" => platform_location
- }
- } = screen
-
- platform_id = elem(platform_ids, direction_id)
-
- struct(Screen,
- vendor: :mercury,
- app_id: :gl_eink_v2,
- app_params:
- struct(GlEink,
- departures:
- struct(Departures,
- sections: [
- struct(Section,
- query:
- struct(Query,
- params: %Query.Params{
- stop_ids: [parent_station_id],
- route_ids: [route_id],
- direction_id: direction_id
- }
- )
- )
- ]
- ),
- alerts: %Alerts{stop_id: platform_id},
- line_map: %LineMap{
- station_id: parent_station_id,
- stop_id: platform_id,
- direction_id: direction_id,
- route_id: route_id
- },
- header: %Destination{route_id: route_id, direction_id: direction_id},
- platform_location: platform_location,
- footer: %Footer{stop_id: parent_station_id}
- )
- )
- end
-
- defp json_to_struct(_, app_id, _, _),
- do: raise("json_to_struct/4 not implemented for app_id: #{app_id}")
-
- defp update_existing_pending_screens(place_id, platform_ids, updated_pending_screens, acc) do
- Enum.reduce(updated_pending_screens, acc, fn
- %{"is_deleted" => true, "screen_id" => screen_id}, acc ->
- # Only delete screen if "is_deleted" is true AND platform is at current place.
- # If a screen ID is deleted at one place and moved to another,
- # we need to make sure we are removing the correct screen.
-
- Map.reject(acc, fn
- {^screen_id, %Screen{app_params: %GlEink{alerts: %Alerts{stop_id: platform_id}}}} ->
- platform_id in Tuple.to_list(platform_ids)
-
- _ ->
- false
- end)
-
- # screen_id was updated
- # Delete the old configuration and treat new ID as a new configuration
- %{"new_id" => new_id, "screen_id" => screen_id} = config, acc ->
- acc
- |> Map.delete(screen_id)
- |> Map.put(
- new_id,
- json_to_struct(config, :gl_eink_v2, place_id, platform_ids)
- )
-
- %{"screen_id" => screen_id} = config, acc ->
- Map.put(acc, screen_id, Screen.from_json(config))
- end)
- end
-
- defp add_new_pending_screens(place_id, platform_ids, new_pending_screens, acc) do
- Enum.reduce(new_pending_screens, acc, fn
- config, acc ->
- Map.put(
- acc,
- config["new_id"],
- json_to_struct(config, :gl_eink_v2, place_id, platform_ids)
- )
- end)
- end
-
- # Each screen type will look in a different part of the configuration to find it's physical location
- defp get_route_id(:gl_eink_v2, updated_pending_screens, new_pending_screens) do
- updated_pending_screens
- |> Enum.map(fn config -> config end)
- |> Enum.concat(new_pending_screens)
- |> List.first()
- |> get_in(["app_params", "header", "route_id"])
- end
-
- defp get_route_id(app_id, _, _),
- do: raise("get_route_id/3 not implemented for app_id: #{app_id}")
-
- # Necessary for new mock framework.
- # Tests will use a mocked module when calling functions meant for the RoutePattern module.
- defp route_pattern_mod do
- Application.get_env(:screenplay, :route_pattern_mod, RoutePattern)
- end
-
- defp get_current_published_config do
- case PublishedScreensFetch.fetch_config() do
- {:ok, config, version_id} -> {config, version_id}
- error -> error
- end
- end
-
- defp get_new_published_screens(published_config, screens_to_publish) do
- %Config{screens: published_screens, devops: devops} =
- published_config |> Jason.decode!() |> Config.from_json()
-
- screens =
- screens_to_publish
- |> Enum.into(%{})
- |> Map.merge(published_screens)
-
- %Config{screens: screens, devops: devops}
- end
-
- defp get_new_places_config(places_config, screens_to_add) do
- screens_to_add
- |> Enum.group_by(
- fn
- {_, %Screen{app_id: :gl_eink_v2} = config} ->
- config.app_params.footer.stop_id
-
- _ ->
- raise("Not implemented")
- end,
- fn {screen_id, %Screen{app_id: app_id, disabled: disabled}} ->
- %ShowtimeScreen{id: screen_id, type: app_id, disabled: disabled}
- end
- )
- |> Enum.reduce(places_config, fn {stop_id, screens}, acc ->
- update_in(
- acc,
- [Access.filter(&(&1.id == stop_id)), Access.key!(:screens)],
- &(&1 ++ screens)
- )
- end)
- end
-
- defp place_has_screen(
- {_screen_id, %Screen{app_id: :gl_eink_v2} = screen},
- place_id
- ) do
- %Screen{app_params: %GlEink{footer: %Footer{stop_id: stop_id}}} = screen
- stop_id == place_id
- end
-
- defp place_has_screen(_screen, _place_id), do: false
-
- defp screen_to_place_id(screen = %Screen{app_id: :gl_eink_v2}) do
- screen.app_params.footer.stop_id
- end
-
- defp screen_to_place_id(screen = %Screen{app_id: :pre_fare_v2}) do
- screen.app_params.header.stop_id
- end
-
- defp screen_to_place_id(screen = %Screen{app_id: :bus_eink_v2}) do
- screen.app_params.header.stop_id
- end
-
- defp screen_to_place_id(screen = %Screen{app_id: :bus_shelter_v2}) do
- screen.app_params.footer.stop_id
- end
-
- defp screen_to_place_id(screen = %Screen{app_id: :dup_v2}) do
- screen.app_params.alerts.stop_id
- end
-
- defp screen_to_place_id(%Screen{app_id: app_id}),
- do: raise("screen_to_place_id/1 not implemented for app_id: #{app_id}")
-
- def add_emergency_takeover_configs(alert_id, showtime_screen_ids, message) do
- with {published_config, _published_version_id} <- get_current_published_config(),
- {:ok, published_config_deserialized} <- Jason.decode(published_config) do
- %Config{screens: published_screens, devops: devops} =
- published_config_deserialized |> Config.from_json()
-
- updated_screens =
- update_screens_with_emergency_takeover(
- published_screens,
- showtime_screen_ids,
- alert_id,
- message
- )
-
- %Config{screens: updated_screens, devops: devops}
- |> publish_new_config()
- else
- _error ->
- {:error, "Could not fetch published screens config"}
- end
- end
-
- defp update_screens_with_emergency_takeover(screens, screen_ids, alert_id, message) do
- for {id, screen} <- screens,
- into: %{} do
- if id in screen_ids do
- case screen do
- %Screen{app_params: %{emergency_messaging_location: eml}} when not is_nil(eml) ->
- emergency_takeover =
- EmergencyTakeoverConfig.build_emergency_takeover(
- message,
- alert_id,
- screen.app_id,
- eml
- )
-
- {id,
- put_in(
- screen,
- [Access.key!(:app_params), Access.key!(:emergency_takeover)],
- emergency_takeover
- )}
-
- _ ->
- Logger.error("Tried to takeover #{id} without an emergency_messaging_location")
-
- {id, screen}
- end
- else
- {id, screen}
- end
- end
- end
-
- def clear_emergency_takeover_configs(showtime_screen_ids) do
- with {published_config, _published_version_id} <- get_current_published_config(),
- {:ok, published_config_deserialized} <- Jason.decode(published_config) do
- %Config{screens: published_screens, devops: devops} =
- published_config_deserialized |> Config.from_json()
-
- updated_screens = clear_screens_emergency_takeover(published_screens, showtime_screen_ids)
-
- %Config{screens: updated_screens, devops: devops}
- |> publish_new_config()
- else
- _error ->
- {:error, "Could not fetch published screens config"}
- end
- end
-
- defp clear_screens_emergency_takeover(screens, screen_ids) do
- for {id, screen} <- screens, into: %{} do
- if id in screen_ids do
- {id, put_in(screen, [Access.key!(:app_params), Access.key!(:emergency_takeover)], nil)}
- else
- {id, screen}
- end
- end
- end
-
- def publish_new_config(new_config) do
- with(:ok <- PublishedScreensFetch.put_config(new_config)) do
- PublishedScreensFetch.commit()
- end
- end
-end
diff --git a/lib/screenplay_web/auth_manager.ex b/lib/screenplay_web/auth_manager.ex
index 516d98ddd..445fd0d26 100644
--- a/lib/screenplay_web/auth_manager.ex
+++ b/lib/screenplay_web/auth_manager.ex
@@ -12,7 +12,6 @@ defmodule ScreenplayWeb.AuthManager do
@roles %{
"screenplay-emergency-admin" => :emergency_admin,
- "screens-admin" => :screens_admin,
"pa-message-admin" => :pa_message_admin,
"suppression-admin" => :suppression_admin
}
diff --git a/lib/screenplay_web/controllers/config_controller.ex b/lib/screenplay_web/controllers/config_controller.ex
deleted file mode 100644
index 43a93a4f5..000000000
--- a/lib/screenplay_web/controllers/config_controller.ex
+++ /dev/null
@@ -1,172 +0,0 @@
-defmodule ScreenplayWeb.ConfigController do
- alias ScreensConfig.PendingConfig
- use ScreenplayWeb, :controller
-
- require Logger
-
- alias Screenplay.PendingScreensConfig.Fetch, as: PendingScreensConfig
- alias Screenplay.PermanentConfig
- alias Screenplay.ScreensConfig, as: ScreensConfigStore
- alias ScreensConfig.Screen
- alias ScreensConfig.Screen.GlEink
-
- plug :check_pending_screens_version when action == :publish
-
- def index(conn, _params) do
- render(conn, "index.html")
- end
-
- def put(conn, %{
- "places_and_screens" => places_and_screens,
- "screen_type" => screen_type,
- "version_id" => version_id
- }) do
- case PermanentConfig.put_pending_screens(
- places_and_screens,
- String.to_existing_atom(screen_type),
- version_id
- ) do
- :ok ->
- send_resp(conn, 200, "OK")
-
- {:error, :version_mismatch} ->
- json(%{conn | status: 400}, %{error: :version_mismatch})
-
- {:error, :config_not_fetched} ->
- send_resp(conn, 500, "S3 Operation Failed: Get")
-
- {:error, :config_not_written} ->
- send_resp(conn, 500, "S3 Operation Failed: Put")
-
- {:error, {:duplicate_screen_ids, duplicate_screen_ids}} ->
- json(%{conn | status: 400}, %{
- duplicate_screen_ids: duplicate_screen_ids,
- error: :duplicate_screen_ids
- })
- end
- end
-
- def existing_screens(conn, %{"place_ids" => place_ids, "app_id" => app_id}) do
- app_id_atom = String.to_existing_atom(app_id)
-
- {pending_screens_config, version_id} =
- case PendingScreensConfig.fetch_config() do
- {:ok, config, metadata} ->
- %PendingConfig{screens: pending_screens} =
- config
- |> Jason.decode!()
- |> PendingConfig.from_json()
-
- {pending_screens, metadata.version_id}
-
- _ ->
- raise("Could not fetch pending screens config in existing_screens/2")
- end
-
- places_and_screens =
- place_ids
- |> String.split(",")
- |> Enum.map(fn place_id ->
- filter_fn = fn
- {_, %Screen{app_id: ^app_id_atom} = config} ->
- place_id_has_screen?(place_id, app_id_atom, config)
-
- _ ->
- false
- end
-
- live_screens =
- ScreensConfigStore.screens()
- |> Enum.filter(filter_fn)
- |> Enum.map(fn {k, v} -> {k, Screen.to_json(v)} end)
- |> Enum.into(%{})
-
- pending_screens =
- pending_screens_config
- |> Enum.filter(filter_fn)
- |> Enum.map(fn {k, v} -> {k, Screen.to_json(v)} end)
- |> Enum.into(%{})
-
- {place_id, %{live_screens: live_screens, pending_screens: pending_screens}}
- end)
- |> Enum.into(%{})
-
- json(conn, %{
- places_and_screens: places_and_screens,
- version_id: version_id
- })
- end
-
- @doc """
- Responds with a map whose top-level keys are `:places_and_screens`, `:version_id`, and `:last_modified_ms`.
- The response will also contain an `etag` header.
-
- The `:places_and_screens` map's keys are unique pairs of place ID+app ID (as strings, for JSON compatibility),
- and values are maps containing the live and pending screens at that place / with that app ID,
- plus the place ID and app ID as separate values for ease of use on the frontend.
-
- Each entry in the `:places_and_screens` map corresponds to one accordion in the Pending page UI.
-
- See spec for `PermanentConfig.get_existing_screens_at_places_with_pending_screens/0` for more detail.
- """
- def existing_screens_at_places_with_pending_screens(conn, _params) do
- data = PermanentConfig.get_existing_screens_at_places_with_pending_screens()
- {etag, data} = Map.pop!(data, :etag)
-
- conn
- |> put_resp_header("etag", etag)
- |> json(data)
- end
-
- def publish(conn, %{
- "place_id" => place_id,
- "app_id" => app_id,
- "hidden_from_screenplay_ids" => hidden_from_screenplay_ids
- }) do
- app_id_atom = String.to_existing_atom(app_id)
-
- case PermanentConfig.publish_pending_screens(
- place_id,
- app_id_atom,
- hidden_from_screenplay_ids
- ) do
- {:ok, new_config} ->
- json(conn, %{message: "OK", new_config: new_config})
-
- _ ->
- conn
- |> put_status(500)
- |> json(%{message: "Could not publish screens. Please contact an engineer."})
- end
- end
-
- # To be used as a plug on actions that modify pending screens config.
- defp check_pending_screens_version(conn, _opts) do
- case get_req_header(conn, "if-match") do
- [] ->
- conn
- |> send_resp(428, "Missing if-match header")
- |> halt()
-
- matches ->
- {:ok, _, metadata} = PendingScreensConfig.fetch_config()
-
- if metadata.etag in matches do
- conn
- else
- conn
- |> send_resp(412, "Pending screens config version mismatch")
- |> halt()
- end
- end
- end
-
- defp place_id_has_screen?(place_id, :gl_eink_v2, %Screen{
- app_params: %GlEink{footer: %{stop_id: stop_id}}
- }),
- do: place_id === stop_id
-
- defp place_id_has_screen?(place_id, app_id, _),
- do:
- raise("place_id_has_screen/2 not implemented for app_id: #{app_id}, place_id: #{place_id}")
-end
diff --git a/lib/screenplay_web/controllers/emergency_takeover_tool/alert_controller.ex b/lib/screenplay_web/controllers/emergency_takeover_tool/alert_controller.ex
index 7fae0c457..1713ed308 100644
--- a/lib/screenplay_web/controllers/emergency_takeover_tool/alert_controller.ex
+++ b/lib/screenplay_web/controllers/emergency_takeover_tool/alert_controller.ex
@@ -2,9 +2,8 @@ defmodule ScreenplayWeb.EmergencyTakeoverTool.AlertController do
use ScreenplayWeb, :controller
alias Screenplay.EmergencyTakeovers
- alias Screenplay.EmergencyTakeoverTool.EmergencyTakeover
+ alias Screenplay.EmergencyTakeoverTool.{ConfigUpdater, EmergencyTakeover}
alias Screenplay.Outfront.SFTP
- alias Screenplay.PermanentConfig
alias Screenplay.Places
alias Screenplay.Places.Place.ShowtimeScreen
alias ScreenplayWeb.UserActionLogger
@@ -167,7 +166,7 @@ defmodule ScreenplayWeb.EmergencyTakeoverTool.AlertController do
%{String.t() => String.t()}
) :: :ok | {:error, String.t()}
def add_showtime_takeovers(alert_id, screen_ids, message = %{type: :canned}, _images) do
- case PermanentConfig.add_emergency_takeover_configs(alert_id, screen_ids, message) do
+ case ConfigUpdater.add_emergency_takeover_configs(alert_id, screen_ids, message) do
:ok -> :ok
{:error, reason} -> {:error, "Failed to add canned message takeovers: #{reason}"}
end
@@ -175,7 +174,7 @@ defmodule ScreenplayWeb.EmergencyTakeoverTool.AlertController do
def add_showtime_takeovers(alert_id, screen_ids, message = %{type: :custom}, images) do
with :ok <- upload_takeover_images(alert_id, images),
- :ok <- PermanentConfig.add_emergency_takeover_configs(alert_id, screen_ids, message) do
+ :ok <- ConfigUpdater.add_emergency_takeover_configs(alert_id, screen_ids, message) do
:ok
else
{:error, reason} -> {:error, "Failed to add custom message takeovers: #{reason}"}
@@ -272,7 +271,7 @@ defmodule ScreenplayWeb.EmergencyTakeoverTool.AlertController do
defp remove_takeovers_from_showtime_screens(station_ids) do
station_ids
|> showtime_screens_at_stations()
- |> PermanentConfig.clear_emergency_takeover_configs()
+ |> ConfigUpdater.clear_emergency_takeover_configs()
end
@spec showtime_screens_at_stations(list(String.t())) :: list(String.t())
diff --git a/lib/screenplay_web/router.ex b/lib/screenplay_web/router.ex
index fbace4f62..8102981a6 100644
--- a/lib/screenplay_web/router.ex
+++ b/lib/screenplay_web/router.ex
@@ -35,7 +35,6 @@ defmodule ScreenplayWeb.Router do
pipeline :ensure_emergency_admin, do: plug(EnsureRole, :emergency_admin)
pipeline :ensure_pa_message_admin, do: plug(EnsureRole, :pa_message_admin)
- pipeline :ensure_screens_admin, do: plug(EnsureRole, :screens_admin)
pipeline :ensure_suppression_admin, do: plug(EnsureRole, :suppression_admin)
# Load balancer health check, exempt from authentication, SSL redirects, etc.
@@ -128,29 +127,6 @@ defmodule ScreenplayWeb.Router do
delete("/", SuppressedPredictionsApiController, :delete)
end
- # Permanent Configuration
-
- scope "/", ScreenplayWeb do
- pipe_through([:browser, :authenticate, :ensure_screens_admin])
-
- get("/pending", ConfigController, :index)
- get("/configure-screens/*app_id", ConfigController, :index)
- end
-
- scope "/config", ScreenplayWeb do
- pipe_through([:api, :authenticate, :ensure_screens_admin])
-
- post("/put", ConfigController, :put)
- post("/publish/:place_id/:app_id", ConfigController, :publish)
- get("/existing-screens/:app_id", ConfigController, :existing_screens)
-
- get(
- "/existing-screens-at-places-with-pending-screens",
- ConfigController,
- :existing_screens_at_places_with_pending_screens
- )
- end
-
# Emergency Takeover Tool
scope "/emergency-takeover", ScreenplayWeb.EmergencyTakeoverTool do
diff --git a/lib/screenplay_web/templates/layout/app.html.heex b/lib/screenplay_web/templates/layout/app.html.heex
index dd26cfd9f..435da6805 100644
--- a/lib/screenplay_web/templates/layout/app.html.heex
+++ b/lib/screenplay_web/templates/layout/app.html.heex
@@ -23,9 +23,6 @@
<%= if :emergency_admin in @roles do %>
<% end %>
- <%= if :screens_admin in @roles do %>
-
- <% end %>
<%= if :pa_message_admin in @roles do %>
<% end %>
diff --git a/test/screenplay/emergency_takeover_tool/config_updater_test.exs b/test/screenplay/emergency_takeover_tool/config_updater_test.exs
new file mode 100644
index 000000000..e676305e8
--- /dev/null
+++ b/test/screenplay/emergency_takeover_tool/config_updater_test.exs
@@ -0,0 +1,221 @@
+defmodule Screenplay.EmergencyTakeoverTool.ConfigUpdaterTest do
+ use ExUnit.Case
+
+ alias Screenplay.EmergencyTakeoverTool.ConfigUpdater
+ alias Screenplay.ScreensConfig.Fetch.Local
+
+ alias ScreensConfig.{
+ Alerts,
+ Config,
+ ContentSummary,
+ Departures,
+ ElevatorStatus,
+ EmergencyTakeover,
+ Footer,
+ Header,
+ LineMap,
+ Screen
+ }
+
+ alias ScreensConfig.Screen.{GlEink, PreFare}
+
+ @screen_without_takeover %Screen{
+ vendor: :mercury,
+ device_id: nil,
+ name: nil,
+ app_id: :pre_fare_v2,
+ refresh_if_loaded_before: nil,
+ disabled: false,
+ hidden_from_screenplay: false,
+ app_params: %PreFare{
+ emergency_messaging_location: :inside,
+ emergency_takeover: nil,
+ content_summary: %ContentSummary{parent_station_id: "place-test"},
+ elevator_status: %ElevatorStatus{parent_station_id: "place-test"},
+ full_line_map: [],
+ header: %Header.StopId{stop_id: "place-test"},
+ reconstructed_alert_widget: %ScreensConfig.Alerts{stop_id: "place-test"}
+ },
+ tags: []
+ }
+ @gl_eink_screen %Screen{
+ vendor: :mercury,
+ device_id: nil,
+ name: nil,
+ app_id: :gl_eink_v2,
+ refresh_if_loaded_before: nil,
+ disabled: false,
+ hidden_from_screenplay: false,
+ app_params: %GlEink{
+ departures: %Departures{
+ sections: [
+ %Departures.Section{
+ query: %Departures.Query{
+ params: %Departures.Query.Params{
+ stop_ids: ["place-test"],
+ route_ids: ["Green-B"],
+ direction_id: 1
+ }
+ }
+ }
+ ]
+ },
+ footer: %Footer{stop_id: "place-test"},
+ header: %Header.Destination{
+ route_id: "Green-B",
+ direction_id: 1
+ },
+ alerts: %Alerts{stop_id: "456"},
+ line_map: %LineMap{
+ stop_id: "456",
+ station_id: "place-test",
+ direction_id: 1,
+ route_id: "Green-B"
+ },
+ evergreen_content: [],
+ platform_location: :back
+ },
+ tags: []
+ }
+
+ def get_fixture_path(file_name) do
+ Path.join(~w[#{File.cwd!()} test fixtures #{file_name}])
+ end
+
+ setup_all do
+ on_exit(fn ->
+ empty_config = %{screens: %{}}
+ published_screens_path = get_fixture_path("screens_config.json")
+
+ File.write(
+ published_screens_path,
+ Jason.encode!(empty_config)
+ )
+
+ File.rm(published_screens_path <> ".temp")
+ end)
+ end
+
+ describe "add_emergency_takeover_configs/3" do
+ setup do
+ published_screens_path = get_fixture_path("screens_config.json")
+
+ config =
+ %Config{
+ screens: %{
+ "PRE-1" => @screen_without_takeover,
+ "PRE-2" => @screen_without_takeover,
+ "GL-1" => @gl_eink_screen
+ }
+ }
+ |> Config.to_json()
+ |> Jason.encode!()
+
+ File.write(published_screens_path, config)
+ end
+
+ test "adds an emergency takeover config to a screen" do
+ alert_id = "alert-1"
+ takeover_screen_id = "PRE-1"
+ message = %{type: :custom, text: %{indoor: "Indoor Message", outdoor: "Outdoor Message"}}
+
+ assert ConfigUpdater.add_emergency_takeover_configs(
+ alert_id,
+ [takeover_screen_id],
+ message
+ ) == :ok
+
+ {:ok, file_contents, _metadata} = Local.fetch_config()
+ %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
+
+ expected_takeover = %EmergencyTakeover{
+ audio_asset_path: nil,
+ text_for_audio: "Indoor Message",
+ visual_asset_path: "test/fixtures/emergency_takeover_images/alert-1/indoor_portrait.png"
+ }
+
+ assert screens[takeover_screen_id] ==
+ put_in(
+ @screen_without_takeover.app_params.emergency_takeover,
+ expected_takeover
+ )
+
+ assert screens["PRE-2"] == @screen_without_takeover
+ assert screens["GL-1"] == @gl_eink_screen
+ end
+
+ test "adds a canned emergency takeover config to a screen" do
+ alert_id = "alert-1"
+ takeover_screen_id = "PRE-1"
+ message = %{type: :canned, id: 1}
+
+ assert ConfigUpdater.add_emergency_takeover_configs(
+ alert_id,
+ [takeover_screen_id],
+ message
+ ) == :ok
+
+ {:ok, file_contents, _metadata} = Local.fetch_config()
+ %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
+
+ expected_takeover = %EmergencyTakeover{
+ audio_asset_path:
+ "test/fixtures/emergency_takeover_images/canned/audio/LeaveStation-Indoor.mp3",
+ text_for_audio: nil,
+ visual_asset_path:
+ "test/fixtures/emergency_takeover_images/canned/images/LeaveStation-indoor-portrait.gif"
+ }
+
+ assert screens[takeover_screen_id] ==
+ put_in(
+ @screen_without_takeover.app_params.emergency_takeover,
+ expected_takeover
+ )
+
+ assert screens["PRE-2"] == @screen_without_takeover
+ end
+ end
+
+ describe "clear_emergency_takeover_configs/1" do
+ setup do
+ published_screens_path = get_fixture_path("screens_config.json")
+
+ screen_with_takeover =
+ put_in(
+ @screen_without_takeover.app_params.emergency_takeover,
+ %EmergencyTakeover{
+ audio_asset_path: nil,
+ text_for_audio: "Indoor Message",
+ visual_asset_path:
+ "test/fixtures/emergency_takeover_images/alert-1/indoor_portrait.png"
+ }
+ )
+
+ config =
+ %Config{
+ screens: %{
+ "PRE-1" => screen_with_takeover,
+ "PRE-2" => @screen_without_takeover,
+ "GL-1" => @gl_eink_screen
+ }
+ }
+ |> Config.to_json()
+ |> Jason.encode!()
+
+ File.write(published_screens_path, config)
+ end
+
+ test "clears emergency takeover configs from screens" do
+ takeover_screen_id = "PRE-1"
+
+ assert ConfigUpdater.clear_emergency_takeover_configs([takeover_screen_id]) == :ok
+
+ {:ok, file_contents, _metadata} = Local.fetch_config()
+ %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
+
+ assert screens[takeover_screen_id] == @screen_without_takeover
+ assert screens["PRE-2"] == @screen_without_takeover
+ assert screens["GL-1"] == @gl_eink_screen
+ end
+ end
+end
diff --git a/test/screenplay/permanent_config_test.exs b/test/screenplay/permanent_config_test.exs
deleted file mode 100644
index c0bf77b42..000000000
--- a/test/screenplay/permanent_config_test.exs
+++ /dev/null
@@ -1,623 +0,0 @@
-defmodule Screenplay.PermanentConfigTest do
- use ExUnit.Case
-
- import Mox
-
- alias Screenplay.PendingScreensConfig.Fetch.Local
- alias Screenplay.PermanentConfig
- alias Screenplay.Places.{Cache, Place}
- alias Screenplay.Places.Place.ShowtimeScreen
- alias ScreensConfig.ContentSummary
- alias ScreensConfig.ElevatorStatus
- alias ScreensConfig.Screen.PreFare
-
- alias ScreensConfig.{
- Alerts,
- Config,
- Departures,
- EmergencyTakeover,
- Footer,
- Header,
- LineMap,
- PendingConfig,
- Screen
- }
-
- alias ScreensConfig.Screen.GlEink
-
- @screen_without_takeover %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :pre_fare_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %PreFare{
- emergency_messaging_location: :inside,
- emergency_takeover: nil,
- content_summary: %ContentSummary{parent_station_id: "place-test"},
- elevator_status: %ElevatorStatus{parent_station_id: "place-test"},
- full_line_map: [],
- header: %Header.StopId{stop_id: "place-test"},
- reconstructed_alert_widget: %ScreensConfig.Alerts{stop_id: "place-test"}
- },
- tags: []
- }
- @gl_eink_screen %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :gl_eink_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %GlEink{
- departures: %Departures{
- sections: [
- %Departures.Section{
- query: %Departures.Query{
- params: %Departures.Query.Params{
- stop_ids: ["place-test"],
- route_ids: ["Green-B"],
- direction_id: 1
- }
- }
- }
- ]
- },
- footer: %Footer{stop_id: "place-test"},
- header: %Header.Destination{
- route_id: "Green-B",
- direction_id: 1
- },
- alerts: %Alerts{stop_id: "456"},
- line_map: %LineMap{
- stop_id: "456",
- station_id: "place-test",
- direction_id: 1,
- route_id: "Green-B"
- },
- evergreen_content: [],
- platform_location: :back
- },
- tags: []
- }
-
- def fetch_current_config_version do
- {:ok, _config, metadata} = Local.fetch_config()
- metadata.version_id
- end
-
- def get_fixture_path(file_name) do
- Path.join(~w[#{File.cwd!()} test fixtures #{file_name}])
- end
-
- setup_all do
- start_supervised!(Screenplay.Places.Cache)
-
- on_exit(fn ->
- empty_config = %{screens: %{}}
- pending_screens_path = get_fixture_path("pending_config.json")
- published_screens_path = get_fixture_path("screens_config.json")
-
- File.write(
- pending_screens_path,
- Jason.encode!(empty_config)
- )
-
- File.write(
- published_screens_path,
- Jason.encode!(empty_config)
- )
-
- File.rm(pending_screens_path <> ".temp")
- File.rm(published_screens_path <> ".temp")
- end)
- end
-
- describe "put_pending_screens/3" do
- setup do
- empty_config = %{screens: %{}}
- pending_screens_path = get_fixture_path("pending_config.json")
- published_screens_path = get_fixture_path("screens_config.json")
-
- File.write(
- pending_screens_path,
- Jason.encode!(empty_config)
- )
-
- File.write(
- published_screens_path,
- Jason.encode!(empty_config)
- )
- end
-
- test "adds and updates a new config for GL E-Ink" do
- version = fetch_current_config_version()
-
- expect(Screenplay.RoutePatterns.Mock, :fetch_platform_ids_for_route_at_stop, 2, fn stop_id,
- route_id ->
- assert stop_id == "place-test"
- assert route_id == "Green-B"
-
- {"123", "456"}
- end)
-
- places_and_screens = %{
- "place-test" => %{
- "updated_pending_screens" => [],
- "new_pending_screens" => [
- %{
- "new_id" => "1234",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- }
- ]
- }
- }
-
- assert PermanentConfig.put_pending_screens(places_and_screens, :gl_eink_v2, version) == :ok
-
- expected_file_contents =
- %PendingConfig{
- screens: %{
- "1234" => %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :gl_eink_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %GlEink{
- departures: %Departures{
- sections: [
- %Departures.Section{
- query: %Departures.Query{
- params: %Departures.Query.Params{
- stop_ids: ["place-test"],
- route_ids: ["Green-B"],
- direction_id: 0
- }
- }
- }
- ]
- },
- footer: %Footer{stop_id: "place-test"},
- header: %Header.Destination{
- route_id: "Green-B",
- direction_id: 0
- },
- alerts: %Alerts{stop_id: "123"},
- line_map: %LineMap{
- stop_id: "123",
- station_id: "place-test",
- direction_id: 0,
- route_id: "Green-B"
- },
- evergreen_content: [],
- platform_location: "front"
- },
- tags: []
- }
- }
- }
- |> PendingConfig.to_json()
- |> Jason.encode!(pretty: true)
-
- {:ok, config, metadata} = Local.fetch_config()
- assert expected_file_contents == config
-
- places_and_screens = %{
- "place-test" => %{
- "updated_pending_screens" => [
- %{
- "new_id" => "12345",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 1},
- "platform_location" => "back"
- },
- "screen_id" => "1234"
- }
- ],
- "new_pending_screens" => []
- }
- }
-
- assert PermanentConfig.put_pending_screens(
- places_and_screens,
- :gl_eink_v2,
- metadata.version_id
- ) == :ok
-
- expected_file_contents =
- %PendingConfig{
- screens: %{
- "12345" => %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :gl_eink_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %GlEink{
- departures: %Departures{
- sections: [
- %Departures.Section{
- query: %Departures.Query{
- params: %Departures.Query.Params{
- stop_ids: ["place-test"],
- route_ids: ["Green-B"],
- direction_id: 1
- }
- }
- }
- ]
- },
- footer: %Footer{stop_id: "place-test"},
- header: %Header.Destination{
- route_id: "Green-B",
- direction_id: 1
- },
- alerts: %Alerts{stop_id: "456"},
- line_map: %LineMap{
- stop_id: "456",
- station_id: "place-test",
- direction_id: 1,
- route_id: "Green-B"
- },
- evergreen_content: [],
- platform_location: "back"
- },
- tags: []
- }
- }
- }
- |> PendingConfig.to_json()
- |> Jason.encode!(pretty: true)
-
- {:ok, config, _metadata} = Local.fetch_config()
- assert expected_file_contents == config
- end
-
- test "returns version_mismatch error if version is outdated" do
- places_and_screens = %{
- "place-test" => %{
- "updated_pending_screens" => [],
- "new_pending_screens" => [
- %{
- "new_id" => "1234",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- }
- ]
- }
- }
-
- assert PermanentConfig.put_pending_screens(places_and_screens, :gl_eink_v2, "1234") ==
- {:error, :version_mismatch}
- end
-
- test "returns error when duplicate screen IDs are found" do
- version = fetch_current_config_version()
-
- expect(Screenplay.RoutePatterns.Mock, :fetch_platform_ids_for_route_at_stop, 2, fn stop_id,
- route_id ->
- assert stop_id == "place-test"
- assert route_id == "Green-B"
-
- {"123", "456"}
- end)
-
- places_and_screens = %{
- "place-test" => %{
- "updated_pending_screens" => [],
- "new_pending_screens" => [
- %{
- "new_id" => "1234",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- }
- ]
- }
- }
-
- assert PermanentConfig.put_pending_screens(places_and_screens, :gl_eink_v2, version) == :ok
- version = fetch_current_config_version()
-
- places_and_screens = %{
- "place-test" => %{
- "updated_pending_screens" => [],
- "new_pending_screens" => [
- %{
- "new_id" => "1234",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- },
- %{
- "new_id" => "5678",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- },
- %{
- "new_id" => "5678",
- "app_params" => %{
- "header" => %{"route_id" => "Green-B", "direction_id" => 0},
- "platform_location" => "front"
- }
- }
- ]
- }
- }
-
- assert PermanentConfig.put_pending_screens(places_and_screens, :gl_eink_v2, version) ==
- {:error, {:duplicate_screen_ids, ["1234", "5678"]}}
- end
- end
-
- describe "publish_pending_screens/1" do
- setup do
- pending_screens_path = get_fixture_path("pending_config.json")
-
- config =
- %PendingConfig{
- screens: %{
- "12345" => %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :gl_eink_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %GlEink{
- departures: %Departures{
- sections: [
- %Departures.Section{
- query: %Departures.Query{
- params: %Departures.Query.Params{
- stop_ids: ["place-test"],
- route_ids: ["Green-B"],
- direction_id: 1
- }
- }
- }
- ]
- },
- footer: %Footer{stop_id: "place-test"},
- header: %Header.Destination{
- route_id: "Green-B",
- direction_id: 1
- },
- alerts: %Alerts{stop_id: "456"},
- line_map: %LineMap{
- stop_id: "456",
- station_id: "place-test",
- direction_id: 1,
- route_id: "Green-B"
- },
- evergreen_content: [],
- platform_location: "back"
- },
- tags: []
- },
- "23456" => %Screen{
- vendor: :mercury,
- device_id: nil,
- name: nil,
- app_id: :gl_eink_v2,
- refresh_if_loaded_before: nil,
- disabled: false,
- hidden_from_screenplay: false,
- app_params: %GlEink{
- departures: %Departures{
- sections: [
- %Departures.Section{
- query: %Departures.Query{
- params: %Departures.Query.Params{
- stop_ids: ["place-test"],
- route_ids: ["Green-B"],
- direction_id: 1
- }
- }
- }
- ]
- },
- footer: %Footer{stop_id: "place-test"},
- header: %Header.Destination{
- route_id: "Green-B",
- direction_id: 1
- },
- alerts: %Alerts{stop_id: "456"},
- line_map: %LineMap{
- stop_id: "456",
- station_id: "place-test",
- direction_id: 1,
- route_id: "Green-B"
- },
- evergreen_content: [],
- platform_location: "back"
- },
- tags: []
- }
- }
- }
- |> PendingConfig.to_json()
- |> Jason.encode!()
-
- File.write(pending_screens_path, config)
-
- [
- %Place{
- id: "place-test",
- name: "Test Place",
- routes: ["Green-B"],
- screens: [],
- description: nil
- }
- ]
- |> Enum.map(&{&1.id, &1})
- |> Cache.put_all()
-
- on_exit(fn ->
- Cache.delete_all()
- end)
- end
-
- test "publishes pending screens" do
- assert {:ok,
- [
- %Place{
- id: "place-test",
- name: "Test Place",
- routes: ["Green-B"],
- screens: [
- %ShowtimeScreen{
- id: "12345",
- type: :gl_eink_v2,
- disabled: false,
- direction_id: nil,
- location: ""
- }
- ],
- description: nil
- }
- ]} = PermanentConfig.publish_pending_screens("place-test", :gl_eink_v2, ["23456"])
- end
- end
-
- describe "add_emergency_takeover_configs/3" do
- setup do
- published_screens_path = get_fixture_path("screens_config.json")
-
- config =
- %Config{
- screens: %{
- "PRE-1" => @screen_without_takeover,
- "PRE-2" => @screen_without_takeover,
- "GL-1" => @gl_eink_screen
- }
- }
- |> Config.to_json()
- |> Jason.encode!()
-
- File.write(published_screens_path, config)
- end
-
- test "adds an emergency takeover config to a screen" do
- alert_id = "alert-1"
- takeover_screen_id = "PRE-1"
- message = %{type: :custom, text: %{indoor: "Indoor Message", outdoor: "Outdoor Message"}}
-
- assert PermanentConfig.add_emergency_takeover_configs(
- alert_id,
- [takeover_screen_id],
- message
- ) == :ok
-
- {:ok, file_contents, _metadata} = Screenplay.ScreensConfig.Fetch.Local.fetch_config()
- %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
-
- expected_takeover = %EmergencyTakeover{
- audio_asset_path: nil,
- text_for_audio: "Indoor Message",
- visual_asset_path: "test/fixtures/emergency_takeover_images/alert-1/indoor_portrait.png"
- }
-
- assert screens[takeover_screen_id] ==
- put_in(
- @screen_without_takeover.app_params.emergency_takeover,
- expected_takeover
- )
-
- assert screens["PRE-2"] == @screen_without_takeover
- assert screens["GL-1"] == @gl_eink_screen
- end
-
- test "adds a canned emergency takeover config to a screen" do
- alert_id = "alert-1"
- takeover_screen_id = "PRE-1"
- message = %{type: :canned, id: 1}
-
- assert PermanentConfig.add_emergency_takeover_configs(
- alert_id,
- [takeover_screen_id],
- message
- ) == :ok
-
- {:ok, file_contents, _metadata} = Screenplay.ScreensConfig.Fetch.Local.fetch_config()
- %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
-
- expected_takeover = %EmergencyTakeover{
- audio_asset_path:
- "test/fixtures/emergency_takeover_images/canned/audio/LeaveStation-Indoor.mp3",
- text_for_audio: nil,
- visual_asset_path:
- "test/fixtures/emergency_takeover_images/canned/images/LeaveStation-indoor-portrait.gif"
- }
-
- assert screens[takeover_screen_id] ==
- put_in(
- @screen_without_takeover.app_params.emergency_takeover,
- expected_takeover
- )
-
- assert screens["PRE-2"] == @screen_without_takeover
- end
- end
-
- describe "clear_emergency_takeover_configs/1" do
- setup do
- published_screens_path = get_fixture_path("screens_config.json")
-
- screen_with_takeover =
- put_in(
- @screen_without_takeover.app_params.emergency_takeover,
- %EmergencyTakeover{
- audio_asset_path: nil,
- text_for_audio: "Indoor Message",
- visual_asset_path:
- "test/fixtures/emergency_takeover_images/alert-1/indoor_portrait.png"
- }
- )
-
- config =
- %Config{
- screens: %{
- "PRE-1" => screen_with_takeover,
- "PRE-2" => @screen_without_takeover,
- "GL-1" => @gl_eink_screen
- }
- }
- |> Config.to_json()
- |> Jason.encode!()
-
- File.write(published_screens_path, config)
- end
-
- test "clears emergency takeover configs from screens" do
- takeover_screen_id = "PRE-1"
-
- assert PermanentConfig.clear_emergency_takeover_configs([takeover_screen_id]) == :ok
-
- {:ok, file_contents, _metadata} = Screenplay.ScreensConfig.Fetch.Local.fetch_config()
- %Config{screens: screens} = file_contents |> Jason.decode!() |> Config.from_json()
-
- assert screens[takeover_screen_id] == @screen_without_takeover
- assert screens["PRE-2"] == @screen_without_takeover
- assert screens["GL-1"] == @gl_eink_screen
- end
- end
-end
diff --git a/test/screenplay_web/controllers/auth_controller_test.exs b/test/screenplay_web/controllers/auth_controller_test.exs
index 56992873a..3c1a33211 100644
--- a/test/screenplay_web/controllers/auth_controller_test.exs
+++ b/test/screenplay_web/controllers/auth_controller_test.exs
@@ -9,7 +9,7 @@ defmodule ScreenplayWeb.Controllers.AuthControllerTest do
conn
|> init_test_session(%{})
|> Plug.Conn.put_session(:previous_path_from_auth, "/test")
- |> get("/auth/keycloak/callback?email=user@test.com&roles[]=screens-admin")
+ |> get("/auth/keycloak/callback?email=user@test.com&roles[]=pa-message-admin")
assert redirected_to(conn) == "/test"
end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 1670a9366..74d1441e8 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -63,7 +63,7 @@ defmodule ScreenplayWeb.ConnCase do
Phoenix.ConnTest.build_conn()
|> Plug.Test.init_test_session(%{})
|> Guardian.Plug.sign_in(ScreenplayWeb.AuthManager, user, %{
- "roles" => ["screens-admin"]
+ "roles" => ["pa-message-admin"]
})
|> Plug.Conn.put_session(:username, user)