33* Licensed under the MIT License. See License.txt in the project root for license information.
44*/
55
6- /* eslint-disable prefer-arrow/prefer-arrow-functions, @typescript-eslint/naming-convention, jsdoc/require-jsdoc */
7-
86import pauseIcon from "../../../assets/images/icons/pause.svg" ;
97import pencilIcon from "../../../assets/images/icons/pencil_white.svg" ;
108import playIcon from "../../../assets/images/icons/play.svg" ;
119
12- import { useCallback , useContext , useRef , useState } from "preact/hooks" ;
13- import type { JSX } from "preact/jsx-runtime" ;
14-
15- import type { ArrangementView } from "../../../core/index.js" ;
10+ import type { ComponentChild } from "preact" ;
11+ import type { ArrangementView , Subscription } from "../../../core/index.js" ;
1612import { getEventEngine } from "../../../player/EventEngine.js" ;
17- import { useStateSubscription } from "../../../ui/hooks/useStateSubscription.js" ;
18- import { useSubscription } from "../../../ui/hooks/useSubscription.js" ;
1913import { ServicesContext } from "../BananaDrumViewer.js" ;
14+ import { ComponentBase } from "../ComponentBase/ComponentBase.js" ;
2015import { ExpandingSpacer } from "../ExpandingSpacer.js" ;
2116import { Overlay , toggleOverlay } from "../Overlay.js" ;
2217import { SelectionControls } from "../SelectionControls.js" ;
@@ -29,81 +24,150 @@ import { UndoRedo } from "./UndoRedo.js";
2924
3025const eventEngine = getEventEngine ( ) ;
3126
32- export function ArrangementControlsTop ( ) : JSX . Element {
33- const [ playing , setPlaying ] = useState ( eventEngine . state === "playing" ) ;
34- useSubscription ( eventEngine , ( ) => {
35- setPlaying ( eventEngine . state === "playing" ) ;
36- } ) ;
27+ interface IArrangementControlsTopState {
28+ playing : boolean ;
29+ editingTitle : boolean ;
30+ title : string ;
31+ }
3732
38- const arrangement : ArrangementView = useContext ( ArrangementPlayerContext ) ! . arrangement ;
33+ export class ArrangementControlsTop extends ComponentBase < { } , IArrangementControlsTopState > {
3934
40- const selectionManager = useContext ( ServicesContext ) ! . selectionManager ;
41- useSubscription ( selectionManager , ( ) => {
42- toggleOverlay ( "selection_controls" , selectionManager . selections . size ? "show" : "hide" ) ;
43- } ) ;
44-
45- const [ editingTitle , setEditingTitle ] = useState ( false ) ;
46- const title = useStateSubscription ( arrangement , ( arrangement : ArrangementView ) => {
47- return arrangement . title ;
48- } ) ;
49- const titleVisible = title || editingTitle ;
50-
51- const justFinishedEditingTitle = useRef ( false ) ;
52- const onEditEnd = useCallback ( ( ) => {
53- setEditingTitle ( false ) ;
54- justFinishedEditingTitle . current = true ;
35+ private arrangementPlayerContext ?: React . ContextType < typeof ArrangementPlayerContext > ;
36+ private servicesContext ?: React . ContextType < typeof ServicesContext > ;
37+
38+ private justFinishedEditingTitle = false ;
39+
40+ public constructor ( props : { } ) {
41+ super ( props ) ;
42+
43+ this . state = {
44+ playing : eventEngine . state === "playing" ,
45+ editingTitle : false ,
46+ title : "" ,
47+ } ;
48+ }
49+
50+ public override componentWillUnmount ( ) : void {
51+ const arrangementPlayerContext = this . context as React . ContextType < typeof ArrangementPlayerContext > ;
52+ const arrangement : ArrangementView = arrangementPlayerContext ! . arrangement ;
53+ const selectionManager = this . servicesContext ! . selectionManager ;
54+
55+ arrangement . unsubscribe ( this . titleChangeSubscription as Subscription ) ;
56+ eventEngine . unsubscribe ( this . onPlaybackStateChange ) ;
57+ selectionManager . unsubscribe ( this . onSelectionChange as Subscription ) ;
58+ }
59+
60+ public override render ( ) : ComponentChild {
61+ const { playing, editingTitle, title } = this . state ;
62+
63+ return (
64+ < ArrangementPlayerContext . Consumer >
65+ { ( arrangementPlayerContext ) => {
66+ return (
67+ < ServicesContext . Consumer >
68+ { ( servicesContext ) => {
69+ this . useSubscriptions ( arrangementPlayerContext , servicesContext ) ;
70+ const arrangement : ArrangementView = arrangementPlayerContext ! . arrangement ;
71+ const titleVisible = title . length > 0 || editingTitle ;
72+
73+ return (
74+ < >
75+ < div className = { titleVisible ? "" : "hidden" } >
76+ < ArrangementTitle editMode = { editingTitle } onEditEnd = { this . onEditEnd } />
77+ </ div >
78+ < div className = "arrangement-controls arrangement-controls-top" >
79+ {
80+ playing ? (
81+ < button className = "playback-control push-button" onClick = { ( ) => {
82+ eventEngine . stop ( ) ;
83+ } } >
84+ < img src = { pauseIcon } alt = "stop" />
85+ </ button >
86+ ) : (
87+ < button className = "playback-control push-button" onClick = { ( ) => {
88+ void eventEngine . play ( ) ;
89+ } } >
90+ < img src = { playIcon } alt = "play" />
91+ </ button >
92+ )
93+ }
94+ < SmallSpacer />
95+ < TimeControls arrangement = { arrangement } />
96+ < SmallSpacer />
97+
98+ < div className = 'other-controls-wrapper' >
99+ < button
100+ className = "push-button medium gray edit-title-button"
101+ onClick = { this . onClickEditTitle }
102+ >
103+ T < img src = { pencilIcon } style = { { height : "0.78em" } } />
104+ </ button >
105+ < SmallSpacer />
106+ < UndoRedo />
107+ </ div >
108+
109+ < SmallSpacer />
110+ < ExpandingSpacer />
111+
112+ < ShareButton />
113+ < Overlay name = "selection_controls" >
114+ < SelectionControls />
115+ </ Overlay >
116+ </ div >
117+ </ >
118+ ) ;
119+ } }
120+ </ ServicesContext . Consumer >
121+ ) ;
122+ } }
123+ </ ArrangementPlayerContext . Consumer >
124+ ) ;
125+ }
126+
127+ private useSubscriptions = (
128+ arrangementPlayerContext : React . ContextType < typeof ArrangementPlayerContext > ,
129+ servicesContext ?: React . ContextType < typeof ServicesContext >
130+ ) : void => {
131+ if ( this . arrangementPlayerContext !== arrangementPlayerContext ) {
132+ this . arrangementPlayerContext = arrangementPlayerContext ;
133+ this . servicesContext = servicesContext ;
134+
135+ eventEngine . subscribe ( this . onPlaybackStateChange ) ;
136+
137+ const arrangement = arrangementPlayerContext ! . arrangement ;
138+ arrangement . subscribe ( this . titleChangeSubscription as Subscription ) ;
139+
140+ const selectionManager = this . servicesContext ! . selectionManager ;
141+ selectionManager . subscribe ( this . onSelectionChange as Subscription ) ;
142+
143+ }
144+ } ;
145+
146+ private onEditEnd = ( ) => {
147+ this . setState ( { editingTitle : false } ) ;
148+ this . justFinishedEditingTitle = true ;
55149 setTimeout ( ( ) => {
56- return justFinishedEditingTitle . current = false ;
150+ return this . justFinishedEditingTitle = false ;
57151 } , 100 ) ;
58- } , [ ] ) ;
152+ } ;
59153
60- const onClickEditTitle = useCallback ( ( ) => {
61- if ( ! justFinishedEditingTitle . current ) {
62- setEditingTitle ( true ) ;
154+ private onClickEditTitle = ( ) => {
155+ if ( ! this . justFinishedEditingTitle ) {
156+ this . setState ( { editingTitle : true } ) ;
63157 }
64- } , [ ] ) ;
65-
66- return (
67- < >
68- < div className = { titleVisible ? "" : "hidden" } >
69- < ArrangementTitle editMode = { editingTitle } onEditEnd = { onEditEnd } />
70- </ div >
71- < div className = "arrangement-controls arrangement-controls-top" >
72- {
73- playing ? (
74- < button className = "playback-control push-button" onClick = { ( ) => {
75- eventEngine . stop ( ) ;
76- } } >
77- < img src = { pauseIcon } alt = "stop" />
78- </ button >
79- ) : (
80- < button className = "playback-control push-button" onClick = { ( ) => {
81- void eventEngine . play ( ) ;
82- } } >
83- < img src = { playIcon } alt = "play" />
84- </ button >
85- )
86- }
87- < SmallSpacer />
88- < TimeControls arrangement = { arrangement } />
89- < SmallSpacer />
90- < div className = 'other-controls-wrapper' >
91- < button
92- className = "push-button medium gray edit-title-button"
93- onClick = { onClickEditTitle }
94- >
95- T < img src = { pencilIcon } style = { { height : "0.78em" } } />
96- </ button >
97- < SmallSpacer />
98- < UndoRedo />
99- </ div >
100- < SmallSpacer />
101- < ExpandingSpacer />
102- < ShareButton />
103- < Overlay name = "selection_controls" >
104- < SelectionControls />
105- </ Overlay >
106- </ div >
107- </ >
108- ) ;
158+ } ;
159+
160+ private titleChangeSubscription = ( ) => {
161+ const arrangement = this . arrangementPlayerContext ! . arrangement ;
162+ this . setState ( { title : arrangement . title } ) ;
163+ } ;
164+
165+ private onPlaybackStateChange = ( ) => {
166+ this . setState ( { playing : eventEngine . state === "playing" } ) ;
167+ } ;
168+
169+ private onSelectionChange = ( ) => {
170+ const selectionManager = this . servicesContext ! . selectionManager ;
171+ toggleOverlay ( "selection_controls" , selectionManager . selections . size ? "show" : "hide" ) ;
172+ } ;
109173}
0 commit comments