Skip to content

Commit

Permalink
feat: Editable directions in state machine (#2880)
Browse files Browse the repository at this point in the history
* feat: Put directions text in state machine to allow edits

* feat: Split apart copyable text and editable text in diversionPage

* fix: Needed to include fallback for older detours
  • Loading branch information
hannahpurcell authored Oct 31, 2024
1 parent c9dc3b2 commit ce2ea77
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 70 deletions.
4 changes: 4 additions & 0 deletions assets/css/_diversion_page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@
flex: 1 1 0;
overflow-y: auto;
}

.form-control {
min-height: 200px;
}
}

.c-diversion-panel__origin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Stop } from "../../../schedule"
interface DetourFinishedPanelProps extends PropsWithChildren {
onNavigateBack: () => void
copyableDetourText: string
editableDirections: string
connectionPoints?: [string, string]
missedStops?: Stop[]
onChangeDetourText: (value: string) => void
Expand All @@ -21,6 +22,7 @@ interface DetourFinishedPanelProps extends PropsWithChildren {
export const DetourFinishedPanel = ({
onNavigateBack,
copyableDetourText,
editableDirections,
connectionPoints,
missedStops,
onChangeDetourText,
Expand All @@ -44,9 +46,10 @@ export const DetourFinishedPanel = ({
<BsIcons.ArrowLeft /> Edit
</Button>

<h2 className="c-diversion-panel__h2">Directions</h2>
<Form.Control
as="textarea"
value={copyableDetourText}
value={editableDirections}
onChange={({ target: { value } }) => onChangeDetourText(value)}
className="flex-grow-1 mb-3"
style={{
Expand Down
62 changes: 26 additions & 36 deletions assets/src/components/detours/diversionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, {
ComponentPropsWithoutRef,
PropsWithChildren,
useContext,
useEffect,
useState,
} from "react"
import { DrawDetourPanel } from "./detourPanels/drawDetourPanel"
Expand Down Expand Up @@ -113,16 +112,15 @@ export const DiversionPage = ({
: { input: useDetourProps.originalRoute }
)

const [textArea, setTextArea] = useState("")

const nearestIntersectionDirection = [
{ instruction: "From " + nearestIntersection },
]
const extendedDirections = directions
? nearestIntersectionDirection.concat(directions)
: undefined

const { route, routePattern, routePatterns } = snapshot.context
const { route, routePattern, routePatterns, editedDirections } =
snapshot.context
const routePatternsById = Object.fromEntries(
routePatterns?.map((rp) => [rp.id, rp]) ?? []
)
Expand All @@ -137,35 +135,20 @@ export const DiversionPage = ({
? displayFieldsFromRouteAndPattern(route, routePattern)
: {}

useEffect(() => {
if (snapshot.matches({ "Detour Drawing": "Share Detour" })) {
setTextArea(
[
`Detour ${routeName} ${routeDirection}`,
routeOrigin,
,
"Connection Points:",
connectionPoints?.start?.name ?? "N/A",
connectionPoints?.end?.name ?? "N/A",
,
`Missed Stops (${missedStops?.length}):`,
...(missedStops?.map(({ name }) => name) ?? ["no stops"]),
,
"Turn-by-Turn Directions:",
...(extendedDirections?.map((v) => v.instruction) ?? []),
].join("\n")
)
}
}, [
snapshot,
routeName,
routeDirection,
const copyableDetourText = [
`Detour ${routeName} ${routeDirection}`,
routeOrigin,
extendedDirections,
missedStops,
connectionPoints?.start?.name,
connectionPoints?.end?.name,
])
,
"Connection Points:",
connectionPoints?.start?.name ?? "N/A",
connectionPoints?.end?.name ?? "N/A",
,
`Missed Stops (${missedStops?.length}):`,
...(missedStops?.map(({ name }) => name) ?? ["no stops"]),
,
"Turn-by-Turn Directions:",
...(extendedDirections?.map((v) => v.instruction) ?? []),
].join("\n")

const routes = useContext(RoutesContext)
const epochNowInSeconds = useCurrentTimeSeconds()
Expand Down Expand Up @@ -260,13 +243,20 @@ export const DiversionPage = ({
return (
<DetourFinishedPanel
onNavigateBack={editDetour}
copyableDetourText={textArea}
copyableDetourText={copyableDetourText}
// Include fallback if editedDirections was not initialized on an older detour
editableDirections={
editedDirections ||
(extendedDirections?.map((v) => v.instruction).join("\n") ?? "")
}
connectionPoints={[
connectionPoints?.start?.name ?? "N/A",
connectionPoints?.end?.name ?? "N/A",
]}
missedStops={missedStops}
onChangeDetourText={setTextArea}
onChangeDetourText={(detourText: string) =>
send({ type: "detour.share.edit-directions", detourText })
}
onActivateDetour={
inTestGroup(TestGroups.DetoursList)
? () => {
Expand Down Expand Up @@ -350,7 +340,7 @@ export const DiversionPage = ({
} else if (snapshot.matches({ "Detour Drawing": "Active" })) {
return (
<ActiveDetourPanel
copyableDetourText="Hello World"
copyableDetourText={copyableDetourText}
directions={extendedDirections}
connectionPoints={[
connectionPoints?.start?.name ?? "N/A",
Expand Down Expand Up @@ -391,7 +381,7 @@ export const DiversionPage = ({
} else if (snapshot.matches({ "Detour Drawing": "Past" })) {
return (
<PastDetourPanel
copyableDetourText="Hello World"
copyableDetourText={copyableDetourText}
directions={extendedDirections}
connectionPoints={[
connectionPoints?.start?.name ?? "N/A",
Expand Down
24 changes: 23 additions & 1 deletion assets/src/models/createDetourMachine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { setup, assign, fromPromise, ActorLogicFrom, InputFrom } from "xstate"
import { RoutePatternId, ShapePoint } from "../schedule"
import { Route, RouteId, RoutePattern } from "../schedule"
import { Ok, Result } from "../util/result"
import { isOk, Ok, Result } from "../util/result"
import {
FetchDetourDirectionsError,
fetchDetourDirections,
Expand Down Expand Up @@ -31,6 +31,8 @@ export const createDetourMachine = setup({

finishedDetour: FinishedDetour | undefined | null

editedDirections?: string

selectedDuration?: string
selectedReason?: string
},
Expand Down Expand Up @@ -67,6 +69,7 @@ export const createDetourMachine = setup({
| { type: "detour.edit.place-waypoint-on-route"; location: ShapePoint }
| { type: "detour.edit.place-waypoint"; location: ShapePoint }
| { type: "detour.edit.undo" }
| { type: "detour.share.edit-directions"; detourText: string }
| { type: "detour.share.copy-detour"; detourText: string }
| { type: "detour.share.open-activate-modal" }
| {
Expand Down Expand Up @@ -488,6 +491,19 @@ export const createDetourMachine = setup({

onDone: {
target: "Share Detour",
actions: assign({
editedDirections: ({ context }) => {
const detourShape =
context.detourShape && isOk(context.detourShape)
? context.detourShape.ok
: null

return [
"From " + context.nearestIntersection,
...(detourShape?.directions?.map((v) => v.instruction) ?? []),
].join("\n")
},
}),
},
},
"Share Detour": {
Expand All @@ -506,6 +522,12 @@ export const createDetourMachine = setup({
"detour.share.open-activate-modal": {
target: "Activating",
},
"detour.share.edit-directions": {
target: "Reviewing",
actions: assign({
editedDirections: ({ event }) => event.detourText,
}),
},
},
},
Activating: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,11 @@ import { ActiveDetourPanel } from "../../../src/components/detours/detourPanels/
import { stopFactory } from "../../../tests/factories/stop"

const defaultText = [
"Detour:",
"66 Harvard via Allston from",
"Andrew Station",
"Outbound",
"",
"Turn-by-Turn Directions:",
"Start at Centre St & John St",
"Right on John St",
"Left on Abbotsford Rd",
"Right on Boston St",
"Regular Route",
"",
"Connection Points:",
"Centre St & John St",
"Boston St",
"",
"Missed Stops (3):",
"Example St @ Sample Ave",
"Example St opp Random Way",
"Example St @ Fake Blvd",
].join("\n")

const meta = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const defaultText = [
"Harvard Ave @ Commonwealth Ave",
].join("\n")

const turnByTurn = [
"From Harvard St & Babcock St",
"Right on Babcock St.",
"Regular Route",
].join("\n")

const meta = {
component: DetourFinishedPanel,
parameters: {
Expand All @@ -34,6 +40,7 @@ const meta = {
},
args: {
copyableDetourText: defaultText,
editableDirections: turnByTurn,
},
// The bootstrap CSS reset is supposed to set box-sizing: border-box by
// default, we should be able to remove this after that is added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,11 @@ import { PastDetourPanel } from "../../../src/components/detours/detourPanels/pa
import { stopFactory } from "../../../tests/factories/stop"

const defaultText = [
"Detour:",
"66 Harvard via Allston from",
"Andrew Station",
"Outbound",
"",
"Turn-by-Turn Directions:",
"Start at Centre St & John St",
"Right on John St",
"Left on Abbotsford Rd",
"Right on Boston St",
"Regular Route",
"",
"Connection Points:",
"Centre St & John St",
"Boston St",
"",
"Missed Stops (3):",
"Example St @ Sample Ave",
"Example St opp Random Way",
"Example St @ Fake Blvd",
].join("\n")

const meta = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,18 @@ exports[`Detours Page: Open a Detour renders detour details in an open drawer on
</svg>
Edit
</button>
<h2
class="c-diversion-panel__h2"
>
Directions
</h2>
<textarea
class="flex-grow-1 mb-3 form-control"
data-fs-element="Detour Text"
style="resize: none;"
/>
>
From null
</textarea>
<section
class="pb-3"
>
Expand Down Expand Up @@ -1257,11 +1264,18 @@ exports[`Detours Page: Open a Detour renders detour details modal to match mocke
</svg>
Edit
</button>
<h2
class="c-diversion-panel__h2"
>
Directions
</h2>
<textarea
class="flex-grow-1 mb-3 form-control"
data-fs-element="Detour Text"
style="resize: none;"
/>
>
From null
</textarea>
<section
class="pb-3"
>
Expand Down
22 changes: 22 additions & 0 deletions assets/tests/components/detours/diversionPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,28 @@ describe("DiversionPage", () => {
).toBeVisible()
})

test("'View Draft Detour' screen allows user to edit detour text", async () => {
const { container } = render(<DiversionPage />)

act(() => {
fireEvent.click(originalRouteShape.get(container))
})

act(() => {
fireEvent.click(originalRouteShape.get(container))
})

await userEvent.click(reviewDetourButton.get())

const input = screen.getByRole("textbox") as HTMLTextAreaElement

const startText = `From null`

await userEvent.type(input, "\nHello World!")

expect(input.value).toBe(startText + "\nHello World!")
})

test("Attempting to close the page calls the onClose callback", async () => {
const onClose = jest.fn()

Expand Down

0 comments on commit ce2ea77

Please sign in to comment.