You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

style-panel.tsx 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import styled from "styles"
  2. import state, { useSelector } from "state"
  3. import * as Panel from "components/panel"
  4. import { useRef } from "react"
  5. import { IconButton } from "components/shared"
  6. import { Circle, Square, Trash, X } from "react-feather"
  7. import { deepCompare, deepCompareArrays, getSelectedShapes } from "utils/utils"
  8. import { colors } from "state/data"
  9. import ColorPicker from "./color-picker"
  10. import AlignDistribute from "./align-distribute"
  11. import { ShapeByType, ShapeStyles } from "types"
  12. export default function StylePanel() {
  13. const rContainer = useRef<HTMLDivElement>(null)
  14. const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
  15. return (
  16. <StylePanelRoot ref={rContainer} isOpen={isOpen}>
  17. {isOpen ? (
  18. <SelectedShapeStyles />
  19. ) : (
  20. <IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
  21. <Circle />
  22. </IconButton>
  23. )}
  24. </StylePanelRoot>
  25. )
  26. }
  27. // This panel is going to be hard to keep cool, as we're selecting computed
  28. // information, based on the user's current selection. We might have to keep
  29. // track of this data manually within our state.
  30. function SelectedShapeStyles({}: {}) {
  31. const selectedIds = useSelector(
  32. (s) => Array.from(s.data.selectedIds.values()),
  33. deepCompareArrays
  34. )
  35. const shapesStyle = useSelector((s) => {
  36. const { currentStyle } = s.data
  37. const shapes = getSelectedShapes(s.data)
  38. if (shapes.length === 0) {
  39. return currentStyle
  40. }
  41. const style: Partial<ShapeStyles> = {}
  42. const overrides = new Set<string>([])
  43. for (const shape of shapes) {
  44. for (let key in currentStyle) {
  45. if (overrides.has(key)) continue
  46. if (style[key] === undefined) {
  47. style[key] = shape.style[key]
  48. } else {
  49. if (style[key] === shape.style[key]) continue
  50. style[key] = currentStyle[key]
  51. overrides.add(key)
  52. }
  53. }
  54. }
  55. return style
  56. }, deepCompare)
  57. return (
  58. <Panel.Layout>
  59. <Panel.Header>
  60. <IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
  61. <X />
  62. </IconButton>
  63. <h3>Style</h3>
  64. <Panel.ButtonsGroup>
  65. <IconButton onClick={() => state.send("DELETED")}>
  66. <Trash />
  67. </IconButton>
  68. </Panel.ButtonsGroup>
  69. </Panel.Header>
  70. <Content>
  71. <ColorPicker
  72. label="Fill"
  73. color={shapesStyle.fill}
  74. onChange={(color) => state.send("CHANGED_STYLE", { fill: color })}
  75. />
  76. <ColorPicker
  77. label="Stroke"
  78. color={shapesStyle.stroke}
  79. onChange={(color) => state.send("CHANGED_STYLE", { stroke: color })}
  80. />
  81. <AlignDistribute />
  82. </Content>
  83. </Panel.Layout>
  84. )
  85. }
  86. const StylePanelRoot = styled(Panel.Root, {
  87. minWidth: 1,
  88. width: 184,
  89. maxWidth: 184,
  90. position: "relative",
  91. variants: {
  92. isOpen: {
  93. true: {},
  94. false: {
  95. height: 34,
  96. width: 34,
  97. },
  98. },
  99. },
  100. })
  101. const Content = styled(Panel.Content, {
  102. padding: 8,
  103. })