File size: 3,468 Bytes
b91e262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import './devtools-indicator.css'
import type { CSSProperties } from 'react'
import type { DevToolsIndicatorPosition } from '../../shared'
import { NextLogo } from './next-logo'
import { Toast } from '../toast'
import {
  MENU_CURVE,
  MENU_DURATION_MS,
} from '../errors/dev-tools-indicator/utils'
import {
  ACTION_DEVTOOLS_POSITION,
  STORE_KEY_SHARED_PANEL_LOCATION,
  STORAGE_KEY_PANEL_POSITION_PREFIX,
  ACTION_DEVTOOLS_PANEL_POSITION,
} from '../../shared'
import { Draggable } from '../errors/dev-tools-indicator/draggable'
import { useDevOverlayContext } from '../../../dev-overlay.browser'
import { usePanelRouterContext } from '../../menu/context'
import { saveDevToolsConfig } from '../../utils/save-devtools-config'

export const INDICATOR_PADDING = 20

export function DevToolsIndicator() {
  const { state, dispatch } = useDevOverlayContext()
  const { panel, setPanel, setSelectedIndex } = usePanelRouterContext()
  const updateAllPanelPositions = useUpdateAllPanelPositions()
  const [vertical, horizontal] = state.devToolsPosition.split('-', 2)

  return (
    // TODO: why is this called a toast
    <Toast
      id="devtools-indicator"
      data-nextjs-toast
      style={
        {
          '--animate-out-duration-ms': `${MENU_DURATION_MS}ms`,
          '--animate-out-timing-function': MENU_CURVE,
          boxShadow: 'none',
          [vertical]: `${INDICATOR_PADDING}px`,
          [horizontal]: `${INDICATOR_PADDING}px`,
        } as CSSProperties
      }
    >
      <Draggable
        // avoids a lot of weird edge cases that would cause jank if the logo and panel were de-synced
        disableDrag={panel !== null}
        padding={INDICATOR_PADDING}
        position={state.devToolsPosition}
        setPosition={(p) => {
          dispatch({
            type: ACTION_DEVTOOLS_POSITION,
            devToolsPosition: p,
          })
          saveDevToolsConfig({ devToolsPosition: p })

          updateAllPanelPositions(p)
        }}
      >
        <NextLogo
          onTriggerClick={() => {
            const newPanel =
              panel === 'panel-selector' ? null : 'panel-selector'
            setPanel(newPanel)
            if (!newPanel) {
              setSelectedIndex(-1)
              return
            }
          }}
        />
      </Draggable>
    </Toast>
  )
}

/**
 * makes sure we eventually sync the panel to the logo, otherwise
 * it will be jarring if the panels start appearing on the other
 * side of the logo. This wont teleport the panel because the indicator
 * cannot be dragged when any panel is open
 */
export const useUpdateAllPanelPositions = () => {
  const { state, dispatch } = useDevOverlayContext()
  return (position: DevToolsIndicatorPosition) => {
    dispatch({
      type: ACTION_DEVTOOLS_PANEL_POSITION,
      devToolsPanelPosition: position,
      key: STORE_KEY_SHARED_PANEL_LOCATION,
    })

    const panelPositionKeys = Object.keys(state.devToolsPanelPosition).filter(
      (key) => key.startsWith(STORAGE_KEY_PANEL_POSITION_PREFIX)
    )

    const panelPositionPatch: Record<string, DevToolsIndicatorPosition> = {
      [STORE_KEY_SHARED_PANEL_LOCATION]: position,
    }

    panelPositionKeys.forEach((key) => {
      dispatch({
        type: ACTION_DEVTOOLS_PANEL_POSITION,
        devToolsPanelPosition: position,
        key,
      })

      panelPositionPatch[key] = position
    })

    saveDevToolsConfig({
      devToolsPanelPosition: panelPositionPatch,
    })
  }
}