Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Nov 22, 2023
1 parent 7aa0e21 commit 141672c
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 389 deletions.
62 changes: 44 additions & 18 deletions webui/src/App.jsx → webui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { ConnectionsPage } from './Connections'
import { ButtonsPage } from './Buttons'
import { ContextData } from './ContextData'
import { CloudPage } from './CloudPage'
import { WizardModal, WIZARD_CURRENT_VERSION } from './Wizard'
import { WizardModal, WIZARD_CURRENT_VERSION, WizardModalRef } from './Wizard'
import { Navigate, useLocation } from 'react-router-dom'
import { useIdleTimer } from 'react-idle-timer'
import { ImportExport } from './ImportExport'
Expand All @@ -58,7 +58,7 @@ export default function App() {
const onConnected = () => {
setWasConnected((wasConnected0) => {
if (wasConnected0) {
window.location.reload(true)
window.location.reload()
} else {
setConnected(true)
}
Expand Down Expand Up @@ -162,7 +162,14 @@ export default function App() {
)
}

function AppMain({ connected, loadingComplete, loadingProgress, buttonGridHotPress }) {
interface AppMainProps {
connected: boolean
loadingComplete: boolean
loadingProgress: number
buttonGridHotPress: boolean
}

function AppMain({ connected, loadingComplete, loadingProgress, buttonGridHotPress }: AppMainProps) {
const config = useContext(UserConfigContext)

const [showSidebar, setShowSidebar] = useState(true)
Expand All @@ -178,10 +185,10 @@ function AppMain({ connected, loadingComplete, loadingProgress, buttonGridHotPre
}
}, [canLock])

const wizardModal = useRef()
const wizardModal = useRef<WizardModalRef>(null)
const showWizard = useCallback(() => {
if (unlocked) {
wizardModal.current.show()
wizardModal.current?.show()
}
}, [unlocked])

Expand Down Expand Up @@ -229,16 +236,21 @@ function AppMain({ connected, loadingComplete, loadingProgress, buttonGridHotPre
)
}

interface IdleTimerWrapperProps {
setLocked: () => void
timeoutMinutes: number
}

/** Wrap the idle timer in its own component, as it invalidates every second */
function IdleTimerWrapper({ setLocked, timeoutMinutes }) {
function IdleTimerWrapper({ setLocked, timeoutMinutes }: IdleTimerWrapperProps) {
const notifier = useContext(NotifierContext)

const [, setIdleTimeout] = useState(null)
const [, setIdleTimeout] = useState<NodeJS.Timeout | null>(null)

const TOAST_ID = 'SESSION_TIMEOUT_TOAST'
const TOAST_DURATION = 45 * 1000

const handleOnActive = (event) => {
const handleOnActive = () => {
// user is now active, abort the lock
setIdleTimeout((v) => {
if (v) {
Expand All @@ -253,15 +265,15 @@ function IdleTimerWrapper({ setLocked, timeoutMinutes }) {
return null
})
}
const handleAction = (event) => {
const handleAction = () => {
// setShouldShowIdleWarning(false)
}

const handleIdle = () => {
notifier.current.show(
notifier.current?.show(
'Session timeout',
'Your session is about to timeout, and Companion will be locked',
null,
undefined,
TOAST_ID
)

Expand Down Expand Up @@ -305,10 +317,15 @@ function IdleTimerWrapper({ setLocked, timeoutMinutes }) {
}
})

return ''
return null
}

interface AppLoadingProps {
progress: number
connected: boolean
}

function AppLoading({ progress, connected }) {
function AppLoading({ progress, connected }: AppLoadingProps) {
const message = connected ? 'Syncing' : 'Connecting'
return (
<CContainer fluid className="fadeIn loading">
Expand All @@ -325,7 +342,11 @@ function AppLoading({ progress, connected }) {
)
}

function AppAuthWrapper({ setUnlocked }) {
interface AppAuthWrapperProps {
setUnlocked: () => void
}

function AppAuthWrapper({ setUnlocked }: AppAuthWrapperProps) {
const config = useContext(UserConfigContext)

const [password, setPassword] = useState('')
Expand All @@ -341,7 +362,7 @@ function AppAuthWrapper({ setUnlocked }) {
e.preventDefault()

setPassword((currentPassword) => {
if (currentPassword === config.admin_password) {
if (currentPassword === config?.admin_password) {
setShowError(false)
setUnlocked()
return ''
Expand All @@ -354,7 +375,7 @@ function AppAuthWrapper({ setUnlocked }) {

return false
},
[config.admin_password, setUnlocked]
[config?.admin_password, setUnlocked]
)

return (
Expand All @@ -370,6 +391,7 @@ function AppAuthWrapper({ setUnlocked }) {
value={password}
onChange={(e) => passwordChanged(e.currentTarget.value)}
invalid={showError}
readOnly={!config}
/>
<CButton type="submit" color="primary">
Unlock
Expand All @@ -382,10 +404,14 @@ function AppAuthWrapper({ setUnlocked }) {
)
}

function AppContent({ buttonGridHotPress }) {
interface AppContentProps {
buttonGridHotPress: boolean
}

function AppContent({ buttonGridHotPress }: AppContentProps) {
const routerLocation = useLocation()
let hasMatchedPane = false
const getClassForPane = (prefix) => {
const getClassForPane = (prefix: string) => {
// Require the path to be the same, or to be a prefix with a sub-route
if (routerLocation.pathname.startsWith(prefix + '/') || routerLocation.pathname === prefix) {
hasMatchedPane = true
Expand Down
4 changes: 3 additions & 1 deletion webui/src/Components/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export interface NotificationsManagerRef {
close(messageId: string): void
}

type NotificationsManagerProps = Record<string, never>
interface NotificationsManagerProps {
// Nothing
}

interface CurrentToast {
id: string
Expand Down
120 changes: 74 additions & 46 deletions webui/src/ContextData.jsx → webui/src/ContextData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,49 @@ import {
RecentActionsContext,
RecentFeedbacksContext,
} from './util'
import { NotificationsManager } from './Components/Notifications'
import { NotificationsManager, NotificationsManagerRef } from './Components/Notifications'
import { cloneDeep } from 'lodash-es'
import jsonPatch from 'fast-json-patch'
import jsonPatch, { Operation as JsonPatchOperation } from 'fast-json-patch'
import { useUserConfigSubscription } from './Hooks/useUserConfigSubscription'
import { usePagesInfoSubscription } from './Hooks/usePagesInfoSubscription'
import type { ClientConnectionConfig, ClientEventDefinition, ModuleDisplayInfo } from '@companion/shared/Model/Common'
import { InternalActionDefinition, InternalFeedbackDefinition } from '@companion/shared/Model/Options'
import { AllVariableDefinitions, ModuleVariableDefinitions } from '@companion/shared/Model/Variables'
import { CustomVariablesModel } from '@companion/shared/Model/CustomVariableModel'
import { ClientDevicesList } from '@companion/shared/Model/Surfaces'
import { ClientTriggerData } from '@companion/shared/Model/TriggerModel'

interface ContextDataProps {
children: (progressPercent: number, loadingComplete: boolean) => React.JSX.Element | React.JSX.Element[]
}

export function ContextData({ children }) {
export function ContextData({ children }: ContextDataProps) {
const socket = useContext(SocketContext)

const [eventDefinitions, setEventDefinitions] = useState(null)
const [connections, setConnections] = useState(null)
const [modules, setModules] = useState(null)
const [actionDefinitions, setActionDefinitions] = useState(null)
const [feedbackDefinitions, setFeedbackDefinitions] = useState(null)
const [variableDefinitions, setVariableDefinitions] = useState(null)
const [customVariables, setCustomVariables] = useState(null)
const [surfaces, setSurfaces] = useState(null)
const [triggers, setTriggers] = useState(null)

const [recentActions, setRecentActions] = useState(() => {
const [eventDefinitions, setEventDefinitions] = useState<Record<string, ClientEventDefinition | undefined> | null>(
null
)
const [connections, setConnections] = useState<Record<string, ClientConnectionConfig> | null>(null)
const [modules, setModules] = useState<Record<string, ModuleDisplayInfo> | null>(null)
const [actionDefinitions, setActionDefinitions] = useState<Record<
string,
Record<string, InternalActionDefinition | undefined> | undefined
> | null>(null)
const [feedbackDefinitions, setFeedbackDefinitions] = useState<Record<
string,
Record<string, InternalFeedbackDefinition | undefined> | undefined
> | null>(null)
const [variableDefinitions, setVariableDefinitions] = useState<AllVariableDefinitions | null>(null)
const [customVariables, setCustomVariables] = useState<CustomVariablesModel | null>(null)
const [surfaces, setSurfaces] = useState<ClientDevicesList | null>(null)
const [triggers, setTriggers] = useState<Record<string, ClientTriggerData | undefined> | null>(null)

const [recentActions, setRecentActions] = useState<string[]>(() => {
const recent = JSON.parse(window.localStorage.getItem('recent_actions') || '[]')
return Array.isArray(recent) ? recent : []
})

const trackRecentAction = useCallback((actionType) => {
const trackRecentAction = useCallback((actionType: string) => {
setRecentActions((existing) => {
const newActions = [actionType, ...existing.filter((v) => v !== actionType)].slice(0, 20)

Expand All @@ -60,12 +78,12 @@ export function ContextData({ children }) {
[recentActions, trackRecentAction]
)

const [recentFeedbacks, setRecentFeedbacks] = useState(() => {
const [recentFeedbacks, setRecentFeedbacks] = useState<string[]>(() => {
const recent = JSON.parse(window.localStorage.getItem('recent_feedbacks') || '[]')
return Array.isArray(recent) ? recent : []
})

const trackRecentFeedback = useCallback((feedbackType) => {
const trackRecentFeedback = useCallback((feedbackType: string) => {
setRecentFeedbacks((existing) => {
const newFeedbacks = [feedbackType, ...existing.filter((v) => v !== feedbackType)].slice(0, 20)

Expand All @@ -82,10 +100,10 @@ export function ContextData({ children }) {
[recentFeedbacks, trackRecentFeedback]
)

const completeVariableDefinitions = useMemo(() => {
const completeVariableDefinitions = useMemo<AllVariableDefinitions>(() => {
if (variableDefinitions) {
// Generate definitions for all the custom variables
const customVariableDefinitions = {}
const customVariableDefinitions: ModuleVariableDefinitions = {}
for (const [id, info] of Object.entries(customVariables || {})) {
customVariableDefinitions[`custom_${id}`] = {
label: info.description,
Expand All @@ -100,7 +118,7 @@ export function ContextData({ children }) {
},
}
} else {
return null
return {}
}
}, [customVariables, variableDefinitions])

Expand Down Expand Up @@ -153,22 +171,30 @@ export function ContextData({ children }) {
console.error('Failed to load custom values list', e)
})

const updateVariableDefinitions = (label, patch) => {
setVariableDefinitions((oldDefinitions) => applyPatchOrReplaceSubObject(oldDefinitions, label, patch, {}))
const updateVariableDefinitions = (label: string, patch: JsonPatchOperation[]) => {
setVariableDefinitions(
(oldDefinitions) =>
oldDefinitions &&
applyPatchOrReplaceSubObject<ModuleVariableDefinitions | undefined>(oldDefinitions, label, patch, {})
)
}
const updateFeedbackDefinitions = (id, patch) => {
setFeedbackDefinitions((oldDefinitions) => applyPatchOrReplaceSubObject(oldDefinitions, id, patch, {}))
const updateFeedbackDefinitions = (id: string, patch: JsonPatchOperation[]) => {
setFeedbackDefinitions(
(oldDefinitions) => oldDefinitions && applyPatchOrReplaceSubObject(oldDefinitions, id, patch, {})
)
}
const updateActionDefinitions = (id, patch) => {
setActionDefinitions((oldDefinitions) => applyPatchOrReplaceSubObject(oldDefinitions, id, patch, {}))
const updateActionDefinitions = (id: string, patch: JsonPatchOperation[]) => {
setActionDefinitions(
(oldDefinitions) => oldDefinitions && applyPatchOrReplaceSubObject(oldDefinitions, id, patch, {})
)
}

const updateCustomVariables = (patch) => {
setCustomVariables((oldVariables) => applyPatchOrReplaceObject(oldVariables, patch))
const updateCustomVariables = (patch: JsonPatchOperation[]) => {
setCustomVariables((oldVariables) => oldVariables && applyPatchOrReplaceObject(oldVariables, patch))
}
const updateTriggers = (controlId, patch) => {
const updateTriggers = (controlId: string, patch: JsonPatchOperation[]) => {
console.log('trigger', controlId, patch)
setTriggers((oldTriggers) => applyPatchOrReplaceSubObject(oldTriggers, controlId, patch, {}))
setTriggers((oldTriggers) => oldTriggers && applyPatchOrReplaceSubObject(oldTriggers, controlId, patch, null))
}

socketEmitPromise(socket, 'connections:subscribe', [])
Expand All @@ -180,21 +206,21 @@ export function ContextData({ children }) {
setConnections(null)
})

const patchInstances = (patch) => {
const patchInstances = (patch: JsonPatchOperation[] | false) => {
setConnections((oldConnections) => {
if (patch === false) {
return false
return {}
} else {
return jsonPatch.applyPatch(cloneDeep(oldConnections) || {}, patch).newDocument
}
})
}
socket.on('connections:patch', patchInstances)

const patchModules = (patch) => {
const patchModules = (patch: JsonPatchOperation[] | false) => {
setModules((oldModules) => {
if (patch === false) {
return false
return {}
} else {
return jsonPatch.applyPatch(cloneDeep(oldModules) || {}, patch).newDocument
}
Expand All @@ -216,9 +242,9 @@ export function ContextData({ children }) {
console.error('Failed to load surfaces', e)
})

const patchSurfaces = (patch) => {
const patchSurfaces = (patch: JsonPatchOperation[]) => {
setSurfaces((oldSurfaces) => {
return jsonPatch.applyPatch(cloneDeep(oldSurfaces) || {}, patch).newDocument
return oldSurfaces && jsonPatch.applyPatch(cloneDeep(oldSurfaces) || {}, patch).newDocument
})
}
socket.on('surfaces:patch', patchSurfaces)
Expand Down Expand Up @@ -270,10 +296,12 @@ export function ContextData({ children }) {
console.error('Failed to unsubscribe from custom variables', e)
})
}
} else {
return
}
}, [socket])

const notifierRef = useRef()
const notifierRef = useRef<NotificationsManagerRef>(null)

const steps = [
eventDefinitions,
Expand All @@ -295,17 +323,17 @@ export function ContextData({ children }) {

return (
<NotifierContext.Provider value={notifierRef}>
<EventDefinitionsContext.Provider value={eventDefinitions}>
<ModulesContext.Provider value={modules}>
<ActionsContext.Provider value={actionDefinitions}>
<FeedbacksContext.Provider value={feedbackDefinitions}>
<ConnectionsContext.Provider value={connections}>
<EventDefinitionsContext.Provider value={eventDefinitions!}>
<ModulesContext.Provider value={modules!}>
<ActionsContext.Provider value={actionDefinitions!}>
<FeedbacksContext.Provider value={feedbackDefinitions!}>
<ConnectionsContext.Provider value={connections!}>
<VariableDefinitionsContext.Provider value={completeVariableDefinitions}>
<CustomVariableDefinitionsContext.Provider value={customVariables}>
<CustomVariableDefinitionsContext.Provider value={customVariables!}>
<UserConfigContext.Provider value={userConfig}>
<SurfacesContext.Provider value={surfaces}>
<PagesContext.Provider value={pages}>
<TriggersContext.Provider value={triggers}>
<SurfacesContext.Provider value={surfaces!}>
<PagesContext.Provider value={pages!}>
<TriggersContext.Provider value={triggers!}>
<RecentActionsContext.Provider value={recentActionsContext}>
<RecentFeedbacksContext.Provider value={recentFeedbacksContext}>
<NotificationsManager ref={notifierRef} />
Expand Down
Loading

0 comments on commit 141672c

Please sign in to comment.