From 4f8378264ffc40e407370ef4617714df9945d6a8 Mon Sep 17 00:00:00 2001 From: Cy Rossignol Date: Tue, 2 Dec 2025 14:50:25 -0800 Subject: [PATCH 1/3] Improve workspaces types --- services/import/tdei.ts | 19 ++++++--- services/osm.ts | 16 +++++--- services/pathways.ts | 2 +- services/workspaces.ts | 86 +++++++++++++++++++++++++---------------- types/bbox.ts | 7 ++++ types/workspaces.ts | 33 ++++++++++++++++ 6 files changed, 116 insertions(+), 47 deletions(-) create mode 100644 types/bbox.ts diff --git a/services/import/tdei.ts b/services/import/tdei.ts index 6ead802..32596e1 100644 --- a/services/import/tdei.ts +++ b/services/import/tdei.ts @@ -5,6 +5,8 @@ import { openTdeiPathwaysArchive, pathways2osc } from '~/services/pathways'; import { TdeiClient, TdeiClientError } from '~/services/tdei'; import { WorkspacesClient, WorkspacesClientError } from '~/services/workspaces'; +import type { WorkspaceCreation } from '~/types/workspaces'; + const status = { idle: 'Idle', createWorkspace: 'Initializing workspace...', @@ -17,9 +19,9 @@ const status = { }; export class TdeiImporterContext { - constructor() { - this.reset(); - } + active: boolean = false; + status: string = status.idle; + error?: string; get complete(): boolean { return this.status === status.complete; @@ -28,11 +30,16 @@ export class TdeiImporterContext { reset() { this.active = false; this.status = status.idle; - this.error = null; + this.error = undefined; } } export class TdeiImporter { + readonly _workspacesClient: WorkspacesClient; + readonly _tdeiClient: TdeiClient; + readonly _osmClient: OsmApiClient; + readonly _context: TdeiImporterContext; + constructor( workspacesClient: WorkspacesClient, tdeiClient: TdeiClient, @@ -49,7 +56,7 @@ export class TdeiImporter { return this._context; } - async import(workspace): Promise { + async import(workspace: WorkspaceCreation): Promise { this._context.reset(); this._context.active = true; @@ -62,7 +69,7 @@ export class TdeiImporter { } } - async _run(workspace): Promise { + async _run(workspace: WorkspaceCreation): Promise { // Create the workspace in parallel: const workspacePromise = this._workspacesClient.createWorkspace(workspace); diff --git a/services/osm.ts b/services/osm.ts index 91ef12d..9f41aea 100644 --- a/services/osm.ts +++ b/services/osm.ts @@ -239,15 +239,15 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient { }); } - async createWorkspace(workspaceId: number) { + async createWorkspace(workspaceId: WorkspaceId) { await this._put(`workspaces/${workspaceId}`); } - async deleteWorkspace(workspaceId: number) { + async deleteWorkspace(workspaceId: WorkspaceId) { await this._delete(`workspaces/${workspaceId}`); } - async getWorkspaceBbox(workspaceId: number) { + async getWorkspaceBbox(workspaceId: WorkspaceId) { const response = await this._get(`workspaces/${workspaceId}/bbox.json`); if (response.status === 204) { @@ -433,7 +433,11 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient { return Number(await response.text()); } - async uploadChangeset(workspaceId: number, changesetId: number, changesetXml: string) { + async uploadChangeset( + workspaceId: WorkspaceId, + changesetId: number, + changesetXml: string + ) { await this._post(`changeset/${changesetId}/upload`, changesetXml, { headers: { 'Content-Type': 'application/xml', @@ -489,7 +493,7 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient { return notesGeoJsonToEntities(await response.json()); } - async getWorkspaceData(workspaceId: number): Promise { + async getWorkspaceData(workspaceId: WorkspaceId): Promise { const bboxParam = await this.getExportBbox(workspaceId); const response = await this._get(`map.json?bbox=${bboxParam}`, { headers: { @@ -502,7 +506,7 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient { return (await response.json()).elements; } - async exportWorkspaceXml(workspaceId: number): Promise { + async exportWorkspaceXml(workspaceId: WorkspaceId): Promise { const bboxParam = await this.getExportBbox(workspaceId); const response = await this._get(`map?bbox=${bboxParam}`, { headers: { diff --git a/services/pathways.ts b/services/pathways.ts index 7c546b3..ea480de 100644 --- a/services/pathways.ts +++ b/services/pathways.ts @@ -149,7 +149,7 @@ function makePathwayColumnMap() { return map; } -export async function buildPathwaysCsvArchive(elements, gtfsFiles: Map): Blob { +export async function buildPathwaysCsvArchive(elements, gtfsFiles?: Map): Blob { if (!gtfsFiles) { gtfsFiles = createEmptyGtfsDataset(); } diff --git a/services/workspaces.ts b/services/workspaces.ts index bfed5aa..bddd87d 100644 --- a/services/workspaces.ts +++ b/services/workspaces.ts @@ -1,11 +1,21 @@ import { BaseHttpClient, BaseHttpClientError } from "~/services/http"; -import type { ICancelableClient } from '~/services/loading'; -import type { OsmApiClient } from '~/services/osm'; import { buildPathwaysCsvArchive } from '~/services/pathways'; -import type { TdeiClient } from '~/services/tdei'; -export function compareWorkspaceCreatedAtDesc(a, b) { - return b.createdAt - a.createdAt; +import type { ICancelableClient } from '~/services/loading'; +import type { OsmApiClient } from '~/services/osm'; +import type { TdeiAuthStore, TdeiClient } from '~/services/tdei'; +import type { BoundingBox } from '~/types/bbox' +import type { + QuestSettings, + QuestSettingsPatch, + Workspace, + WorkspaceCreation, + WorkspaceId, + WorkspacePatch +} from '~/types/workspaces'; + +export function compareWorkspaceCreatedAtDesc(a: Workspace, b: Workspace) { + return b.createdAt.getTime() - a.createdAt.getTime(); } export class WorkspacesClientError extends Error { @@ -33,11 +43,11 @@ export class WorkspacesClient extends BaseHttpClient implements ICancelableClien this.#osmClient = osmClient; } - get auth() { + get auth(): TdeiAuthStore { return this.#tdeiClient.auth; } - clone(signal?: AbortSignal) { + clone(signal?: AbortSignal): WorkspacesClient { return new WorkspacesClient( this._baseUrl, this.#tdeiClient, @@ -46,7 +56,7 @@ export class WorkspacesClient extends BaseHttpClient implements ICancelableClien ); } - async getMyWorkspaces() { + async getMyWorkspaces(): Promise { const response = await this._get('workspaces/mine'); const workspaces = (await response.json()) ?? []; @@ -58,20 +68,17 @@ export class WorkspacesClient extends BaseHttpClient implements ICancelableClien return workspaces; } - async getWorkspace(id: number) { - try { - const response = await this._get(`workspaces/${id}`); - return await response.json(); - } catch (e: any) { - return null; - } + async getWorkspace(id: WorkspaceId): Promise { + const response = await this._get(`workspaces/${id}`); + + return await response.json(); } - getWorkspaceBbox(id: number) { + getWorkspaceBbox(id: WorkspaceId): Promise { return this.#osmClient.getWorkspaceBbox(id); } - async createWorkspace(workspace): Promise { + async createWorkspace(workspace: WorkspaceCreation): Promise { workspace.createdBy = this.#tdeiClient.auth.subject; workspace.createdByName = this.#tdeiClient.auth.displayName; @@ -82,52 +89,63 @@ export class WorkspacesClient extends BaseHttpClient implements ICancelableClien return workspaceId; } - async updateWorkspace(id: number, workspaceDetails: object): Promise { + async updateWorkspace(id: WorkspaceId, workspaceDetails: WorkspacePatch) + : Promise + { await this._patch(`workspaces/${id}`, workspaceDetails); } - async exportWorkspaceArchive(workspace): Promise { + async exportWorkspaceArchive(workspace: Workspace): Promise { if (workspace.type === 'pathways') { const elements = await this.#osmClient.getWorkspaceData(workspace.id); return await buildPathwaysCsvArchive(elements); } - const osm = await this.#osmClient.exportWorkspaceXml(workspace.id); + const osmXml = await this.#osmClient.exportWorkspaceXml(workspace.id); - return await this.#tdeiClient.convertDataset(osm, 'osm', 'osw', workspace.tdeiProjectGroupId); + return await this.#tdeiClient.convertDataset( + osmXml, + 'osm', + 'osw', + workspace.tdeiProjectGroupId + ); } - async deleteWorkspace(id: number): Promise { + async deleteWorkspace(id: WorkspaceId): Promise { await Promise.all([ this._delete(`workspaces/${id}`), this.#osmClient.deleteWorkspace(id) ]); } - async getLongFormQuestSettings(workspaceId: number) { - const response = this._get(`workspaces/${workspaceId}/quests/long/settings`); - return response.then(data => { - // case when no existing settings exist, just show empty form - if(data.status === 204) return {}; - return data.json(); - }); + async getLongFormQuestSettings(id: WorkspaceId): Promise { + const response = await this._get(`workspaces/${id}/quests/long/settings`); + + return await response.json(); } - async saveLongFormQuestSettings(workspaceId: number, settings: object): Promise { - this._patch(`workspaces/${workspaceId}/quests/long/settings`, settings); + async saveLongFormQuestSettings(id: WorkspaceId, settings: QuestSettingsPatch) + : Promise + { + await this._patch(`workspaces/${id}/quests/long/settings`, settings); } async saveImageryDefSettings(workspaceId: number, settings: object): Promise { - this._patch(`workspaces/${workspaceId}/imagery/settings`, settings); + await this._patch(`workspaces/${workspaceId}/imagery/settings`, settings); } #setAuthHeader() { if (this.#tdeiClient.auth.complete) { - this._requestHeaders.Authorization = 'Bearer ' + this.#tdeiClient.auth.accessToken; + this._requestHeaders.Authorization = 'Bearer ' + this.auth.accessToken; } } - override async _send(url: string, method: string, body?: any, config?: object): Promise { + override async _send( + url: string, + method: string, + body?: any, + config?: object + ): Promise { try { await this.#tdeiClient.tryRefreshAuth(); this.#setAuthHeader(); diff --git a/types/bbox.ts b/types/bbox.ts new file mode 100644 index 0000000..a8f080b --- /dev/null +++ b/types/bbox.ts @@ -0,0 +1,7 @@ + +export interface BoundingBox { + min_lat: number; + min_lon: number; + max_lat: number; + max_lon: number; +} diff --git a/types/workspaces.ts b/types/workspaces.ts index ff9a61f..61c6615 100644 --- a/types/workspaces.ts +++ b/types/workspaces.ts @@ -18,3 +18,36 @@ export interface Workspace { externalAppAccess: WorkspaceAppAccess; kartaViewToken?: string; } + +export interface WorkspaceCreation { + type: WorkspaceType; + title: string; + description?: string; + tdeiRecordId?: string; + tdeiProjectGroupId: string; + tdeiServiceId?: string; + tdeiMetadata?: string; + createdBy?: string; + createdByName?: string; +} + +export interface WorkspacePatch { + title?: string; + description?: string; + externalAppAccess?: WorkspaceAppAccess; +} + +export type QuestSettingsType = 'JSON' | 'URL'; + +export interface QuestSettingsPatch { + type: QuestSettingsType; + definition?: string; + url?: string; +} + +export interface QuestSettings extends QuestSettingsPatch { + workspace_id: WorkspaceId; + modified_at: Date; + modified_by: string; + modified_by_name: string; +} From e25cc91a0f0fb9bd51e8e20ae3bcbd236ad480a6 Mon Sep 17 00:00:00 2001 From: Cy Rossignol Date: Thu, 4 Dec 2025 10:01:25 -0800 Subject: [PATCH 2/3] Replace bootstrap JS bundle with bootstrap-vue --- app.vue | 8 +- components/AppNavbar.vue | 25 +++-- components/review/FilterDropdown.vue | 119 ++++++++++---------- nuxt.config.ts | 8 +- package-lock.json | 156 ++++++++++++++++++++++++++- package.json | 5 +- 6 files changed, 239 insertions(+), 82 deletions(-) diff --git a/app.vue b/app.vue index e385020..e803de5 100644 --- a/app.vue +++ b/app.vue @@ -1,5 +1,7 @@ diff --git a/components/AppNavbar.vue b/components/AppNavbar.vue index 8266eeb..b988b3a 100644 --- a/components/AppNavbar.vue +++ b/components/AppNavbar.vue @@ -29,18 +29,21 @@ Sign In - -