This commit is contained in:
49lf
2024-10-17 18:55:20 -04:00
parent 713886b274
commit 570d159c29
8 changed files with 110 additions and 44 deletions

View File

@ -23,11 +23,24 @@ import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu' import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle' import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher' import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
import { useCommandsContext } from 'hooks/useCommandsContext'
export function App() { export function App() {
const { project, file } = useLoaderData() as IndexLoaderData const { project, file } = useLoaderData() as IndexLoaderData
const { commandBarSend } = useCommandsContext()
// Keep a lookout for a URL query string that invokes the 'import file from URL' command // Keep a lookout for a URL query string that invokes the 'import file from URL' command
useCreateFileLinkQuery() useCreateFileLinkQuery((argDefaultValues) => {
commandBarSend({
type: 'Find and select command',
data: {
groupId: 'projects',
name: 'Import file from URL',
argDefaultValues,
},
})
})
useRefreshSettings(PATHS.FILE + 'SETTINGS') useRefreshSettings(PATHS.FILE + 'SETTINGS')
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()

View File

@ -43,6 +43,7 @@ import { useMemo } from 'react'
import { AppStateProvider } from 'AppState' import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider' import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
import { ProtocolHandler } from 'components/ProtocolHandler'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
@ -53,6 +54,7 @@ const router = createRouter([
/* Make sure auth is the outermost provider or else we will have /* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */ * inefficient re-renders, use the react profiler to see. */
element: ( element: (
<ProtocolHandler>
<CommandBarProvider> <CommandBarProvider>
<SettingsAuthProvider> <SettingsAuthProvider>
<LspProvider> <LspProvider>
@ -66,6 +68,7 @@ const router = createRouter([
</LspProvider> </LspProvider>
</SettingsAuthProvider> </SettingsAuthProvider>
</CommandBarProvider> </CommandBarProvider>
</ProtocolHandler>
), ),
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [

View File

@ -0,0 +1,41 @@
import { getSystemTheme } from 'lib/theme'
import { ZOO_STUDIO_PROTOCOL } from 'lib/link'
import { useState } from 'react'
import { useCreateFileLinkQuery, CreateFileSchemaMethodOptional } from 'hooks/useCreateFileLinkQueryWatcher'
import { isDesktop } from 'lib/isDesktop'
export const ProtocolHandler = (props: { children: ReactNode } ) => {
const [hasCustomProtocolScheme, setHasCustomProtocolScheme] = useState(false)
const [hasAsked, setHasAsked] = useState(false)
useCreateFileLinkQuery((args) => {
if (hasAsked) return
window.location.href = `zoo-studio:${JSON.stringify(args)}`
setHasAsked(true)
setHasCustomProtocolScheme(true)
})
const continueToWebApp = () => {
setHasCustomProtocolScheme(false)
}
const pathLogomarkSvg = `${isDesktop() ? '.' : ''}/zma-logomark.svg`
return hasCustomProtocolScheme ? <div className="flex items-center justify-center h-full">
<div style={{
background: `url(${pathLogomarkSvg})`,
backgroundRepeat: 'repeat',
backgroundSize: '100%',
transform: 'rotate(45deg)',
filter: `brightness(${getSystemTheme() === 'light' ? 97 : 0}%)`,
height: '100%',
width: '100%',
position: 'absolute',
}} className="flex items-center justify-center h-full"
></div>
<div className="flex items-center justify-center h-full" style={{ zIndex: 10 }}>
<div className="p-4 mx-auto border rounded rounded-tl-none shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70">
<span>Loading model into Zoo Design Studio,&nbsp;</span><a className="cursor-pointer" onClick={continueToWebApp}>or continue to the web app</a>
</div>
</div>
</div> : props.children
}

View File

@ -1,24 +1,30 @@
import { base64ToString } from 'lib/base64' import { base64ToString } from 'lib/base64'
import { CREATE_FILE_URL_PARAM, DEFAULT_FILE_NAME } from 'lib/constants' import { CREATE_FILE_URL_PARAM, DEFAULT_FILE_NAME } from 'lib/constants'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useLocation, useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
import { useCommandsContext } from './useCommandsContext'
import { useSettingsAuthContext } from './useSettingsAuthContext' import { useSettingsAuthContext } from './useSettingsAuthContext'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { FileLinkParams } from 'lib/createFileLink' import { FileLinkParams } from 'lib/createFileLink'
import { ProjectsCommandSchema } from 'lib/commandBarConfigs/projectsCommandConfig' import { ProjectsCommandSchema } from 'lib/commandBarConfigs/projectsCommandConfig'
import { baseUnitsUnion } from 'lib/settings/settingsTypes' import { baseUnitsUnion } from 'lib/settings/settingsTypes'
// For initializing the command arguments, we actually want `method` to be undefined
// so that we don't skip it in the command palette.
export type CreateFileSchemaMethodOptional = Omit<
ProjectsCommandSchema['Import file from URL'],
'method'
> & {
method?: 'newProject' | 'existingProject'
}
/** /**
* companion to createFileLink. This hook runs an effect on mount that * companion to createFileLink. This hook runs an effect on mount that
* checks the URL for the CREATE_FILE_URL_PARAM and triggers the "Create file" * checks the URL for the CREATE_FILE_URL_PARAM and triggers the "Create file"
* command if it is present, loading the command's default values from the other * command if it is present, loading the command's default values from the other
* URL parameters. * URL parameters.
*/ */
export function useCreateFileLinkQuery() { export function useCreateFileLinkQuery(callback: (args: CreateFileSchemaMethodOptional) => void) {
const location = useLocation()
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const { commandBarSend } = useCommandsContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
useEffect(() => { useEffect(() => {
@ -44,15 +50,6 @@ export function useCreateFileLinkQuery() {
} }
console.log('createFileParam', { ...params, location }) console.log('createFileParam', { ...params, location })
// For initializing the command arguments, we actually want `method` to be undefined
// so that we don't skip it in the command palette.
type CreateFileSchemaMethodOptional = Omit<
ProjectsCommandSchema['Import file from URL'],
'method'
> & {
method?: 'newProject' | 'existingProject'
}
const argDefaultValues: CreateFileSchemaMethodOptional = { const argDefaultValues: CreateFileSchemaMethodOptional = {
name: params.name name: params.name
? isDesktop() ? isDesktop()
@ -66,14 +63,7 @@ export function useCreateFileLinkQuery() {
method: isDesktop() ? undefined : 'existingProject', method: isDesktop() ? undefined : 'existingProject',
} }
commandBarSend({ callback(argDefaultValues)
type: 'Find and select command',
data: {
groupId: 'projects',
name: 'Import file from URL',
argDefaultValues,
},
})
} }
}, [searchParams]) }, [searchParams])
} }

View File

@ -10,14 +10,32 @@ export interface FileLinkParams {
/** /**
* Given a file's code, name, and units, creates shareable link * Given a file's code, name, and units, creates shareable link
* TODO: make the app respect this link
*/ */
export function createFileLink({ code, name, units }: FileLinkParams) { export async function createFileLink({ code, name, units }: FileLinkParams) {
const token = await getAndSyncStoredToken(input)
const urlUserShortlinks = withBaseURL('/user/shortlinks')
const origin = globalThis.window.location.origin const origin = globalThis.window.location.origin
return new URL(
if (!token && isDesktop()) return Promise.reject(new Error('No token found'))
let headers = {
'Content-Type': 'application/json',
}
if (token) headers['Authorization'] = `Bearer ${token}`
const urlFileToShare = new URL(
`/?${CREATE_FILE_URL_PARAM}&name=${encodeURIComponent( `/?${CREATE_FILE_URL_PARAM}&name=${encodeURIComponent(
name name
)}&units=${units}&code=${encodeURIComponent(stringToBase64(code))}`, )}&units=${units}&code=${encodeURIComponent(stringToBase64(code))}`,
origin origin
).href ).toString()
const resp = await fetch(urlUserShortlinks, {
headers,
body: JSON.stringify({ url: urlFileToShare }),
})
const shortlink = await resp.json()
return shortlink.url
} }

3
src/lib/link.ts Normal file
View File

@ -0,0 +1,3 @@
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio'

View File

@ -21,6 +21,7 @@ import minimist from 'minimist'
import getCurrentProjectFile from 'lib/getCurrentProjectFile' import getCurrentProjectFile from 'lib/getCurrentProjectFile'
import os from 'node:os' import os from 'node:os'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { ZOO_STUDIO_PROTOCOL } from 'lib/link'
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
@ -52,9 +53,8 @@ if (require('electron-squirrel-startup')) {
app.quit() app.quit()
} }
const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
/// Register our application to handle all "zoo-studio://" protocols. /// Register our application to handle all "zoo-studio:" protocols.
if (process.defaultApp) { if (process.defaultApp) {
if (process.argv.length >= 2) { if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [ app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [

View File

@ -28,8 +28,6 @@ import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
// This route only opens in the desktop context for now, // This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types. // as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => { const Home = () => {
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
useCreateFileLinkQuery()
const { state, send } = useProjectsContext() const { state, send } = useProjectsContext()
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)