Skip to content

Commit

Permalink
Merge branch 'main' into chore/invite-event-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
cimigree committed Jan 7, 2025
2 parents d0d0965 + 7380da9 commit f779fac
Show file tree
Hide file tree
Showing 13 changed files with 751 additions and 75 deletions.
607 changes: 604 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/sodium-native": "^2.3.9",
"@vis.gl/react-maplibre": "1.0.0-alpha.4",
"@vitejs/plugin-react": "^4.3.3",
"electron": "33.2.0",
"eslint": "9.17.0",
"globals": "^15.12.0",
"husky": "^9.1.7",
"jsdom": "25.0.1",
"lint-staged": "^15.2.10",
"maplibre-gl": "5.0.0",
"npm-run-all2": "^7.0.1",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
Expand Down
30 changes: 25 additions & 5 deletions src/main/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { defineMessages } from '@formatjs/intl'
import debug from 'debug'
import {
BrowserWindow,
MessageChannelMain,
app,
dialog,
ipcMain,
safeStorage,
utilityProcess,
} from 'electron/main'

import { Intl, getSystemLocale } from './intl.js'
import { APP_IPC_EVENT_TO_PARAMS_PARSER } from './ipc.js'

const log = debug('comapeo:main:app')

Expand Down Expand Up @@ -213,17 +214,36 @@ function initMainWindow({ appMode, services }) {

// Set up communication channel between window and core service
// https://www.electronjs.org/docs/latest/tutorial/message-ports/#messageports-in-the-main-process
mainWindow.webContents.ipc.on('request-comapeo-port', (event) => {
const { port1, port2 } = new MessageChannelMain()
mainWindow.webContents.ipc.on('comapeo-port', (event) => {
const [port] = event.ports
if (!port) return // TODO: throw/report error
services.core.postMessage(
/** @satisfies {NewClientMessage} */
{
type: 'core:new-client',
payload: { clientId: `window-${mainWindow.id}` },
},
[port1],
[port],
)
event.senderFrame?.postMessage('provide-comapeo-port', null, [port2])
})

// Set up IPC specific to the main window
mainWindow.webContents.ipc.handle('files:select', async (_event, params) => {
const parsedParams = APP_IPC_EVENT_TO_PARAMS_PARSER['files:select'](params)

const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: parsedParams?.extensionFilters
? [
{
name: 'Custom file type',
extensions: parsedParams.extensionFilters,
},
]
: undefined,
})

return result.filePaths[0]
})

APP_STATE.browserWindows.set(mainWindow, {
Expand Down
29 changes: 29 additions & 0 deletions src/main/ipc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
array,
object,
optional,
parse,
string,
undefined,
union,
} from 'valibot'

const FilesSelectParamsSchema = union([
object({
extensionFilters: optional(array(string())),
}),
undefined(),
])

export const APP_IPC_EVENT_TO_PARAMS_PARSER = /** @type {const} */ ({
/**
* @param {unknown} value
*
* @returns {import('valibot').InferOutput<typeof FilesSelectParamsSchema>}
*/
'files:select': (value) => {
return parse(FilesSelectParamsSchema, value)
},
})

/** @typedef {keyof typeof APP_IPC_EVENT_TO_PARAMS_PARSER} AppIPCEvents */
41 changes: 25 additions & 16 deletions src/preload/main-window.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
const { contextBridge, ipcRenderer } = require('electron/renderer')

// We need to wait until the main world is ready to receive the message before
// sending the port. We create this promise in the preload so it's guaranteed
// to register the onload listener before the load event is fired.
const windowLoaded = new Promise((resolve) => {
window.onload = resolve
})
window.onmessage = (event) => {
// event.source === window means the message is coming from the preload
// script, as opposed to from an <iframe> or other source.
if (event.source !== window) return
if (event.data !== 'comapeo-port') return
const [port] = event.ports
if (!port) return // TODO: throw/report error
ipcRenderer.postMessage('comapeo-port', null, [port])
}

/**
* @type {import('./runtime.js').RuntimeApi}
*/
const runtimeApi = {
// Setup
init() {
ipcRenderer.send('request-comapeo-port')
ipcRenderer.once('provide-comapeo-port', async (event) => {
await windowLoaded
window.postMessage('comapeo-port', '*', event.ports)
})
},

// Locale
async getLocale() {
const locale = await ipcRenderer.invoke('locale:get')
if (typeof locale !== 'string') throw Error('Locale must be a string')
if (typeof locale !== 'string') {
throw new Error('Locale must be a string')
}
return locale
},
updateLocale(locale) {
ipcRenderer.send('locale:update', locale)
},

// Files
async selectFile(extensionFilters) {
const filePath = await ipcRenderer.invoke('files:select', {
extensionFilters,
})

if (!(typeof filePath === 'string' || typeof filePath === 'undefined')) {
throw new Error(`File path is unexpected type: ${typeof filePath}`)
}

return filePath
},
}

contextBridge.exposeInMainWorld('runtime', runtimeApi)
2 changes: 1 addition & 1 deletion src/preload/runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type RuntimeApi = {
init: () => void
getLocale: () => Promise<string>
updateLocale: (locale: string) => void
selectFile: (extensionFilters?: Array<string>) => Promise<string | undefined>
}
13 changes: 7 additions & 6 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { ClientApiProvider } from '@comapeo/core-react'
import { CssBaseline, ThemeProvider } from '@mui/material'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'

import { theme } from './Theme'
import { initComapeoClient } from './comapeo-client'
import {
ActiveProjectIdProvider,
createActiveProjectIdStore,
} from './contexts/ActiveProjectIdProvider'
import { ApiProvider } from './contexts/ApiContext'
import { IntlProvider } from './contexts/IntlContext'
import { routeTree } from './routeTree.gen'

const queryClient = new QueryClient()

const clientApi = initComapeoClient()
const router = createRouter({ routeTree })

declare module '@tanstack/react-router' {
Expand All @@ -30,11 +31,11 @@ export const App = () => (
<CssBaseline />
<IntlProvider>
<QueryClientProvider client={queryClient}>
<ActiveProjectIdProvider store={PersistedProjectIdStore}>
<ApiProvider>
<ClientApiProvider clientApi={clientApi}>
<ActiveProjectIdProvider store={PersistedProjectIdStore}>
<RouterProvider router={router} />
</ApiProvider>
</ActiveProjectIdProvider>
</ActiveProjectIdProvider>
</ClientApiProvider>
</QueryClientProvider>
</IntlProvider>
</ThemeProvider>
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/src/comapeo-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createMapeoClient } from '@comapeo/ipc'

export function initComapeoClient() {
const { port1, port2 } = new MessageChannel()
window.postMessage('comapeo-port', '*', [port2])
const client = createMapeoClient(port1, { timeout: Infinity })
port1.start()
return client
}
28 changes: 28 additions & 0 deletions src/renderer/src/components/Map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Map as ReactMapLibre } from '@vis.gl/react-maplibre'

import 'maplibre-gl/dist/maplibre-gl.css'

export function Map() {
const center = [-72.312023, -10.38787]

return (
<ReactMapLibre
initialViewState={{
longitude: center[0],
latitude: center[1],
zoom: 6,
}}
dragPan={true}
scrollZoom={true}
doubleClickZoom={true}
style={{ width: '100%', height: '100%' }}
mapStyle="https://demotiles.maplibre.org/style.json"
onError={(evt) => {
console.error('Map error:', evt.error)
}}
onLoad={() => {
console.log('Map loaded successfully!')
}}
/>
)
}
41 changes: 0 additions & 41 deletions src/renderer/src/contexts/ApiContext.tsx

This file was deleted.

13 changes: 13 additions & 0 deletions src/renderer/src/hooks/mutations/file-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useMutation } from '@tanstack/react-query'

/**
* If the resolved value is `undefined` that means the user did not select a
* file (i.e. cancelled the selection dialog)
*/
export function useSelectProjectConfigFile() {
return useMutation({
mutationFn: async () => {
return window.runtime.selectFile(['comapeocat'])
},
})
}
6 changes: 4 additions & 2 deletions src/renderer/src/routes/(MapTabs)/_Map.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
createRouter,
} from '@tanstack/react-router'
import { render, screen } from '@testing-library/react'
import { expect, test } from 'vitest'
import { expect, test, vi } from 'vitest'

import { IntlProvider } from '../../contexts/IntlContext'
import { MapLayout } from './_Map'
Expand All @@ -33,8 +33,10 @@ const catchAllRoute = createRoute({
const routeTree = rootRoute.addChildren([mapRoute.addChildren([catchAllRoute])])

const router = createRouter({ routeTree })

test('clicking tabs navigate to correct tab', () => {
vi.mock('../../components/Map', () => ({
Map: () => <div>Mocked Map</div>,
}))
// @ts-expect-error - typings
render(<RouterProvider router={router} />, { wrapper: Wrapper })
const settingsButton = screen.getByText('Settings')
Expand Down
5 changes: 4 additions & 1 deletion src/renderer/src/routes/(MapTabs)/_Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { styled } from '@mui/material/styles'
import { Outlet, createFileRoute } from '@tanstack/react-router'

import { VERY_LIGHT_GREY, WHITE } from '../../colors'
import { Map } from '../../components/Map'
import { Tabs } from '../../components/Tabs'

const Container = styled('div')({
Expand Down Expand Up @@ -35,7 +36,9 @@ export function MapLayout() {
</div>
</Paper>
<Suspense fallback={<CircularProgress />}>
<div>map component here</div>
<div style={{ flex: 1, position: 'relative' }}>
<Map />
</div>
</Suspense>
</Container>
)
Expand Down

0 comments on commit f779fac

Please sign in to comment.