Skip to content

Instantly share code, notes, and snippets.

@WebFreak001
Created October 4, 2019 16:54
Show Gist options
  • Save WebFreak001/7d9a6bf849220ee8d402fc1c98c25afb to your computer and use it in GitHub Desktop.
Save WebFreak001/7d9a6bf849220ee8d402fc1c98c25afb to your computer and use it in GitHub Desktop.
/**
* @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