Franknoirot/cmd bar (#328)
* Add XState and naive ActionBar
* Add basic dialog and combobox
* Selectable commands in command bar
* Add a few (broken) file actions
* Home commands
* Add subcommand descriptions, cleanup on navigate
* Refactor: move command creation and types to lib
* Refactor to allow any machine to add commands
* Add auth to command bar, add ability to hide cmds
* Refactor: consolidate theme utilities
* Add settings as machine and command set
* Fix: type tweaks
* Fix: only allow auth to navigate from signin
* Remove zustand-powered settings
* Fix: remove zustand settings from App
* Fix: browser infinite redirect
* Feature: allow commands to be hidden per-platform
* Fix: tsc errors
* Fix: hide default project directory from cmd bar
* Polish: transitions, css tweaks
* Feature: label current value in options settings
* Fix broken debug panel UI
* Refactor: move settings toasts to actions
* Tweak: css rounding
* Fix: set default directory recursion and reload 🐞
* Refactor: move machines to their own directory
* Fix formatting
* @Irev-Dev clean-up catches, import cleanup
This commit is contained in:
@ -1,8 +1,16 @@
|
||||
import { createActorContext } from '@xstate/react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { paths } from '../Router'
|
||||
import { authMachine, TOKEN_PERSIST_KEY } from '../lib/authMachine'
|
||||
import {
|
||||
authCommandBarMeta,
|
||||
authMachine,
|
||||
TOKEN_PERSIST_KEY,
|
||||
} from '../machines/authMachine'
|
||||
import withBaseUrl from '../lib/withBaseURL'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import CommandBar, { CommandsContext } from '../components/CommandBar'
|
||||
import { Command } from '../lib/commands'
|
||||
import useStateMachineCommands from './useStateMachineCommands'
|
||||
|
||||
export const AuthMachineContext = createActorContext(authMachine)
|
||||
|
||||
@ -11,7 +19,19 @@ export const GlobalStateProvider = ({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [commands, internalSetCommands] = useState([] as Command[])
|
||||
const [commandBarOpen, setCommandBarOpen] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const addCommands = (newCommands: Command[]) => {
|
||||
internalSetCommands((prevCommands) => [...newCommands, ...prevCommands])
|
||||
}
|
||||
const removeCommands = (newCommands: Command[]) => {
|
||||
internalSetCommands((prevCommands) =>
|
||||
prevCommands.filter((command) => !newCommands.includes(command))
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthMachineContext.Provider
|
||||
machine={() =>
|
||||
@ -21,12 +41,27 @@ export const GlobalStateProvider = ({
|
||||
navigate(paths.SIGN_IN)
|
||||
logout()
|
||||
},
|
||||
goToIndexPage: () => navigate(paths.INDEX),
|
||||
goToIndexPage: () => {
|
||||
if (window.location.pathname.includes(paths.SIGN_IN)) {
|
||||
navigate(paths.INDEX)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{children}
|
||||
<CommandsContext.Provider
|
||||
value={{
|
||||
commands,
|
||||
addCommands,
|
||||
removeCommands,
|
||||
commandBarOpen,
|
||||
setCommandBarOpen,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<CommandBar />
|
||||
</CommandsContext.Provider>
|
||||
</AuthMachineContext.Provider>
|
||||
)
|
||||
}
|
||||
@ -52,3 +87,18 @@ export function logout() {
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
|
||||
export function AuthMachineCommandProvider(props: React.PropsWithChildren<{}>) {
|
||||
const [state, send] = AuthMachineContext.useActor()
|
||||
const { commands } = useContext(CommandsContext)
|
||||
|
||||
useStateMachineCommands({
|
||||
state,
|
||||
send,
|
||||
commands,
|
||||
commandBarMeta: authCommandBarMeta,
|
||||
owner: 'auth',
|
||||
})
|
||||
|
||||
return <>{props.children}</>
|
||||
}
|
||||
|
||||
42
src/hooks/useStateMachineCommands.ts
Normal file
42
src/hooks/useStateMachineCommands.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { useContext, useEffect } from 'react'
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
import { Command, CommandBarMeta, createMachineCommand } from '../lib/commands'
|
||||
import { CommandsContext } from '../components/CommandBar'
|
||||
|
||||
interface UseStateMachineCommandsArgs<T extends AnyStateMachine> {
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
commandBarMeta?: CommandBarMeta
|
||||
commands: Command[]
|
||||
owner: string
|
||||
}
|
||||
|
||||
export default function useStateMachineCommands<T extends AnyStateMachine>({
|
||||
state,
|
||||
send,
|
||||
commandBarMeta,
|
||||
owner,
|
||||
}: UseStateMachineCommandsArgs<T>) {
|
||||
const { addCommands, removeCommands } = useContext(CommandsContext)
|
||||
|
||||
useEffect(() => {
|
||||
const newCommands = state.nextEvents
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.map((type) =>
|
||||
createMachineCommand<T>({
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
commandBarMeta,
|
||||
owner,
|
||||
})
|
||||
)
|
||||
.filter((c) => c !== null) as Command[]
|
||||
|
||||
addCommands(newCommands)
|
||||
|
||||
return () => {
|
||||
removeCommands(newCommands)
|
||||
}
|
||||
}, [state])
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from '../useStore'
|
||||
import { parse } from 'toml'
|
||||
import {
|
||||
createDir,
|
||||
BaseDirectory,
|
||||
readDir,
|
||||
readTextFile,
|
||||
} from '@tauri-apps/api/fs'
|
||||
|
||||
export const useTauriBoot = () => {
|
||||
const { defaultDir, setDefaultDir, setHomeMenuItems } = useStore((s) => ({
|
||||
defaultDir: s.defaultDir,
|
||||
setDefaultDir: s.setDefaultDir,
|
||||
setHomeMenuItems: s.setHomeMenuItems,
|
||||
}))
|
||||
useEffect(() => {
|
||||
const isTauri = (window as any).__TAURI__
|
||||
if (!isTauri) return
|
||||
const run = async () => {
|
||||
if (!defaultDir.base) {
|
||||
createDir('puffin-projects/example', {
|
||||
dir: BaseDirectory.Home,
|
||||
recursive: true,
|
||||
})
|
||||
setDefaultDir({
|
||||
base: BaseDirectory.Home,
|
||||
dir: 'puffin-projects',
|
||||
})
|
||||
} else {
|
||||
const directoryResult = await readDir(defaultDir.dir, {
|
||||
dir: defaultDir.base,
|
||||
recursive: true,
|
||||
})
|
||||
const puffinProjects = directoryResult.filter(
|
||||
(file) =>
|
||||
!file?.name?.startsWith('.') &&
|
||||
file?.children?.find((child) => child?.name === 'wax.toml')
|
||||
)
|
||||
|
||||
const tomlFiles = await Promise.all(
|
||||
puffinProjects.map(async (file) => {
|
||||
const parsedToml = parse(
|
||||
await readTextFile(`${file.path}/wax.toml`, {
|
||||
dir: defaultDir.base,
|
||||
})
|
||||
)
|
||||
const mainPath = parsedToml?.package?.main
|
||||
const projectName = parsedToml?.package?.name
|
||||
return {
|
||||
file,
|
||||
mainPath,
|
||||
projectName,
|
||||
}
|
||||
})
|
||||
)
|
||||
setHomeMenuItems(
|
||||
tomlFiles.map(({ file, mainPath, projectName }) => ({
|
||||
name: projectName,
|
||||
path: mainPath ? `${file.path}/${mainPath}` : file.path,
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
run()
|
||||
}, [])
|
||||
}
|
||||
Reference in New Issue
Block a user