diff --git a/gsuiChannel/gsuiChannel.js b/gsuiChannel/gsuiChannel.js index af66574e..711da9be 100644 --- a/gsuiChannel/gsuiChannel.js +++ b/gsuiChannel/gsuiChannel.js @@ -1,116 +1,110 @@ -"use strict"; - -class gsuiChannel extends HTMLElement { - #dispatch = GSUdispatchEvent.bind( null, this, "gsuiChannel" ); - #children = GSUgetTemplate( "gsui-channel" ); - #elements = GSUfindElements( this.#children, { - toggle: "gsui-toggle", - nameWrap: ".gsuiChannel-nameWrap", - name: ".gsuiChannel-name", - analyser: "gsui-analyser", - effects: ".gsuiChannel-effects", - pan: ".gsuiChannel-pan gsui-slider", - gain: ".gsuiChannel-gain gsui-slider", - connecta: ".gsuiChannel-connectA", - connectb: ".gsuiChannel-connectB", - } ); - analyser = this.#elements.analyser; - - constructor() { - super(); - Object.seal( this ); - - this.#elements.nameWrap.onclick = - this.#elements.analyser.onclick = () => { - this.#dispatch( "selectChannel" ); - }; - this.#elements.effects.onclick = e => { - if ( e.target.dataset.id ) { - this.#dispatch( "selectChannel" ); - this.#dispatch( "selectEffect", e.target.dataset.id ); - } - }; - GSUlistenEvents( this, { - gsuiToggle: { - toggle: d => { - GSUsetAttribute( this, "muted", !d.args[ 0 ] ); - this.#dispatch( "toggle", d.args[ 0 ] ); - }, - toggleSolo: () => { - GSUsetAttribute( this, "muted", false ); - this.#dispatch( "toggleSolo" ); - }, - }, - gsuiSlider: { - inputStart: GSUnoop, - inputEnd: GSUnoop, - input: ( d, sli ) => { - this.#dispatch( "liveChange", sli.dataset.prop, d.args[ 0 ] ); - }, - change: ( d, sli ) => { - this.#dispatch( "change", sli.dataset.prop, d.args[ 0 ] ); - }, - }, - } ); - } - - // ......................................................................... - connectedCallback() { - if ( !this.firstChild ) { - this.append( ...this.#children ); - this.#children = null; - GSUsetAttribute( this, "draggable", "true" ); - GSUrecallAttributes( this, { - name: "chan", - pan: 0, - gain: 1, - connecta: "down", - } ); - } - } - static get observedAttributes() { - return [ "name", "muted", "pan", "gain", "connecta", "connectb" ]; - } - attributeChangedCallback( prop, prev, val ) { - if ( !this.#children && prev !== val ) { - switch ( prop ) { - case "name": - this.#elements.name.textContent = val; - break; - case "muted": - GSUsetAttribute( this.#elements.toggle, "off", val !== null ); - break; - case "pan": - case "gain": - this.#elements[ prop ].setValue( val ); - break; - case "connecta": - case "connectb": - this.#elements[ prop ].dataset.icon = val ? `caret-${ val }` : ""; - break; - } - } - } - - // ......................................................................... - $addEffect( id, obj ) { - this.#elements.effects.append( GSUgetTemplate( "gsui-channel-effect", id, obj.type ) ); - } - $removeEffect( id ) { - this.#getEffect( id ).remove(); - } - $updateEffect( id, obj ) { - if ( "order" in obj ) { - this.#getEffect( id ).style.order = obj.order; - } - if ( "toggle" in obj ) { - this.#getEffect( id ).classList.toggle( "gsuiChannel-effect-enable", obj.toggle ); - } - } - #getEffect( id ) { - return this.#elements.effects.querySelector( `[data-id="${ id }"]` ); - } -} - -Object.freeze( gsuiChannel ); -customElements.define( "gsui-channel", gsuiChannel ); +"use strict"; + +class gsuiChannel extends gsui0ne { + $analyser = null; + + constructor() { + super( { + $cmpName: "gsuiChannel", + $tagName: "gsui-channel", + $elements: { + toggle: "gsui-toggle", + nameWrap: ".gsuiChannel-nameWrap", + name: ".gsuiChannel-name", + analyser: "gsui-analyser", + effects: ".gsuiChannel-effects", + pan: ".gsuiChannel-pan gsui-slider", + gain: ".gsuiChannel-gain gsui-slider", + connecta: ".gsuiChannel-connectA", + connectb: ".gsuiChannel-connectB", + }, + $attributes: { + draggable: "true", + name: "chan", + pan: 0, + gain: 1, + connecta: "down", + }, + } ); + Object.seal( this ); + + this.$analyser = this.$elements.analyser; + this.$analyser.onclick = + this.$elements.nameWrap.onclick = () => { + this.$dispatch( "selectChannel" ); + }; + this.$elements.effects.onclick = e => { + if ( e.target.dataset.id ) { + this.$dispatch( "selectChannel" ); + this.$dispatch( "selectEffect", e.target.dataset.id ); + } + }; + GSUlistenEvents( this, { + gsuiToggle: { + toggle: d => { + GSUsetAttribute( this, "muted", !d.args[ 0 ] ); + this.$dispatch( "toggle", d.args[ 0 ] ); + }, + toggleSolo: () => { + GSUsetAttribute( this, "muted", false ); + this.$dispatch( "toggleSolo" ); + }, + }, + gsuiSlider: { + inputStart: GSUnoop, + inputEnd: GSUnoop, + input: ( d, sli ) => { + this.$dispatch( "liveChange", sli.dataset.prop, d.args[ 0 ] ); + }, + change: ( d, sli ) => { + this.$dispatch( "change", sli.dataset.prop, d.args[ 0 ] ); + }, + }, + } ); + } + + // ......................................................................... + static get observedAttributes() { + return [ "name", "muted", "pan", "gain", "connecta", "connectb" ]; + } + $attributeChanged( prop, val ) { + switch ( prop ) { + case "name": + this.$elements.name.textContent = val; + break; + case "muted": + GSUsetAttribute( this.$elements.toggle, "off", val !== null ); + break; + case "pan": + case "gain": + this.$elements[ prop ].setValue( val ); + break; + case "connecta": + case "connectb": + this.$elements[ prop ].dataset.icon = val ? `caret-${ val }` : ""; + break; + } + } + + // ......................................................................... + $addEffect( id, obj ) { + this.$elements.effects.append( GSUgetTemplate( "gsui-channel-effect", id, obj.type ) ); + } + $removeEffect( id ) { + this.#getEffect( id ).remove(); + } + $updateEffect( id, obj ) { + if ( "order" in obj ) { + this.#getEffect( id ).style.order = obj.order; + } + if ( "toggle" in obj ) { + this.#getEffect( id ).classList.toggle( "gsuiChannel-effect-enable", obj.toggle ); + } + } + #getEffect( id ) { + return this.$elements.effects.querySelector( `[data-id="${ id }"]` ); + } +} + +Object.freeze( gsuiChannel ); +customElements.define( "gsui-channel", gsuiChannel ); diff --git a/gsuiChannel/index.html b/gsuiChannel/index.html index 7b7e5284..87340d8a 100644 --- a/gsuiChannel/index.html +++ b/gsuiChannel/index.html @@ -1,62 +1,63 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + diff --git a/gsuiChannels/gsuiChannels.js b/gsuiChannels/gsuiChannels.js index e81de7c7..2c0138c2 100644 --- a/gsuiChannels/gsuiChannels.js +++ b/gsuiChannels/gsuiChannels.js @@ -1,196 +1,194 @@ -"use strict"; - -class gsuiChannels extends HTMLElement { - oninput = null; - onchange = null; - onselectChan = null; - onselectEffect = null; - #chans = {}; - #chanSelected = null; - #analyserW = 10; - #analyserH = 50; - #onresizeBind = this.#onresize.bind( this ); - #children = GSUgetTemplate( "gsui-channels" ); - #elements = GSUfindElements( this.#children, { - pmain: ".gsuiChannels-panMain", - pchans: ".gsuiChannels-panChannels", - addBtn: ".gsuiChannels-addChan", - } ); - static #selectChanPopup = GSUgetTemplate( "gsui-channels-selectPopup" ); - static #selectChanInput = gsuiChannels.#selectChanPopup.querySelector( "select" ); - - constructor() { - super(); - Object.seal( this ); - - this.#elements.addBtn.onclick = () => this.onchange( "addChannel" ); - GSUlistenEvents( this, { - gsuiChannel: { - selectChannel: ( d, chan ) => this.selectChannel( chan.dataset.id ), - selectEffect: ( d, chan ) => this.onselectEffect( chan.dataset.id, d.args[ 0 ] ), - liveChange: ( d, chan ) => this.oninput( chan.dataset.id, ...d.args ), - change: ( d, chan ) => this.onchange( "changeChannel", chan.dataset.id, ...d.args ), - toggle: ( d, chan ) => this.onchange( "toggleChannel", chan.dataset.id ), - }, - } ); - new gsuiReorder( { - rootElement: this, - direction: "row", - dataTransferType: "channel", - itemSelector: "gsui-channel", - handleSelector: ".gsuiChannel-grip", - parentSelector: ".gsuiChannels-panChannels", - onchange: elChan => { - this.onchange( "reorderChannel", elChan.dataset.id, - gsuiReorder.listComputeOrderChange( this.#elements.pchans, {} ) ); - }, - } ); - } - - // ......................................................................... - connectedCallback() { - if ( !this.firstChild ) { - this.append( ...this.#children ); - this.#children = null; - } - GSUobserveSizeOf( this, this.#onresizeBind ); - } - disconnectedCallback() { - GSUunobserveSizeOf( this, this.#onresizeBind ); - } - - // ......................................................................... - #onresize() { - const chans = Object.values( this.#chans ); - - if ( chans.length ) { - const { width, height } = chans[ 0 ].analyser.getBoundingClientRect(); - - this.#analyserW = width; - this.#analyserH = height; - chans.forEach( chan => chan.analyser.setResolution( width, height ) ); - } - } - updateAudioData( id, ldata, rdata ) { - this.#chans[ id ].analyser.draw( ldata, rdata ); - } - selectChannel( id ) { - const chan = this.#chans[ id ]; - const pchan = this.#chans[ this.#chanSelected ]; - - pchan && GSUsetAttribute( pchan, "selected", false ); - GSUsetAttribute( chan, "selected", true ); - this.#chanSelected = id; - this.#updateChanConnections(); - this.onselectChan?.( id ); - } - static openSelectChannelPopup( chans, currChanId ) { - return new Promise( res => { - gsuiChannels.#selectChanInput.append( - ...Object.entries( chans ).map( - kv => GSUcreateOption( { value: kv[ 0 ] }, kv[ 1 ].name ) ) - ); - gsuiChannels.#selectChanInput.value = currChanId; - GSUpopup.custom( { - title: "Channels", - element: gsuiChannels.#selectChanPopup, - submit( data ) { - res( data.channel !== currChanId ? data.channel : null ); - } - } ).then(() => GSUemptyElement( gsuiChannels.#selectChanInput ) ); - } ); - } - - // ......................................................................... - $getChannel( id ) { - return this.#chans[ id ]; - } - addChannel( id ) { - const chan = GSUcreateElement( "gsui-channel", { "data-id": id } ); - const qs = n => chan.querySelector( `.gsuiChannel-${ n }` ); - - ( id === "main" ? this.#elements.pmain : this.#elements.pchans ).append( chan ); - this.#chans[ id ] = chan; - qs( "delete" ).onclick = () => this.onchange( "removeChannel", id ); - qs( "connect" ).onclick = () => this.onchange( "redirectChannel", this.#chanSelected, id ); - qs( "rename" ).onclick = () => { - GSUpopup.prompt( "Rename channel", "", GSUgetAttribute( this.#chans[ id ], "name" ) ) - .then( name => this.onchange( "renameChannel", id, name ) ); - }; - chan.analyser.setResolution( this.#analyserW, this.#analyserH ); - if ( this.#chanSelected ) { - this.#updateChanConnections(); - } else if ( id === "main" ) { - this.selectChannel( id ); - } - } - removeChannel( id ) { - const chan = this.#chans[ id ]; - - if ( id === this.#chanSelected ) { - const next = this.#getNextChan( chan, "nextElementSibling" ); - - if ( next ) { - this.selectChannel( next.dataset.id ); - } else { - const prev = this.#getNextChan( chan, "previousElementSibling" ); - - this.selectChannel( prev ? prev.dataset.id : "main" ); - } - } - delete this.#chans[ id ]; - chan.remove(); - } - toggleChannel( id, b ) { - GSUsetAttribute( this.#chans[ id ], "muted", !b ); - } - changePanChannel( id, val ) { - GSUsetAttribute( this.#chans[ id ], "pan", val ); - } - changeGainChannel( id, val ) { - GSUsetAttribute( this.#chans[ id ], "gain", val ); - } - renameChannel( id, name ) { - GSUsetAttribute( this.#chans[ id ], "name", name ); - } - reorderChannel( id, n ) { - this.#chans[ id ].dataset.order = n; - } - reorderChannels( channels ) { - gsuiReorder.listReorder( this.#elements.pchans, channels ); - } - redirectChannel( id, dest ) { - this.#chans[ id ].dataset.dest = dest; - this.#updateChanConnections(); - } - - // ......................................................................... - #getNextChan( el, dir ) { - const sibling = el[ dir ]; - - return sibling && "id" in sibling.dataset ? sibling : null; - } - #updateChanConnections() { - const selId = this.#chanSelected; - - if ( selId ) { - const chan = this.#chans[ selId ]; - const chanDest = chan.dataset.dest; - let bOnce = false; - - Object.entries( this.#chans ).forEach( ( [ id, chan ] ) => { - const a = id === chanDest; - const b = chan.dataset.dest === selId; - - GSUsetAttribute( chan, "connecta", a ? "up" : "" ); - GSUsetAttribute( chan, "connectb", b ? "down" : "" ); - bOnce = bOnce || b; - } ); - GSUsetAttribute( chan, "connecta", selId !== "main" ? "down" : "" ); - GSUsetAttribute( chan, "connectb", bOnce ? "up" : "" ); - } - } -} - -Object.freeze( gsuiChannels ); -customElements.define( "gsui-channels", gsuiChannels ); +"use strict"; + +class gsuiChannels extends gsui0ne { + $oninput = null; + $onchange = null; + $onselectChan = null; + $onselectEffect = null; + #chans = {}; + #chanSelected = null; + #analyserW = 10; + #analyserH = 50; + #onresizeBind = this.#onresize.bind( this ); + static #selectChanPopup = GSUgetTemplate( "gsui-channels-selectPopup" ); + static #selectChanInput = gsuiChannels.#selectChanPopup.querySelector( "select" ); + + constructor() { + super( { + $cmpName: "gsuiChannels", + $tagName: "gsui-channels", + $elements: { + pmain: ".gsuiChannels-panMain", + pchans: ".gsuiChannels-panChannels", + addBtn: ".gsuiChannels-addChan", + }, + } ); + Object.seal( this ); + + this.$elements.addBtn.onclick = () => this.$onchange( "addChannel" ); + GSUlistenEvents( this, { + gsuiChannel: { + selectChannel: ( d, chan ) => this.$selectChannel( chan.dataset.id ), + selectEffect: ( d, chan ) => this.$onselectEffect( chan.dataset.id, d.args[ 0 ] ), + liveChange: ( d, chan ) => this.$oninput( chan.dataset.id, ...d.args ), + change: ( d, chan ) => this.$onchange( "changeChannel", chan.dataset.id, ...d.args ), + toggle: ( d, chan ) => this.$onchange( "toggleChannel", chan.dataset.id ), + }, + } ); + new gsuiReorder( { + rootElement: this, + direction: "row", + dataTransferType: "channel", + itemSelector: "gsui-channel", + handleSelector: ".gsuiChannel-grip", + parentSelector: ".gsuiChannels-panChannels", + onchange: elChan => { + this.$onchange( "reorderChannel", elChan.dataset.id, + gsuiReorder.listComputeOrderChange( this.$elements.pchans, {} ) ); + }, + } ); + } + + // ......................................................................... + $connected() { + GSUobserveSizeOf( this, this.#onresizeBind ); + } + $disconnected() { + GSUunobserveSizeOf( this, this.#onresizeBind ); + } + + // ......................................................................... + #onresize() { + const chans = Object.values( this.#chans ); + + if ( chans.length ) { + const { width, height } = chans[ 0 ].$analyser.getBoundingClientRect(); + + this.#analyserW = width; + this.#analyserH = height; + chans.forEach( chan => chan.$analyser.setResolution( width, height ) ); + } + } + $updateAudioData( id, ldata, rdata ) { + this.#chans[ id ].$analyser.draw( ldata, rdata ); + } + $selectChannel( id ) { + const chan = this.#chans[ id ]; + const pchan = this.#chans[ this.#chanSelected ]; + + pchan && GSUsetAttribute( pchan, "selected", false ); + GSUsetAttribute( chan, "selected", true ); + this.#chanSelected = id; + this.#updateChanConnections(); + this.$onselectChan?.( id ); + } + static $openSelectChannelPopup( chans, currChanId ) { + return new Promise( res => { + gsuiChannels.#selectChanInput.append( + ...Object.entries( chans ).map( + kv => GSUcreateOption( { value: kv[ 0 ] }, kv[ 1 ].name ) ) + ); + gsuiChannels.#selectChanInput.value = currChanId; + GSUpopup.custom( { + title: "Channels", + element: gsuiChannels.#selectChanPopup, + submit( data ) { + res( data.channel !== currChanId ? data.channel : null ); + } + } ).then(() => GSUemptyElement( gsuiChannels.#selectChanInput ) ); + } ); + } + + // ......................................................................... + $getChannel( id ) { + return this.#chans[ id ]; + } + $addChannel( id ) { + const chan = GSUcreateElement( "gsui-channel", { "data-id": id } ); + const qs = n => chan.querySelector( `.gsuiChannel-${ n }` ); + + ( id === "main" ? this.$elements.pmain : this.$elements.pchans ).append( chan ); + this.#chans[ id ] = chan; + qs( "delete" ).onclick = () => this.$onchange( "removeChannel", id ); + qs( "connect" ).onclick = () => this.$onchange( "redirectChannel", this.#chanSelected, id ); + qs( "rename" ).onclick = () => { + GSUpopup.prompt( "Rename channel", "", GSUgetAttribute( this.#chans[ id ], "name" ) ) + .then( name => this.$onchange( "renameChannel", id, name ) ); + }; + chan.$analyser.setResolution( this.#analyserW, this.#analyserH ); + if ( this.#chanSelected ) { + this.#updateChanConnections(); + } else if ( id === "main" ) { + this.$selectChannel( id ); + } + } + $removeChannel( id ) { + const chan = this.#chans[ id ]; + + if ( id === this.#chanSelected ) { + const next = this.#getNextChan( chan, "nextElementSibling" ); + + if ( next ) { + this.$selectChannel( next.dataset.id ); + } else { + const prev = this.#getNextChan( chan, "previousElementSibling" ); + + this.$selectChannel( prev ? prev.dataset.id : "main" ); + } + } + delete this.#chans[ id ]; + chan.remove(); + } + $toggleChannel( id, b ) { + GSUsetAttribute( this.#chans[ id ], "muted", !b ); + } + $changePanChannel( id, val ) { + GSUsetAttribute( this.#chans[ id ], "pan", val ); + } + $changeGainChannel( id, val ) { + GSUsetAttribute( this.#chans[ id ], "gain", val ); + } + $renameChannel( id, name ) { + GSUsetAttribute( this.#chans[ id ], "name", name ); + } + $reorderChannel( id, n ) { + this.#chans[ id ].dataset.order = n; + } + $reorderChannels( channels ) { + gsuiReorder.listReorder( this.$elements.pchans, channels ); + } + $redirectChannel( id, dest ) { + this.#chans[ id ].dataset.dest = dest; + this.#updateChanConnections(); + } + + // ......................................................................... + #getNextChan( el, dir ) { + const sibling = el[ dir ]; + + return sibling && "id" in sibling.dataset ? sibling : null; + } + #updateChanConnections() { + const selId = this.#chanSelected; + + if ( selId ) { + const chan = this.#chans[ selId ]; + const chanDest = chan.dataset.dest; + let bOnce = false; + + Object.entries( this.#chans ).forEach( ( [ id, chan ] ) => { + const a = id === chanDest; + const b = chan.dataset.dest === selId; + + GSUsetAttribute( chan, "connecta", a ? "up" : "" ); + GSUsetAttribute( chan, "connectb", b ? "down" : "" ); + bOnce = bOnce || b; + } ); + GSUsetAttribute( chan, "connecta", selId !== "main" ? "down" : "" ); + GSUsetAttribute( chan, "connectb", bOnce ? "up" : "" ); + } + } +} + +Object.freeze( gsuiChannels ); +customElements.define( "gsui-channels", gsuiChannels ); diff --git a/gsuiMixer/index.html b/gsuiMixer/index.html index 56dd6a13..6bc34b21 100644 --- a/gsuiMixer/index.html +++ b/gsuiMixer/index.html @@ -1,89 +1,90 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gsuiPatterns/gsuiPatterns.js b/gsuiPatterns/gsuiPatterns.js index 9913e02f..17ef0274 100644 --- a/gsuiPatterns/gsuiPatterns.js +++ b/gsuiPatterns/gsuiPatterns.js @@ -1,311 +1,311 @@ -"use strict"; - -class gsuiPatterns extends HTMLElement { - $getChannels = () => []; - #dispatch = GSUdispatchEvent.bind( null, this, "gsuiPatterns" ); - #fnsPattern = Object.freeze( { - clone: id => this.onchange( "clonePattern", id ), - remove: id => this.onchange( "removePattern", id ), - editInfo: ( id, el ) => this.#openInfoPopup( id, el ), - undefined: id => this.onchange( "openPattern", id ), - redirect: ( id, el, e ) => this.#openChannelsPopup( "redirectPatternBuffer", id, e.target.dataset.id ), - } ); - #fnsSynth = Object.freeze( { - expand: id => this.expandSynth( id ), - undefined: id => this.onchange( "openSynth", id ), - redirect: ( id, e ) => this.#openChannelsPopup( "redirectSynth", id, e.target.dataset.id ), - newPattern: id => { - this.onchange( "addPatternKeys", id ); - this.expandSynth( id, true ); - }, - delete: id => { - this.#elements.lists.synth.children.length > 1 - ? this.onchange( "removeSynth", id ) - : GSUpopup.alert( "Error", "You have to keep at least one synthesizer" ); - }, - } ); - #children = GSUgetTemplate( "gsui-patterns" ); - #elements = GSUfindElements( this.#children, { - lists: { - slices: ".gsuiPatterns-panelSlices .gsuiPatterns-panel-list", - drums: ".gsuiPatterns-panelDrums .gsuiPatterns-panel-list", - synth: ".gsuiPatterns-panelKeys .gsuiPatterns-panel-list", - buffer: ".gsuiPatterns-panelBuffers .gsuiPatterns-panel-list", - }, - newSlices: "[data-action='newSlices']", - newDrums: "[data-action='newDrums']", - newSynth: "[data-action='newSynth']", - } ); - #nlKeysLists = this.#elements.lists.synth.getElementsByClassName( "gsuiPatterns-synth-patterns" ); - static infoPopupContent = GSUgetTemplate( "gsui-patterns-infoPopup" ); - - constructor() { - super(); - this.onchange = - this.onpatternDataTransfer = null; - Object.seal( this ); - - new gsuiReorder( { - rootElement: this.#elements.lists.buffer, - direction: "column", - dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), - dataTransferType: "pattern-buffer", - itemSelector: ".gsuiPatterns-pattern", - handleSelector: ".gsuiPatterns-pattern-grip", - parentSelector: ".gsuiPatterns-panel-list", - onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.buffer ), - } ); - new gsuiReorder( { - rootElement: this.#elements.lists.slices, - direction: "column", - dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), - dataTransferType: "pattern-slices", - itemSelector: ".gsuiPatterns-pattern", - handleSelector: ".gsuiPatterns-pattern-grip", - parentSelector: ".gsuiPatterns-panel-list", - onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.slices ), - } ); - new gsuiReorder( { - rootElement: this.#elements.lists.drums, - direction: "column", - dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), - dataTransferType: "pattern-drums", - itemSelector: ".gsuiPatterns-pattern", - handleSelector: ".gsuiPatterns-pattern-grip", - parentSelector: ".gsuiPatterns-panel-list", - onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.drums ), - } ); - new gsuiReorder( { - rootElement: this.#elements.lists.synth, - direction: "column", - dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), - dataTransferType: "pattern-keys", - itemSelector: ".gsuiPatterns-pattern", - handleSelector: ".gsuiPatterns-pattern-grip", - parentSelector: ".gsuiPatterns-synth-patterns", - onchange: this.#onreorderPatternsKeys.bind( this ), - } ); - this.#elements.lists.buffer.ondrop = e => { - const defBufId = e.dataTransfer.getData( "library-buffer:default" ); - const locBufId = e.dataTransfer.getData( "library-buffer:local" ); - - if ( defBufId ) { - this.#dispatch( "libraryBufferDropped", "default", defBufId ); - } else if ( locBufId ) { - this.#dispatch( "libraryBufferDropped", "local", locBufId ); - } - }; - this.#elements.lists.synth.ondragover = e => { - const syn = e.target.closest( ".gsuiPatterns-synth" ); - - if ( syn ) { - this.expandSynth( syn.dataset.id, true ); - } - }; - this.#elements.lists.synth.ondblclick = e => { - if ( e.target.classList.contains( "gsuiPatterns-synth-info" ) ) { - this.expandSynth( e.target.closest( ".gsuiPatterns-synth" ).dataset.id ); - } - }; - this.#elements.lists.buffer.onclick = - this.#elements.lists.slices.onclick = - this.#elements.lists.drums.onclick = this.#onclickListPatterns.bind( this ); - this.#elements.lists.synth.onclick = this.#onclickSynths.bind( this ); - this.#elements.newSlices.onclick = () => this.onchange( "addPatternSlices" ); - this.#elements.newDrums.onclick = () => this.onchange( "addPatternDrums" ); - this.#elements.newSynth.onclick = () => this.onchange( "addSynth" ); - } - - // ......................................................................... - connectedCallback() { - if ( !this.firstChild ) { - this.append( this.#children ); - this.#children = null; - } - } - - // ......................................................................... - expandSynth( id, b ) { - const elSyn = this.#getSynth( id ); - const show = elSyn.classList.toggle( "gsuiPatterns-synth-expanded", b ); - - elSyn.querySelector( ".gsuiPatterns-synth-expand" ).dataset.icon = `caret-${ show ? "down" : "right" }`; - } - reorderPatterns( patterns ) { - gsuiReorder.listReorder( this.#elements.lists.buffer, patterns ); - gsuiReorder.listReorder( this.#elements.lists.slices, patterns ); - gsuiReorder.listReorder( this.#elements.lists.drums, patterns ); - Array.prototype.forEach.call( this.#nlKeysLists, list => { - gsuiReorder.listReorder( list, patterns ); - } ); - } - #openChannelsPopup( action, objId, currChanId ) { - gsuiChannels.openSelectChannelPopup( this.$getChannels(), currChanId ) - .then( chanId => chanId && this.onchange( action, objId, chanId ) ); - } - #openInfoPopup( id, el ) { - const radio = gsuiPatterns.infoPopupContent.querySelector( `[value="${ el.dataset.bufferType }"]` ); - - if ( radio ) { - radio.checked = true; - } else { - const radio = gsuiPatterns.infoPopupContent.querySelector( "input:checked" ); - - if ( radio ) { - radio.checked = false; - } - } - gsuiPatterns.infoPopupContent.querySelector( "[name='bpm']" ).value = el.dataset.bufferBpm; - gsuiPatterns.infoPopupContent.querySelector( "[name='name']" ).value = el.dataset.name; - GSUpopup.custom( { - title: "Buffer's info", - element: gsuiPatterns.infoPopupContent, - submit: data => { - data.bpm = data.bpm || null; - this.onchange( "changePatternBufferInfo", id, data ); - } - } ); - } - - // ......................................................................... - updateChannel( id, name ) { - this.querySelectorAll( `.gsuiPatterns-btnSolid[data-id="${ id }"] .gsuiPatterns-btnText` ) - .forEach( el => el.textContent = name ); - } - - // ......................................................................... - addSynth( id ) { - const elSyn = GSUgetTemplate( "gsui-patterns-synth" ); - - elSyn.dataset.id = id; - this.#elements.lists.synth.prepend( elSyn ); - } - changeSynth( id, prop, val ) { - const elSyn = this.#getSynth( id ); - - switch ( prop ) { - case "name": elSyn.querySelector( ".gsuiPatterns-synth-name" ).textContent = val; break; - case "dest": elSyn.querySelector( ".gsuiPatterns-synth-dest" ).dataset.id = val; break; - case "destName": elSyn.querySelector( ".gsuiPatterns-synth-dest .gsuiPatterns-btnText" ).textContent = val; break; - } - } - deleteSynth( id ) { - this.#getSynth( id ).remove(); - } - - // ......................................................................... - addPattern( id, { type, synth } ) { - const elPat = GSUgetTemplate( "gsui-patterns-pattern" ); - - elPat.dataset.id = id; - if ( type !== "buffer" ) { - elPat.querySelector( ".gsuiPatterns-pattern-btnInfo" ).remove(); - elPat.querySelector( ".gsuiPatterns-destArrow" ).remove(); - elPat.querySelector( ".gsuiPatterns-pattern-dest" ).remove(); - } else { - elPat.querySelector( "[data-action='clone']" ).remove(); // 1. - } - this.#getPatternParent( type, synth ).append( elPat ); - } - changePattern( id, prop, val ) { - const elPat = this.getPattern( id ); - - switch ( prop ) { - case "data-missing": GSUsetAttribute( elPat, "data-missing", val ); break; - case "order": elPat.dataset.order = val; break; - case "name": - elPat.dataset.name = val; - elPat.querySelector( ".gsuiPatterns-pattern-name" ).textContent = val; - break; - case "dest": elPat.querySelector( ".gsuiPatterns-pattern-dest" ).dataset.id = val; break; - case "destName": elPat.querySelector( ".gsuiPatterns-pattern-dest .gsuiPatterns-btnText" ).textContent = val; break; - case "synth": this.#getPatternParent( "keys", val ).append( elPat ); break; - case "bufferType": - GSUsetAttribute( elPat, "data-buffer-type", val ); - elPat.querySelector( ".gsuiPatterns-pattern-btnInfo" ).dataset.icon = `buf-${ val || "undefined" }`; - break; - case "bufferBpm": - GSUsetAttribute( elPat, "data-buffer-bpm", val ); - break; - } - } - appendPatternSVG( id, svg ) { - svg.classList.add( "gsuiPatterns-pattern-svg" ); - this.getPattern( id ).querySelector( ".gsuiPatterns-pattern-content" ).append( svg ); - } - deletePattern( id ) { - this.getPattern( id )?.remove(); // 2. - } - - // ......................................................................... - selectPattern( type, id ) { - const elList = this.#elements.lists[ type === "keys" ? "synth" : type ]; - - elList.querySelector( ".gsuiPatterns-pattern-selected" )?.classList?.remove( "gsuiPatterns-pattern-selected" ); - this.getPattern( id )?.classList?.add( "gsuiPatterns-pattern-selected" ); - } - selectSynth( id ) { - this.#elements.lists.synth.querySelector( ".gsuiPatterns-synth-selected" )?.classList?.remove( "gsuiPatterns-synth-selected" ); - this.#getSynth( id ).classList.add( "gsuiPatterns-synth-selected" ); - } - - // ......................................................................... - getPattern( id ) { - return this.querySelector( `.gsuiPatterns-pattern[data-id="${ id }"]` ); - } - #getSynth( id ) { - return this.#elements.lists.synth.querySelector( `.gsuiPatterns-synth[data-id="${ id }"]` ); - } - #getPatternParent( type, synthId ) { - switch ( type ) { - case "slices": - case "buffer": - case "drums": return this.#elements.lists[ type ]; - case "keys": return this.#elements.lists.synth.querySelector( `.gsuiPatterns-synth[data-id="${ synthId }"] .gsuiPatterns-synth-patterns` ); - } - } - - // ......................................................................... - #onreorderPatterns( list, elPat ) { - this.onchange( "reorderPattern", elPat.dataset.id, - gsuiReorder.listComputeOrderChange( list, {} ) ); - } - #onreorderPatternsKeys( elPat, indA, indB, parA, parB ) { - if ( parA === parB ) { - this.#onreorderPatterns( parA, elPat ); - } else { - const patId = elPat.dataset.id; - const synth = parB.parentNode.dataset.id; - const patterns = { [ patId ]: { synth } }; - - gsuiReorder.listComputeOrderChange( parA, patterns ); - gsuiReorder.listComputeOrderChange( parB, patterns ); - this.onchange( "redirectPatternKeys", patId, synth, patterns ); - } - } - #onclickListPatterns( e ) { - const pat = e.target.closest( ".gsuiPatterns-pattern" ); - - if ( pat ) { - this.#fnsPattern[ e.target.dataset.action ]( pat.dataset.id, pat, e ); - return false; - } - } - #onclickSynths( e ) { - if ( this.#onclickListPatterns( e ) !== false ) { - const syn = e.target.closest( ".gsuiPatterns-synth" ); - - if ( syn ) { - this.#fnsSynth[ e.target.dataset.action ]( syn.dataset.id, e ); - } - } - } -} - -Object.freeze( gsuiPatterns ); -customElements.define( "gsui-patterns", gsuiPatterns ); - -/* -1. The cloning feature for the patterns of type buffer is removed because it's for the moment useless. -2. We are checking if the pattern exists because the entire synth could have been removed before. -*/ +"use strict"; + +class gsuiPatterns extends HTMLElement { + $getChannels = () => []; + #dispatch = GSUdispatchEvent.bind( null, this, "gsuiPatterns" ); + #fnsPattern = Object.freeze( { + clone: id => this.onchange( "clonePattern", id ), + remove: id => this.onchange( "removePattern", id ), + editInfo: ( id, el ) => this.#openInfoPopup( id, el ), + undefined: id => this.onchange( "openPattern", id ), + redirect: ( id, el, e ) => this.#openChannelsPopup( "redirectPatternBuffer", id, e.target.dataset.id ), + } ); + #fnsSynth = Object.freeze( { + expand: id => this.expandSynth( id ), + undefined: id => this.onchange( "openSynth", id ), + redirect: ( id, e ) => this.#openChannelsPopup( "redirectSynth", id, e.target.dataset.id ), + newPattern: id => { + this.onchange( "addPatternKeys", id ); + this.expandSynth( id, true ); + }, + delete: id => { + this.#elements.lists.synth.children.length > 1 + ? this.onchange( "removeSynth", id ) + : GSUpopup.alert( "Error", "You have to keep at least one synthesizer" ); + }, + } ); + #children = GSUgetTemplate( "gsui-patterns" ); + #elements = GSUfindElements( this.#children, { + lists: { + slices: ".gsuiPatterns-panelSlices .gsuiPatterns-panel-list", + drums: ".gsuiPatterns-panelDrums .gsuiPatterns-panel-list", + synth: ".gsuiPatterns-panelKeys .gsuiPatterns-panel-list", + buffer: ".gsuiPatterns-panelBuffers .gsuiPatterns-panel-list", + }, + newSlices: "[data-action='newSlices']", + newDrums: "[data-action='newDrums']", + newSynth: "[data-action='newSynth']", + } ); + #nlKeysLists = this.#elements.lists.synth.getElementsByClassName( "gsuiPatterns-synth-patterns" ); + static infoPopupContent = GSUgetTemplate( "gsui-patterns-infoPopup" ); + + constructor() { + super(); + this.onchange = + this.onpatternDataTransfer = null; + Object.seal( this ); + + new gsuiReorder( { + rootElement: this.#elements.lists.buffer, + direction: "column", + dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), + dataTransferType: "pattern-buffer", + itemSelector: ".gsuiPatterns-pattern", + handleSelector: ".gsuiPatterns-pattern-grip", + parentSelector: ".gsuiPatterns-panel-list", + onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.buffer ), + } ); + new gsuiReorder( { + rootElement: this.#elements.lists.slices, + direction: "column", + dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), + dataTransferType: "pattern-slices", + itemSelector: ".gsuiPatterns-pattern", + handleSelector: ".gsuiPatterns-pattern-grip", + parentSelector: ".gsuiPatterns-panel-list", + onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.slices ), + } ); + new gsuiReorder( { + rootElement: this.#elements.lists.drums, + direction: "column", + dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), + dataTransferType: "pattern-drums", + itemSelector: ".gsuiPatterns-pattern", + handleSelector: ".gsuiPatterns-pattern-grip", + parentSelector: ".gsuiPatterns-panel-list", + onchange: this.#onreorderPatterns.bind( this, this.#elements.lists.drums ), + } ); + new gsuiReorder( { + rootElement: this.#elements.lists.synth, + direction: "column", + dataTransfer: ( ...args ) => this.onpatternDataTransfer( ...args ), + dataTransferType: "pattern-keys", + itemSelector: ".gsuiPatterns-pattern", + handleSelector: ".gsuiPatterns-pattern-grip", + parentSelector: ".gsuiPatterns-synth-patterns", + onchange: this.#onreorderPatternsKeys.bind( this ), + } ); + this.#elements.lists.buffer.ondrop = e => { + const defBufId = e.dataTransfer.getData( "library-buffer:default" ); + const locBufId = e.dataTransfer.getData( "library-buffer:local" ); + + if ( defBufId ) { + this.#dispatch( "libraryBufferDropped", "default", defBufId ); + } else if ( locBufId ) { + this.#dispatch( "libraryBufferDropped", "local", locBufId ); + } + }; + this.#elements.lists.synth.ondragover = e => { + const syn = e.target.closest( ".gsuiPatterns-synth" ); + + if ( syn ) { + this.expandSynth( syn.dataset.id, true ); + } + }; + this.#elements.lists.synth.ondblclick = e => { + if ( e.target.classList.contains( "gsuiPatterns-synth-info" ) ) { + this.expandSynth( e.target.closest( ".gsuiPatterns-synth" ).dataset.id ); + } + }; + this.#elements.lists.buffer.onclick = + this.#elements.lists.slices.onclick = + this.#elements.lists.drums.onclick = this.#onclickListPatterns.bind( this ); + this.#elements.lists.synth.onclick = this.#onclickSynths.bind( this ); + this.#elements.newSlices.onclick = () => this.onchange( "addPatternSlices" ); + this.#elements.newDrums.onclick = () => this.onchange( "addPatternDrums" ); + this.#elements.newSynth.onclick = () => this.onchange( "addSynth" ); + } + + // ......................................................................... + connectedCallback() { + if ( !this.firstChild ) { + this.append( this.#children ); + this.#children = null; + } + } + + // ......................................................................... + expandSynth( id, b ) { + const elSyn = this.#getSynth( id ); + const show = elSyn.classList.toggle( "gsuiPatterns-synth-expanded", b ); + + elSyn.querySelector( ".gsuiPatterns-synth-expand" ).dataset.icon = `caret-${ show ? "down" : "right" }`; + } + reorderPatterns( patterns ) { + gsuiReorder.listReorder( this.#elements.lists.buffer, patterns ); + gsuiReorder.listReorder( this.#elements.lists.slices, patterns ); + gsuiReorder.listReorder( this.#elements.lists.drums, patterns ); + Array.prototype.forEach.call( this.#nlKeysLists, list => { + gsuiReorder.listReorder( list, patterns ); + } ); + } + #openChannelsPopup( action, objId, currChanId ) { + gsuiChannels.$openSelectChannelPopup( this.$getChannels(), currChanId ) + .then( chanId => chanId && this.onchange( action, objId, chanId ) ); + } + #openInfoPopup( id, el ) { + const radio = gsuiPatterns.infoPopupContent.querySelector( `[value="${ el.dataset.bufferType }"]` ); + + if ( radio ) { + radio.checked = true; + } else { + const radio = gsuiPatterns.infoPopupContent.querySelector( "input:checked" ); + + if ( radio ) { + radio.checked = false; + } + } + gsuiPatterns.infoPopupContent.querySelector( "[name='bpm']" ).value = el.dataset.bufferBpm; + gsuiPatterns.infoPopupContent.querySelector( "[name='name']" ).value = el.dataset.name; + GSUpopup.custom( { + title: "Buffer's info", + element: gsuiPatterns.infoPopupContent, + submit: data => { + data.bpm = data.bpm || null; + this.onchange( "changePatternBufferInfo", id, data ); + } + } ); + } + + // ......................................................................... + updateChannel( id, name ) { + this.querySelectorAll( `.gsuiPatterns-btnSolid[data-id="${ id }"] .gsuiPatterns-btnText` ) + .forEach( el => el.textContent = name ); + } + + // ......................................................................... + addSynth( id ) { + const elSyn = GSUgetTemplate( "gsui-patterns-synth" ); + + elSyn.dataset.id = id; + this.#elements.lists.synth.prepend( elSyn ); + } + changeSynth( id, prop, val ) { + const elSyn = this.#getSynth( id ); + + switch ( prop ) { + case "name": elSyn.querySelector( ".gsuiPatterns-synth-name" ).textContent = val; break; + case "dest": elSyn.querySelector( ".gsuiPatterns-synth-dest" ).dataset.id = val; break; + case "destName": elSyn.querySelector( ".gsuiPatterns-synth-dest .gsuiPatterns-btnText" ).textContent = val; break; + } + } + deleteSynth( id ) { + this.#getSynth( id ).remove(); + } + + // ......................................................................... + addPattern( id, { type, synth } ) { + const elPat = GSUgetTemplate( "gsui-patterns-pattern" ); + + elPat.dataset.id = id; + if ( type !== "buffer" ) { + elPat.querySelector( ".gsuiPatterns-pattern-btnInfo" ).remove(); + elPat.querySelector( ".gsuiPatterns-destArrow" ).remove(); + elPat.querySelector( ".gsuiPatterns-pattern-dest" ).remove(); + } else { + elPat.querySelector( "[data-action='clone']" ).remove(); // 1. + } + this.#getPatternParent( type, synth ).append( elPat ); + } + changePattern( id, prop, val ) { + const elPat = this.getPattern( id ); + + switch ( prop ) { + case "data-missing": GSUsetAttribute( elPat, "data-missing", val ); break; + case "order": elPat.dataset.order = val; break; + case "name": + elPat.dataset.name = val; + elPat.querySelector( ".gsuiPatterns-pattern-name" ).textContent = val; + break; + case "dest": elPat.querySelector( ".gsuiPatterns-pattern-dest" ).dataset.id = val; break; + case "destName": elPat.querySelector( ".gsuiPatterns-pattern-dest .gsuiPatterns-btnText" ).textContent = val; break; + case "synth": this.#getPatternParent( "keys", val ).append( elPat ); break; + case "bufferType": + GSUsetAttribute( elPat, "data-buffer-type", val ); + elPat.querySelector( ".gsuiPatterns-pattern-btnInfo" ).dataset.icon = `buf-${ val || "undefined" }`; + break; + case "bufferBpm": + GSUsetAttribute( elPat, "data-buffer-bpm", val ); + break; + } + } + appendPatternSVG( id, svg ) { + svg.classList.add( "gsuiPatterns-pattern-svg" ); + this.getPattern( id ).querySelector( ".gsuiPatterns-pattern-content" ).append( svg ); + } + deletePattern( id ) { + this.getPattern( id )?.remove(); // 2. + } + + // ......................................................................... + selectPattern( type, id ) { + const elList = this.#elements.lists[ type === "keys" ? "synth" : type ]; + + elList.querySelector( ".gsuiPatterns-pattern-selected" )?.classList?.remove( "gsuiPatterns-pattern-selected" ); + this.getPattern( id )?.classList?.add( "gsuiPatterns-pattern-selected" ); + } + selectSynth( id ) { + this.#elements.lists.synth.querySelector( ".gsuiPatterns-synth-selected" )?.classList?.remove( "gsuiPatterns-synth-selected" ); + this.#getSynth( id ).classList.add( "gsuiPatterns-synth-selected" ); + } + + // ......................................................................... + getPattern( id ) { + return this.querySelector( `.gsuiPatterns-pattern[data-id="${ id }"]` ); + } + #getSynth( id ) { + return this.#elements.lists.synth.querySelector( `.gsuiPatterns-synth[data-id="${ id }"]` ); + } + #getPatternParent( type, synthId ) { + switch ( type ) { + case "slices": + case "buffer": + case "drums": return this.#elements.lists[ type ]; + case "keys": return this.#elements.lists.synth.querySelector( `.gsuiPatterns-synth[data-id="${ synthId }"] .gsuiPatterns-synth-patterns` ); + } + } + + // ......................................................................... + #onreorderPatterns( list, elPat ) { + this.onchange( "reorderPattern", elPat.dataset.id, + gsuiReorder.listComputeOrderChange( list, {} ) ); + } + #onreorderPatternsKeys( elPat, indA, indB, parA, parB ) { + if ( parA === parB ) { + this.#onreorderPatterns( parA, elPat ); + } else { + const patId = elPat.dataset.id; + const synth = parB.parentNode.dataset.id; + const patterns = { [ patId ]: { synth } }; + + gsuiReorder.listComputeOrderChange( parA, patterns ); + gsuiReorder.listComputeOrderChange( parB, patterns ); + this.onchange( "redirectPatternKeys", patId, synth, patterns ); + } + } + #onclickListPatterns( e ) { + const pat = e.target.closest( ".gsuiPatterns-pattern" ); + + if ( pat ) { + this.#fnsPattern[ e.target.dataset.action ]( pat.dataset.id, pat, e ); + return false; + } + } + #onclickSynths( e ) { + if ( this.#onclickListPatterns( e ) !== false ) { + const syn = e.target.closest( ".gsuiPatterns-synth" ); + + if ( syn ) { + this.#fnsSynth[ e.target.dataset.action ]( syn.dataset.id, e ); + } + } + } +} + +Object.freeze( gsuiPatterns ); +customElements.define( "gsui-patterns", gsuiPatterns ); + +/* +1. The cloning feature for the patterns of type buffer is removed because it's for the moment useless. +2. We are checking if the pattern exists because the entire synth could have been removed before. +*/