Advertisement
Guest User

Untitled

a guest
Oct 23rd, 2019
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.71 KB | None | 0 0
  1. /**
  2. * @fires Event#change when checked is changed
  3. * @fires Event#change-mod when mod is changed
  4. * @fires Event#next when the mod was left-clicked or selected with enter
  5. * @fires Event#prev when the mod was right-clicked
  6. */
  7. export class OsuModCheckbox extends HTMLElement {
  8. protected wrapper: HTMLLabelElement;
  9. protected cb: HTMLInputElement;
  10. protected span: HTMLElement;
  11. protected shadow: ShadowRoot;
  12. protected css: HTMLStyleElement;
  13.  
  14. constructor() {
  15. // Always call super first in constructor
  16. super();
  17. this.shadow = this.attachShadow({ mode: "open" });
  18.  
  19. this.css = document.createElement("style");
  20.  
  21. this.css.textContent = `
  22. :host {
  23. display: inline-block;
  24. max-width: calc(4.1282em * var(--icon-scale, 1));
  25. margin: 0 0.3em;
  26.  
  27. -webkit-touch-callout: none;
  28. -webkit-user-select: none;
  29. -khtml-user-select: none;
  30. -moz-user-select: none;
  31. -ms-user-select: none;
  32. user-select: none;
  33. }
  34.  
  35. .mapmod:focus-within span:before {
  36. filter: drop-shadow(0 2px 4px white);
  37. }
  38.  
  39. .mapmod {
  40. display: block;
  41. position: relative;
  42. z-index: 11;
  43. }
  44.  
  45. .mapmod input {
  46. width: 0;
  47. height: 0;
  48. opacity: 0;
  49. position: absolute;
  50. left: 0;
  51. top: 0;
  52. }
  53.  
  54. .mapmod input + span {
  55. display: block;
  56. text-align: center;
  57. }
  58.  
  59. .mapmod input + span:before {
  60. content: "";
  61. display: block;
  62. text-align: center;
  63. width: calc(4.1282em * var(--icon-scale, 1));
  64. height: calc(2.8em * var(--icon-scale, 1));
  65. opacity: 0.6;
  66. background-image: url(/img/mod_fallback.min.svg), url(/img/modbg.min.svg);
  67. background-size: auto 70%, 100%;
  68. background-repeat: no-repeat;
  69. background-position: center center;
  70. transition: transform 0.1s ease-out, opacity 0.1s ease-out;
  71. z-index: -5;
  72. }
  73.  
  74. .mapmod:hover input + span:before {
  75. opacity: 0.8;
  76. }
  77.  
  78. .mapmod input:checked + span:before {
  79. opacity: 1;
  80. transform: scale(1.15) rotate(6deg);
  81. opacity: 1;
  82. }
  83.  
  84. .mapmod.mod_ez input + span:before { background-image: url(/img/mod_ez.min.svg), url(/img/modbg.min.svg); }
  85. .mapmod.mod_nf input + span:before { background-image: url(/img/mod_nf.min.svg), url(/img/modbg.min.svg); }
  86. .mapmod.mod_ht input + span:before { background-image: url(/img/mod_ht.min.svg), url(/img/modbg.min.svg); }
  87. .mapmod.mod_so input + span:before { background-image: url(/img/mod_so.min.svg), url(/img/modbg.min.svg); }
  88. .mapmod.mod_hr input + span:before { background-image: url(/img/mod_hr.min.svg), url(/img/modbg.min.svg); }
  89. .mapmod.mod_sd input + span:before { background-image: url(/img/mod_sd.min.svg), url(/img/modbg.min.svg); }
  90. .mapmod.mod_dt input + span:before { background-image: url(/img/mod_dt.min.svg), url(/img/modbg.min.svg); }
  91. .mapmod.mod_nc input + span:before { background-image: url(/img/mod_nc.min.svg), url(/img/modbg.min.svg); }
  92. .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); }
  93. .mapmod.mod_fl input + span:before {background-image: url(/img/mod_fl.min.svg), url(/img/modbg.min.svg);}
  94. .mapmod.mod_rx input + span:before { background-image: url(/img/mod_rx.min.svg), url(/img/modbg.min.svg); }
  95. .mapmod.mod_ap input + span:before { background-image: url(/img/mod_ap.min.svg), url(/img/modbg.min.svg); }
  96. .mapmod.mod_rn input + span:before { background-image: url(/img/mod_rn.min.svg), url(/img/modbg.min.svg); }
  97.  
  98. .mapmod.hidden {
  99. pointer-events: none;
  100. opacity: 0;
  101. }
  102.  
  103. @keyframes modentry {
  104. 0% {
  105. opacity: 1;
  106. transform: translate(7%, 17%) scale(1.15) rotate(6deg);
  107. transform-origin: 100% 100%;
  108. z-index: -10;
  109. filter: brightness(0.8);
  110. }
  111. 40% {
  112. transform: translate(7%, 17%) scale(1.15) rotate(-11deg);
  113. transform-origin: 100% 100%;
  114. z-index: -1;
  115. filter: brightness(0.8);
  116. }
  117. 60% {
  118. transform: translate(4%, 15%) scale(1.15) rotate(-11deg);
  119. transform-origin: 100% 100%;
  120. z-index: -1;
  121. filter: brightness(1);
  122. }
  123. 100% {
  124. transform: translate(4%, 15%) scale(1.15) rotate(6deg);
  125. transform-origin: 100% 100%;
  126. z-index: -5;
  127. }
  128. }
  129.  
  130. .mapmod.multi.entry span:before {
  131. animation: 0.2s modentry linear;
  132. }
  133.  
  134. .mapmod.multi.entry.reverse span:before {
  135. animation: 0.2s modentry linear reverse;
  136. }
  137.  
  138. @keyframes modexit {
  139. 0% {
  140. opacity: 1;
  141. transform: translate(7%, 17%) scale(1.15) rotate(6deg);
  142. transform-origin: 100% 100%;
  143. filter: brightness(1);
  144. }
  145. 40% {
  146. transform: translate(7%, 17%) scale(1.15) rotate(17deg);
  147. transform-origin: 100% 100%;
  148. filter: brightness(1);
  149. }
  150. 60% {
  151. transform: translate(4%, 15%) scale(1.15) rotate(17deg);
  152. transform-origin: 100% 100%;
  153. filter: brightness(0.8);
  154. }
  155. 100% {
  156. opacity: 1;
  157. transform: translate(4%, 15%) scale(1.15) rotate(6deg);
  158. transform-origin: 100% 100%;
  159. filter: brightness(0.8);
  160. }
  161. }
  162.  
  163. @keyframes opacityexit {
  164. from { opacity: 1; }
  165. to { opacity: 0; }
  166. }
  167.  
  168. .mapmod.multi.exit.hidden {
  169. animation: 0.2s opacityexit step-end;
  170. color: transparent;
  171. }
  172.  
  173. .mapmod.multi.exit span:before {
  174. animation: 0.2s modexit linear;
  175. }
  176.  
  177. .mapmod.multi.exit.reverse span:before {
  178. animation: 0.2s modexit linear reverse;
  179. }
  180. `;
  181.  
  182. // attach the created elements to the shadow dom
  183.  
  184. this.shadow.appendChild(this.css);
  185.  
  186. this.wrapper = document.createElement("label");
  187. this.wrapper.classList.add("mapmod", "checkbox");
  188. this.shadow.appendChild(this.wrapper);
  189.  
  190. this.cb = document.createElement("input");
  191. this.cb.setAttribute("type", "checkbox");
  192. this.cb.setAttribute("aria-label", "Mod");
  193. this.cb.onchange = () => {
  194. this.checked = this.cb.checked;
  195. this.dispatchEvent(new CustomEvent("next", {
  196. bubbles: true,
  197. cancelable: false
  198. }));
  199. if (this.wrapper.classList.contains("reverse"))
  200. this.wrapper.classList.remove("reverse");
  201. };
  202. this.wrapper.appendChild(this.cb);
  203.  
  204. this.span = document.createElement("span");
  205. this.wrapper.appendChild(this.span);
  206.  
  207. this.oncontextmenu = (e) => {
  208. e.preventDefault();
  209. this.checked = !this.checked;
  210.  
  211. this.dispatchEvent(new CustomEvent("prev", {
  212. bubbles: true,
  213. cancelable: false
  214. }));
  215. if (!this.wrapper.classList.contains("reverse"))
  216. this.wrapper.classList.add("reverse");
  217. };
  218. }
  219.  
  220. toggle() {
  221. this.checked = !this.checked;
  222. }
  223.  
  224. static get observedAttributes() { return ["mod", "checked", "multi", "data-hidden"]; }
  225.  
  226. attributeChangedCallback(name: string, oldValue: string, newValue: string) {
  227. switch (name) {
  228. case "mod":
  229. if (oldValue)
  230. this.wrapper.classList.remove("mod_" + oldValue.toLowerCase());
  231. this.wrapper.classList.add("mod_" + newValue.toLowerCase());
  232. this.span.textContent = newValue;
  233. this.cb.setAttribute("aria-label", "Mod " + newValue);
  234.  
  235. this.dispatchEvent(new CustomEvent("change-mod", {
  236. bubbles: true,
  237. cancelable: false
  238. }));
  239. break;
  240. case "checked":
  241. if (this.cb.checked != this.checked)
  242. this.cb.checked = this.checked;
  243.  
  244. this.dispatchEvent(new CustomEvent("change", {
  245. bubbles: true,
  246. cancelable: false
  247. }));
  248. break;
  249. case "multi":
  250. if (newValue) {
  251. this.wrapper.classList.add("multi");
  252. this.wrapper.classList.remove("single");
  253. }
  254. else {
  255. this.wrapper.classList.remove("multi");
  256. this.wrapper.classList.add("single");
  257. }
  258. break;
  259. case "data-hidden":
  260. if (newValue) {
  261. this.wrapper.classList.add("hidden");
  262. this.wrapper.classList.remove("entry");
  263. if (this.checked)
  264. this.wrapper.classList.add("exit");
  265. this.cb.setAttribute("tabindex", "-1");
  266. this.cb.setAttribute("readonly", "readonly");
  267. }
  268. else {
  269. this.wrapper.classList.remove("hidden");
  270. this.wrapper.classList.remove("exit");
  271. if (this.checked)
  272. this.wrapper.classList.add("entry");
  273. this.cb.removeAttribute("tabindex");
  274. this.cb.removeAttribute("readonly");
  275. this.cb.focus();
  276. }
  277. break;
  278. default:
  279. console.warn("unknown property change", name, oldValue, newValue);
  280. break;
  281. }
  282. }
  283.  
  284. get checked(): boolean { return !!this.getAttribute("checked"); }
  285. set checked(checked: boolean) { checked ? this.setAttribute("checked", "checked") : this.removeAttribute("checked"); }
  286.  
  287. /**
  288. * Turns on fancy flip on/off animations when hiding/showing
  289. */
  290. get multi(): boolean { return !!this.getAttribute("multi"); }
  291. set multi(multi: boolean) { multi ? this.setAttribute("multi", "multi") : this.removeAttribute("multi"); }
  292.  
  293. /**
  294. * Plays a hide/show animation
  295. */
  296. get hidden(): boolean { return !!this.getAttribute("data-hidden"); }
  297. set hidden(hidden: boolean) { hidden ? this.setAttribute("data-hidden", "data-hidden") : this.removeAttribute("data-hidden"); }
  298.  
  299. get mod(): string { return this.getAttribute("mod") || ""; }
  300. set mod(mod: string) { this.setAttribute("mod", mod || ""); }
  301.  
  302. get value(): string | null { return this.checked ? this.mod : null; }
  303. }
  304.  
  305. window.customElements.define("osu-mod-checkbox", OsuModCheckbox);
  306.  
  307. export class OsuModsButton extends HTMLElement {
  308. mods: OsuModCheckbox[];
  309. css: HTMLStyleElement;
  310. shadow: ShadowRoot;
  311.  
  312. constructor() {
  313. // Always call super first in constructor
  314. super();
  315. this.shadow = this.attachShadow({ mode: "open" });
  316.  
  317.  
  318. this.css = document.createElement("style");
  319.  
  320. this.css.textContent = `
  321. :host {
  322. display: inline-block;
  323. position: relative;
  324. width: calc(4.2em * var(--icon-scale, 1));
  325. height: calc(2.8em * var(--icon-scale, 1) + 1.5em);
  326. }
  327.  
  328. osu-mod-checkbox {
  329. position: absolute;
  330. top: 0;
  331. left: 0;
  332. z-index: 10;
  333. transition: z-index 0.2s linear;
  334. }
  335.  
  336. osu-mod-checkbox[data-hidden] {
  337. pointer-events: none;
  338. z-index: 0;
  339. }
  340. `;
  341.  
  342. // attach the created elements to the shadow dom
  343.  
  344. this.shadow.appendChild(this.css);
  345.  
  346. this.mods = [];
  347. }
  348.  
  349. static get observedAttributes() { return ["modset"]; }
  350.  
  351. attributeChangedCallback(name: string, oldValue: string, newValue: string) {
  352. switch (name) {
  353. case "modset":
  354. var newMods = newValue.split(/\s+/g);
  355. while (this.mods.length < newMods.length) {
  356. var cb = <OsuModCheckbox>document.createElement("osu-mod-checkbox");
  357. this.mods.push(cb);
  358. this.shadow.appendChild(cb);
  359.  
  360. cb.addEventListener("next", () => this.next());
  361. cb.addEventListener("prev", () => this.prev());
  362. }
  363.  
  364. while (this.mods.length > newMods.length) {
  365. this.shadow.removeChild(<any>this.mods.pop());
  366. }
  367.  
  368. for (let index = 0; index < newMods.length; index++) {
  369. this.mods[index].checked = false;
  370. this.mods[index].mod = newMods[index];
  371. this.mods[index].hidden = true;
  372. this.mods[index].multi = true;
  373. }
  374.  
  375. this.mods[0].hidden = false;
  376. break;
  377. default:
  378. console.warn("unknown property change", name, oldValue, newValue);
  379. break;
  380. }
  381. }
  382.  
  383. get index(): number {
  384. for (let i = 0; i < this.mods.length; i++) {
  385. if (!this.mods[i].hidden)
  386. return i;
  387. }
  388. return 0;
  389. }
  390.  
  391. get value(): string | null {
  392. return this.mods[this.index].checked ? this.mods[this.index].mod : null;
  393. }
  394.  
  395. next() {
  396. let i = this.index;
  397. let j = (i + 1) % this.mods.length;
  398. if (!this.mods[i].checked) {
  399. this.mods[i].checked = true;
  400. this.mods[i].hidden = true;
  401. this.mods[i].checked = false;
  402.  
  403. this.mods[j].checked = true;
  404. this.mods[j].hidden = false;
  405. this.mods[j].checked = j != 0;
  406. }
  407.  
  408. this.dispatchEvent(new CustomEvent("change", {
  409. bubbles: true,
  410. cancelable: false
  411. }));
  412. }
  413.  
  414. prev() {
  415. let i = this.index;
  416. let j = (i + this.mods.length - 1) % this.mods.length;
  417. if (i != 0 || this.mods[i].checked) {
  418. this.mods[i].checked = true;
  419. this.mods[i].hidden = true;
  420. this.mods[i].checked = false;
  421.  
  422. this.mods[j].checked = true;
  423. this.mods[j].hidden = false;
  424. }
  425.  
  426. this.dispatchEvent(new CustomEvent("change", {
  427. bubbles: true,
  428. cancelable: false
  429. }));
  430. }
  431. }
  432.  
  433. window.customElements.define("osu-mods-button", OsuModsButton);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement