From a16870e8a87157645f5801b682f3f864f4dcc481 Mon Sep 17 00:00:00 2001 From: Beatrice Mkumbo Date: Fri, 22 Dec 2023 03:37:37 +0300 Subject: [PATCH] chore(web): timeline with padding (#853) Co-authored-by: KaWaite <34051327+KaWaite@users.noreply.github.com> Co-authored-by: keiya sasaki Co-authored-by: nina992 <89770889+nina992@users.noreply.github.com> Co-authored-by: nina992 Co-authored-by: pyshx --- .../Block/builtin/Timeline/Editor.tsx | 189 +++++++++++------- .../StoryPanel/Block/builtin/Timeline/hook.ts | 63 +++++- .../Block/builtin/Timeline/index.tsx | 2 + .../beta/lib/core/StoryPanel/Block/types.ts | 6 + .../beta/lib/core/StoryPanel/Page/index.tsx | 1 + web/src/beta/lib/core/StoryPanel/utils.ts | 2 +- 6 files changed, 176 insertions(+), 87 deletions(-) diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/Editor.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/Editor.tsx index 062e8e99a..b10958406 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/Editor.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/Editor.tsx @@ -7,12 +7,21 @@ import { styled } from "@reearth/services/theme"; import { TimelineValues } from "."; +export type PaddingProp = { + bottom: number; + top: number; + left?: number; + right?: number; +}; + type TimelineProps = { blockId?: string; isSelected?: boolean; timelineValues?: TimelineValues; inEditor?: boolean; playMode?: string; + padding?: PaddingProp; + property?: any; }; const TimelineEditor = ({ @@ -21,9 +30,10 @@ const TimelineEditor = ({ timelineValues, inEditor, playMode, + padding, + property, }: TimelineProps) => { const t = useT(); - const { currentTime, range, @@ -50,6 +60,8 @@ const TimelineEditor = ({ selected, sliderPosition, isActive, + blockRef, + isMinimized, handleOnSelect, handlePopOver, toggleIsPlaying, @@ -67,6 +79,8 @@ const TimelineEditor = ({ inEditor, speed, playMode, + padding, + property, onPlay, onSpeedChange, onPause, @@ -80,54 +94,58 @@ const TimelineEditor = ({ }); return ( - - - - - - - - - - { - if (isPlaying || isPlayingReversed || isPause) { - toggleIsPause(); - } - }}> - - - - - - - - - - - - - - - {playSpeedOptions?.map((playSpeed, key) => ( - { - handleOnSelect(playSpeed.timeString, playSpeed.seconds); - }}> - {playSpeed.timeString} - - ))} - - - {currentTime && formattedCurrentTime} - + + + + + + + + + + + { + if (isPlaying || isPlayingReversed || isPause) { + toggleIsPause(); + } + }}> + + + + + + + + + + + + + + + + {playSpeedOptions?.map((playSpeed, key) => ( + { + handleOnSelect(playSpeed.timeString, playSpeed.seconds); + }}> + {playSpeed.timeString} + + ))} + + + + + {currentTime && formattedCurrentTime} + ( {idx === 0 ? ( - <> - {timeRange?.startTime?.date} - {timeRange?.startTime?.time} - + + {timeRange?.startTime?.date} +
+ {timeRange?.startTime?.time} +
) : idx === 5 ? ( - <> - {timeRange?.midTime?.date} - {timeRange?.midTime?.time} - + + {timeRange?.midTime?.date} +
+ {timeRange?.midTime?.time} +
) : idx === 10 ? ( - <> - {timeRange?.endTime?.date} - {timeRange?.endTime?.time} - + + {timeRange?.endTime?.date} +
+ {timeRange?.endTime?.time} +
) : null}
))} @@ -158,7 +179,7 @@ const TimelineEditor = ({ onMouseDown={handleOnStartMove} isPlaying={isPlaying || isPlayingReversed || isPause} style={{ - left: `${sliderPosition}px`, + left: `${sliderPosition}%`, }}> @@ -178,11 +199,20 @@ const Wrapper = styled.div` user-select: none; `; -const TimelineControl = styled.div` +const TimelineWrapper = styled.div<{ isMinimized: boolean }>` display: flex; align-items: center; padding-bottom: 6px; - gap: 16px; + gap: ${({ isMinimized }) => (isMinimized ? "" : "25px")}; + flex-direction: ${({ isMinimized }) => (isMinimized ? "column" : "row")}; +`; + +const TimelineControl = styled.div<{ isMinimized: boolean }>` + display: flex; + align-items: center; + justify-content: space-between; + gap: ${({ isMinimized }) => (isMinimized ? "0" : "18px")}; + width: ${({ isMinimized }) => (isMinimized ? "100%" : "auto")}; `; const StyledIcon = styled.div<{ activeBlock: boolean }>` @@ -191,10 +221,20 @@ const StyledIcon = styled.div<{ activeBlock: boolean }>` background: ${({ activeBlock, theme }) => (activeBlock ? theme.select.main : theme.bg[4])}; padding: 4px 6px 2px; border-radius: 6px 0 8px 0; + margin-bottom: 6px; `; -const PlayControl = styled.div` + +const PlayControl = styled.div<{ isMinimized: boolean }>` display: flex; gap: 10px; + margin-left: ${({ isMinimized }) => (isMinimized ? "auto" : "0")}; +`; + +const CurrentTime = styled.div<{ isMinimized: boolean }>` + color: ${({ theme }) => theme.content.weaker}; + padding-right: ${({ isMinimized }) => (isMinimized ? "8px" : "0")}; + font-size: 12px; + margin-left: ${({ isMinimized }) => (isMinimized ? "auto" : "0")}; `; const PlayButton = styled.div<{ isPlaying?: boolean; isClicked?: boolean }>` @@ -206,12 +246,11 @@ const PlayButton = styled.div<{ isPlaying?: boolean; isClicked?: boolean }>` const InputWrapper = styled.div` position: relative; cursor: pointer; - width: 90px; `; const ArrowIcon = styled(Icon)<{ open: boolean }>` position: absolute; - right: 10px; + right: -6px; top: 60%; transform: ${({ open }) => (open ? "translateY(-50%) scaleY(-1)" : "translateY(-50%)")}; color: ${({ theme }) => theme.content.weaker}; @@ -223,6 +262,9 @@ const Select = styled.div` padding-right: 12px; color: ${({ theme }) => theme.content.weaker}; `; +const PopoverWrapper = styled.div<{ isMinimized: boolean }>` + padding: ${({ isMinimized }) => (isMinimized ? "0 10px" : "0")}; +`; const PickerWrapper = styled(Popover.Content)` min-width: 100px; @@ -249,12 +291,6 @@ const InputOptions = styled.option` color: ${({ theme }) => theme.content.main}; `; -const CurrentTime = styled.div` - color: ${({ theme }) => theme.content.weaker}; - position: relative; - font-size: 13px; -`; - const TimelineSlider = styled.div` background: #e0e0e0; height: 38px; @@ -290,9 +326,10 @@ const Scale = styled.div` width: calc(100% / 11); `; -const ScaleLabel = styled.div` - font-size: 10px; +const ScaleLabel = styled.div<{ isMinimized: boolean }>` + font-size: ${({ isMinimized }) => (isMinimized ? "8px" : "10px")}; position: relative; bottom: 28px; - right: 15px; + right: 16px; + width: 34px; `; diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/hook.ts b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/hook.ts index 855f0a6e9..1fa0c052c 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/hook.ts +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/hook.ts @@ -14,6 +14,7 @@ import { } from "@reearth/beta/lib/core/Map/useTimelineManager"; import { Range } from "@reearth/beta/lib/core/StoryPanel/Block/types"; import { + calculatePaddingValue, convertPositionToTime, formatDateForSliderTimeline, formatDateForTimeline, @@ -21,6 +22,9 @@ import { } from "@reearth/beta/lib/core/StoryPanel/utils"; import { getNewDate } from "../../../hooks/useTimelineBlock"; +import { DEFAULT_BLOCK_PADDING } from "../common/hooks"; + +import { PaddingProp } from "./Editor"; import { TimelineValues } from "."; @@ -33,6 +37,8 @@ type TimelineProps = { speed: number; playMode?: string; timelineValues?: TimelineValues; + padding?: PaddingProp; + property?: any; onPlay?: (committer: TimelineCommitter) => void; onSpeedChange?: (speed: number, committerId?: string) => void; onPause: (committerId: string) => void; @@ -55,6 +61,8 @@ export default ({ speed, playMode, timelineValues, + padding, + property, onPlay, onSpeedChange, onPause, @@ -70,6 +78,7 @@ export default ({ const [activeBlock, setActiveBlock] = useState(""); const [isPlayingReversed, setIsPlayingReversed] = useState(false); const [isPlaying, setIsPlaying] = useState(false); + const [isMinimized, setIsMinimized] = useState(false); const [committer, setCommiter] = useState({ source: "storyTimelineBlock", @@ -101,6 +110,16 @@ export default ({ return {}; }, [range]); + const panelSettings = useMemo(() => { + if (!property?.panel) return undefined; + return { + padding: { + ...property?.panel?.padding, + value: calculatePaddingValue(DEFAULT_BLOCK_PADDING, property?.panel?.padding?.value), + }, + }; + }, [property?.panel]); + const handlePopOver = useCallback(() => { !inEditor && setIsOpen(!isOpen); }, [inEditor, isOpen]); @@ -250,11 +269,6 @@ export default ({ return; } if (target && target.style.pointerEvents === "none" && !inEditor) { - const evt = e; - let newPosition = evt.clientX - distX.current; - newPosition = Math.max(newPosition, 16); - newPosition = Math.min(newPosition, 372); - target.style.left = `${newPosition}px`; const conv = convertPositionToTime(e as unknown as MouseEvent, range.start, range.end); committer?.id && handleOnDrag(new Date(conv), committer?.id); } @@ -337,22 +351,49 @@ export default ({ removeOnCommitEventListener, removeTickEventListener, ]); + const blockRef = useRef(null); + + const handleResize = useCallback(() => { + if ((padding || panelSettings?.padding.value) && blockRef.current) { + const blockWidth = blockRef.current.offsetWidth; + const thresholdWidth = 360; + setIsMinimized(blockWidth < thresholdWidth); + } + }, [padding, panelSettings?.padding.value]); + + useEffect(() => { + window.addEventListener("resize", handleResize); + handleResize(); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [handleResize]); const sliderPosition = useMemo(() => { + const initialPosition = (() => { + if (!isMinimized) { + return 4; + } + + return 4.5; + })(); + + const finalPosition = isMinimized ? 94.5 : 93.5; + if (range) { if (!inEditor) { const totalRange = range?.end - range.start; const currentPosition = currentTime - range.start; - let positionPercentage = (currentPosition / totalRange) * 356 + 16; + let positionPercentage = (currentPosition / totalRange) * 90 + initialPosition; positionPercentage = Math.round(positionPercentage); - positionPercentage = Math.max(positionPercentage, 16); - positionPercentage = Math.min(positionPercentage, 372); + positionPercentage = Math.max(positionPercentage, initialPosition); + positionPercentage = Math.min(positionPercentage, finalPosition); return positionPercentage; } } - return 16; - }, [range, inEditor, currentTime]); + return initialPosition; + }, [isMinimized, range, inEditor, currentTime]); return { formattedCurrentTime, @@ -364,6 +405,8 @@ export default ({ isOpen, selected, isActive, + isMinimized, + blockRef, handleOnSelect, handlePopOver, toggleIsPlaying: handleOnPlay, diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/index.tsx index 9eda6bd64..029fd2c25 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Timeline/index.tsx @@ -36,9 +36,11 @@ const TimelineBlock: React.FC = ({ block, isSelected, ...props }) => {...props}> ); diff --git a/web/src/beta/lib/core/StoryPanel/Block/types.ts b/web/src/beta/lib/core/StoryPanel/Block/types.ts index 9b2d4523e..816a81460 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/types.ts +++ b/web/src/beta/lib/core/StoryPanel/Block/types.ts @@ -21,6 +21,12 @@ export type CommonProps = { block?: StoryBlock; theme?: Theme; currentCamera?: Camera; + padding?: { + bottom: number; + top: number; + left?: number; + right?: number; + }; onClick?: () => void; onBlockDoubleClick?: () => void; onClickAway?: () => void; diff --git a/web/src/beta/lib/core/StoryPanel/Page/index.tsx b/web/src/beta/lib/core/StoryPanel/Page/index.tsx index a88df7305..7b029f09e 100644 --- a/web/src/beta/lib/core/StoryPanel/Page/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Page/index.tsx @@ -228,6 +228,7 @@ const StoryPanel: React.FC = ({ onPropertyItemAdd={onPropertyItemAdd} onPropertyItemMove={onPropertyItemMove} onPropertyItemDelete={onPropertyItemDelete} + padding={panelSettings?.padding?.value} /> {isEditable && !disableSelection && ( {