Skip to content

Commit d2812b4

Browse files
Style nav bar
1 parent 21463cf commit d2812b4

File tree

10 files changed

+102
-35
lines changed

10 files changed

+102
-35
lines changed

apps/osm-merge/src/components/browser-check.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function BrowserCheck() {
1313
return (
1414
<Dialog>
1515
<DialogTrigger asChild>
16-
<Button size="sm" variant="link">
16+
<Button size="xs" variant="link">
1717
Check system
1818
</Button>
1919
</DialogTrigger>

apps/osm-merge/src/components/merge-page.tsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { useBitmapTileLayer } from "@/hooks/map"
22
import { useOsmFile, useOsmWorker } from "@/hooks/osm"
33
import { DEFAULT_BASE_PBF_URL, DEFAULT_PATCH_PBF_URL } from "@/settings"
44
import { ArrowLeft, ArrowRight, Loader2Icon } from "lucide-react"
5-
import type { OsmChanges } from "osm.ts"
5+
import { Osm, writeOsmToPbfStream, type OsmChanges } from "osm.ts"
6+
import { showSaveFilePicker } from "native-file-system-adapter"
67
import { useCallback, useEffect, useRef, useState, useTransition } from "react"
78
import Basemap from "./basemap"
89
import DeckGlOverlay from "./deckgl-overlay"
@@ -11,18 +12,25 @@ import LogContent from "./log"
1112
import OsmInfoTable from "./osm-info-table"
1213
import OsmPbfFileInput from "./osm-pbf-file-input"
1314
import { Button } from "./ui/button"
15+
import useStartTask from "@/hooks/log"
1416

1517
export default function Merge() {
1618
const [baseFile, setBaseFile] = useState<File | null>(null)
1719
const [patchFile, setPatchFile] = useState<File | null>(null)
18-
const [baseOsm, baseOsmIsLoading] = useOsmFile(baseFile, "base")
19-
const [patchOsm, patchOsmIsLoading] = useOsmFile(patchFile, "patch")
20+
const [baseOsm, setBaseOsm, baseOsmIsLoading] = useOsmFile(baseFile, "base")
21+
const [patchOsm, setPatchOsm, patchOsmIsLoading] = useOsmFile(
22+
patchFile,
23+
"patch",
24+
)
25+
const [mergedOsm, setMergedOsm] = useState<Osm | null>(null)
2026
const osmWorker = useOsmWorker()
2127
const [isTransitioning, startTransition] = useTransition()
2228
const [changes, setChanges] = useState<OsmChanges | null>(null)
29+
const startTask = useStartTask()
2330

2431
const baseTileLayer = useBitmapTileLayer(baseOsm)
2532
const patchTileLayer = useBitmapTileLayer(patchOsm)
33+
const mergedTileLayer = useBitmapTileLayer(mergedOsm)
2634

2735
const [step, setStep] = useState<number>(1)
2836

@@ -60,6 +68,30 @@ export default function Merge() {
6068
setStep((s) => s + 1)
6169
}, [])
6270

71+
const downloadOsm = useCallback(
72+
async (osm: Osm, name?: string) => {
73+
startTransition(async () => {
74+
const task = startTask("Generating OSM file to download", "info")
75+
const suggestedName =
76+
name ?? (osm.id.endsWith(".pbf") ? osm.id : `${osm.id}.pbf`)
77+
const fileHandle = await showSaveFilePicker({
78+
suggestedName,
79+
types: [
80+
{
81+
description: "OSM PBF",
82+
accept: { "application/x-protobuf": [".pbf"] },
83+
},
84+
],
85+
})
86+
const stream = await fileHandle.createWritable()
87+
await writeOsmToPbfStream(osm, stream)
88+
await stream.close()
89+
task.end(`Created ${fileHandle.name} PBF for download`, "ready")
90+
})
91+
},
92+
[startTask],
93+
)
94+
6395
return (
6496
<Main>
6597
<Sidebar>
@@ -199,7 +231,7 @@ export default function Merge() {
199231
</If>
200232

201233
<If t={step === 3}>
202-
{isTransitioning ? (
234+
{isTransitioning || changes == null ? (
203235
<div className="flex items-center gap-1">
204236
<Loader2Icon className="animate-spin size-4" />
205237
<div className="font-bold">GENERATING CHANGESET</div>
@@ -210,34 +242,39 @@ export default function Merge() {
210242
<div>
211243
Changes have been generated and can be reviewed below. Once
212244
the review is complete you can apply changes to generate a new
213-
OSM file ready to be downloaded. You cannot go back from the
214-
next step.
245+
OSM file ready to be downloaded.
215246
</div>
216247
</>
217248
)}
218249
<div className="border border-slate-950 p-1 max-h-96">
219250
{changes && <ChangesSummary changes={changes} />}
220251
</div>
221252
<Button
222-
variant="outline"
253+
disabled={changes == null || isTransitioning}
223254
onClick={() => {
224255
if (!osmWorker) throw Error("No OSM worker")
225256
nextStep()
226257
startTransition(async () => {
227258
if (!changes) throw Error("No changes to apply")
228-
// await osmWorker.applyChangeset()
259+
if (!baseOsm) throw Error("No base OSM")
260+
const newOsm = await osmWorker.applyChanges(
261+
`merged-${baseOsm.id}`,
262+
)
263+
setBaseOsm(null)
264+
setPatchOsm(null)
265+
setMergedOsm(Osm.from(newOsm))
229266
})
230267
}}
231268
>
232-
<ArrowRight /> Apply changes
269+
<ArrowRight /> Apply changes (irreversible)
233270
</Button>
234271
<Button variant="outline" onClick={prevStep}>
235272
<ArrowLeft /> Back
236273
</Button>
237274
</If>
238275

239276
<If t={step === 4}>
240-
{isTransitioning ? (
277+
{isTransitioning || mergedOsm == null ? (
241278
<div className="flex items-center gap-1">
242279
<Loader2Icon className="animate-spin size-4" />
243280
<div className="font-bold">APPLYING CHANGES</div>
@@ -249,6 +286,9 @@ export default function Merge() {
249286
Changes have been applied and a new OSM dataset has been
250287
created. It can be inspected here and downloaded as a new PBF.
251288
</div>
289+
<Button onClick={() => downloadOsm(mergedOsm)}>
290+
Download merged OSM PBF
291+
</Button>
252292
{/* INSERT OSM VIEW CODE HERE */}
253293
</>
254294
)}
@@ -257,7 +297,9 @@ export default function Merge() {
257297
</Sidebar>
258298
<MapContent>
259299
<Basemap>
260-
<DeckGlOverlay layers={[baseTileLayer, patchTileLayer]} />
300+
<DeckGlOverlay
301+
layers={[baseTileLayer, patchTileLayer, mergedTileLayer]}
302+
/>
261303
</Basemap>
262304
</MapContent>
263305
</Main>

apps/osm-merge/src/components/nav.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@ import ZoomInfo from "@/components/zoom-info"
33
import BrowserCheck from "./browser-check"
44
import Status from "./status"
55
import { NavLink } from "react-router"
6+
import { cn } from "@/lib/utils"
67

78
export default function Nav() {
89
return (
9-
<div className="border-b flex flex-row justify-between items-center px-4">
10-
<div className="flex flex-row gap-4 items-center py-2">
11-
<div className="font-bold">OSM.ts</div>
10+
<div className="border-b flex flex-row justify-between items-center px-4 h-12">
11+
<div className="flex flex-row gap-2 items-center">
12+
<div className="font-bold pr-2">OSM.ts</div>
1213
<NavLink
1314
className={({ isActive }) =>
14-
isActive ? "text-blue-600" : "text-slate-950"
15+
cn("text-slate-950 py-4 px-2", isActive && "text-blue-600")
1516
}
1617
to="merge"
1718
>
1819
Merge
1920
</NavLink>
2021
<NavLink
2122
className={({ isActive }) =>
22-
isActive ? "text-blue-600" : "text-slate-950"
23+
cn("text-slate-950 py-4 px-2", isActive && "text-blue-600")
2324
}
2425
to="view"
2526
>

apps/osm-merge/src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const buttonVariants = cva(
2323
},
2424
size: {
2525
md: "h-9 px-4 py-2 has-[>svg]:px-3",
26-
xs: "h-6 rounded-md gap-1 px-2 has-[>svg]:px-1.5",
26+
xs: "rounded-md gap-1 px-2 has-[>svg]:px-1.5",
2727
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
2828
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
2929
icon: "size-9",

apps/osm-merge/src/components/view-page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export default function ViewPage() {
7070
extent: bbox,
7171
getTileData: async (tile) => {
7272
if (tile.index.z < MIN_PICKABLE_ZOOM) {
73-
const endBitmapGenerationTask = startTask(
73+
const task = startTask(
7474
`generating bitmap for tile ${tile.index.z}/${tile.index.x}/${tile.index.y}`,
7575
"debug",
7676
)
@@ -81,7 +81,7 @@ export default function ViewPage() {
8181
tile.index,
8282
TILE_SIZE,
8383
)
84-
endBitmapGenerationTask(
84+
task.end(
8585
`bitmap for tile ${tile.index.z}/${tile.index.x}/${tile.index.y} generated`,
8686
"debug",
8787
)
@@ -90,7 +90,7 @@ export default function ViewPage() {
9090

9191
// Show pickable data
9292
const bbox = tile.bbox as GeoBoundingBox
93-
const endTask = startTask(
93+
const task = startTask(
9494
`generating data for tile ${tile.index.z}/${tile.index.x}/${tile.index.y}`,
9595
"debug",
9696
)
@@ -100,7 +100,7 @@ export default function ViewPage() {
100100
bbox.east,
101101
bbox.north,
102102
])
103-
endTask(
103+
task.end(
104104
`tile data for ${tile.index.z}/${tile.index.x}/${tile.index.y} generated`,
105105
"debug",
106106
)

apps/osm-merge/src/hooks/log.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ export default function useStartTask() {
1111
function startTaskMessage(message: string, type: Status["type"] = "info") {
1212
setTasks((t) => t + 1)
1313
logMessage(message, type)
14-
return function endTaskMessage(
15-
message: string,
16-
type: Status["type"] = "info",
17-
) {
18-
setTasks((t) => t - 1)
19-
logMessage(message, type)
14+
return {
15+
update: (message: string, type: Status["type"] = "info") => {
16+
logMessage(message, type)
17+
},
18+
end: (message: string, type: Status["type"] = "info") => {
19+
setTasks((t) => t - 1)
20+
logMessage(message, type)
21+
},
2022
}
2123
},
2224
[setTasks, logMessage],

apps/osm-merge/src/hooks/osm.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,24 @@ export function useOsmFile(file: File | null, id?: string) {
4747

4848
useEffect(() => {
4949
if (!osmWorker || !file) return
50-
const endOsmInitTask = startTask(`Processing file ${file.name}...`)
50+
const task = startTask(`Processing file ${file.name}...`)
5151
const stream = file.stream()
5252
setOsm(null)
5353
setIsLoading(true)
5454
osmWorker
5555
.initFromPbfData(id ?? file.name, Comlink.transfer(stream, [stream]))
5656
.then(async (osmBuffers) => {
5757
setOsm(Osm.from(osmBuffers))
58-
endOsmInitTask(`${file.name} fully loaded.`)
58+
task.end(`${file.name} fully loaded.`)
5959
})
6060
.catch((e) => {
6161
console.error(e)
62-
endOsmInitTask(`${file.name} failed to load.`, "error")
62+
task.end(`${file.name} failed to load.`, "error")
6363
})
6464
.finally(() => {
6565
setIsLoading(false)
6666
})
6767
}, [file, id, osmWorker, startTask])
6868

69-
return [osm, isLoading] as const
69+
return [osm, setOsm, isLoading] as const
7070
}

apps/osm-merge/src/main.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,12 @@
130130
}
131131
a {
132132
color: var(--color-blue-600);
133-
text-decoration-line: underline;
134133
text-underline-offset: 4px;
135134
}
135+
136+
a:hover {
137+
text-decoration-line: underline;
138+
}
136139
}
137140

138141
/* roboto-mono-latin-wght-normal */

apps/osm-merge/src/workers/osm.worker.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const osmWorker = {
4343

4444
observer.observe({ entryTypes: ["mark", "measure"] })
4545
},
46+
clearAllOsm() {
47+
this.ids = {}
48+
},
4649
osm(id: string) {
4750
if (!this.ids[id]) throw Error(`Osm for ${id} not loaded.`)
4851
return this.ids[id]
@@ -115,6 +118,14 @@ const osmWorker = {
115118
stats: changeset.stats,
116119
}
117120
},
121+
applyChanges(newId: string) {
122+
if (!this.activeChangeset) throw Error("No active changeset")
123+
const osm = this.activeChangeset.applyChanges(newId)
124+
this.activeChangeset = null
125+
this.clearAllOsm()
126+
this.ids[osm.id] = osm
127+
return osm.transferables()
128+
},
118129
}
119130

120131
export type OsmWorker = typeof osmWorker

packages/osm.ts/src/changeset.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,21 @@ export default class OsmChangeset {
312312
this.handleIntersectingWays(way, patch)
313313
}
314314
}
315+
316+
applyChanges(newId: string) {
317+
return applyChangesetToOsm(newId, this.osm, this)
318+
}
315319
}
316320

317321
/**
318322
* Apply a changeset to an Osm index, generating a new Osm index. Usually done on a changeset made from the base osm index.
319323
*/
320-
export function applyChangesetToOsm(baseOsm: Osm, changeset: OsmChangeset) {
321-
const osm = new Osm(baseOsm.id, baseOsm.header)
324+
export function applyChangesetToOsm(
325+
newId: string,
326+
baseOsm: Osm,
327+
changeset: OsmChangeset,
328+
) {
329+
const osm = new Osm(newId, baseOsm.header)
322330

323331
const { nodeChanges, wayChanges, relationChanges } = changeset
324332

0 commit comments

Comments
 (0)