diff options
Diffstat (limited to 'subprojects/frontend/src/editor/EditorAreaDecorations.tsx')
-rw-r--r-- | subprojects/frontend/src/editor/EditorAreaDecorations.tsx | 148 |
1 files changed, 0 insertions, 148 deletions
diff --git a/subprojects/frontend/src/editor/EditorAreaDecorations.tsx b/subprojects/frontend/src/editor/EditorAreaDecorations.tsx deleted file mode 100644 index e00cc290..00000000 --- a/subprojects/frontend/src/editor/EditorAreaDecorations.tsx +++ /dev/null | |||
@@ -1,148 +0,0 @@ | |||
1 | import { type CSSObject, type Theme, styled } from '@mui/material/styles'; | ||
2 | import React, { memo, useEffect, useState } from 'react'; | ||
3 | |||
4 | const SHADOW_SIZE = 10; | ||
5 | |||
6 | const EditorAreaDecoration = styled('div')({ | ||
7 | position: 'absolute', | ||
8 | pointerEvents: 'none', | ||
9 | }); | ||
10 | |||
11 | function shadowTheme( | ||
12 | origin: string, | ||
13 | scaleX: boolean, | ||
14 | scaleY: boolean, | ||
15 | ): CSSObject { | ||
16 | function radialGradient(opacity: number, scale: string): string { | ||
17 | return `radial-gradient( | ||
18 | farthest-side at ${origin}, | ||
19 | rgba(0, 0, 0, ${opacity}), | ||
20 | rgba(0, 0, 0, 0) | ||
21 | ) | ||
22 | ${origin} / | ||
23 | ${scaleX ? scale : '100%'} | ||
24 | ${scaleY ? scale : '100%'} | ||
25 | no-repeat`; | ||
26 | } | ||
27 | |||
28 | return { | ||
29 | background: ` | ||
30 | ${radialGradient(0.2, '40%')}, | ||
31 | ${radialGradient(0.14, '50%')}, | ||
32 | ${radialGradient(0.12, '100%')} | ||
33 | `, | ||
34 | }; | ||
35 | } | ||
36 | |||
37 | function animateSize( | ||
38 | theme: Theme, | ||
39 | direction: 'height' | 'width', | ||
40 | opacity: number, | ||
41 | ): CSSObject { | ||
42 | return { | ||
43 | [direction]: opacity * SHADOW_SIZE, | ||
44 | transition: theme.transitions.create(direction, { | ||
45 | duration: theme.transitions.duration.shortest, | ||
46 | }), | ||
47 | }; | ||
48 | } | ||
49 | |||
50 | const TopDecoration = memo( | ||
51 | styled(EditorAreaDecoration, { | ||
52 | shouldForwardProp: (prop) => prop !== 'visible' && prop !== 'opacity', | ||
53 | })<{ | ||
54 | visible: boolean; | ||
55 | opacity: number; | ||
56 | }>(({ theme, visible, opacity }) => ({ | ||
57 | display: visible ? 'block' : 'none', | ||
58 | top: 0, | ||
59 | left: 0, | ||
60 | right: 0, | ||
61 | ...shadowTheme('50% 0', false, true), | ||
62 | ...animateSize(theme, 'height', opacity), | ||
63 | })), | ||
64 | ); | ||
65 | |||
66 | const GutterDecoration = memo( | ||
67 | styled(EditorAreaDecoration, { | ||
68 | shouldForwardProp: (prop) => | ||
69 | prop !== 'top' && | ||
70 | prop !== 'bottom' && | ||
71 | prop !== 'guttersWidth' && | ||
72 | prop !== 'opacity', | ||
73 | })<{ | ||
74 | top: number; | ||
75 | bottom: number; | ||
76 | guttersWidth: number; | ||
77 | opacity: number; | ||
78 | }>(({ theme, top, bottom, guttersWidth, opacity }) => ({ | ||
79 | top, | ||
80 | left: guttersWidth, | ||
81 | bottom, | ||
82 | ...shadowTheme('0 50%', true, false), | ||
83 | ...animateSize(theme, 'width', opacity), | ||
84 | })), | ||
85 | ); | ||
86 | |||
87 | function convertToOpacity(scroll: number): number { | ||
88 | return Math.max(0, Math.min(1, scroll / SHADOW_SIZE)); | ||
89 | } | ||
90 | |||
91 | export default function EditorAreaDecorations({ | ||
92 | parent, | ||
93 | scroller, | ||
94 | }: { | ||
95 | parent: HTMLElement | undefined; | ||
96 | scroller: HTMLElement | undefined; | ||
97 | }): JSX.Element { | ||
98 | const [top, setTop] = useState(0); | ||
99 | const [bottom, setBottom] = useState(0); | ||
100 | const [guttersWidth, setGuttersWidth] = useState(0); | ||
101 | const [topOpacity, setTopOpacity] = useState(0); | ||
102 | const [gutterOpacity, setGutterOpacity] = useState(0); | ||
103 | |||
104 | useEffect(() => { | ||
105 | if (parent === undefined || scroller === undefined) { | ||
106 | return () => {}; | ||
107 | } | ||
108 | const gutters = scroller.querySelector('.cm-gutters'); | ||
109 | |||
110 | const updateBounds = () => { | ||
111 | const parentRect = parent.getBoundingClientRect(); | ||
112 | const rect = scroller.getBoundingClientRect(); | ||
113 | setTop(rect.top - parentRect.top); | ||
114 | setBottom(parentRect.bottom - rect.bottom); | ||
115 | setGuttersWidth(gutters?.clientWidth ?? 0); | ||
116 | }; | ||
117 | updateBounds(); | ||
118 | const resizeObserver = new ResizeObserver(updateBounds); | ||
119 | resizeObserver.observe(scroller); | ||
120 | if (gutters !== null) { | ||
121 | resizeObserver.observe(gutters); | ||
122 | } | ||
123 | |||
124 | const updateScroll = () => { | ||
125 | setTopOpacity(convertToOpacity(scroller.scrollTop)); | ||
126 | setGutterOpacity(convertToOpacity(scroller.scrollLeft)); | ||
127 | }; | ||
128 | updateScroll(); | ||
129 | scroller.addEventListener('scroll', updateScroll); | ||
130 | |||
131 | return () => { | ||
132 | resizeObserver.disconnect(); | ||
133 | scroller.removeEventListener('scroll', updateScroll); | ||
134 | }; | ||
135 | }, [parent, scroller, setTop]); | ||
136 | |||
137 | return ( | ||
138 | <> | ||
139 | <GutterDecoration | ||
140 | top={top} | ||
141 | bottom={bottom} | ||
142 | guttersWidth={guttersWidth} | ||
143 | opacity={gutterOpacity} | ||
144 | /> | ||
145 | <TopDecoration visible={top === 0} opacity={topOpacity} /> | ||
146 | </> | ||
147 | ); | ||
148 | } | ||