Skip to content

Commit

Permalink
feat: London Studios SmartMotorways integration (#1811)
Browse files Browse the repository at this point in the history
  • Loading branch information
casperiv0 authored Oct 1, 2023
1 parent 6f84233 commit 067eede
Show file tree
Hide file tree
Showing 33 changed files with 304 additions and 7 deletions.
3 changes: 2 additions & 1 deletion apps/client/locales/en/cad-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"SetUserDefinedCallsignOnOfficer": "Set User Defined Callsign On Officer",
"SetUserDefinedCallsignOnEmsFd": "Set User Defined Callsign On EMS/FD Deputy",
"LeoManageCitizenProfile": "Manage Citizen Profile (non-admin)",
"ManageSmartSigns": "Manage Smart Signs"
"ManageSmartSigns": "Manage Smart Signs",
"ManageSmartMotorwaySigns": "Manage Smart Motorway Signs"
}
}
23 changes: 22 additions & 1 deletion apps/client/locales/en/leo.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@
"showSmartSigns": "Show Smart Signs",
"smartSignUpdated": "SmartSign Updated",
"smartSignUpdatedMessage": "We sent a request to the FXServer to update the SmartSign.",
"hideSmartMotorwaySigns": "Hide Smart Motorway Signs",
"showSmartMotorwaySigns": "Show Smart Motorway Signs",
"smartMotorwaySignUpdated": "Smart Motorway Sign Updated",
"smartMotorwaySignUpdatedMessage": "We sent a request to the FXServer to update the Smart Motorway Sign.",
"departmentInformation": "Department Info",
"departmentInformationDesc": "Here you can view further external links and information for your department.",
"noDepartmentLinks": "This department doesn't have any extra information yet.",
Expand Down Expand Up @@ -360,7 +364,24 @@
"assignToIncident": "Assign to incident",
"myRecordReports": "My Record Reports",
"myRecordReportsDescription": "Here you can view all the tickets, arrest reports, written warnings and warrants that officers associated to your account have created.",
"noReportsCreated": "You have not created any reports yet."
"noReportsCreated": "You have not created any reports yet.",
"motorway_sign_1": "Arrow Left",
"motorway_sign_2": "Arrow Right",
"motorway_sign_3": "Red X",
"motorway_sign_20": "Speed 20",
"motorway_sign_40": "Speed 30",
"motorway_sign_30": "Speed 30",
"motorway_sign_50": "Speed 50",
"motorway_sign_60": "Speed 60",
"motorway_sign_70": "Speed 70",
"motorway_sign_80": "Speed 80",
"motorway_sign_90": "Speed 90",
"motorway_sign_100": "Speed 100",
"motorway_sign_110": "Speed 110",
"motorway_sign_120": "Speed 120",
"motorway_sign_130": "Speed 130",
"motorway_sign_140": "Speed 140",
"motorway_sign_150": "Speed 150"
},
"Bolos": {
"activeBolos": "Active Bolos",
Expand Down
Binary file added apps/client/public/map/smart-motorways/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/110.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/120.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/130.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/140.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/50.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/60.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/70.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/80.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/client/public/map/smart-motorways/90.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/client/public/map/smart-motorways/CREDITS.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# London Studios

All credits to London Studios (https://londonstudios.net) for the default SmartMotorways images/signs.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions apps/client/src/components/dispatch/map/map-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export function MapActions() {
const { hasPermissions } = usePermission();
const hasManageUsersPermissions = hasPermissions([Permissions.ManageUsers]);
const hasManageSmartSignsPermissions = hasPermissions([Permissions.ManageSmartSigns]);
const hasManageSmartMotorwaySignsPermissions = hasPermissions([
Permissions.ManageSmartMotorwaySigns,
]);

return (
portalRef &&
Expand All @@ -49,6 +52,13 @@ export function MapActions() {
: t("Leo.hideSmartSigns")}
</DropdownMenuItem>
) : null}
{hasManageSmartMotorwaySignsPermissions ? (
<DropdownMenuItem onClick={() => mapState.setItem(MapItem.SMART_MOTORWAY_SIGNS)}>
{mapState.hiddenItems[MapItem.SMART_MOTORWAY_SIGNS]
? t("Leo.showSmartMotorwaySigns")
: t("Leo.hideSmartMotorwaySigns")}
</DropdownMenuItem>
) : null}
<DropdownMenuItem onClick={() => mapState.setItem(MapItem.BLIPS)}>
{mapState.hiddenItems[MapItem.BLIPS] ? t("Leo.showBlips") : t("Leo.hideBlips")}
</DropdownMenuItem>
Expand Down
2 changes: 2 additions & 0 deletions apps/client/src/components/dispatch/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MapActions } from "./map-actions";
import { RenderMapPlayers } from "./units/render-map-players";
import { SelectMapServerModal } from "./modals/select-map-server-modal";
import { RenderMapSmartSigns } from "./smart-signs/render-map-smart-signs";
import { RenderMapSmartMotorwaySigns } from "./smart-motorway-signs/render-map-smart-motorway-signs";

const TILES_URL = "/tiles/minimap_sea_{y}_{x}.webp" as const;

Expand Down Expand Up @@ -50,6 +51,7 @@ export function Map() {
<RenderActiveCalls />
<MapActions />
<RenderMapSmartSigns />
<RenderMapSmartMotorwaySigns />

<SelectMapServerModal />
</MapContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SmartMotorwaySignsMarker } from "./smart-motorway-sign-marker";
import { useSmartMotorwaySigns } from "./use-smart-motorway-signs";

export function RenderMapSmartMotorwaySigns() {
const smartMotorwaySigns = useSmartMotorwaySigns();
return smartMotorwaySigns?.map((marker, idx) => (
<SmartMotorwaySignsMarker key={idx} marker={{ ...marker, id: idx }} />
));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import * as React from "react";
import { convertToMap } from "lib/map/utils";
import { Marker, Popup, Tooltip, useMap } from "react-leaflet";
import { SmartMotorwaySignSpeedType, type SmartMotorwaySignMarker } from "types/map";
import { icon as leafletIcon } from "leaflet";
import { useTranslations } from "next-intl";
import { MapItem, useDispatchMapState, useSocketStore } from "state/mapState";
import { generateMarkerTypes } from "../render-map-blips";
import { Button, SelectField } from "@snailycad/ui";
import { Permissions, usePermission } from "hooks/usePermission";
import { toastMessage } from "lib/toastMessage";
import Image from "next/image";

interface Props {
marker: SmartMotorwaySignMarker & { id: number };
}

const SPEED_INDICATORS = Object.values(SmartMotorwaySignSpeedType).filter(
(v) => !isNaN(Number(v)),
) as number[];

export function SmartMotorwaySignsMarker({ marker }: Props) {
const map = useMap();
const socket = useSocketStore((state) => state.socket);
const [markerConfiguration, setMarkerConfiguration] = React.useState<string[]>([]);

const t = useTranslations("Leo");
const hiddenItems = useDispatchMapState((state) => state.hiddenItems);
const markerTypes = React.useMemo(generateMarkerTypes, []);

const { hasPermissions } = usePermission();
const hasManageSmartSignsPermissions = hasPermissions([Permissions.ManageSmartSigns]);

const pos = React.useMemo(
() => convertToMap(marker.position.x, marker.position.y, map),
[marker.position], // eslint-disable-line react-hooks/exhaustive-deps
);
const markerIcon = React.useMemo(() => {
// eslint-disable-next-line prefer-destructuring
const icon = markerTypes[781];

if (icon) {
return leafletIcon(icon);
}

return undefined;
}, [markerTypes]);

React.useEffect(() => {
const speeds = marker.speeds ?? marker.defaultSpeeds;
if (speeds) {
setMarkerConfiguration(speeds.map((speed) => String(speed)));
}
}, [marker.speeds, marker.defaultSpeeds]);

function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!socket?.connected) return;

socket.emit("sna-live-map:update-smart-motorway-sign", {
...marker,
speeds: markerConfiguration.map((v) => Number(v)),
id: marker.id + 1,
});

toastMessage({
icon: "success",
title: t("smartSignUpdated"),
message: t("smartSignUpdatedMessage"),
});
}

if (hiddenItems[MapItem.SMART_MOTORWAY_SIGNS]) {
return null;
}

return (
<Marker icon={markerIcon} position={pos}>
<Tooltip direction="top">Smart Motorway Sign</Tooltip>

<Popup minWidth={300}>
<p style={{ margin: 2 }}>
<strong>Direction:</strong> {marker.direction}
</p>

<p style={{ margin: 2 }}>
<strong>Lanes:</strong> {marker.lanes}
</p>

<form onSubmit={handleSubmit} className="mt-3 flex flex-col gap-y-2">
{new Array(marker.lanes).fill(null).map((_, idx) => {
return (
<SelectField
selectedKey={markerConfiguration[idx]}
onSelectionChange={(key) =>
setMarkerConfiguration((prev) => {
const newConfig = [...prev];
newConfig[idx] = String(key);
return newConfig;
})
}
key={idx}
label={`Lane ${idx + 1}`}
options={SPEED_INDICATORS.map((key) => ({
textValue: t(`motorway_sign_${key}`),
label: (
<p className="flex items-center gap-2">
<Image
alt={t(`motorway_sign_${key}`)}
src={`/map/smart-motorways/${key}.png`}
width={30}
height={30}
/>
{t(`motorway_sign_${key}`)}
</p>
),
value: String(key),
}))}
/>
);
})}

<section>
<h3 className="text-lg font-semibold mb-1.5">Preview</h3>

{markerConfiguration.length <= 0 ? (
<p className="!m-0">Configure the lanes to see a preview</p>
) : (
<div className="flex items-center justify-between bg-black p-2 rounded-md">
{new Array(marker.lanes).fill(null).map((_, idx) => {
const speed = markerConfiguration[idx];

if (!speed) {
return <div key={idx} />;
}

return (
<Image
className="border-2 border-secondary p-1 rounded"
key={idx}
alt={t(`motorway_sign_${speed}`)}
src={`/map/smart-motorways/${speed}.png`}
width={40}
height={40}
/>
);
})}
</div>
)}
</section>

<div className="flex mt-2 gap-2">
<Button
variant="danger"
className="w-full"
type="button"
onClick={() => setMarkerConfiguration([])}
disabled={!hasManageSmartSignsPermissions}
>
Reset
</Button>
<Button className="w-full" type="submit" disabled={!hasManageSmartSignsPermissions}>
Save
</Button>
</div>
</form>
</Popup>
</Marker>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from "react";
import { useDispatchMapState, useSocketStore } from "state/mapState";
import type { SmartMotorwaySignMarker } from "types/map";

export function useSmartMotorwaySigns() {
const socket = useSocketStore((state) => state.socket);
const { smartMotorwaySigns, setSmartMotorwaySigns } = useDispatchMapState((state) => ({
smartMotorwaySigns: state.smartMotorwaySigns,
setSmartMotorwaySigns: state.setSmartMotorwaySigns,
}));

const onInitialize = React.useCallback(
(data: { smartMotorwaySigns: SmartMotorwaySignMarker[] }) => {
setSmartMotorwaySigns(data.smartMotorwaySigns);
},
[], // eslint-disable-line react-hooks/exhaustive-deps
);

React.useEffect(() => {
const s = socket;

if (s) {
s.on("sna-live-map:smart-motorways-signs", onInitialize);
}

return () => {
s?.off("sna-live-map:smart-motorways-signs", onInitialize);
};
}, [socket, onInitialize]);

return smartMotorwaySigns;
}
6 changes: 5 additions & 1 deletion apps/client/src/hooks/use-permissions-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export function usePermissionsModal(options: UsePermissionsModalOptions) {
},
{
name: t("dispatch"),
permissions: [...defaultPermissions.defaultDispatchPermissions, Permissions.ManageSmartSigns],
permissions: [
...defaultPermissions.defaultDispatchPermissions,
Permissions.ManageSmartSigns,
Permissions.ManageSmartMotorwaySigns,
],
},
{
name: t("emsFd"),
Expand Down
1 change: 1 addition & 0 deletions apps/client/src/lib/map/blips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,5 @@ export const blipTypes: Record<string, BlipType> = {
LSCarMeet: { id: 777, x: 4, y: 29 },
LSCarMeetGarage: { id: 779, x: 2, y: 29 },
Computer: { id: 780, x: 11, y: 113.75 },
Computer2: { id: 781, x: 4, y: 109.75 },
};
2 changes: 0 additions & 2 deletions apps/client/src/pages/officer/my-record-reports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, local
["/leo/my-record-reports", { reports: [], totalCount: 0 }],
]);

console.log(reports);

return {
props: {
session: user,
Expand Down
11 changes: 10 additions & 1 deletion apps/client/src/state/mapState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConnectionStatus } from "@snailycad/ui";
import { Socket } from "socket.io-client";
import { SmartSignMarker } from "types/map";
import { SmartMotorwaySignMarker, SmartSignMarker } from "types/map";
import { persist, createJSONStorage } from "zustand/middleware";
import { shallow } from "zustand/shallow";
import { createWithEqualityFn } from "zustand/traditional";
Expand All @@ -10,12 +10,16 @@ export enum MapItem {
UNITS_ONLY,
BLIPS,
SMART_SIGNS,
SMART_MOTORWAY_SIGNS,
}

interface DispatchMapState {
smartSigns: SmartSignMarker[];
setSmartSigns(signs: SmartSignMarker[]): void;

smartMotorwaySigns: SmartMotorwaySignMarker[];
setSmartMotorwaySigns(signs: SmartMotorwaySignMarker[]): void;

hiddenItems: Partial<Record<MapItem, boolean>>;
setItem(item: MapItem): void;

Expand All @@ -39,6 +43,11 @@ export const useDispatchMapState = createWithEqualityFn<DispatchMapState>()(
set({ smartSigns: signs });
},

smartMotorwaySigns: [],
setSmartMotorwaySigns(signs) {
set({ smartMotorwaySigns: signs });
},

currentMapServerURL: null,
setCurrentMapServerURL(url) {
set({ currentMapServerURL: url });
Expand Down
Loading

0 comments on commit 067eede

Please sign in to comment.