Created
October 4, 2019 16:54
-
-
Save WebFreak001/7d9a6bf849220ee8d402fc1c98c25afb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fires Event#change when checked is changed | |
* @fires Event#change-mod when mod is changed | |
* @fires Event#next when the mod was left-clicked or selected with enter | |
* @fires Event#prev when the mod was right-clicked | |
*/ | |
export class OsuModCheckbox extends HTMLElement { | |
protected wrapper: HTMLLabelElement; | |
protected cb: HTMLInputElement; | |
protected span: HTMLElement; | |
protected shadow: ShadowRoot; | |
protected css: HTMLStyleElement; | |
constructor() { | |
// Always call super first in constructor | |
super(); | |
this.shadow = this.attachShadow({ mode: "open" }); | |
this.css = document.createElement("style"); | |
this.css.textContent = ` | |
:host { | |
display: inline-block; | |
max-width: calc(4.1282em * var(--icon-scale, 1)); | |
margin: 0 0.3em; | |
-webkit-touch-callout: none; | |
-webkit-user-select: none; | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
.mapmod:focus-within span:before { | |
filter: drop-shadow(0 2px 4px white); | |
} | |
.mapmod { | |
display: block; | |
position: relative; | |
z-index: 11; | |
} | |
.mapmod input { | |
width: 0; | |
height: 0; | |
opacity: 0; | |
position: absolute; | |
left: 0; | |
top: 0; | |
} | |
.mapmod input + span { | |
display: block; | |
text-align: center; | |
} | |
.mapmod input + span:before { | |
content: ""; | |
display: block; | |
text-align: center; | |
width: calc(4.1282em * var(--icon-scale, 1)); | |
height: calc(2.8em * var(--icon-scale, 1)); | |
opacity: 0.6; | |
background-image: url(/img/mod_fallback.min.svg), url(/img/modbg.min.svg); | |
background-size: auto 70%, 100%; | |
background-repeat: no-repeat; | |
background-position: center center; | |
transition: transform 0.1s ease-out, opacity 0.1s ease-out; | |
z-index: -5; | |
} | |
.mapmod:hover input + span:before { | |
opacity: 0.8; | |
} | |
.mapmod input:checked + span:before { | |
opacity: 1; | |
transform: scale(1.15) rotate(6deg); | |
opacity: 1; | |
} | |
.mapmod.mod_ez input + span:before { background-image: url(/img/mod_ez.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_nf input + span:before { background-image: url(/img/mod_nf.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_ht input + span:before { background-image: url(/img/mod_ht.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_so input + span:before { background-image: url(/img/mod_so.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_hr input + span:before { background-image: url(/img/mod_hr.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_sd input + span:before { background-image: url(/img/mod_sd.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_dt input + span:before { background-image: url(/img/mod_dt.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_nc input + span:before { background-image: url(/img/mod_nc.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_hd input + span:before, .mapmod.mod_fi input + span:before { background-image: url(/img/mod_hd.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_fl input + span:before {background-image: url(/img/mod_fl.min.svg), url(/img/modbg.min.svg);} | |
.mapmod.mod_rx input + span:before { background-image: url(/img/mod_rx.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_ap input + span:before { background-image: url(/img/mod_ap.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.mod_rn input + span:before { background-image: url(/img/mod_rn.min.svg), url(/img/modbg.min.svg); } | |
.mapmod.hidden { | |
pointer-events: none; | |
opacity: 0; | |
} | |
@keyframes modentry { | |
0% { | |
opacity: 1; | |
transform: translate(7%, 17%) scale(1.15) rotate(6deg); | |
transform-origin: 100% 100%; | |
z-index: -10; | |
filter: brightness(0.8); | |
} | |
40% { | |
transform: translate(7%, 17%) scale(1.15) rotate(-11deg); | |
transform-origin: 100% 100%; | |
z-index: -1; | |
filter: brightness(0.8); | |
} | |
60% { | |
transform: translate(4%, 15%) scale(1.15) rotate(-11deg); | |
transform-origin: 100% 100%; | |
z-index: -1; | |
filter: brightness(1); | |
} | |
100% { | |
transform: translate(4%, 15%) scale(1.15) rotate(6deg); | |
transform-origin: 100% 100%; | |
z-index: -5; | |
} | |
} | |
.mapmod.multi.entry span:before { | |
animation: 0.2s modentry linear; | |
} | |
.mapmod.multi.entry.reverse span:before { | |
animation: 0.2s modentry linear reverse; | |
} | |
@keyframes modexit { | |
0% { | |
opacity: 1; | |
transform: translate(7%, 17%) scale(1.15) rotate(6deg); | |
transform-origin: 100% 100%; | |
filter: brightness(1); | |
} | |
40% { | |
transform: translate(7%, 17%) scale(1.15) rotate(17deg); | |
transform-origin: 100% 100%; | |
filter: brightness(1); | |
} | |
60% { | |
transform: translate(4%, 15%) scale(1.15) rotate(17deg); | |
transform-origin: 100% 100%; | |
filter: brightness(0.8); | |
} | |
100% { | |
opacity: 1; | |
transform: translate(4%, 15%) scale(1.15) rotate(6deg); | |
transform-origin: 100% 100%; | |
filter: brightness(0.8); | |
} | |
} | |
@keyframes opacityexit { | |
from { opacity: 1; } | |
to { opacity: 0; } | |
} | |
.mapmod.multi.exit.hidden { | |
animation: 0.2s opacityexit step-end; | |
color: transparent; | |
} | |
.mapmod.multi.exit span:before { | |
animation: 0.2s modexit linear; | |
} | |
.mapmod.multi.exit.reverse span:before { | |
animation: 0.2s modexit linear reverse; | |
} | |
`; | |
// attach the created elements to the shadow dom | |
this.shadow.appendChild(this.css); | |
this.wrapper = document.createElement("label"); | |
this.wrapper.classList.add("mapmod", "checkbox"); | |
this.shadow.appendChild(this.wrapper); | |
this.cb = document.createElement("input"); | |
this.cb.setAttribute("type", "checkbox"); | |
this.cb.setAttribute("aria-label", "Mod"); | |
this.cb.onchange = () => { | |
this.checked = this.cb.checked; | |
this.dispatchEvent(new CustomEvent("next", { | |
bubbles: true, | |
cancelable: false | |
})); | |
if (this.wrapper.classList.contains("reverse")) | |
this.wrapper.classList.remove("reverse"); | |
}; | |
this.wrapper.appendChild(this.cb); | |
this.span = document.createElement("span"); | |
this.wrapper.appendChild(this.span); | |
this.oncontextmenu = (e) => { | |
e.preventDefault(); | |
this.checked = !this.checked; | |
this.dispatchEvent(new CustomEvent("prev", { | |
bubbles: true, | |
cancelable: false | |
})); | |
if (!this.wrapper.classList.contains("reverse")) | |
this.wrapper.classList.add("reverse"); | |
}; | |
} | |
toggle() { | |
this.checked = !this.checked; | |
} | |
static get observedAttributes() { return ["mod", "checked", "multi", "data-hidden"]; } | |
attributeChangedCallback(name: string, oldValue: string, newValue: string) { | |
switch (name) { | |
case "mod": | |
if (oldValue) | |
this.wrapper.classList.remove("mod_" + oldValue.toLowerCase()); | |
this.wrapper.classList.add("mod_" + newValue.toLowerCase()); | |
this.span.textContent = newValue; | |
this.cb.setAttribute("aria-label", "Mod " + newValue); | |
this.dispatchEvent(new CustomEvent("change-mod", { | |
bubbles: true, | |
cancelable: false | |
})); | |
break; | |
case "checked": | |
if (this.cb.checked != this.checked) | |
this.cb.checked = this.checked; | |
this.dispatchEvent(new CustomEvent("change", { | |
bubbles: true, | |
cancelable: false | |
})); | |
break; | |
case "multi": | |
if (newValue) { | |
this.wrapper.classList.add("multi"); | |
this.wrapper.classList.remove("single"); | |
} | |
else { | |
this.wrapper.classList.remove("multi"); | |
this.wrapper.classList.add("single"); | |
} | |
break; | |
case "data-hidden": | |
if (newValue) { | |
this.wrapper.classList.add("hidden"); | |
this.wrapper.classList.remove("entry"); | |
if (this.checked) | |
this.wrapper.classList.add("exit"); | |
this.cb.setAttribute("tabindex", "-1"); | |
this.cb.setAttribute("readonly", "readonly"); | |
} | |
else { | |
this.wrapper.classList.remove("hidden"); | |
this.wrapper.classList.remove("exit"); | |
if (this.checked) | |
this.wrapper.classList.add("entry"); | |
this.cb.removeAttribute("tabindex"); | |
this.cb.removeAttribute("readonly"); | |
this.cb.focus(); | |
} | |
break; | |
default: | |
console.warn("unknown property change", name, oldValue, newValue); | |
break; | |
} | |
} | |
get checked(): boolean { return !!this.getAttribute("checked"); } | |
set checked(checked: boolean) { checked ? this.setAttribute("checked", "checked") : this.removeAttribute("checked"); } | |
/** | |
* Turns on fancy flip on/off animations when hiding/showing | |
*/ | |
get multi(): boolean { return !!this.getAttribute("multi"); } | |
set multi(multi: boolean) { multi ? this.setAttribute("multi", "multi") : this.removeAttribute("multi"); } | |
/** | |
* Plays a hide/show animation | |
*/ | |
get hidden(): boolean { return !!this.getAttribute("data-hidden"); } | |
set hidden(hidden: boolean) { hidden ? this.setAttribute("data-hidden", "data-hidden") : this.removeAttribute("data-hidden"); } | |
get mod(): string { return this.getAttribute("mod") || ""; } | |
set mod(mod: string) { this.setAttribute("mod", mod || ""); } | |
get value(): string | null { return this.checked ? this.mod : null; } | |
} | |
window.customElements.define("osu-mod-checkbox", OsuModCheckbox); | |
export class OsuModsButton extends HTMLElement { | |
mods: OsuModCheckbox[]; | |
css: HTMLStyleElement; | |
shadow: ShadowRoot; | |
constructor() { | |
// Always call super first in constructor | |
super(); | |
this.shadow = this.attachShadow({ mode: "open" }); | |
this.css = document.createElement("style"); | |
this.css.textContent = ` | |
:host { | |
display: inline-block; | |
position: relative; | |
width: calc(4.2em * var(--icon-scale, 1)); | |
height: calc(2.8em * var(--icon-scale, 1) + 1.5em); | |
} | |
osu-mod-checkbox { | |
position: absolute; | |
top: 0; | |
left: 0; | |
z-index: 10; | |
transition: z-index 0.2s linear; | |
} | |
osu-mod-checkbox[data-hidden] { | |
pointer-events: none; | |
z-index: 0; | |
} | |
`; | |
// attach the created elements to the shadow dom | |
this.shadow.appendChild(this.css); | |
this.mods = []; | |
} | |
static get observedAttributes() { return ["modset"]; } | |
attributeChangedCallback(name: string, oldValue: string, newValue: string) { | |
switch (name) { | |
case "modset": | |
var newMods = newValue.split(/\s+/g); | |
while (this.mods.length < newMods.length) { | |
var cb = <OsuModCheckbox>document.createElement("osu-mod-checkbox"); | |
this.mods.push(cb); | |
this.shadow.appendChild(cb); | |
cb.addEventListener("next", () => this.next()); | |
cb.addEventListener("prev", () => this.prev()); | |
} | |
while (this.mods.length > newMods.length) { | |
this.shadow.removeChild(<any>this.mods.pop()); | |
} | |
for (let index = 0; index < newMods.length; index++) { | |
this.mods[index].checked = false; | |
this.mods[index].mod = newMods[index]; | |
this.mods[index].hidden = true; | |
this.mods[index].multi = true; | |
} | |
this.mods[0].hidden = false; | |
break; | |
default: | |
console.warn("unknown property change", name, oldValue, newValue); | |
break; | |
} | |
} | |
get index(): number { | |
for (let i = 0; i < this.mods.length; i++) { | |
if (!this.mods[i].hidden) | |
return i; | |
} | |
return 0; | |
} | |
get value(): string | null { | |
return this.mods[this.index].checked ? this.mods[this.index].mod : null; | |
} | |
next() { | |
let i = this.index; | |
let j = (i + 1) % this.mods.length; | |
if (!this.mods[i].checked) { | |
this.mods[i].checked = true; | |
this.mods[i].hidden = true; | |
this.mods[i].checked = false; | |
this.mods[j].checked = true; | |
this.mods[j].hidden = false; | |
this.mods[j].checked = j != 0; | |
} | |
this.dispatchEvent(new CustomEvent("change", { | |
bubbles: true, | |
cancelable: false | |
})); | |
} | |
prev() { | |
let i = this.index; | |
let j = (i + this.mods.length - 1) % this.mods.length; | |
if (i != 0 || this.mods[i].checked) { | |
this.mods[i].checked = true; | |
this.mods[i].hidden = true; | |
this.mods[i].checked = false; | |
this.mods[j].checked = true; | |
this.mods[j].hidden = false; | |
} | |
this.dispatchEvent(new CustomEvent("change", { | |
bubbles: true, | |
cancelable: false | |
})); | |
} | |
} | |
window.customElements.define("osu-mods-button", OsuModsButton); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment