Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 77 additions & 28 deletions src/components/ActiveWindowsPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import SidePanel from 'components/SidePanel'
import ScrollArea from 'components/RadixUI/ScrollArea'
import OSButton from 'components/OSButton'
import { navigate } from 'gatsby'
import { IconCheck, IconCopy } from '@posthog/icons'
import KeyboardShortcut from 'components/KeyboardShortcut'

export default function ActiveWindowsPanel() {
const {
Expand All @@ -14,12 +16,21 @@ export default function ActiveWindowsPanel() {
bringToFront,
closeWindow,
animateClosingAllWindows,
desktopParams,
shareableDesktopURL,
desktopCopied,
copyDesktopParams,
} = useApp()

const closeActiveWindowsPanel = () => {
setIsActiveWindowsPanelOpen(false)
}

const handleCopyDesktopParams = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
copyDesktopParams()
}

// Add keyboard listener for Escape key
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand Down Expand Up @@ -69,35 +80,73 @@ export default function ActiveWindowsPanel() {
}
width="w-80"
>
<ScrollArea className="p-2">
<div className="flex flex-col gap-1">
{windows.map((window) => (
<OSButton
key={window.key}
size="md"
width="full"
align="left"
active={focusedWindow === window}
onClick={() => handleWindowClick(window)}
className="group"
>
<span className={`truncate flex-1 ${window.minimized ? 'italic opacity-60' : ''}`}>
{window.meta?.title || 'Untitled'}
</span>
<button
onClick={(e) => {
e.stopPropagation()
closeWindow(window)
}}
className="ml-2 text-secondary hover:text-primary opacity-0 group-hover:opacity-100 transition-opacity text-lg leading-none px-1"
<div className="h-full flex flex-col">
<ScrollArea className="p-2">
<div className="flex flex-col gap-1">
{windows.map((window) => (
<OSButton
key={window.key}
size="md"
width="full"
align="left"
active={focusedWindow === window}
onClick={() => handleWindowClick(window)}
className="group"
>
<span className={`truncate flex-1 ${window.minimized ? 'italic opacity-60' : ''}`}>
{window.meta?.title || 'Untitled'}
</span>
<button
onClick={(e) => {
e.stopPropagation()
closeWindow(window)
}}
className="ml-2 text-secondary hover:text-primary opacity-0 group-hover:opacity-100 transition-opacity text-lg leading-none px-1"
>
×
</button>
</OSButton>
))}
{totalWindows === 0 && <div className="text-center text-secondary p-4">No active windows</div>}
</div>
</ScrollArea>
{totalWindows > 0 && (
<div className="p-4 mt-auto border-t border-primary">
<h3 className="text-sm font-semibold m-0 mb-0.5">Share your windows</h3>
<p className="text-xs text-secondary m-0 mb-2 leading-relaxed">
Copy the URL to share your open windows & layout.
</p>
<form onSubmit={handleCopyDesktopParams} className="flex gap-1">
<input
disabled
type="url"
value={shareableDesktopURL}
className="text-xs font-mono border border-primary rounded-md flex-grow bg-white dark:bg-dark"
/>
<OSButton
type="submit"
size="sm"
disabled={!desktopParams}
className="shrink-0 !w-[34px] border border-primary rounded-md"
>
×
</button>
</OSButton>
))}
{totalWindows === 0 && <div className="text-center text-secondary p-4">No active windows</div>}
</div>
</ScrollArea>
{desktopCopied ? (
<IconCheck className="size-4 text-green" />
) : (
<IconCopy className="size-4" />
)}
</OSButton>
</form>
<p className="text-xs text-secondary m-0 mt-2 flex items-center gap-1">
<span>Tip: Press</span>
<div className="flex items-center gap-1">
<KeyboardShortcut text="Shift" size="sm" />
<KeyboardShortcut text="C" size="sm" />
</div>
<span>to copy instantly.</span>
</p>
</div>
)}
</div>
</SidePanel>
)
}
6 changes: 5 additions & 1 deletion src/components/TaskBarMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
IconBookmark,
IconUpload,
IconCode,
IconCheck,
IconCopy,
IconShare,
} from '@posthog/icons'
import { useApp } from '../../context/App'

Expand All @@ -23,8 +26,8 @@ import getAvatarURL from 'components/Squeak/util/getAvatar'
import { useMenuData } from './menuData'
import CloudinaryImage from 'components/CloudinaryImage'
import MediaUploadModal from 'components/MediaUploadModal'
import { navigate } from 'gatsby'
import KeyboardShortcut from 'components/KeyboardShortcut'
import { Popover } from 'components/RadixUI/Popover'

export default function TaskBarMenu() {
const {
Expand All @@ -40,6 +43,7 @@ export default function TaskBarMenu() {
addWindow,
taskbarRef,
posthogInstance,
copyDesktopParams,
} = useApp()
const [isAnimating, setIsAnimating] = useState(false)
const totalWindows = windows.length
Expand Down
133 changes: 130 additions & 3 deletions src/context/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useToast } from './Toast'
import { IconDay, IconLaptop, IconNight } from '@posthog/icons'
import { themeOptions } from '../hooks/useTheme'
import ContactSales from 'components/ContactSales'
import qs from 'qs'

declare global {
interface Window {
Expand Down Expand Up @@ -119,6 +120,10 @@ interface AppContextType {
setConfetti: (isActive: boolean) => void
confetti: boolean
posthogInstance?: string
desktopParams?: string
copyDesktopParams: () => void
desktopCopied: boolean
shareableDesktopURL: string
}

interface AppProviderProps {
Expand Down Expand Up @@ -274,6 +279,10 @@ export const Context = createContext<AppContextType>({
setConfetti: () => {},
confetti: false,
posthogInstance: undefined,
desktopParams: undefined,
copyDesktopParams: () => {},
desktopCopied: false,
shareableDesktopURL: '',
})

export interface AppSetting {
Expand Down Expand Up @@ -997,8 +1006,17 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
const [isMobile, setIsMobile] = useState(!isSSR && window.innerWidth < 768)
const [taskbarHeight, setTaskbarHeight] = useState(38)
const [lastClickedElement, setLastClickedElement] = useState<HTMLElement | null>(null)
const [desktopCopied, setDesktopCopied] = useState(false)
const urlObj = isSSR ? null : new URL(location.href)
const queryString = isSSR ? '' : urlObj?.search.substring(1)
const parsed = isSSR ? {} : qs.parse(queryString)
const paramsWindows = parsed?.windows
const stateWindows = element.props?.location?.state?.savedWindows

const [windows, setWindows] = useState<AppWindow[]>(
location.key === 'initial' && location.pathname === '/' && isMobile ? [] : getInitialWindows(element)
(location.key === 'initial' && location.pathname === '/' && isMobile) || !!paramsWindows
? []
: getInitialWindows(element)
)
const focusedWindow = useMemo(() => {
return windows.reduce<AppWindow | undefined>(
Expand Down Expand Up @@ -1028,6 +1046,36 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
[destinationNav, transformationNav, sourceWebhooksNav]
)

const desktopParams = useMemo(() => {
const innerWidth = isSSR ? 0 : window.innerWidth
const innerHeight = isSSR ? 0 : window.innerHeight

const savedWindows = [...windows]
.filter((win) => !win.minimized && win.path.startsWith('/'))
.sort((a, b) => a.zIndex - b.zIndex)
.map((win) => ({
path: win.path,
position: {
x: (win.position.x / innerWidth) * 100,
y: (win.position.y / (innerHeight - taskbarHeight)) * 100,
},
size: {
width: (win.size.width / innerWidth) * 100,
height: (win.size.height / innerHeight) * 100,
},
zIndex: win.zIndex,
}))

return savedWindows.length > 0
? `${location.pathname}?${qs.stringify({ windows: savedWindows }, { encode: false })}`
: undefined
}, [windows, taskbarHeight, location, isSSR])

const shareableDesktopURL = useMemo(() => {
const url = `${location.origin}${desktopParams}`
return url
}, [location, desktopParams])

const injectDynamicChildren = useCallback((menu: Menu) => {
return menu?.map((item) => {
const processedItem = { ...item }
Expand Down Expand Up @@ -1281,8 +1329,9 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
zIndex?: number
}
) {
const size = getInitialSize(element.key)
const size = element.props?.location?.state?.size || element.props.size || getInitialSize(element.key)
const position =
element.props?.location?.state?.position ||
element.props.position ||
appSettings[element.key]?.position?.getPositionDefaults?.(size, windows, getDesktopCenterPosition) ||
getPositionDefaults(element.key, size, windows)
Expand Down Expand Up @@ -1518,8 +1567,26 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
setWindows([])
}

const copyDesktopParams = () => {
if (!desktopParams) return
try {
navigator.clipboard.writeText(shareableDesktopURL)
setDesktopCopied(true)
setTimeout(() => {
setDesktopCopied(false)
}, 2000)
} catch (error) {
console.error(error)
addToast({
error: true,
description: 'Failed to copy desktop link to clipboard',
duration: 2000,
})
}
}

useEffect(() => {
if (location.key === 'initial' && location.pathname === '/' && isMobile) {
if ((location.key === 'initial' && location.pathname === '/' && isMobile) || paramsWindows) {
return
}
updatePages(element)
Expand Down Expand Up @@ -1724,6 +1791,15 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
}
}
}
if (e.shiftKey && e.key === 'C') {
e.preventDefault()
if (!desktopParams) return
copyDesktopParams()
addToast({
description: 'Desktop link copied to clipboard',
duration: 2000,
})
}
}

document.addEventListener('keydown', handleKeyDown)
Expand Down Expand Up @@ -1845,6 +1921,53 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
}
}, [])

const convertWindowsToPixels = (windows: any[]) => {
const innerWidth = window.innerWidth
const innerHeight = window.innerHeight

return windows.map((win) => ({
...win,
size: {
width: (parseFloat(win.size.width) / 100) * innerWidth,
height: (parseFloat(win.size.height) / 100) * innerHeight,
},
position: {
x: (parseFloat(win.position.x) / 100) * innerWidth,
y: (parseFloat(win.position.y) / 100) * (innerHeight - taskbarHeight),
},
}))
}

useEffect(() => {
if (isSSR) return

if (paramsWindows) {
const [initialWindow, ...rest] = convertWindowsToPixels(parsed.windows)

navigate(initialWindow.path, {
state: {
newWindow: true,
size: initialWindow.size,
position: initialWindow.position,
savedWindows: rest,
},
})
}

if (stateWindows) {
const [nextWindow, ...rest] = stateWindows
if (!nextWindow) return
navigate(nextWindow.path, {
state: {
newWindow: true,
size: nextWindow.size,
position: nextWindow.position,
savedWindows: rest.length > 0 ? rest : undefined,
},
})
}
}, [stateWindows])

return (
<Context.Provider
value={{
Expand Down Expand Up @@ -1889,6 +2012,10 @@ export const Provider = ({ children, element, location }: AppProviderProps) => {
setConfetti,
confetti,
posthogInstance,
desktopParams,
copyDesktopParams,
desktopCopied,
shareableDesktopURL,
}}
>
{children}
Expand Down