Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement custom map file support #758

Merged
merged 21 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@
"Screens.Settings.AppSettings.languageDesc": {
"message": "Display language for app"
},
"Screens.Settings.AppSettings.mapManagement": {
"message": "Map Management"
},
"Screens.Settings.AppSettings.mapManagementDesc": {
"message": "Backgrounds, Map Data"
},
"Screens.Settings.AppSettings.title": {
"message": "App Settings"
},
Expand Down Expand Up @@ -1264,6 +1270,69 @@
"screens.Settings.CreateOrJoinProject.whatIsAProject": {
"message": "What is a Project"
},
"screens.Settings.MapManagement.BackgroundMaps.ChooseMapFile.acceptedFileTypes": {
"message": "Accepted file types are .smp"
},
"screens.Settings.MapManagement.BackgroundMaps.ChooseMapFile.chooseFile": {
"message": "Choose File"
},
"screens.Settings.MapManagement.BackgroundMaps.about": {
"message": "About Custom Map"
},
"screens.Settings.MapManagement.BackgroundMaps.cannotBeUndone": {
"message": "This cannot be undone."
},
"screens.Settings.MapManagement.BackgroundMaps.close": {
"message": "Close"
},
"screens.Settings.MapManagement.BackgroundMaps.customMapAddedDescription": {
"message": "You will see this map when you are offline, but you will not see a map outside the area defined in your custom map."
},
"screens.Settings.MapManagement.BackgroundMaps.customMapAddedTitle": {
"message": "Custom Map Added"
},
"screens.Settings.MapManagement.BackgroundMaps.customMapInfoLoadError": {
"message": "Could not get custom map information from file. Please remove it or choose a different file."
},
"screens.Settings.MapManagement.BackgroundMaps.deleteCustomMapDescription": {
"message": "This will delete the map and its offline areas. No collected observation data will be deleted."
},
"screens.Settings.MapManagement.BackgroundMaps.deleteCustomMapTitle": {
"message": "Delete Custom Map?"
},
"screens.Settings.MapManagement.BackgroundMaps.deleteMapButtonText": {
"message": "Delete Map"
},
"screens.Settings.MapManagement.BackgroundMaps.description1": {
"message": "Adding a custom map will enable you to see a map when you are offline."
},
"screens.Settings.MapManagement.BackgroundMaps.description2": {
"message": "Your custom map is not shared with other devices in your project."
},
"screens.Settings.MapManagement.BackgroundMaps.removeMapFile": {
"message": "Remove Map File"
},
"screens.Settings.MapManagement.BackgroundMaps.screenTitle": {
"message": "Background Maps"
},
"screens.Settings.MapManagement.MapsList.CustomMapDetails.dateAdded": {
"message": "Date Added"
},
"screens.Settings.MapManagement.MapsList.CustomMapDetails.mapNameColumn": {
"message": "Map Name"
},
"screens.Settings.MapManagement.MapsList.CustomMapDetails.removeMap": {
"message": "Remove Map"
},
"screens.Settings.MapManagement.MapsList.CustomMapDetails.sizeInMegabytes": {
"message": "{value} MB"
},
"screens.Settings.MapManagement.backgroundMaps": {
"message": "Background Maps"
},
"screens.Settings.MapManagement.screenTitle": {
"message": "Map Management"
},
"screens.Settings.YourTeam.InviteDeclined": {
"message": "Invitation Declined"
},
Expand Down
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"tiny-typed-emitter": "^2.1.0",
"uint8array-extras": "^0.5.0",
"utm": "^1.1.1",
"valibot": "^0.42.1",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm introducing this dep for response body validation, which is used when interfacing with the map server

"validate-color": "^2.2.4",
"zustand": "^4.4.6"
},
Expand Down
13 changes: 10 additions & 3 deletions src/backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ import debug from 'debug'
import { join } from 'path'
import { mkdirSync } from 'fs'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
/** @type {import('../types/rn-bridge.js')} */
const rnBridge = require('rn-bridge')
import { MapeoManager, FastifyController } from '@comapeo/core'
import { createMapeoServer } from '@comapeo/ipc'
import Fastify from 'fastify'

import MessagePortLike from './message-port-like.js'
import { ServerStatus } from './status.js'

const require = createRequire(import.meta.url)

/** @type {import('../types/rn-bridge.js')} */
const rnBridge = require('rn-bridge')

// Do not touch these!
const DB_DIR_NAME = 'sqlite-dbs'
const CORE_STORAGE_DIR_NAME = 'core-storage'
const CUSTOM_MAPS_DIR_NAME = 'maps'
const DEFAULT_CUSTOM_MAP_FILE_NAME = 'default.smp'
Comment on lines 18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming that this requires the developer to know the names of the directories? If so, that seems quite fragile, maybe something to consider refactoring at the UK retreat.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah there's some duplication between frontend and backend in terms of knowing where this directory is. However, there's no good way to share code between the two unless you involve an unnecessary IPC roundtrip, which I'd rather avoid in this case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for posterity: the way this custom map feature works is the following:

  1. The frontend is responsible for moving a file to and from an app-specific directory on the device. In this case, we the android "document directory" which is something like file:///data/user/0/com.comapeo.dev/files/ and make a maps/default.smp there.
  2. The backend is configured to use the path in (1) as the path to watch for determining if there's a custom map file that can be used for the map server.


const MAPBOX_ACCESS_TOKEN =
'pk.eyJ1IjoiZGlnaWRlbSIsImEiOiJjbHRyaGh3cm0wN3l4Mmpsam95NDI3c2xiIn0.daq2iZFZXQ08BD0VZWAGUw'
Expand Down Expand Up @@ -64,9 +68,11 @@ export async function init({
const privateStorageDir = rnBridge.app.datadir()
const dbDir = join(privateStorageDir, DB_DIR_NAME)
const indexDir = join(privateStorageDir, CORE_STORAGE_DIR_NAME)
const customMapsDir = join(privateStorageDir, CUSTOM_MAPS_DIR_NAME)

mkdirSync(dbDir, { recursive: true })
mkdirSync(indexDir, { recursive: true })
mkdirSync(customMapsDir, { recursive: true })

const fastify = Fastify()
const fastifyController = new FastifyController({ fastify })
Expand All @@ -80,6 +86,7 @@ export async function init({
fastify,
defaultConfigPath,
defaultOnlineStyleUrl: DEFAULT_ONLINE_MAP_STYLE_URL,
customMapPath: join(customMapsDir, DEFAULT_CUSTOM_MAP_FILE_NAME),
})

// Don't await, methods that use the server will await this internally
Expand Down
22 changes: 20 additions & 2 deletions src/frontend/Navigation/Stack/AppScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ import {
Audio,
navigationOptions as audioNavigationOptions,
} from '../../screens/Audio/index.tsx';
import {
createNavigationOptions as createMapManagementNavigationOptions,
MapManagementScreen,
} from '../../screens/Settings/MapManagement';
import {
createNavigationOptions as createBackgroundMapsNavigationOptions,
BackgroundMapsScreen,
} from '../../screens/Settings/MapManagement/BackgroundMaps.tsx';

export const TAB_BAR_HEIGHT = 70;

Expand Down Expand Up @@ -314,13 +322,11 @@ export const createDefaultScreenGroup = ({
component={TrackEdit}
options={{headerTitle: intl(TrackEdit.navTitle)}}
/>

<RootStack.Screen
name="Config"
component={Config}
options={{headerTitle: intl(Config.navTitle)}}
/>

<RootStack.Screen
name="HowToLeaveProject"
component={HowToLeaveProject}
Expand All @@ -333,6 +339,18 @@ export const createDefaultScreenGroup = ({
component={Audio}
/>
)}

<RootStack.Screen
name="MapManagement"
component={MapManagementScreen}
options={createMapManagementNavigationOptions({intl})}
/>
<RootStack.Screen
name="BackgroundMaps"
component={BackgroundMapsScreen}
options={createBackgroundMapsNavigationOptions({intl})}
/>

{process.env.EXPO_PUBLIC_FEATURE_TEST_DATA_UI && (
<RootStack.Screen
name="CreateTestData"
Expand Down
42 changes: 42 additions & 0 deletions src/frontend/hooks/refreshTokenStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {create} from 'zustand';

interface RefreshTokenStoreSlice {
value: number;
actions: {
refresh: () => void;
};
}

/**
* Factory for creating a bound store instance and return its relevant hooks,
* which allows the creation of isolated stores to account for contextual needs.
*/
export function createRefreshTokenStore(initialValue?: number) {
const useRefreshTokenStore = create<RefreshTokenStoreSlice>(set => {
return {
value: typeof initialValue === 'number' ? initialValue : Date.now(),
actions: {
refresh: () => {
set({value: Date.now()});
},
},
};
});

return {
useRefreshToken: () => {
return useRefreshTokenStore(valueSelector);
},
useRefreshTokenActions: () => {
return useRefreshTokenStore(actionsSelector);
},
};
}

function valueSelector(state: RefreshTokenStoreSlice) {
return state.value;
}

function actionsSelector(state: RefreshTokenStoreSlice) {
return state.actions;
}
16 changes: 0 additions & 16 deletions src/frontend/hooks/server/mapStyleUrl.ts

This file was deleted.

Loading
Loading