Get primary user flow working on desktop
This commit is contained in:
@ -8,3 +8,5 @@ VITE_KC_SKIP_AUTH=false
|
|||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||||
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||||
|
# Add a prod token if you want to use the share URL feature in local dev
|
||||||
|
#VITE_KC_PROD_TOKEN="your token from prod.zoo.dev should go in .env.development.local"
|
1
interface.d.ts
vendored
1
interface.d.ts
vendored
@ -62,6 +62,7 @@ export interface IElectronAPI {
|
|||||||
TEST_SETTINGS_FILE_KEY: string
|
TEST_SETTINGS_FILE_KEY: string
|
||||||
IS_PLAYWRIGHT: string
|
IS_PLAYWRIGHT: string
|
||||||
VITE_KC_DEV_TOKEN: string
|
VITE_KC_DEV_TOKEN: string
|
||||||
|
VITE_KC_PROD_TOKEN: string
|
||||||
VITE_KC_API_WS_MODELING_URL: string
|
VITE_KC_API_WS_MODELING_URL: string
|
||||||
VITE_KC_API_BASE_URL: string
|
VITE_KC_API_BASE_URL: string
|
||||||
VITE_KC_SITE_BASE_URL: string
|
VITE_KC_SITE_BASE_URL: string
|
||||||
|
@ -47,7 +47,6 @@ import { AppStateProvider } from 'AppState'
|
|||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { RouteProvider } from 'components/RouteProvider'
|
import { RouteProvider } from 'components/RouteProvider'
|
||||||
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
|
||||||
|
|
||||||
@ -59,7 +58,6 @@ 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>
|
||||||
<RouteProvider>
|
<RouteProvider>
|
||||||
<SettingsAuthProvider>
|
<SettingsAuthProvider>
|
||||||
@ -77,7 +75,6 @@ const router = createRouter([
|
|||||||
</SettingsAuthProvider>
|
</SettingsAuthProvider>
|
||||||
</RouteProvider>
|
</RouteProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
</ProtocolHandler>
|
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
children: [
|
children: [
|
||||||
|
@ -18,6 +18,8 @@ import Tooltip from './Tooltip'
|
|||||||
import { createFileLink } from 'lib/createFileLink'
|
import { createFileLink } from 'lib/createFileLink'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { DEV, VITE_KC_PROD_TOKEN } from 'env'
|
||||||
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -189,21 +191,41 @@ function ProjectMenuPopover({
|
|||||||
Element: 'button',
|
Element: 'button',
|
||||||
children: 'Share link to file',
|
children: 'Share link to file',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
if (!auth.context.token) {
|
/**
|
||||||
|
* We don't have a dev shortlink API service,
|
||||||
|
* so we need to hit the prod API even in local dev.
|
||||||
|
* This override allows us to shim in an environment variable
|
||||||
|
* for the prod token.
|
||||||
|
*/
|
||||||
|
const token = DEV ? VITE_KC_PROD_TOKEN : auth.context.token
|
||||||
|
if (DEV && !VITE_KC_PROD_TOKEN) {
|
||||||
|
toast.error(
|
||||||
|
'You need to set a prod token in your environment to share a file in development.',
|
||||||
|
{
|
||||||
|
duration: 5000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
} else if (!token) {
|
||||||
toast.error('You need to be signed in to share a file.', {
|
toast.error('You need to be signed in to share a file.', {
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const shareUrl = await createFileLink(auth.context.token, {
|
const shareUrl = await createFileLink(token, {
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: file?.name || '',
|
name: project?.name || '',
|
||||||
units: settings.context.modeling.defaultUnit.current,
|
units: settings.context.modeling.defaultUnit.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(shareUrl)
|
if (err(shareUrl)) {
|
||||||
|
toast.error(shareUrl.message, {
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await globalThis.navigator.clipboard.writeText(shareUrl)
|
await globalThis.navigator.clipboard.writeText(shareUrl.url)
|
||||||
toast.success(
|
toast.success(
|
||||||
'Link copied to clipboard. Anyone who clicks this link will get a copy of this file. Share carefully!',
|
'Link copied to clipboard. Anyone who clicks this link will get a copy of this file. Share carefully!',
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,7 @@ export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
|
|||||||
export const VITE_KC_CONNECTION_TIMEOUT_MS =
|
export const VITE_KC_CONNECTION_TIMEOUT_MS =
|
||||||
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
|
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
|
||||||
export const VITE_KC_DEV_TOKEN = env.VITE_KC_DEV_TOKEN as string | undefined
|
export const VITE_KC_DEV_TOKEN = env.VITE_KC_DEV_TOKEN as string | undefined
|
||||||
|
export const VITE_KC_PROD_TOKEN = env.VITE_KC_PROD_TOKEN as string | undefined
|
||||||
export const PROD = env.PROD as string | undefined
|
export const PROD = env.PROD as string | undefined
|
||||||
export const TEST = env.TEST as string | undefined
|
export const TEST = env.TEST as string | undefined
|
||||||
export const DEV = env.DEV as string | undefined
|
export const DEV = env.DEV as string | undefined
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { postUserShortlink } from 'lib/desktop'
|
|
||||||
import { CREATE_FILE_URL_PARAM } from './constants'
|
import { CREATE_FILE_URL_PARAM } from './constants'
|
||||||
import { stringToBase64 } from './base64'
|
import { stringToBase64 } from './base64'
|
||||||
import withBaseURL from 'lib/withBaseURL'
|
import { ZOO_STUDIO_PROTOCOL } from './link'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
|
||||||
|
|
||||||
export interface FileLinkParams {
|
export interface FileLinkParams {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
@ -13,41 +10,44 @@ 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: update the return type to use TS library after its updated
|
||||||
*/
|
*/
|
||||||
export async function createFileLink(
|
export async function createFileLink(
|
||||||
token: string,
|
token: string,
|
||||||
{ code, name, units }: FileLinkParams
|
{ code, name, units }: FileLinkParams
|
||||||
) {
|
): Promise<Error | { key: string; url: string }> {
|
||||||
let urlUserShortlinks = withBaseURL('/users/shortlinks')
|
|
||||||
|
|
||||||
// During development, the "handler" needs to first be the web app version,
|
// During development, the "handler" needs to first be the web app version,
|
||||||
// which exists on localhost:3000 typically.
|
// which exists on localhost:3000 typically.
|
||||||
let origin = 'http://localhost:3000'
|
let origin = 'http://localhost:3000'
|
||||||
|
|
||||||
let urlFileToShare = new URL(
|
let 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
|
||||||
).toString()
|
).toString()
|
||||||
|
|
||||||
// Remove this monkey patching
|
/**
|
||||||
function fixTheBrokenShitUntilItsFixedOnDev() {
|
* We don't use our `withBaseURL` function here because
|
||||||
urlUserShortlinks = urlUserShortlinks.replace(
|
* there is no URL shortener service in the dev API.
|
||||||
'https://api.dev.zoo.dev',
|
*/
|
||||||
'https://api.zoo.dev'
|
const response = await fetch('https://api.zoo.dev/user/shortlinks', {
|
||||||
)
|
|
||||||
console.log(urlUserShortlinks)
|
|
||||||
}
|
|
||||||
|
|
||||||
fixTheBrokenShitUntilItsFixedOnDev()
|
|
||||||
|
|
||||||
return await fetch(urlUserShortlinks, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-type': 'application/json',
|
'Content-type': 'application/json',
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ url: urlFileToShare }),
|
body: JSON.stringify({
|
||||||
}).then((resp) => resp.json())
|
url: urlFileToShare,
|
||||||
|
// In future we can support org-scoped and password-protected shortlinks here
|
||||||
|
// https://zoo.dev/docs/api/shortlinks/create-a-shortlink-for-a-user?lang=typescript
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
console.log('response', response)
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json()
|
||||||
|
return new Error(`Failed to create shortlink: ${error.message}`)
|
||||||
|
} else {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
|
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio:'
|
||||||
|
32
src/main.ts
32
src/main.ts
@ -67,7 +67,7 @@ if (process.defaultApp) {
|
|||||||
// Must be done before ready event.
|
// Must be done before ready event.
|
||||||
registerStartupListeners()
|
registerStartupListeners()
|
||||||
|
|
||||||
const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
|
const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
|
||||||
let newWindow
|
let newWindow
|
||||||
|
|
||||||
if (reuse) {
|
if (reuse) {
|
||||||
@ -92,12 +92,27 @@ const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pathIsCustomProtocolLink = pathToOpen?.startsWith(ZOO_STUDIO_PROTOCOL) ?? false
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
newWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL).catch(reportRejection)
|
const filteredPath = pathToOpen ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) : ''
|
||||||
|
const fullHashBasedUrl = `${MAIN_WINDOW_VITE_DEV_SERVER_URL}/#/${filteredPath}`
|
||||||
|
newWindow.loadURL(fullHashBasedUrl).catch(reportRejection)
|
||||||
} else {
|
} else {
|
||||||
console.log('Loading from file', filePath)
|
if (pathIsCustomProtocolLink && pathToOpen) {
|
||||||
getProjectPathAtStartup(filePath)
|
// We're trying to open a custom protocol link
|
||||||
|
const filteredPath = pathToOpen ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) : ''
|
||||||
|
const startIndex = path.join(
|
||||||
|
__dirname,
|
||||||
|
`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`
|
||||||
|
)
|
||||||
|
newWindow.loadFile(startIndex, {
|
||||||
|
hash: filteredPath,
|
||||||
|
}).catch(reportRejection)
|
||||||
|
} else {
|
||||||
|
// otherwise we're trying to open a local file from the command line
|
||||||
|
getProjectPathAtStartup(pathToOpen)
|
||||||
.then(async (projectPath) => {
|
.then(async (projectPath) => {
|
||||||
const startIndex = path.join(
|
const startIndex = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -109,8 +124,6 @@ const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Loading file', projectPath)
|
|
||||||
|
|
||||||
const fullUrl = `/file/${encodeURIComponent(projectPath)}`
|
const fullUrl = `/file/${encodeURIComponent(projectPath)}`
|
||||||
console.log('Full URL', fullUrl)
|
console.log('Full URL', fullUrl)
|
||||||
|
|
||||||
@ -120,6 +133,7 @@ const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
|
|||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
// mainWindow.webContents.openDevTools()
|
// mainWindow.webContents.openDevTools()
|
||||||
@ -467,12 +481,6 @@ function registerStartupListeners() {
|
|||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
console.log('open-url', url)
|
|
||||||
fs.writeFileSync(
|
|
||||||
'/Users/frankjohnson/open-url.txt',
|
|
||||||
`at ${new Date().toLocaleTimeString()} opened url: ${url}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// If we have a mainWindow, lets open another window.
|
// If we have a mainWindow, lets open another window.
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
createWindow(url)
|
createWindow(url)
|
||||||
|
@ -188,6 +188,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
'VITE_KC_SKIP_AUTH',
|
'VITE_KC_SKIP_AUTH',
|
||||||
'VITE_KC_CONNECTION_TIMEOUT_MS',
|
'VITE_KC_CONNECTION_TIMEOUT_MS',
|
||||||
'VITE_KC_DEV_TOKEN',
|
'VITE_KC_DEV_TOKEN',
|
||||||
|
'VITE_KC_PROD_TOKEN',
|
||||||
'IS_PLAYWRIGHT',
|
'IS_PLAYWRIGHT',
|
||||||
|
|
||||||
// Really we shouldn't use these and our code should use NODE_ENV
|
// Really we shouldn't use these and our code should use NODE_ENV
|
||||||
|
Reference in New Issue
Block a user