-
-
-
-
-
-
Filter:
- {sortedIncidentFilters.map((type) => (
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
+ const navigate = useNavigate();
+
+ const { sort: originalSort, incidentTypeFilters: originalIncidentTypeFilters, updateSettings } = useSettings();
+
+ const [{ sort, incidentFilters }, dispatch] = useReducer(settingsReducer, settingsStateDefault);
+
+ const sortedIncidentFilters = useMemo(() => {
+ const kvps = Object.keys(incidentFilters).map(key => ({
+ key,
+ value: incidentFilters[key],
+ }));
+
+ return chain(kvps).sortBy("key").value();
+ }, [incidentFilters]);
+
+ const setIncidentFilter = (key: string) => {
+ dispatch({ type: "toggleIncidentFilter", key });
+ };
+
+ const goBack = useCallback(() => {
+ navigate(-1);
+ }, [navigate]);
+
+ useAppLayout(
+ () => ({
+ headerLeft:
Settings,
+ }),
+ []
+ );
+
+ const handleApply = useCallback(() => {
+ updateSettings(incidentFilters, sort);
+ goBack();
+ }, [updateSettings, incidentFilters, sort, goBack]);
+
+ const handleSort = (sort: Sort) => {
+ dispatch({ type: "setSort", sort });
+ };
+
+ const isDirty = originalSort !== sort || !_.isEqual(originalIncidentTypeFilters, incidentFilters);
+
+ useEffect(() => {
+ dispatch({
+ type: "initialize",
+ sort: originalSort,
+ incidentFilters: originalIncidentTypeFilters,
+ });
+ }, [originalSort, originalIncidentTypeFilters]);
+
+ return (
+
+
+
+
+
+
+
Filter:
+ {sortedIncidentFilters.map(type => (
+
+
+
+ ))}
+
+
+
+
+
+
+
- );
+
+
+ );
};
export default Settings;
diff --git a/src/lanco-incidents-app/src/providers/service-worker-provider.tsx b/src/lanco-incidents-app/src/providers/service-worker-provider.tsx
index d2151de..ad79c93 100644
--- a/src/lanco-incidents-app/src/providers/service-worker-provider.tsx
+++ b/src/lanco-incidents-app/src/providers/service-worker-provider.tsx
@@ -1,61 +1,59 @@
import useWorkbox from "hooks/use-workbox";
-import React, { createContext, PropsWithChildren, useState } from "react";
+import React, { PropsWithChildren, createContext, useState } from "react";
export const ServiceWorkerContext = createContext({
- appNeedsRefresh: false,
- offlineAppReady: false,
- updateIgnored: false,
- ignoreUpdate: () => {},
- updateServiceWorker: () => {},
+ appNeedsRefresh: false,
+ offlineAppReady: false,
+ updateIgnored: false,
+ ignoreUpdate: () => {},
+ updateServiceWorker: () => {},
});
interface ServiceWorkerProviderProps {
- immediate?: boolean;
- registerOptions?: {
- scope: string;
- };
- serviceWorkerPath: string;
+ immediate?: boolean;
+ registerOptions?: {
+ scope: string;
+ };
+ serviceWorkerPath: string;
}
-const ServiceWorkerProvider: React.FC<
- PropsWithChildren
-> = ({
- children,
- immediate,
- registerOptions = { scope: "/" },
- serviceWorkerPath,
+const ServiceWorkerProvider: React.FC> = ({
+ children,
+ immediate,
+ registerOptions = { scope: "/" },
+ serviceWorkerPath,
}) => {
- const [updateIgnored, setUpdateIgnored] = useState(false);
-
- const {
- isReady: offlineAppReady,
- hasUpdate: appNeedsRefresh,
- sendMessage,
- } = useWorkbox({
- serviceWorkerPath,
- registerOptions,
- immediate,
+ const [updateIgnored, setUpdateIgnored] = useState(false);
+
+ const {
+ isReady: offlineAppReady,
+ hasUpdate: appNeedsRefresh,
+ sendMessage,
+ } = useWorkbox({
+ serviceWorkerPath,
+ registerOptions,
+ immediate,
+ });
+
+ const updateServiceWorker = () =>
+ sendMessage({
+ type: "SKIP_WAITING",
});
- const updateServiceWorker = () =>
- sendMessage({
- type: "SKIP_WAITING",
- });
-
- const ignoreUpdate = () => setUpdateIgnored(true);
-
- return (
-
- {children}
-
- );
+ const ignoreUpdate = () => setUpdateIgnored(true);
+
+ return (
+
+ {children}
+
+ );
};
export default ServiceWorkerProvider;
diff --git a/src/lanco-incidents-app/src/providers/settings-provider.tsx b/src/lanco-incidents-app/src/providers/settings-provider.tsx
index 6fe523d..70a8521 100644
--- a/src/lanco-incidents-app/src/providers/settings-provider.tsx
+++ b/src/lanco-incidents-app/src/providers/settings-provider.tsx
@@ -1,105 +1,81 @@
import { SettingsRecord, Sort } from "models/view-models/settings-record";
-import React, {
- createContext,
- Dispatch,
- PropsWithChildren,
- useReducer,
-} from "react";
+import React, { Dispatch, PropsWithChildren, createContext, useReducer } from "react";
interface SettingsContextState {
- error?: any;
- settings: SettingsRecord;
+ error?: Error;
+ settings: SettingsRecord;
}
type SettingsContextAction =
- | {
- type: "UpdateSettings";
- sort: Sort;
- incidentTypeFilters: Record;
- }
- | {
- type: "SetIncidentTypeFilters";
- incidentTypes: string[];
- };
+ | {
+ type: "UpdateSettings";
+ sort: Sort;
+ incidentTypeFilters: Record;
+ }
+ | {
+ type: "SetIncidentTypeFilters";
+ incidentTypes: string[];
+ };
const defaultState: SettingsContextState = {
- settings: new SettingsRecord(),
+ settings: new SettingsRecord(),
};
export type SettingsContextType = SettingsContextState & {
- dispatch: Dispatch;
+ dispatch: Dispatch;
};
export const SettingsContext = createContext({
- ...defaultState,
- dispatch: () => {},
+ ...defaultState,
+ dispatch: () => {},
});
-function settingsContextReducer(
- state: SettingsContextState,
- action: SettingsContextAction
-): SettingsContextState {
- switch (action.type) {
- case "UpdateSettings": {
- const settingsWithIncidentTypeFilter = state.settings.with({
- sort: action.sort,
- incidentTypeFilters: {
- ...state.settings.incidentTypeFilters,
- ...action.incidentTypeFilters,
- },
- });
+function settingsContextReducer(state: SettingsContextState, action: SettingsContextAction): SettingsContextState {
+ switch (action.type) {
+ case "UpdateSettings": {
+ const settingsWithIncidentTypeFilter = state.settings.with({
+ sort: action.sort,
+ incidentTypeFilters: {
+ ...state.settings.incidentTypeFilters,
+ ...action.incidentTypeFilters,
+ },
+ });
- localStorage.setItem(
- "@settings",
- JSON.stringify(settingsWithIncidentTypeFilter)
- );
+ localStorage.setItem("@settings", JSON.stringify(settingsWithIncidentTypeFilter));
- return { ...state, settings: settingsWithIncidentTypeFilter };
- }
- case "SetIncidentTypeFilters": {
- const { incidentTypes } = action;
- const savedIncidentTypeFilters = Object.keys(
- state.settings.incidentTypeFilters
- );
+ return { ...state, settings: settingsWithIncidentTypeFilter };
+ }
+ case "SetIncidentTypeFilters": {
+ const { incidentTypes } = action;
+ const savedIncidentTypeFilters = Object.keys(state.settings.incidentTypeFilters);
- const incidentTypeFiltersToAdd = incidentTypes
- .filter((i) => !savedIncidentTypeFilters.includes(i))
- .reduce((prev: Record, current: string) => {
- return {
- ...prev,
- [current]: true,
- };
- }, {});
+ const incidentTypeFiltersToAdd = incidentTypes
+ .filter(i => !savedIncidentTypeFilters.includes(i))
+ .reduce((acc: Record, current: string) => {
+ acc[current] = true;
+ return acc;
+ }, {});
- const settingsWithIncidentTypes = state.settings.with({
- incidentTypeFilters: {
- ...state.settings.incidentTypeFilters,
- ...incidentTypeFiltersToAdd,
- },
- });
+ const settingsWithIncidentTypes = state.settings.with({
+ incidentTypeFilters: {
+ ...state.settings.incidentTypeFilters,
+ ...incidentTypeFiltersToAdd,
+ },
+ });
- localStorage.setItem(
- "@settings",
- JSON.stringify(settingsWithIncidentTypes)
- );
+ localStorage.setItem("@settings", JSON.stringify(settingsWithIncidentTypes));
- return { ...state, settings: settingsWithIncidentTypes };
- }
+ return { ...state, settings: settingsWithIncidentTypes };
}
+ }
}
-const SettingsProvider: React.FC> = ({ children }) => {
- const [{ settings }, dispatch] = useReducer(settingsContextReducer, {
- settings: new SettingsRecord(
- JSON.parse(localStorage.getItem("@settings") ?? "{}")
- ),
- });
+const SettingsProvider: React.FC = ({ children }) => {
+ const [{ settings }, dispatch] = useReducer(settingsContextReducer, {
+ settings: new SettingsRecord(JSON.parse(localStorage.getItem("@settings") ?? "{}")),
+ });
- return (
-
- {children}
-
- );
+ return {children};
};
export default SettingsProvider;
diff --git a/src/lanco-incidents-app/src/router.tsx b/src/lanco-incidents-app/src/router.tsx
index c12e026..f4b4a41 100644
--- a/src/lanco-incidents-app/src/router.tsx
+++ b/src/lanco-incidents-app/src/router.tsx
@@ -1,30 +1,30 @@
-import React from "react";
+import AppLayout from "containers/app-layout";
import Home from "pages/home";
-import { createBrowserRouter, Navigate } from "react-router-dom";
-import Settings from "pages/settings";
import { IncidentDetail } from "pages/incident-detail/incident-detail";
-import AppLayout from "containers/app-layout";
import { NotFound } from "pages/not-found";
+import Settings from "pages/settings";
+import React from "react";
+import { Navigate, createBrowserRouter } from "react-router-dom";
export const router = createBrowserRouter([
- {
- path: "/",
- element: ,
- children: [
- {
- index: true,
- element: ,
- },
- {
- path: "settings",
- element: ,
- },
- {
- path: "incidents/:id",
- element: ,
- },
- { path: "/not-found", element: },
- { path: "*", element: },
- ],
- },
+ {
+ path: "/",
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: "settings",
+ element: ,
+ },
+ {
+ path: "incidents/:id",
+ element: ,
+ },
+ { path: "/not-found", element: },
+ { path: "*", element: },
+ ],
+ },
]);
diff --git a/src/lanco-incidents-app/src/stores/geolocation-store.ts b/src/lanco-incidents-app/src/stores/geolocation-store.ts
index ac09593..3cc63aa 100644
--- a/src/lanco-incidents-app/src/stores/geolocation-store.ts
+++ b/src/lanco-incidents-app/src/stores/geolocation-store.ts
@@ -1,175 +1,151 @@
-import { TypedEventTarget } from "utils/typed-event-target";
+import { GeolocationStateRecord } from "models/view-models/geolocation-state-record";
import { TypedEvent } from "utils/typed-event";
+import { TypedEventTarget } from "utils/typed-event-target";
import {
- GeolocationStoreConifguration,
- GeolocationStoreConifgurationRecord,
+ GeolocationStoreConifguration,
+ GeolocationStoreConifgurationRecord,
} from "../models/view-models/geolocation-store-conifguration-record";
-import { GeolocationStateRecord } from "models/view-models/geolocation-state-record";
enum GeolocationStatus {
- Initialized,
- PermissionGranted,
- PermissionDenied,
+ Initialized = 0,
+ PermissionGranted = 1,
+ PermissionDenied = 2,
}
interface GeolocationStoreOptions {
- geolocation?: Geolocation;
+ geolocation?: Geolocation;
}
export enum GeoplocationEvent {
- StatusChange = "statusChange",
- PositionChange = "positionChange",
- PositionError = "positionError",
+ StatusChange = "statusChange",
+ PositionChange = "positionChange",
+ PositionError = "positionError",
}
-class GeoplocationStatusChangeEvent extends TypedEvent(
- GeoplocationEvent.StatusChange,
-) {}
+class GeoplocationStatusChangeEvent extends TypedEvent(GeoplocationEvent.StatusChange) {}
-class GeoplocationPositionChangeEvent extends TypedEvent(
- GeoplocationEvent.PositionChange,
-) {}
+class GeoplocationPositionChangeEvent extends TypedEvent(GeoplocationEvent.PositionChange) {}
-class GeoplocationPositionErrorEvent extends TypedEvent(
- GeoplocationEvent.PositionError,
-) {}
+class GeoplocationPositionErrorEvent extends TypedEvent(GeoplocationEvent.PositionError) {}
export class GeolocationStore extends TypedEventTarget<{
- [GeoplocationEvent.StatusChange]: GeoplocationStatusChangeEvent;
- [GeoplocationEvent.PositionChange]: GeoplocationPositionChangeEvent;
- [GeoplocationEvent.PositionError]: GeoplocationPositionErrorEvent;
+ [GeoplocationEvent.StatusChange]: GeoplocationStatusChangeEvent;
+ [GeoplocationEvent.PositionChange]: GeoplocationPositionChangeEvent;
+ [GeoplocationEvent.PositionError]: GeoplocationPositionErrorEvent;
}>() {
- static readonly Default = new GeolocationStore();
+ static readonly Default = new GeolocationStore();
- private config = new GeolocationStoreConifgurationRecord();
- private state = new GeolocationStateRecord();
+ private config = new GeolocationStoreConifgurationRecord();
+ private state = new GeolocationStateRecord();
- private geolocation?: Geolocation;
- private watchId?: number;
+ private geolocation?: Geolocation;
+ private watchId?: number;
- constructor(options: GeolocationStoreOptions = {}) {
- super();
+ constructor(options: GeolocationStoreOptions = {}) {
+ super();
- this.handlePermissionStateChange =
- this.handlePermissionStateChange.bind(this);
- this.handlePositionError = this.handlePositionError.bind(this);
- this.handlePostionChange = this.handlePostionChange.bind(this);
+ this.handlePermissionStateChange = this.handlePermissionStateChange.bind(this);
+ this.handlePositionError = this.handlePositionError.bind(this);
+ this.handlePostionChange = this.handlePostionChange.bind(this);
- this.geolocation =
- options.geolocation == null && "geolocation" in navigator
- ? navigator.geolocation
- : options.geolocation;
+ this.geolocation =
+ options.geolocation == null && "geolocation" in navigator ? navigator.geolocation : options.geolocation;
- this.setupPermissionCheck();
- }
+ this.setupPermissionCheck();
+ }
- getSnapshot() {
- return this.state;
- }
+ getSnapshot() {
+ return this.state;
+ }
- setConfig(config: Partial = {}) {
- const nextConfig = this.config.with(config);
+ setConfig(config: Partial = {}) {
+ const nextConfig = this.config.with(config);
- if (!this.config.equalTo(nextConfig)) {
- this.checkLocation(nextConfig);
- this.watchId = this.setupWatchPosition(nextConfig);
- this.config = nextConfig;
- }
+ if (!this.config.equalTo(nextConfig)) {
+ this.checkLocation(nextConfig);
+ this.watchId = this.setupWatchPosition(nextConfig);
+ this.config = nextConfig;
}
+ }
- private setupWatchPosition(
- nextConfig?: GeolocationStoreConifgurationRecord,
- ): number | undefined {
- if (
- this.geolocation == null ||
- this.state.status !== GeolocationStatus.PermissionGranted
- ) {
- return;
- }
-
- if (this.watchId != null) {
- this.geolocation.clearWatch(this.watchId);
- }
-
- if (nextConfig == null || nextConfig.watch !== this.config.watch) {
- const { enableHighAccuracy, maximumAge, timeout } = this.config;
-
- if (
- (nextConfig == null && this.config.watch) ||
- nextConfig?.watch
- ) {
- return this.geolocation.watchPosition(
- this.handlePostionChange,
- this.handlePositionError,
- { enableHighAccuracy, maximumAge, timeout },
- );
- }
- }
+ private setupWatchPosition(nextConfig?: GeolocationStoreConifgurationRecord): number | undefined {
+ if (this.geolocation == null || this.state.status !== GeolocationStatus.PermissionGranted) {
+ return;
}
- private checkLocation(config?: GeolocationStoreConifgurationRecord): void {
- if (
- this.geolocation == null ||
- this.state.status !== GeolocationStatus.PermissionGranted
- ) {
- return;
- }
-
- const { enableHighAccuracy, maximumAge, timeout } =
- config ?? this.config;
-
- this.geolocation.getCurrentPosition(
- this.handlePostionChange,
- this.handlePositionError,
- { enableHighAccuracy, maximumAge, timeout },
- );
+ if (this.watchId != null) {
+ this.geolocation.clearWatch(this.watchId);
}
- private setupPermissionCheck() {
- navigator.permissions
- .query({ name: "geolocation" })
- .then((result: PermissionStatus) => {
- result.onchange = () =>
- this.handlePermissionStateChange(result.state);
+ if (nextConfig == null || nextConfig.watch !== this.config.watch) {
+ const { enableHighAccuracy, maximumAge, timeout } = this.config;
- this.handlePermissionStateChange(result.state);
- });
+ if ((nextConfig == null && this.config.watch) || nextConfig?.watch) {
+ return this.geolocation.watchPosition(this.handlePostionChange, this.handlePositionError, {
+ enableHighAccuracy,
+ maximumAge,
+ timeout,
+ });
+ }
}
+ }
+
+ private checkLocation(config?: GeolocationStoreConifgurationRecord): void {
+ if (this.geolocation == null || this.state.status !== GeolocationStatus.PermissionGranted) {
+ return;
+ }
+
+ const { enableHighAccuracy, maximumAge, timeout } = config ?? this.config;
+
+ this.geolocation.getCurrentPosition(this.handlePostionChange, this.handlePositionError, {
+ enableHighAccuracy,
+ maximumAge,
+ timeout,
+ });
+ }
+
+ private setupPermissionCheck() {
+ navigator.permissions.query({ name: "geolocation" }).then((result: PermissionStatus) => {
+ result.onchange = () => this.handlePermissionStateChange(result.state);
+
+ this.handlePermissionStateChange(result.state);
+ });
+ }
- private handlePermissionStateChange(state: PermissionState) {
- const nextStatus =
- state === "granted" || state === "prompt"
- ? GeolocationStatus.PermissionGranted
- : GeolocationStatus.PermissionDenied;
+ private handlePermissionStateChange(state: PermissionState) {
+ const nextStatus =
+ state === "granted" || state === "prompt"
+ ? GeolocationStatus.PermissionGranted
+ : GeolocationStatus.PermissionDenied;
- const nextState = this.state.withStatus(nextStatus);
+ const nextState = this.state.withStatus(nextStatus);
- if (this.state !== nextState) {
- this.state = nextState;
- super.dispatchEvent(new GeoplocationStatusChangeEvent(nextStatus));
+ if (this.state !== nextState) {
+ this.state = nextState;
+ super.dispatchEvent(new GeoplocationStatusChangeEvent(nextStatus));
- if (nextStatus === GeolocationStatus.PermissionGranted) {
- this.checkLocation();
- this.watchId = this.setupWatchPosition();
- }
- }
+ if (nextStatus === GeolocationStatus.PermissionGranted) {
+ this.checkLocation();
+ this.watchId = this.setupWatchPosition();
+ }
}
+ }
- private handlePostionChange(position: GeolocationPosition) {
- const nextState = this.state.withPosition(position);
+ private handlePostionChange(position: GeolocationPosition) {
+ const nextState = this.state.withPosition(position);
- if (this.state !== nextState) {
- this.state = nextState;
- super.dispatchEvent(new GeoplocationPositionChangeEvent(position));
- }
+ if (this.state !== nextState) {
+ this.state = nextState;
+ super.dispatchEvent(new GeoplocationPositionChangeEvent(position));
}
+ }
- private handlePositionError(error: GeolocationPositionError) {
- const nextState = this.state.withError(error);
+ private handlePositionError(error: GeolocationPositionError) {
+ const nextState = this.state.withError(error);
- if (this.state !== nextState) {
- this.state = nextState;
- super.dispatchEvent(new GeoplocationPositionErrorEvent(error));
- }
+ if (this.state !== nextState) {
+ this.state = nextState;
+ super.dispatchEvent(new GeoplocationPositionErrorEvent(error));
}
+ }
}
diff --git a/src/lanco-incidents-app/src/utils/distance-utils.ts b/src/lanco-incidents-app/src/utils/distance-utils.ts
index 42ae7df..856ee5d 100644
--- a/src/lanco-incidents-app/src/utils/distance-utils.ts
+++ b/src/lanco-incidents-app/src/utils/distance-utils.ts
@@ -2,21 +2,14 @@ import { getDistance } from "geolib";
type LatLng = { lat: number; lng: number };
-const distanceBetween = (
- from: LatLng,
- to: LatLng,
- measurement: "meters" | "miles" = "miles",
-): number | undefined => {
- const measurementMultiplier = measurement === "miles" ? 0.00062137 : 1;
+const distanceBetween = (from: LatLng, to: LatLng, measurement: "meters" | "miles" = "miles"): number | undefined => {
+ const measurementMultiplier = measurement === "miles" ? 0.00062137 : 1;
- const distance =
- from != null && to != null
- ? getDistance(to, from) * measurementMultiplier
- : undefined;
+ const distance = from != null && to != null ? getDistance(to, from) * measurementMultiplier : undefined;
- return distance;
+ return distance;
};
export const DistanceUtils = {
- distanceBetween,
+ distanceBetween,
};
diff --git a/src/lanco-incidents-app/src/utils/typed-event-target.test.ts b/src/lanco-incidents-app/src/utils/typed-event-target.test.ts
index 1b5aa07..e2af4f2 100644
--- a/src/lanco-incidents-app/src/utils/typed-event-target.test.ts
+++ b/src/lanco-incidents-app/src/utils/typed-event-target.test.ts
@@ -1,82 +1,80 @@
import { TypedEventTarget } from "utils/typed-event-target";
-import { describe, it, expect, vi } from "vitest";
+import { describe, expect, it, vi } from "vitest";
describe("TypedEventTarget", () => {
- it("is an EventTarget", () => {
+ it("is an EventTarget", () => {
+ // Arrange
+ class TestClass extends TypedEventTarget<{
+ "event-one": Event;
+ }>() {}
+
+ // Act
+ const result = new TestClass();
+
+ // Assert
+ expect(result instanceof EventTarget).toBeTruthy();
+ });
+
+ describe("dispatchEvent()", () => {
+ describe("when EventsMap has Custom events", () => {
+ it("it accepts custom events", () => {
// Arrange
+ class TestEvent extends Event {}
class TestClass extends TypedEventTarget<{
- "event-one": Event;
+ "event-one": TestEvent;
+ "event-two": TestEvent;
}>() {}
+ const testClass = new TestClass();
// Act
- const result = new TestClass();
+ const result = testClass.dispatchEvent(new TestEvent("event-one"));
// Assert
- expect(result instanceof EventTarget).toBeTruthy();
+ expect(result).toBeTruthy();
+ });
});
+ });
- describe("dispatchEvent()", () => {
- describe("when EventsMap has Custom events", () => {
- it("it accepts custom events", () => {
- // Arrange
- class TestEvent extends Event {}
- class TestClass extends TypedEventTarget<{
- "event-one": TestEvent;
- "event-two": TestEvent;
- }>() {}
- const testClass = new TestClass();
-
- // Act
- const result = testClass.dispatchEvent(
- new TestEvent("event-one"),
- );
-
- // Assert
- expect(result).toBeTruthy();
- });
- });
- });
-
- describe("addEventListener()", () => {
- describe("when dispatchEvent", () => {
- it("it calls eventListener", () => {
- // Arrange
- const eventOne = "event-one" as const;
- class TestClass extends TypedEventTarget<{
- [eventOne]: Event;
- }>() {}
- const eventListener = vi.fn();
+ describe("addEventListener()", () => {
+ describe("when dispatchEvent", () => {
+ it("it calls eventListener", () => {
+ // Arrange
+ const eventOne = "event-one" as const;
+ class TestClass extends TypedEventTarget<{
+ [eventOne]: Event;
+ }>() {}
+ const eventListener = vi.fn();
- const testClass = new TestClass();
- testClass.addEventListener(eventOne, eventListener);
+ const testClass = new TestClass();
+ testClass.addEventListener(eventOne, eventListener);
- // Act
- testClass.dispatchEvent(new Event(eventOne));
+ // Act
+ testClass.dispatchEvent(new Event(eventOne));
- // Assert
- expect(eventListener).toBeCalled();
- });
- });
+ // Assert
+ expect(eventListener).toBeCalled();
+ });
+ });
- describe("when dispatchEvent of unknown event", () => {
- it("it does not call eventListener", () => {
- // Arrange
- const eventOne = "event-one" as const;
- const eventTwo = "event-two" as const;
- class TestClass extends TypedEventTarget<{
- [eventOne]: Event;
- }>() {}
- const eventListener = vi.fn();
+ describe("when dispatchEvent of unknown event", () => {
+ it("it does not call eventListener", () => {
+ // Arrange
+ const eventOne = "event-one" as const;
+ const eventTwo = "event-two" as const;
+ class TestClass extends TypedEventTarget<{
+ [eventOne]: Event;
+ }>() {}
+ const eventListener = vi.fn();
- const testClass = new TestClass();
- testClass.addEventListener(eventOne, eventListener);
+ const testClass = new TestClass();
+ testClass.addEventListener(eventOne, eventListener);
- // Act
- testClass.dispatchEvent(new Event(eventTwo));
+ // Act
+ testClass.dispatchEvent(new Event(eventTwo));
- // Assert
- expect(eventListener).not.toBeCalled();
- });
- });
+ // Assert
+ expect(eventListener).not.toBeCalled();
+ });
});
+ });
});
diff --git a/src/lanco-incidents-app/src/utils/typed-event-target.ts b/src/lanco-incidents-app/src/utils/typed-event-target.ts
index 6b1027e..4f69cfb 100644
--- a/src/lanco-incidents-app/src/utils/typed-event-target.ts
+++ b/src/lanco-incidents-app/src/utils/typed-event-target.ts
@@ -1,37 +1,37 @@
interface TypedEventListener {
- (evt: TEvent): void;
+ (evt: TEvent): void;
}
interface TypedEventListenerObject {
- handleEvent(object: TEvent): void;
+ handleEvent(object: TEvent): void;
}
type TypedEventListenerOrEventListenerObject =
- | TypedEventListener
- | TypedEventListenerObject;
+ | TypedEventListener
+ | TypedEventListenerObject;
type EventsMap = Record;
type StringKeyOf = keyof T extends string ? keyof T : never;
-interface TypedEventTargetInterface {
- dispatchEvent(evt: TEventsMap[StringKeyOf]): boolean;
- addEventListener>(
- type: TType,
- listener: TypedEventListenerOrEventListenerObject,
- options?: AddEventListenerOptions | boolean,
- ): void;
+interface TypedEventTargetInterface {
+ dispatchEvent(evt: TEventsMap[StringKeyOf]): boolean;
+ addEventListener>(
+ type: TType,
+ listener: TypedEventListenerOrEventListenerObject,
+ options?: AddEventListenerOptions | boolean
+ ): void;
- removeEventListener>(
- type: TType,
- listener: TypedEventListenerOrEventListenerObject,
- options?: AddEventListenerOptions | boolean,
- ): void;
+ removeEventListener>(
+ type: TType,
+ listener: TypedEventListenerOrEventListenerObject,
+ options?: AddEventListenerOptions | boolean
+ ): void;
}
export function TypedEventTarget() {
- return EventTarget as unknown as {
- new (): TypedEventTargetInterface;
- prototype: TypedEventTargetInterface;
- };
+ return EventTarget as unknown as {
+ new (): TypedEventTargetInterface;
+ prototype: TypedEventTargetInterface;
+ };
}
diff --git a/src/lanco-incidents-app/src/utils/typed-event.test.ts b/src/lanco-incidents-app/src/utils/typed-event.test.ts
index a2e9a48..44fa3a9 100644
--- a/src/lanco-incidents-app/src/utils/typed-event.test.ts
+++ b/src/lanco-incidents-app/src/utils/typed-event.test.ts
@@ -1,15 +1,15 @@
import { TypedEvent } from "utils/typed-event";
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
describe("TypedEvent", () => {
- it("is an Event", () => {
- // Arrange
- class TestEvent extends TypedEvent("event") {}
+ it("is an Event", () => {
+ // Arrange
+ class TestEvent extends TypedEvent("event") {}
- // Act
- const event = new TestEvent("test");
+ // Act
+ const event = new TestEvent("test");
- // Assert
- expect(event instanceof Event).toBeTruthy();
- });
+ // Assert
+ expect(event instanceof Event).toBeTruthy();
+ });
});
diff --git a/src/lanco-incidents-app/src/utils/typed-event.ts b/src/lanco-incidents-app/src/utils/typed-event.ts
index 2d6af0c..af3e6a4 100644
--- a/src/lanco-incidents-app/src/utils/typed-event.ts
+++ b/src/lanco-incidents-app/src/utils/typed-event.ts
@@ -1,7 +1,7 @@
export function TypedEvent(type: string) {
- return class extends Event {
- constructor(public data: TData) {
- super(type);
- }
- };
+ return class extends Event {
+ constructor(public data: TData) {
+ super(type);
+ }
+ };
}
diff --git a/src/lanco-incidents-app/tailwind.config.cjs b/src/lanco-incidents-app/tailwind.config.cjs
new file mode 100644
index 0000000..fd45f14
--- /dev/null
+++ b/src/lanco-incidents-app/tailwind.config.cjs
@@ -0,0 +1,19 @@
+module.exports = {
+ content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
+ theme: {
+ extend: {
+ keyframes: {
+ "reverse-spin": {
+ "100%": { transform: "rotate(-360deg)" },
+ },
+ },
+ animation: {
+ "reverse-spin": "reverse-spin 1s linear infinite",
+ },
+ },
+ },
+ variants: {
+ extend: {},
+ },
+ plugins: [require("@tailwindcss/forms")],
+};
diff --git a/src/lanco-incidents-app/tailwind.config.js b/src/lanco-incidents-app/tailwind.config.js
deleted file mode 100644
index 3373cd0..0000000
--- a/src/lanco-incidents-app/tailwind.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = {
- content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
- theme: {
- extend: {
- keyframes: {
- "reverse-spin": {
- "100%": { transform: "rotate(-360deg)" },
- },
- },
- animation: {
- "reverse-spin": "reverse-spin 1s linear infinite",
- },
- },
- },
- variants: {
- extend: {},
- },
- plugins: [require("@tailwindcss/forms")],
-};
diff --git a/src/lanco-incidents-app/vite.config.ts b/src/lanco-incidents-app/vite.config.ts
index 4bed160..a68d9fc 100644
--- a/src/lanco-incidents-app/vite.config.ts
+++ b/src/lanco-incidents-app/vite.config.ts
@@ -3,130 +3,62 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
-import { VitePWA } from "vite-plugin-pwa";
+import { ManifestOptions, VitePWA } from "vite-plugin-pwa";
import tsconfigPaths from "vite-tsconfig-paths";
-interface ManifestOptions {
- /**
- * @default _npm_package_name_
- */
- name: string;
- /**
- * @default _npm_package_name_
- */
- short_name: string;
- /**
- * @default _npm_package_description_
- */
- description: string;
- /**
- *
- */
- icons: Record[];
- /**
- * @default `routerBase + '?standalone=true'`
- */
- start_url: string;
- /**
- * Restricts what web pages can be viewed while the manifest is applied
- */
- scope: string;
- /**
- * Defines the default orientation for all the website's top-level
- */
- orientation:
- | "any"
- | "natural"
- | "landscape"
- | "landscape-primary"
- | "landscape-secondary"
- | "portrait"
- | "portrait-primary"
- | "portrait-secondary";
- /**
- * @default `standalone`
- */
- display: string;
- /**
- * @default `#ffffff`
- */
- background_color: string;
- /**
- * @default '#42b883
- */
- theme_color: string;
- /**
- * @default `ltr`
- */
- dir: "ltr" | "rtl";
- /**
- * @default `en`
- */
- lang: string;
- /**
- * @default A combination of `routerBase` and `options.build.publicPath`
- */
- publicPath: string;
-}
-
-const generateManifest = (): any => {
- return {
- name: "Central Penn Incidents",
- short_name: "Incidents",
- description:
- "A jazzed-up version of https://www.lcwc911.us/live-incident-list",
- theme_color: "#263989",
- background_color: "#263989",
- lang: "en-US",
- display: "standalone",
- orientation: "portrait",
- screenshots: [
- {
- src: "/screenshot1.png",
- sizes: "1079x1919",
- type: "image/png",
- },
- {
- src: "/screenshot2.png",
- sizes: "1079x1919",
- type: "image/png",
- },
- ],
- categories: ["medical", "news", "utilities"],
- icons: [
- {
- src: "/pwa-192x192.png",
- sizes: "192x192",
- type: "image/png",
- },
- {
- src: "/pwa-512x512.png",
- sizes: "512x512",
- type: "image/png",
- },
- {
- src: "/pwa-512x512.png",
- sizes: "512x512",
- type: "image/png",
- purpose: "any maskable",
- },
- ],
- };
+const generateManifest = (): Partial => {
+ return {
+ name: "Central Penn Incidents",
+ short_name: "Incidents",
+ description: "A jazzed-up version of https://www.lcwc911.us/live-incident-list",
+ theme_color: "#263989",
+ background_color: "#263989",
+ lang: "en-US",
+ display: "standalone",
+ orientation: "portrait",
+ screenshots: [
+ {
+ src: "/screenshot1.png",
+ sizes: "1079x1919",
+ type: "image/png",
+ },
+ {
+ src: "/screenshot2.png",
+ sizes: "1079x1919",
+ type: "image/png",
+ },
+ ],
+ categories: ["medical", "news", "utilities"],
+ icons: [
+ {
+ src: "/pwa-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ },
+ {
+ src: "/pwa-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ },
+ {
+ src: "/pwa-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "any maskable",
+ },
+ ],
+ };
};
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [
- react(),
- tsconfigPaths(),
- VitePWA({ injectRegister: false, manifest: generateManifest() }),
- ],
- test: {
- globals: true,
- environment: "jsdom",
- setupFiles: "./src/test/setup.ts",
- // you might want to disable it, if you don't have tests that rely on CSS
- // since parsing CSS is slow
- css: true,
- },
+ plugins: [react(), tsconfigPaths(), VitePWA({ injectRegister: false, manifest: generateManifest() })],
+ test: {
+ globals: true,
+ environment: "jsdom",
+ setupFiles: "./src/test/setup.ts",
+ // you might want to disable it, if you don't have tests that rely on CSS
+ // since parsing CSS is slow
+ css: true,
+ },
});
diff --git a/src/lanco-incidents-func/API/Incidents.cs b/src/lanco-incidents-func/API/Incidents.cs
index 60907d8..9bdf74f 100644
--- a/src/lanco-incidents-func/API/Incidents.cs
+++ b/src/lanco-incidents-func/API/Incidents.cs
@@ -36,36 +36,37 @@ public async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req
)
{
- _log.LogInformation("api/incidents - HTTP trigger function processed a request.", req);
+ _log.LogInformation(
+ "api/incidents - HTTP trigger function processed a request. {Request}",
+ req
+ );
try
{
var incidents = await _feedService.GetIncidentsAsync();
var incidentDtos = await Task.WhenAll(
- incidents.Select(
- async i =>
- {
- var dto = _mapper.Map(i);
+ incidents.Select(async i =>
+ {
+ var dto = _mapper.Map(i);
- if (string.IsNullOrWhiteSpace(i.Location))
- {
- return dto;
- }
+ if (string.IsNullOrWhiteSpace(i.Location))
+ {
+ return dto;
+ }
- var locationEntity = await _locationService.GetLocationAsync(
- i.Location,
- i.Area
- );
+ var locationEntity = await _locationService.GetLocationAsync(
+ i.Location,
+ i.Area
+ );
- dto.GeocodeLocation =
- (locationEntity?.Lat_VC.HasValue ?? false)
- ? _mapper.Map(locationEntity)
- : null;
+ dto.GeocodeLocation =
+ (locationEntity?.Lat_VC.HasValue ?? false)
+ ? _mapper.Map(locationEntity)
+ : null;
- return dto;
- }
- )
+ return dto;
+ })
);
var response = req.CreateResponse(HttpStatusCode.OK);
diff --git a/src/lanco-incidents-func/IncidentProviders/LancasterIncidentProvider.cs b/src/lanco-incidents-func/IncidentProviders/LancasterIncidentProvider.cs
index d400287..ffcf72e 100644
--- a/src/lanco-incidents-func/IncidentProviders/LancasterIncidentProvider.cs
+++ b/src/lanco-incidents-func/IncidentProviders/LancasterIncidentProvider.cs
@@ -19,7 +19,8 @@ public LancasterIncidentProvider(
IEnvironmentProvider env,
IDataCache> feedCache,
IHttpClientFactory httpClientFactory
- ) : base(env)
+ )
+ : base(env)
{
_feedCache = feedCache;
_client = httpClientFactory.CreateClient();
@@ -40,8 +41,7 @@ public override async Task> GetIncidentsAsync()
var cts = new CancellationTokenSource();
var rss = await XDocument.LoadAsync(xmlStream, LoadOptions.None, cts.Token);
- var incidents = rss.Root
- .Descendants("item")
+ var incidents = rss.Root.Descendants("item")
.Select(itm =>
{
var descSplit = itm.Element("description").Value.Split(';');
diff --git a/src/lanco-incidents-func/IncidentProviders/YorklIncidentProvider.cs b/src/lanco-incidents-func/IncidentProviders/YorklIncidentProvider.cs
index 5d9a55e..f4b32d8 100644
--- a/src/lanco-incidents-func/IncidentProviders/YorklIncidentProvider.cs
+++ b/src/lanco-incidents-func/IncidentProviders/YorklIncidentProvider.cs
@@ -19,7 +19,8 @@ public YorklIncidentProvider(
IEnvironmentProvider env,
IDataCache> feedCache,
IHttpClientFactory httpClientFactory
- ) : base(env)
+ )
+ : base(env)
{
_feedCache = feedCache;
_client = httpClientFactory.CreateClient();
@@ -40,17 +41,14 @@ public override async Task> GetIncidentsAsync()
var doc = new HtmlDocument();
doc.LoadHtml(htmlSource);
- var incidents = doc.DocumentNode
- .Descendants("table")
+ var incidents = doc.DocumentNode.Descendants("table")
.Where(x => x.Attributes["class"]?.Value == "incidentList")
.SelectMany(
y =>
- y.ChildNodes
- .Skip(3)
+ y.ChildNodes.Skip(3)
.Select(
tr =>
- tr.ChildNodes
- .Where(node => node.NodeType == HtmlNodeType.Element)
+ tr.ChildNodes.Where(node => node.NodeType == HtmlNodeType.Element)
.ToList()
)
.Where(cells => cells.Count > 0)
@@ -65,7 +63,8 @@ public override async Task> GetIncidentsAsync()
cells
.Skip(8)
?.FirstOrDefault()
- ?.InnerHtml.Split('?')
+ ?.InnerHtml
+ .Split('?')
.Skip(1)
.FirstOrDefault()
?.Split('"')
diff --git a/src/lanco-incidents-func/Program.cs b/src/lanco-incidents-func/Program.cs
index d2762b4..2b7b117 100644
--- a/src/lanco-incidents-func/Program.cs
+++ b/src/lanco-incidents-func/Program.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using LancoIncidentsFunc.IncidentProviders;
using LancoIncidentsFunc.Interfaces;
using LancoIncidentsFunc.Models;
@@ -12,7 +13,7 @@ namespace LancoIncidentsFunc
{
public class Program
{
- public static void Main()
+ static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
@@ -44,7 +45,7 @@ public static void Main()
)
.Build();
- host.Run();
+ await host.RunAsync();
}
}
}
diff --git a/src/lanco-incidents-func/Services/FeedCache.cs b/src/lanco-incidents-func/Services/FeedCache.cs
index a04608e..8a932d3 100644
--- a/src/lanco-incidents-func/Services/FeedCache.cs
+++ b/src/lanco-incidents-func/Services/FeedCache.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
-using Microsoft.Extensions.Caching.Memory;
using LancoIncidentsFunc.Interfaces;
using LancoIncidentsFunc.Models;
+using Microsoft.Extensions.Caching.Memory;
namespace LancoIncidentsFunc.Services
{
diff --git a/src/lanco-incidents-func/Services/LocationCache.cs b/src/lanco-incidents-func/Services/LocationCache.cs
index 9e9c2b6..f301a7d 100644
--- a/src/lanco-incidents-func/Services/LocationCache.cs
+++ b/src/lanco-incidents-func/Services/LocationCache.cs
@@ -1,7 +1,7 @@
using System;
-using Microsoft.Extensions.Caching.Memory;
using LancoIncidentsFunc.Interfaces;
using LancoIncidentsFunc.Models;
+using Microsoft.Extensions.Caching.Memory;
namespace LancoIncidentsFunc.Services
{
diff --git a/src/lanco-incidents-func/Services/LocationService.cs b/src/lanco-incidents-func/Services/LocationService.cs
index 0990224..c6cb489 100644
--- a/src/lanco-incidents-func/Services/LocationService.cs
+++ b/src/lanco-incidents-func/Services/LocationService.cs
@@ -3,9 +3,9 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
using LancoIncidentsFunc.Interfaces;
using LancoIncidentsFunc.Models;
+using Microsoft.Extensions.Logging;
using QuickType;
namespace LancoIncidentsFunc.Services
diff --git a/src/lanco-incidents-func/lanco-incidents-func.csproj b/src/lanco-incidents-func/lanco-incidents-func.csproj
index 25b5c39..d65f691 100644
--- a/src/lanco-incidents-func/lanco-incidents-func.csproj
+++ b/src/lanco-incidents-func/lanco-incidents-func.csproj
@@ -1,7 +1,8 @@
preview
- net7.0
+ false
+ net8.0
v4
Exe
<_FunctionsSkipCleanOutput>true
@@ -9,21 +10,21 @@
true
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lanco-incidents-func/package.json b/src/lanco-incidents-func/package.json
index f594e60..f5b834a 100644
--- a/src/lanco-incidents-func/package.json
+++ b/src/lanco-incidents-func/package.json
@@ -2,8 +2,7 @@
"name": "lanco-incidents-func",
"scripts": {
"azurite": "docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 --rm mcr.microsoft.com/azure-storage/azurite",
- "func": "func start --csharp",
- "dev": "concurrently --names \"Azurite,Functions\" -c \"bgBlue.bold,bgMagenta.bold\" \"pnpm run azurite\" \"pnpm run func\"",
+ "dev": "func start --csharp",
"serve": "pnpm dev",
"build": "dotnet build -c Release",
"restore": "dotnet clean && dotnet restore"
@@ -15,8 +14,5 @@
},
"volta": {
"extends": "../../package.json"
- },
- "devDependencies": {
- "concurrently": "^7.6.0"
}
-}
\ No newline at end of file
+}
diff --git a/turbo.json b/turbo.json
index d4664ca..b069b4c 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,57 +1,61 @@
{
- "$schema": "https://turborepo.org/schema.json",
- "pipeline": {
- "build": {
- "dependsOn": [
- "^build"
- ],
- "outputs": [
- "dist/**"
- ]
- },
- "serve": {
- "dependsOn": [
- "^build", "build"
- ],
- "outputs": [
- "dist/**"
- ]
- },
- "test": {
- "dependsOn": [
- "build"
- ],
- "outputs": [],
- "inputs": [
- "src/**/*.tsx",
- "src/**/*.ts",
- "test/**/*.ts",
- "test/**/*.tsx"
- ]
- },
- "lint": {
- "outputs": []
- },
- "deploy": {
- "dependsOn": [
- "build",
- "test",
- "lint"
- ],
- "outputs": []
- },
- "dev": {
- "cache": false
- },
- "lanco-incidents-func#dev": {
- "outputs": [
- "Debug/**"
- ]
- },
- "lanco-incidents-func#build": {
- "outputs": [
- "Release/**"
- ]
- }
+ "$schema": "https://turborepo.org/schema.json",
+ "pipeline": {
+ "build": {
+ "dependsOn": [
+ "^build"
+ ],
+ "outputs": [
+ "dist/**"
+ ]
+ },
+ "serve": {
+ "dependsOn": [
+ "^build",
+ "build"
+ ],
+ "outputs": [
+ "dist/**"
+ ]
+ },
+ "test": {
+ "dependsOn": [
+ "build"
+ ],
+ "outputs": [],
+ "inputs": [
+ "src/**/*.tsx",
+ "src/**/*.ts",
+ "test/**/*.ts",
+ "test/**/*.tsx"
+ ]
+ },
+ "lint": {
+ "outputs": []
+ },
+ "deploy": {
+ "dependsOn": [
+ "build",
+ "test",
+ "lint"
+ ],
+ "outputs": []
+ },
+ "dev": {
+ "cache": false
+ },
+ "azurite": {
+ "cache": false
+ },
+ "lanco-incidents-func#dev": {
+ "outputs": [
+ "Debug/**"
+ ]
+ },
+ "lanco-incidents-func#build": {
+ "outputs": [
+ "Release/**"
+ ]
}
-}
\ No newline at end of file
+ }
+}