diff --git a/jsHelper/expFeatures.js b/jsHelper/expFeatures.js index 16920e05c6..89ea1caac8 100644 --- a/jsHelper/expFeatures.js +++ b/jsHelper/expFeatures.js @@ -1,4 +1,4 @@ -(() => { +(async () => { let overrideList; let prevSessionOverrideList = []; const newFeatures = []; @@ -126,33 +126,6 @@ notice.innerText = "Waiting for Spotify to finish loading..."; content.appendChild(notice); - new Spicetify.Menu.Item( - "Experimental features", - false, - () => { - Spicetify.PopupModal.display({ - title: "Experimental features", - content, - isLarge: true - }); - if (!isFallback) return; - - const closeButton = document.querySelector("body > generic-modal button.main-trackCreditsModal-closeBtn"); - const modalOverlay = document.querySelector("body > generic-modal > div"); - - if (closeButton && modalOverlay) { - closeButton.onclick = () => location.reload(); - modalOverlay.onclick = e => { - // If clicked on overlay, also reload - if (e.target === modalOverlay) { - location.reload(); - } - }; - } - }, - `` - ).register(); - (function waitForRemoteConfigResolver() { // Don't show options if hooks aren't patched/loaded if (!hooksPatched || (!Spicetify.RemoteConfigResolver && !Spicetify.Platform?.RemoteConfiguration)) { @@ -295,4 +268,33 @@ ${Spicetify.SVGIcons.search} setOverrides(Spicetify.createInternalMap?.(featureMap)); })(); + + await new Promise(Spicetify.Events.webpackLoaded.on); + + new Spicetify.Menu.Item( + "Experimental features", + true, + () => { + Spicetify.PopupModal.display({ + title: "Experimental features", + content, + isLarge: true + }); + if (!isFallback) return; + + const closeButton = document.querySelector("body > generic-modal button.main-trackCreditsModal-closeBtn"); + const modalOverlay = document.querySelector("body > generic-modal > div"); + + if (closeButton && modalOverlay) { + closeButton.onclick = () => location.reload(); + modalOverlay.onclick = e => { + // If clicked on overlay, also reload + if (e.target === modalOverlay) { + location.reload(); + } + }; + } + }, + `` + ).register(); })(); diff --git a/jsHelper/homeConfig.js b/jsHelper/homeConfig.js index 686479a057..e69157aee1 100644 --- a/jsHelper/homeConfig.js +++ b/jsHelper/homeConfig.js @@ -1,6 +1,6 @@ SpicetifyHomeConfig = {}; -(() => { +(async () => { // Status enum const NORMAL = 0; const STICKY = 1; @@ -163,7 +163,9 @@ SpicetifyHomeConfig = {}; } } - const menu = new Spicetify.Menu.Item("Home config", false, self => { + await new Promise(Spicetify.Events.webpackLoaded.on); + + const menu = new Spicetify.Menu.Item("Home config", true, self => { self.isEnabled = !self.isEnabled; if (self.isEnabled) { injectInteraction(); @@ -177,22 +179,17 @@ SpicetifyHomeConfig = {}; menu.deregister(); }; - (function waitForHistoryAPI() { - if (!Spicetify.Platform?.History || !mounted) { - setTimeout(waitForHistoryAPI, 100); - return; - } - // Init - if (Spicetify.Platform.History.location.pathname === "/") { + await new Promise(res => Spicetify.Events.platformLoaded.on(res)); + // Init + if (Spicetify.Platform.History.location.pathname === "/") { + SpicetifyHomeConfig.addToMenu(); + } + + Spicetify.Platform.History.listen(({ pathname }) => { + if (pathname === "/") { SpicetifyHomeConfig.addToMenu(); + } else { + SpicetifyHomeConfig.removeMenu(); } - - Spicetify.Platform.History.listen(({ pathname }) => { - if (pathname === "/") { - SpicetifyHomeConfig.addToMenu(); - } else { - SpicetifyHomeConfig.removeMenu(); - } - }); - })(); + }); })(); diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index c980ca6fb7..a973020ab7 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -16,7 +16,6 @@ // Store sidebar buttons let buttons = []; let ordered = []; - let initialized = false; function arrangeItems(storage) { const newButtons = [...buttons]; @@ -169,25 +168,22 @@ color: var(--spice-button-disabled); writeStorage(); } - const sidebarConfigItem = new Spicetify.Menu.Item( - "Sidebar config", - false, - self => { - self.isEnabled = !self.isEnabled; - if (self.isEnabled) { - injectInteraction(); - } else { - removeInteraction(); - } - }, - `` - ); - - function finishInit() { - if (initialized) return; - sidebarConfigItem.register(); - initialized = true; - } + (async () => { + await new Promise(Spicetify.Events.webpackLoaded.on); + new Spicetify.Menu.Item( + "Sidebar config", + true, + self => { + self.isEnabled = !self.isEnabled; + if (self.isEnabled) { + injectInteraction(); + } else { + removeInteraction(); + } + }, + `` + ).register(); + })(); function InitSidebarConfig() { // STICKY container @@ -254,8 +250,6 @@ color: var(--spice-button-disabled); arrangeItems(storage); appendItems(); - - finishInit(); } function InitSidebarXConfig() { @@ -309,8 +303,6 @@ color: var(--spice-button-disabled); arrangeItems(storage); appendItems(); - - finishInit(); } InitSidebarConfig(); diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 1093589c63..c027d65313 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -305,7 +305,7 @@ window.Spicetify = { (function waitForPlatform() { if (!Spicetify._platform) { - setTimeout(() => waitForPlatform(), 50); + setTimeout(waitForPlatform, 50); return; } const { _platform } = Spicetify; @@ -316,6 +316,7 @@ window.Spicetify = { Spicetify.Platform[key] = _platform[key]; } } + Spicetify.Events.platformLoaded.fire(); })(); (function hotloadWebpackModules() { @@ -382,6 +383,7 @@ window.Spicetify = { Object.assign(Spicetify, { React: cache.find(m => m?.useMemo), + ReactJSX: cache.find(m => m?.jsx), ReactDOM: cache.find(m => m?.createPortal), ReactDOMServer: cache.find(m => m?.renderToString), // classnames @@ -431,6 +433,7 @@ window.Spicetify = { ), Menu: functionModules.find(m => m.toString().includes("getInitialFocusElement") && m.toString().includes("children")), MenuItem: functionModules.find(m => m.toString().includes("handleMouseEnter") && m.toString().includes("onClick")), + MenuSubMenuItem: functionModules.find(f => f.toString().includes("subMenuIcon")), Slider: wrapProvider(functionModules.find(m => m.toString().includes("onStepBackward") && !m.toString().includes("volume"))), RemoteConfigProvider: functionModules.find(m => m.toString().includes("resolveSuspense") && m.toString().includes("configuration")), RightClickMenu: functionModules.find( @@ -727,6 +730,24 @@ window.Spicetify = { // isSameIdentity Spicetify.URI.isSameIdentity = URIModules.find(m => typeof m === "function" && m.toString().match(/[\w$]+\.id===[\w$]+\.id/)); })(); + + Spicetify.Events.webpackLoaded.fire(); +})(); + +Spicetify.Events = (() => { + class Event { + callbacks = []; + on(callback) { + if (!this.callbacks) return void callback(); + this.callbacks.push(callback); + } + fire() { + for (const callback of this.callbacks) callback(); + this.callbacks = undefined; + } + } + + return { webpackLoaded: new Event(), platformLoaded: new Event() }; })(); // Wait for Spicetify.Player.origin._state before adding following APIs @@ -1248,481 +1269,358 @@ Spicetify.SVGIcons = { return document.head.appendChild(fontStyle); })(); -class _HTMLContextMenuItem extends HTMLLIElement { - constructor({ name, disabled = false, icon = undefined, trailingIcon = undefined, divider = false }) { - super(); - this.name = name; - this.icon = icon; - this.trailingIcon = trailingIcon; - this.disabled = disabled; - this.divider = divider; - this.classList.add("main-contextMenu-menuItem"); - } +Spicetify.ContextMenuV2 = (() => { + const registeredItems = new Map(); - render() { - const parseIcon = icon => { - if (icon && Spicetify.SVGIcons[icon]) { - return ``; - } - return icon || ""; - }; + function parseProps(props) { + if (!props) return; - this.innerHTML = ` - `; - } + const uri = props.uri ?? props.item?.uri ?? props.reference?.uri; + const uris = props.uris ?? (uri ? [uri] : undefined); + if (!uris) return; - connectedCallback() { - if (!this.rendered) { - this.render(); - this.rendered = true; - } - } - - update(name, value) { - this[name] = value; - this.render(); - } -} - -class _HTMLContextSubmenu extends HTMLDivElement { - constructor({ items = [], placement = "bottom-start" } = {}) { - super(); - this.items = items; - this.placement = placement; - this.style.zIndex = "9999"; - this.style.position = "absolute"; - this.style.inset = "0px auto auto 0px"; - } - render() { - this._tippy = { - unmount: () => {}, - popperInstance: { - forceUpdate: () => {} - } - }; + const uid = props.uid ?? props.item?.uid; + const uids = props.uids ?? (uid ? [uid] : undefined); - const list = document.createElement("ul"); - list.classList.add("main-contextMenu-menu"); - list.append(...this.items); - this.append(list); - - const { y: parentY, width: parentWidth } = this.parentElement.getBoundingClientRect(); - const { width: thisWidth, height: thisHeight } = this.getBoundingClientRect(); - let x = 0; - let y = this.parentElement.offsetTop; - - switch (this.placement) { - case "top-start": - case "bottom-start": - x += parentWidth - 5; - break; - default: - x -= thisWidth - 5; - break; - } - const realY = y + parentY; - if (realY + thisHeight > window.innerHeight) { - y -= realY + thisHeight - window.innerHeight; - } - this.style.transform = `translate(${x}px, ${y}px)`; - } + const contextUri = props.contextUri ?? props.context?.uri; - addItem(item) { - this.items.push(item); + return [uris, uids, contextUri]; } - connectedCallback() { - if (!this.rendered) { - this.render(); - this.rendered = true; + function parseIcon(icon) { + if (icon && Spicetify.SVGIcons[icon]) { + return ``; } + return icon || ""; } -} -customElements.define("context-menu-item", _HTMLContextMenuItem, { extends: "li" }); -customElements.define("context-submenu", _HTMLContextSubmenu, { extends: "div" }); - -Spicetify.Menu = (() => { - const collection = new Set(); - const _addItems = instance => { - const list = instance.querySelector("ul"); - const elemList = []; - - for (const item of collection) { - if (item._items?.size) { - const htmlSubmenu = new _HTMLContextSubmenu({ - placement: instance.firstChild.dataset.placement - }); - - for (const child of item._items) { - child._element.onclick = () => { - child.onClick(); - htmlSubmenu.remove(); - instance._tippy?.props?.onClickOutside(); - }; - htmlSubmenu.addItem(child._element); + function createIconComponent(icon) { + return Spicetify.React.createElement( + Spicetify.ReactComponent.IconComponent, + { + iconSize: "16", + dangerouslySetInnerHTML: { + __html: parseIcon(icon) } + }, + null + ); + } - item._element.onmouseenter = () => item._element.append(htmlSubmenu); - item._element.onmouseleave = () => htmlSubmenu.remove(); - elemList.push(item._element); - continue; - } - - item._element.onclick = () => { - item.onClick(); - instance._tippy?.props?.onClickOutside(); - }; - elemList.push(item._element); - } - list.prepend(...elemList); - }; - + // these classes bridge the gap between react and js, insuring reactivity class Item { - constructor(name, isEnabled, onClick, icon = "") { - this._name = name; - this._isEnabled = isEnabled; - this._icon = icon; - this.onClick = () => { - onClick(this); - }; - this._element = new _HTMLContextMenuItem({ - name: name, - icon: icon, - trailingIcon: isEnabled ? "check" : "" - }); - } + constructor({ children, disabled = false, leadingIcon, trailingIcon, divider, onClick, shouldAdd = () => true }) { + // maybe use a props object and a setProps + this.shouldAdd = shouldAdd; - setState(isEnabled) { - this._isEnabled = isEnabled; - this._element.update("trailingIcon", isEnabled ? "check" : ""); - } - set isEnabled(bool) { - this.setState(bool); - } - get isEnabled() { - return this._isEnabled; - } + this._children = children; + this._disabled = disabled; + this._leadingIcon = leadingIcon; + this._trailingIcon = trailingIcon; + this._divider = divider; + + this._element = Spicetify.ReactJSX.jsx(() => { + const [_children, setChildren] = Spicetify.React.useState(children); + const [_disabled, setDisabled] = Spicetify.React.useState(disabled); + const [_leadingIcon, setLeadingIcon] = Spicetify.React.useState(leadingIcon); + const [_trailingIcon, setTrailingIcon] = Spicetify.React.useState(trailingIcon); + const [_divider, setDivider] = Spicetify.React.useState(divider); + + Spicetify.React.useEffect(() => { + this._setChildren = setChildren; + this._setDisabled = setDisabled; + this._setIcon = setLeadingIcon; + this._setTrailingIcon = setTrailingIcon; + this._setDivider = setDivider; + + return () => { + this._setChildren = undefined; + this._setDisabled = undefined; + this._setIcon = undefined; + this._setTrailingIcon = undefined; + this._setDivider = undefined; + }; + }); - setName(name) { - this._name = name; - this._element.update("name", name); - } - set name(text) { - this.setName(text); - } - get name() { - return this._name; - } + const context = Spicetify.React.useContext(Spicetify.ContextMenuV2._context) ?? {}; - setIcon(icon) { - this._icon = icon; - this._element.update("icon", icon); - } - set icon(icon) { - this.setIcon(icon); - } - get icon() { - return this._icon; + return Spicetify.React.createElement(Spicetify.ReactComponent.MenuItem, { + disabled: _disabled, + divider: _divider, + onClick: e => { + onClick(context, this, e); + }, + leadingIcon: _leadingIcon && createIconComponent(_leadingIcon), + trailingIcon: _trailingIcon && createIconComponent(_trailingIcon), + children: _children + }); + }, {}); } - register() { - collection.add(this); - } - deregister() { - collection.delete(this); + set children(children) { + this._children = children; + this._setChildren?.(this._children); } - } - - class SubMenu { - constructor(name, items, icon, trailingIcon) { - this._name = name; - this._items = new Set(items); - this._icon = icon; - this._trailingIcon = trailingIcon; - this._element = new _HTMLContextMenuItem({ - name: name, - icon: icon, - trailingIcon: - trailingIcon || - '' - }); + get children() { + return this._children; } - setName(name) { - this._name = name; - this._element.update("name", name); - } - set name(text) { - this.setName(text); + set disabled(bool) { + this._disabled = bool; + this._setDisabled?.(this._disabled); } - get name() { - return this._name; + get disabled() { + return this._disabled; } - addItem(item) { - this._items.add(item); + set leadingIcon(name) { + this._leadingIcon = name; + this._setIcon?.(this._leadingIcon); } - removeItem(item) { - this._items.delete(item); + get leadingIcon() { + return this._leadingIcon; } - setIcon(icon) { - this._icon = icon; - this._element.update("icon", icon); - } - set icon(icon) { - this.setIcon(icon); + set trailingIcon(name) { + this._trailingIcon = name; + this._setTrailingIcon?.(this._trailingIcon); } - get icon() { - return this._icon; + get trailingIcon() { + return this._trailingIcon; } - setTrailingIcon(trailingIcon) { - this._trailingIcon = trailingIcon; - this._element.update("trailingIcon", trailingIcon); + set divider(divider) { + this._divider = divider; + this._setDivider?.(this._divider); } - set trailingIcon(trailingIcon) { - this.setTrailingIcon(trailingIcon); - } - get trailingIcon() { - return this._trailingIcon; + get divider() { + return this._divider; } register() { - collection.add(this); + Spicetify.ContextMenuV2.registerItem(this._element, this.shouldAdd); } deregister() { - collection.delete(this); + Spicetify.ContextMenuV2.unregisterItem(this._element); } } - return { Item, SubMenu, _addItems }; -})(); - -Spicetify.ContextMenu = (() => { - const itemList = new Set(); - const iconList = Object.keys(Spicetify.SVGIcons); + class ItemSubMenu { + static itemsToComponents = items => items.map(item => item._element); - class Item { - constructor(name, onClick, shouldAdd = uris => true, icon = "", trailingIcon = "", disabled = false) { - this.onClick = onClick; + constructor({ text, disabled = false, leadingIcon, divider, items, shouldAdd = () => true }) { this.shouldAdd = shouldAdd; - this._name = name; - this._icon = icon; - this._trailingIcon = trailingIcon; + + this._text = text; this._disabled = disabled; - this._element = new _HTMLContextMenuItem({ - name: name, - icon: icon, - trailingIcon: trailingIcon, - disabled: disabled - }); - } - set name(text) { - this._name = text; - this._element.update("name", text); - } - get name() { - return this._name; - } + this._leadingIcon = leadingIcon; + this._items = items; + this._element = Spicetify.ReactJSX.jsx(() => { + const [_text, setText] = Spicetify.React.useState(text); + const [_disabled, setDisabled] = Spicetify.React.useState(disabled); + const [_leadingIcon, setLeadingIcon] = Spicetify.React.useState(leadingIcon); + const [_divider, setDivider] = Spicetify.React.useState(divider); + const [_items, setItems] = Spicetify.React.useState(items); + + Spicetify.React.useEffect(() => { + this._setText = setText; + this._setDisabled = setDisabled; + this._setLeadingIcon = setLeadingIcon; + this._setDivider = setDivider; + this._setItems = setItems; + return () => { + this._setText = undefined; + this._setDisabled = undefined; + this._setLeadingIcon = undefined; + this._setDivider = undefined; + this._setItems = undefined; + }; + }); - set icon(name) { - this._icon = name; - this._element.update("icon", name); - } - get icon() { - return this._icon; + return Spicetify.React.createElement(Spicetify.ReactComponent.MenuSubMenuItem, { + displayText: _text, + divider: _divider, + depth: 1, + placement: "right-start", + onOpenChange: () => undefined, + onClick: () => undefined, + disabled: _disabled, + leadingIcon: _leadingIcon && createIconComponent(_leadingIcon), + children: ItemSubMenu.itemsToComponents(_items) + }); + }, {}); } - set trailingIcon(name) { - this._trailingIcon = name; - this._element.update("trailingIcon", name); + set text(text) { + this._text = text; + this._setText?.(this._text); } - get trailingIcon() { - return this._trailingIcon; + get text() { + return this._text; } set disabled(bool) { this._disabled = bool; - this._element.update("disabled", bool); + this._setDisabled?.(this._disabled); } get disabled() { return this._disabled; } - register() { - itemList.add(this); + set leadingIcon(name) { + this._leadingIcon = name; + this._setIcon?.(this._leadingIcon); } - deregister() { - itemList.delete(this); + get leadingIcon() { + return this._leadingIcon; } - } - Item.iconList = iconList; - - class SubMenu { - constructor(name, items, shouldAdd = uris => true, disabled = false, icon = "", trailingIcon = "") { - this._items = new Set(items); - this.shouldAdd = shouldAdd; - this._name = name; - this._disabled = disabled; - this._icon = icon; - this._trailingIcon = trailingIcon; - this._element = new _HTMLContextMenuItem({ - name: name, - icon: icon, - trailingIcon: - trailingIcon || - '', - disabled: disabled - }); + set divider(divider) { + this._divider = divider; + this._setDivider?.(this._divider); } - set name(text) { - this._name = text; - this._element.update("name", text); - } - get name() { - return this._name; + get divider() { + return this._divider; } addItem(item) { this._items.add(item); + this._setItems?.(this._items); } removeItem(item) { this._items.delete(item); - } - - set disabled(bool) { - this._disabled = bool; - this._element.update("disabled", bool); - if (this._submenuElement) { - if (!bool) { - this._element.onmouseenter = () => this._element.append(this._submenuElement); - this._element.onmouseleave = () => this._submenuElement.remove(); - } else { - this._element.onmouseenter = this._element.onmouseleave = undefined; - } - } - } - get disabled() { - return this._disabled; + this._setItems?.(this._items); } register() { - itemList.add(this); + registerItem(this._element, this.shouldAdd); } deregister() { - itemList.delete(this); + unregisterItem(this._element); } } - SubMenu.iconList = iconList; + function registerItem(item, shouldAdd = () => true) { + registeredItems.set(item, shouldAdd); + } - function _addItemsRecursive(instance, currentItem, uris, uids, contextUri) { - if (currentItem._items?.size) { - const htmlSubmenu = new _HTMLContextSubmenu({ - placement: instance.firstChild.dataset.placement - }); + function unregisterItem(item) { + registeredItems.delete(item); + } - for (const child of currentItem._items) { - try { - if (!child.shouldAdd(uris, uids, contextUri)) continue; - } catch (e) { - console.error(e); - continue; - } + const renderItems = () => { + const { props, trigger, target } = Spicetify.React.useContext(Spicetify.ContextMenuV2._context) ?? {}; - child._element.onclick = () => { - if (!child._disabled) { - child.onClick(uris, uids, contextUri); - htmlSubmenu.remove(); - instance._tippy?.props?.onClickOutside(); - } - }; - htmlSubmenu.addItem(child._element); + return Array.from(registeredItems.entries()) + .map(([item, shouldAdd]) => shouldAdd?.(props, trigger, target) && item) + .filter(Boolean); + }; - _addItemsRecursive(instance, child, uris, uids, contextUri); - } + return { parseProps, Item, ItemSubMenu, registerItem, unregisterItem, renderItems }; +})(); + +Spicetify.Menu = (() => { + const shouldAdd = (_, trigger, target) => trigger === "click" && target.parentElement.classList.contains("main-topBar-topbarContentRight"); - currentItem._submenuElement = htmlSubmenu; - currentItem.disabled = currentItem._disabled; + class Item extends Spicetify.ContextMenuV2.Item { + constructor(children, isEnabled, onClick, leadingIcon) { + super({ children, disabled: !isEnabled, leadingIcon, onClick: (_, self) => onClick(self), shouldAdd }); + } + + set isEnabled(bool) { + this.disabled = !bool; + } + get isEnabled() { + return !this.disabled; } } - function _addItems(instance) { - const list = instance.querySelector("ul"); - const container = instance.firstChild; - const reactEH = Object.values(container)[1]; - let props = reactEH?.children?.props; - if (!props) { - // v1.1.56 - reactII = Object.values(container)[0]; - props = reactII.pendingProps.children.props; - } - - let uris = []; - let uids; - let contextUri; - if (props.uris) { - uris = props.uris; - } else if (props.uri) { - uris = [props.uri]; - } else if (props.item?.uri) { - uris = [props.item.uri]; - } else if (props.reference?.uri) { - uris = [props.reference.uri]; - } else { - return; + class SubMenu extends Spicetify.ContextMenuV2.ItemSubMenu { + constructor(text, items, leadingIcon) { + super({ text, leadingIcon, items, shouldAdd }); } - if (props.uids) { - uids = props.uids; - } else if (props.uid) { - uids = [props.uid]; - } else if (props.item?.uid) { - uids = [props.item.uid]; + + set name(text) { + this.text = text; + } + get name() { + return this.text; } - if (props.contextUri) { - contextUri = props?.contextUri; - } else if (props.context?.uri) { - contextUri = props.context.uri; + set icon(icon) { + this.leadingIcon = icon; } + get icon() { + return this.leadingIcon; + } + } - const elemList = []; - for (const item of itemList) { - try { - if (!item.shouldAdd(uris, uids, contextUri)) continue; - } catch (e) { - console.error(e); - continue; - } + return { Item, SubMenu }; +})(); - if (item._items?.size) { - _addItemsRecursive(instance, item, uris, uids, contextUri); - elemList.push(item._element); - continue; - } +Spicetify.ContextMenu = (() => { + const iconList = Object.keys(Spicetify.SVGIcons); - item._element.onclick = () => { - if (!item._disabled) { - item.onClick(uris, uids, contextUri); - instance._tippy?.props?.onClickOutside(); + class Item extends Spicetify.ContextMenuV2.Item { + static iconList = iconList; + + constructor(name, onClick, shouldAdd = () => true, icon = undefined, trailingIcon = undefined, disabled = false) { + super({ + children: name, + disabled, + leadingIcon: icon, + trailingIcon, + onClick: context => { + const [uris, uids, contextUri] = Spicetify.ContextMenuV2.parseProps(context.props); + onClick(uris, uids, contextUri); + }, + shouldAdd: props => { + const parsedProps = Spicetify.ContextMenuV2.parseProps(props); + return parsedProps && shouldAdd(...parsedProps); } - }; + }); + } - elemList.push(item._element); + set name(name) { + this.children = name; + } + get name() { + return this.children; + } + + set icon(name) { + this.leadingIcon = name; + } + get icon() { + return this.leadingIcon; + } + } + + class SubMenu extends Spicetify.ContextMenuV2.ItemSubMenu { + static iconList = iconList; + + constructor(name, items, shouldAdd, disabled = false, icon = undefined) { + super({ + text: name, + disabled, + leadingIcon: icon, + items, + shouldAdd: props => { + const parsedProps = Spicetify.ContextMenuV2.parseProps(props); + return parsedProps && shouldAdd(...parsedProps); + } + }); + } + + set name(name) { + this.text = name; + } + get name() { + return this.text; } - list.prepend(...elemList); } - return { Item, SubMenu, _addItems }; + return { Item, SubMenu }; })(); Spicetify._cloneSidebarItem = (list, isLibX = false) => { @@ -1881,17 +1779,17 @@ class _HTMLGenericModal extends HTMLElement { display({ title, content, isLarge = false }) { this.innerHTML = `
`; this.querySelector("button").onclick = this.hide.bind(this); @@ -2439,68 +2337,68 @@ Spicetify.Playbar = (() => { const content = document.createElement("div"); content.id = "spicetify-update"; content.innerHTML = ` - -Update Spicetify to receive new features and bug fixes.
-Current version: ${version}
-Latest version: - - ${semver} - -
-Run these commands in the terminal:
-spicetify update-
Spicetify will automatically apply changes to Spotify after upgrading to the latest version.
-If you installed Spicetify via a package manager, update using said package manager.
-Update Spicetify to receive new features and bug fixes.
+Current version: ${version}
+Latest version: + + ${semver} + +
+Run these commands in the terminal:
+spicetify update+
Spicetify will automatically apply changes to Spotify after upgrading to the latest version.
+If you installed Spicetify via a package manager, update using said package manager.
+