Greasy Fork is available in English.

lib:menu

none

  1. // ==UserScript==
  2. // @name lib:menu
  3. // @version 3
  4. // @description none
  5. // @run-at document-start
  6. // @author You
  7. // @license GPLv3
  8. // @match *://*/*
  9. // @exclude /livereload.net\/files\/ffopen\/index.html$/
  10. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAHJQTFRFAAAAEIijAo2yAI60BYyuF4WaFIifAY6zBI2wB4usGIaZEYigIoiZCIyrE4igG4iYD4mjEomhFoedCoqpDIqnDomlBYyvE4efEYmiDYqlA42xBoytD4mkCYqqGYSUFYidC4qoC4upAo6yCoupDYqmCYur4zowOQAAACZ0Uk5TAO////9vr////1+/D/+/L+/Pf/////+f3///////H4////////+5G91rAAACgUlEQVR4nM2Y22KjIBCGidg1264liZqDadK03X3/V2wNKHMC7MpF/xthHD5mgERAqZhWhfYqH6K+Qf2qNNf625hCoFj9/gblMUi5q5jLkXLCKudgyiRm0FMK82cWJp1fLbV5VmvJbCIc0GCYaFqqlDJgADdBjncqAXYobm1xh72aFMflbysteFfdy2Yi1XGOm5HGBzQ1dq7TzEoxjeNTjQZb7VA3e1c7+ImgasAgQ9+xusNVNZIo5xmOMgihIS2PbCQIiHEUdTvhxCcS/kPomfFI2zHy2PkWmA6aNatIJpKFJyekyy02xh5Y3DI9T4aOT6VhIUrsNTFp1pf79Z4SIIVDegl6IJO6cHiL/GimIZDhgTu/BlYWCQzHMl0zBWT/T3KAhtxOuUB9FtBrpsz0RV4xsjHmW+UCaffcSy/5viMGer0/6HdFNMZBq/vjJL38H9Dqx4Fuy0Em12DbZy+9pGtiDijbglwAehyj11n0tRD3WUBm+lwulE/8h4BuA+iWAQQnteg2Xm63WQLTpnMnpjdge0Mgu/GRPsV4xdjQ94Lfi624fabhDkfUqIKNrM64Q837v8yL0prasepCgrtvw1sJpoqanGEX7b5mQboNW8eawXaWXTMfMGxub472hzWzHSn6Sg2G9+6TAyRruE71s+zAzjWaknoyJCQzwxrghH2k5FDT4eqWunuNxyN9QCGcxVod5oADbYnIUkDTGZEf1xDJnSFteQ3KdsT8zYDMQXcHxsevcLH1TrsABzkNPyA/L7b0jg704viMMlpQI96WsHknCt/3YH0kOEo9zcGkwrFK39ck72rmoehmKqo2RKlilzSy/nJKEV45CT38myJp456fezktHjN5aeMAAAAASUVORK5CYII=
  11. // @grant none
  12. // @namespace https://greasyfork.org/users/1184528
  13. // ==/UserScript==
  14. JSON.tryparse ??= function (json) {
  15. try {
  16. return JSON.parse(json)
  17. } catch (e) {
  18. console.warn("JSON.tryparse error", e)
  19. return undefined
  20. }
  21. }
  22. // hint button style useroption
  23. ;(() => {
  24. function checkformatofformat(obj) {
  25. with (loadlib("strict")) {
  26. obj.forEach((obj) =>
  27. testformat(obj, {
  28. default: setformat({ type: ["any", "none"] }),
  29. round: condfunc(({ type }) =>
  30. setformat({
  31. type: type == "number" ? ["boolean", "none"] : ["none"],
  32. })
  33. ),
  34. text: condfunc(({ key, type }) =>
  35. setformat({
  36. type:
  37. type == "groupend"
  38. ? ["none"]
  39. : key
  40. ? ["string", "none"]
  41. : ["string"],
  42. })
  43. ),
  44. key: condfunc(({ text, type }) =>
  45. setformat({
  46. type:
  47. type == "button" || type == "groupend" || type == "groupstart"
  48. ? ["undefined"]
  49. : text
  50. ? ["string", "none"]
  51. : ["string"],
  52. })
  53. ),
  54. ignorekeycase: condfunc(({ type }) =>
  55. setformat({
  56. type: type == "key" ? ["boolean", "none"] : ["undefined"],
  57. })
  58. ),
  59. type: setformat({
  60. value: [
  61. "button",
  62. "boolean",
  63. "text",
  64. "number",
  65. "key",
  66. "none",
  67. "select",
  68. "range",
  69. "groupstart",
  70. "groupend",
  71. // "color",
  72. // "array",
  73. ],
  74. }),
  75. forcelower: condfunc((obj) =>
  76. setformat({
  77. value: ["text", "key"].includes(obj.type)
  78. ? obj.forceupper
  79. ? [false, undefined]
  80. : [true, false, undefined]
  81. : [undefined],
  82. })
  83. ),
  84. forceupper: condfunc((obj) =>
  85. setformat({
  86. value: ["text", "key"].includes(obj.type)
  87. ? obj.forcelower
  88. ? [false, undefined]
  89. : [true, false, undefined]
  90. : [undefined],
  91. })
  92. ),
  93. tab: optional(setformat({ type: "number" })),
  94. innerstyle: optional(
  95. setformat(
  96. {},
  97. { allowextras: true, extrasformat: { type: ["string"] } }
  98. )
  99. ),
  100. textstyle: optional(
  101. setformat(
  102. {},
  103. { allowextras: true, extrasformat: { type: ["string"] } }
  104. )
  105. ),
  106. buttontext: condfunc((obj) =>
  107. setformat({
  108. type: obj.type == "button" ? ["string"] : ["none"],
  109. })
  110. ),
  111. hint: optional(setformat({ type: ["string"] })),
  112. onclick: condfunc((obj) =>
  113. setformat({
  114. type:
  115. obj.type == "button" ? ["function", "asyncfunction"] : ["none"],
  116. })
  117. ),
  118. options: condfunc((obj) =>
  119. setformat({
  120. type: obj.type == "select" ? ["array"] : ["none"],
  121. })
  122. ),
  123. oncontextmenu: condfunc((obj) =>
  124. setformat({
  125. type:
  126. obj.type == "button"
  127. ? ["function", "asyncfunction", "none"]
  128. : ["none"],
  129. })
  130. ),
  131. min: condfunc(({ type }) =>
  132. setformat({
  133. type:
  134. type == "range"
  135. ? ["number"]
  136. : type == "number"
  137. ? ["number", "none"]
  138. : ["none"],
  139. })
  140. ),
  141. step: condfunc(({ type }) =>
  142. setformat({
  143. type:
  144. type == "range"
  145. ? ["number"]
  146. : type == "number"
  147. ? ["number", "none"]
  148. : ["none"],
  149. })
  150. ),
  151. max: condfunc(({ type }) =>
  152. setformat({
  153. type:
  154. type == "range"
  155. ? ["number"]
  156. : type == "number"
  157. ? ["number", "none"]
  158. : ["none"],
  159. })
  160. ),
  161. hinttype: condfunc((obj) =>
  162. setformat({
  163. value: "hint" in obj ? ["text", "html"] : [undefined],
  164. })
  165. ),
  166. allowmodifiers: condfunc((obj) =>
  167. setformat({
  168. value: obj.type == "key" ? [true, false] : [undefined],
  169. })
  170. ),
  171. keyreturntype: condfunc((obj) =>
  172. setformat({
  173. value: obj.type == "key" ? ["object", "string"] : [undefined],
  174. })
  175. ),
  176. allowanynumber: condfunc(({ type }) =>
  177. setformat({
  178. type: type == "range" ? ["boolean", "none"] : ["undefined"],
  179. })
  180. ),
  181. })
  182. )
  183. }
  184. }
  185. function blockevent(e) {
  186. if (!e) return
  187. e.preventDefault?.()
  188. e.stopPropagation?.()
  189. e.stopImmediatePropagation?.()
  190. }
  191. const a = loadlib("allfuncs")
  192. const e = loadlib("newelem")
  193. const userliboptions = loadlib("libloader").savelib(
  194. "menu",
  195. class menu {
  196. #options
  197. #globalopts
  198. #format
  199. #main
  200. constructor(...args) {
  201. this.#globalopts = {}
  202. this.update(...args)
  203. }
  204. update(options, format, globalopts = {}) {
  205. this.#options = options || this.#options
  206. this.#format = [...(format || this.#format)].map((e) => {
  207. return { ...e }
  208. })
  209. checkformatofformat(this.#format)
  210. const { setformat, testformat } = loadlib("strict")
  211. testformat(
  212. { ...this.#globalopts, ...globalopts },
  213. {
  214. noclosebutton: setformat({ type: ["boolean", "undefined"] }),
  215. onchange: setformat({ type: ["function", "undefined"] }),
  216. onhide: setformat({ type: ["function", "undefined"] }),
  217. onshow: setformat({ type: ["function", "undefined"] }),
  218. position: setformat({
  219. value: [
  220. "unset",
  221. "full screen",
  222. "top left",
  223. "top center",
  224. "top right",
  225. "center left",
  226. "center",
  227. "center right",
  228. "bottom left",
  229. "bottom center",
  230. "bottom right",
  231. ],
  232. }),
  233. dimmeropacity: setformat({ type: ["number", "none"] }),
  234. cursor: setformat({ type: ["string", "none"] }),
  235. },
  236. { functionname: "lib:menu" }
  237. )
  238. Object.assign(this.#globalopts, globalopts)
  239. if (this.#main) {
  240. this.hide()
  241. this.show()
  242. }
  243. }
  244. async show() {
  245. var groupparent = [],
  246. groupcount = 0
  247. this.#globalopts.onshow?.()
  248.  
  249. if (!document.body) await a().bodyload()
  250. var options = this.#options
  251. var format = this.#format
  252. var onsave = this.#globalopts.onchange
  253. const globalopts = this.#globalopts
  254. if (this.#main) this.#main?.remove?.()
  255. this.#main = undefined
  256. const closethis = this.hide.bind(this)
  257. var main = a(document.body).createelem("div", {
  258. position: "fixed",
  259. top: "0",
  260. right: "0",
  261. zIndex: 99999,
  262. colorScheme: "dark",
  263. }).val
  264. var shadow = a(
  265. userliboptions.noshadows ? main : main.attachShadow({ mode: "open" })
  266. )
  267. if (this.#globalopts.cursor)
  268. a(shadow).createelem("style", {
  269. innerHTML: `
  270. * {
  271. cursor: ${this.#globalopts.cursor};
  272. }
  273. input[type="number"]::-webkit-inner-spin-button,
  274. input[type="number"]::-webkit-outer-spin-button{
  275. cursor: ${this.#globalopts.cursor};
  276. }
  277. `,
  278. })
  279. const menupos =
  280. globalopts.position == "unset"
  281. ? userliboptions.menuposition1 == "full screen"
  282. ? "full screen"
  283. : userliboptions.menuposition1 +
  284. " " +
  285. userliboptions.menuposition2
  286. : globalopts.position
  287. var dimmeropacity =
  288. this.#globalopts.dimmeropacity ?? userliboptions.dimmeropacity
  289. shadow = shadow
  290. .createelem("div", {
  291. backgroundColor: this.#globalopts.noclosebutton
  292. ? "#0000"
  293. : `rgba(60, 60, 60, ${dimmeropacity})`,
  294. position: "fixed",
  295. top: 0,
  296. left: 0,
  297. width: "100vw",
  298. height: "100vh",
  299. maxHeight: "100vh",
  300. overflow: "scroll",
  301. pointerEvents:
  302. dimmeropacity == 0 || this.#globalopts.noclosebutton
  303. ? "none"
  304. : "",
  305. onclick({ target }) {
  306. if (this !== target) return
  307. if (userliboptions.clickoffclosesmenu) closethis()
  308. },
  309. })
  310. .createelem("div", {
  311. position: "fixed",
  312. backgroundColor: "#888",
  313. padding: "10px",
  314. border: "10px solid #333",
  315. borderRadius: "10px",
  316. color: "#ddd",
  317. pointerEvents: "all",
  318. width: "fit-content",
  319. height: "fit-content",
  320. maxHeight: "calc(100vh - 40px)",
  321. overflow: "scroll",
  322.  
  323. ...(() => {
  324. if (menupos == "full screen")
  325. return {
  326. top: 0,
  327. left: 0,
  328. width: "calc(100vw - 40px)",
  329. height: "calc(100vh - 40px)",
  330. }
  331. var pos = menupos.split(" ")
  332. var obj = {}
  333. if (pos.includes("center")) {
  334. pos = pos.filter((e) => e && e !== "center")
  335. if (["left", "right"].includes(pos[0])) {
  336. obj.top = "50%"
  337. obj.translate = "0 -50%"
  338. } else if (["top", "bottom"].includes(pos[0])) {
  339. obj.left = "50%"
  340. obj.translate = "-50% 0"
  341. } else
  342. return {
  343. top: "50vh",
  344. left: "50vw",
  345. translate: "-50% -50%",
  346. }
  347. }
  348. pos.forEach((p, i) => {
  349. obj[p] = 0
  350. })
  351. return obj
  352. })(),
  353. })
  354.  
  355. var temp = shadow.createelem("div", {
  356. width: "100%",
  357. height: "fit-content",
  358. display: "flex",
  359. justifyContent: "flex-end",
  360. })
  361.  
  362. if (!this.#globalopts.noclosebutton)
  363. temp.createelem("button", {
  364. innerHTML: "x",
  365. width: "22px",
  366. height: "22px",
  367. onclick: closethis,
  368. color: "#ddd",
  369. })
  370.  
  371. shadow = shadow.val
  372.  
  373. format.forEach((f) => {
  374. var resettodefaultbutton
  375. var canrunonsavefunction = false
  376. function modify(f, val) {
  377. options[f.key] = f.func ? f.func(val) : val
  378. if (canrunonsavefunction && onsave) onsave(f.key, val)
  379. if (resettodefaultbutton)
  380. resettodefaultbutton.style.visibility =
  381. val == f.default ? "hidden" : "visible"
  382. }
  383. if (f.type == "groupend") {
  384. groupparent.pop()
  385. groupcount--
  386. return
  387. }
  388. f.text ??= f.key
  389. f.key ??= f.text
  390. f.innerstyle ??= {}
  391. if (!("default" in f)) f.default = options[f.key]
  392. f.textstyle ??= {}
  393. // if (f.type == "groupstart") groupparent = undefined
  394. // if (groupparent) {
  395. f.tab ??= 0
  396. f.tab += groupcount
  397. // }
  398. var test = (() => {
  399. if (f.type !== "none" && !f.text)
  400. throw new Error("object is missing properties text and key")
  401. if (!f.type) throw new Error("object is missing property key")
  402. switch (f.type) {
  403. case "button":
  404. return [
  405. e("button", {
  406. marginLeft: "4px",
  407. innerHTML: f.buttontext,
  408. onclick: f.onclick,
  409. settovalue: false,
  410. oncontextmenu: f.oncontextmenu,
  411. ...f.innerstyle,
  412. color: "#ddd",
  413. }),
  414. ]
  415. case "boolean":
  416. return [
  417. e("input", {
  418. type: "checkbox",
  419. settovalue(val) {
  420. modify(f, (this.checked = val))
  421. },
  422. checked: options[f.key],
  423. onclick() {
  424. modify(f, this.checked)
  425. },
  426. ...f.innerstyle,
  427. }),
  428. ]
  429. case "text":
  430. return [
  431. e("input", {
  432. marginLeft: "4px",
  433. value: options[f.key],
  434. color: "#ddd",
  435. settovalue(val) {
  436. this.value = val
  437. this.onchange()
  438. },
  439. onchange() {
  440. if (f.forcelower) this.value = this.value.toLowerCase()
  441. if (f.forceupper) this.value = this.value.toUpperCase()
  442. modify(f, this.value)
  443. },
  444. ...f.innerstyle,
  445. }),
  446. ]
  447. case "number":
  448. return [
  449. e("input", {
  450. marginLeft: "4px",
  451. // marginLeft: "auto",
  452.  
  453. // translate: f.hint ? "" : "-17px",
  454. type: "number",
  455. value: options[f.key],
  456. step: f.step,
  457. min: f.min,
  458. max: f.max,
  459. settovalue(val) {
  460. this.value = val
  461. this.onchange()
  462. },
  463. onchange() {
  464. var num = Number(this.value)
  465. if (f.round) num = Math.round(num)
  466. modify(f, num)
  467. },
  468. ...f.innerstyle,
  469. }),
  470. ]
  471. case "key":
  472. function pressed(e = "UNBOUND") {
  473. if (a(e).gettype("string").val) {
  474. e = e.split("+")
  475. var key = e.pop() ?? "+"
  476. e = e.map((e) => e.toLowerCase())
  477. e = {
  478. key,
  479. shiftKey: e.includes("shift"),
  480. altKey: e.includes("alt"),
  481. metaKey: e.includes("meta"),
  482. ctrlKey: e.includes("ctrl"),
  483. }
  484. }
  485. var { shiftKey, ctrlKey, metaKey, altKey, key } = e
  486. e.key ??= "UNBOUND"
  487. if (key == "Unidentified")
  488. throw new Error("key is Unidentified", error(e))
  489. if (key == " ") key = "Space"
  490. blockevent(e)
  491. if (f.allowmodifiers) {
  492. this.value = ""
  493. if (ctrlKey) this.value += "Ctrl+"
  494. if (altKey) this.value += "Alt+"
  495. if (shiftKey) this.value += "Shift+"
  496. if (metaKey) this.value += "Meta+"
  497. this.value +=
  498. f.ignorekeycase && key.length == 1
  499. ? key.toUpperCase()
  500. : key
  501. } else
  502. this.value =
  503. f.ignorekeycase && key.length == 1
  504. ? key.toUpperCase()
  505. : key
  506. switch (f.keyreturntype) {
  507. case "string":
  508. if (f.allowmodifiers) key = this.value
  509. modify(f, f.ignorekeycase ? key.toLowerCase() : key)
  510. break
  511. case "object":
  512. if (f.allowmodifiers)
  513. modify(f, {
  514. shiftKey,
  515. ctrlKey,
  516. metaKey,
  517. altKey,
  518. key: f.ignorekeycase ? key.toLowerCase() : key,
  519. })
  520. else
  521. modify(f, {
  522. key: f.ignorekeycase ? key.toLowerCase() : key,
  523. })
  524. break
  525. default:
  526. throw new Error("invalid key return type")
  527. }
  528. this.blur()
  529. }
  530. var textholder
  531. return [
  532. e(
  533. "div",
  534. {
  535. width: "100px",
  536. height: "20px",
  537. backgroundColor: "#444",
  538. marginLeft: "4px",
  539. settovalue(val) {
  540. pressed.call(this.onmousedown(), val)
  541. },
  542. borderRadius: "10px",
  543. color: "#ccc",
  544. display: "inline-block",
  545. overflow: "scroll",
  546. textAlign: "center",
  547. display: "flex",
  548. justifyContent: "center",
  549. alignItems: "center",
  550. onmousedown(e) {
  551. var lastmd = this.onmousedown
  552. this.onmousedown = undefined
  553. var _this = this
  554. blockevent(e)
  555. var mouselisteners = [
  556. a(window).listen("mousedown", (e) => {
  557. _this.onmousedown = lastmd
  558. e.preventDefault()
  559. blockevent(e)
  560. e.key = "mouse" + e.button
  561. pressed.call(temp, e)
  562. }),
  563. a(window).listen("mouseup", blockevent),
  564. a(window).listen("click", blockevent),
  565. a(window).listen("contextmenu", blockevent),
  566. ]
  567. textholder.lastvals = {
  568. text: textholder.innerHTML,
  569. size: textholder.style.fontSize,
  570. }
  571. textholder.style.fontSize = "100%"
  572. textholder.innerHTML = "press any key"
  573. if (e) e.preventDefault()
  574. var temp = a(this).createelem("input", {
  575. onkeypress: pressed,
  576. onkeyup: pressed,
  577. onkeydown(e) {
  578. e.preventDefault()
  579. },
  580. width: "0px",
  581. height: "0px",
  582. opacity: 0,
  583. position: "absolute",
  584. onblur() {
  585. _this.onmousedown = lastmd
  586. setTimeout((e) => {
  587. mouselisteners.forEach((mouselistener) =>
  588. a(mouselistener).unlisten()
  589. )
  590. })
  591. textholder.style.fontSize = textholder.lastvals.size
  592. textholder.innerHTML = textholder.lastvals.text
  593. if (
  594. this.value.toLowerCase() == "escape" &&
  595. userliboptions.escapeunbindskeypicker
  596. )
  597. this.value = "UNBOUND"
  598. else if (
  599. !this.value ||
  600. (this.value.toLowerCase() == "escape" &&
  601. userliboptions.escapecancelskeypicker)
  602. )
  603. return this.remove()
  604.  
  605. textholder.style.fontSize =
  606. this.value.length > 12
  607. ? this.value.length > 16
  608. ? this.value.length > 21
  609. ? "40%"
  610. : "60%"
  611. : "80%"
  612. : "100%"
  613. textholder.textContent = this.value
  614. this.remove()
  615. },
  616. }).val
  617. temp.select()
  618. return temp
  619. },
  620. display: "inline-block",
  621. },
  622. [
  623. (textholder = e("div", {
  624. marginLeft: "auto",
  625. marginRight: "auto",
  626. height: "fit-content",
  627. position: "relative",
  628. top: "50%",
  629. translate: "0 -50%",
  630. })),
  631. ]
  632. ),
  633. ]
  634. case "select":
  635. return [
  636. e("select", {
  637. marginLeft: "4px",
  638. value: options[f.key],
  639. options: f.options,
  640. settovalue(val) {
  641. modify(f, (this.value = val))
  642. },
  643. onchange() {
  644. modify(f, this.value)
  645. this.blur()
  646. },
  647. ...f.innerstyle,
  648. }),
  649. ]
  650. case "range":
  651. var textwidth = 12
  652. // var ts = String(f.max).length * textwidth
  653. // if (String(f.step).includes(".")) {
  654. // ts +=
  655. // String(f.step).match(/(?<=\.)\d+$/)[0].length * textwidth
  656. // if (
  657. // String(f.max).length !==
  658. // String(f.max - f.step / f.step).length
  659. // ) {
  660. // ts -= textwidth
  661. // }
  662. // }
  663. // if (f.allowanynumber) ts += 10
  664. var numinp = e("input", {
  665. type: f.allowanynumber ? "number" : "text",
  666. width: "60px",
  667. onfocus() {
  668. this.select()
  669. },
  670. readOnly: !f.allowanynumber,
  671. step: f.step ? (f.step < 1 ? f.step : 1) : 1,
  672. oninput() {
  673. modify(f, Number((rangeinp.value = this.value)))
  674. },
  675. settovalue(val) {
  676. this.value = val
  677. },
  678. })
  679. var rangeinp = e("input", {
  680. marginLeft: "4px",
  681. value: options[f.key],
  682. type: "range",
  683. min: f.min,
  684. onfocus() {
  685. this.blur()
  686. },
  687. tabIndex: -1,
  688. step: f.step ?? 1,
  689. max: f.max,
  690. settovalue(val) {
  691. numinp.value = this.value
  692. modify(f, Number((this.value = val)))
  693. },
  694. onchange() {
  695. numinp.value = this.value
  696. modify(f, Number(this.value))
  697. },
  698. onmouseout() {
  699. if (this.display) this.display.style.display = "none"
  700. },
  701. onmousemove(event) {
  702. var rect = event.target.getBoundingClientRect()
  703. // var x = event.clientX - rect.left
  704. // var y = event.clientY - rect.top
  705. var { w, x, y } = a(this).rect().val
  706. var mousex = event.clientX - x
  707. if (!this.display)
  708. this.display = this.parentElement.appendChild(
  709. e("div", {
  710. innerHTML: "loading",
  711. position: "fixed",
  712. pointerEvents: "none",
  713. backgroundColor: "#444",
  714. borderRadius: "6px",
  715. padding: "2px",
  716. translate: userliboptions.showrangenumberbelowrange
  717. ? "-50% 100%"
  718. : "-50% -100%",
  719. })
  720. )
  721. if (userliboptions.keepnumberdisplaysamesize) {
  722. var val = String(this.value)
  723. while (val.length < String(f.max).length) val = "0" + val
  724. this.display.innerHTML = val
  725. } else this.display.innerHTML = this.value
  726. Object.assign(this.display.style, {
  727. top: y + "px",
  728. left: x + Math.min(Math.max(mousex, 0), w) + "px",
  729. display: "",
  730. })
  731. },
  732. ...f.innerstyle,
  733. })
  734. return [rangeinp, numinp]
  735. case "none":
  736. return []
  737. case "groupstart":
  738. groupcount++
  739. groupparent.push(
  740. e("div", {
  741. display: "flex",
  742. width: "100%",
  743. flexDirection: "column",
  744. height: "20px",
  745. minHeight: "20px",
  746. overflow: "hidden",
  747. marginTop: "2px",
  748. marginBottom: "2px",
  749. // marginBottom: "10px",
  750. // marginTop: "10px",
  751. ...f.innerstyle,
  752. })
  753. )
  754. return []
  755. default:
  756. return [
  757. e("span", {
  758. marginLeft: "4px",
  759. innerHTML: `error type:${f.type}, text:${f.text}`,
  760. }),
  761. ]
  762. }
  763. })()
  764. test.unshift(
  765. e("span", {
  766. flexGrow: 1,
  767. })
  768. )
  769. var thisrow = e("div", {
  770. display: "flex",
  771. alignItems: "center",
  772. })
  773. const prevgroupparent = groupparent[groupparent.length - 2]
  774. const currentgroupparent = groupparent[groupparent.length - 1]
  775. if (currentgroupparent) {
  776. ;(prevgroupparent || shadow).appendChild(currentgroupparent)
  777. currentgroupparent.appendChild(thisrow)
  778. // if (f.type !== "groupstart") thisrow.style.display = "none"
  779. } else shadow.appendChild(thisrow)
  780. thisrow.appendChild(
  781. e(
  782. "label",
  783. {
  784. border: "1px solid #aaa",
  785. borderRadius: "5px",
  786. paddingLeft: "5px",
  787. paddingRight: "5px",
  788. marginTop: "2px",
  789. marginBottom: "2px",
  790. innerHTML: f.type == "groupstart" ? `\u02c3 ${f.text}` : f.text,
  791. marginLeft: (f.tab ?? 0) * userliboptions.tabsize + "px",
  792. currentgroupparent,
  793. width: "100%",
  794. color: f.type == "groupstart" ? "#222" : "#ddd",
  795. ...f.textstyle,
  796. onclick:
  797. f.type == "groupstart"
  798. ? function () {
  799. if (
  800. this.currentgroupparent.style.height == "fit-content"
  801. ) {
  802. this.currentgroupparent.style.height = "20px"
  803. this.innerHTML = `\u02c3 ${f.text}`
  804. } else {
  805. this.currentgroupparent.style.height = "fit-content"
  806. this.innerHTML = `\u02c5 ${f.text}`
  807. }
  808. }
  809. : undefined,
  810. },
  811. ["boolean"].includes(f.type) ? test : []
  812. )
  813. )
  814.  
  815. if (!["boolean"].includes(f.type)) {
  816. test.forEach(thisrow.appendChild.bind(thisrow))
  817. }
  818.  
  819. if (test && f.hint) {
  820. f.hint = f.hint.trim()
  821. var showhint = e("div", {
  822. border: "2px solid #444",
  823. borderRadius: "100%",
  824. marginLeft: "4px",
  825. innerHTML: "?",
  826. title: f.hint.length < 60 ? f.hint : "show hint",
  827. width: "10px",
  828. height: "10px",
  829. color: "black",
  830. fontWeight: "bold",
  831. display: "inline-block",
  832. textAlign: "center",
  833. fontSize: "60%",
  834. onmouseup(e) {
  835. e.preventDefault()
  836. var hintbg = a(this.parentElement).createelem("div", {
  837. backgroundColor: "#333",
  838. position: "fixed",
  839. zIndex: 99,
  840. backgroundColor: `rgba(60, 60, 60, ${userliboptions.popupdimmeropacity})`,
  841. top: 0,
  842. left: 0,
  843. width: "100vw",
  844. height: "100vh",
  845. onclick() {
  846. this.remove()
  847. },
  848. }).val
  849. hintbg.focus()
  850. var closehintbutton = a(
  851. userliboptions.noshadows
  852. ? hintbg
  853. : hintbg.attachShadow({ mode: "open" })
  854. ).createelem("div", {
  855. color: "#000",
  856. fontWeight: "normal",
  857. marginLeft: "auto",
  858. marginRight: "4px",
  859. width: "fit-content",
  860. padding: "7px",
  861. marginRight: "auto",
  862. position: "relative",
  863. border: "10px solid #444",
  864. backgroundColor: "#777",
  865. top: "50%",
  866. translate: "0 -50%",
  867. ...(f.hintcss ?? {}),
  868. onclick: blockevent,
  869. }).val
  870. a(closehintbutton).createelem("button", {
  871. width: "20px",
  872. height: "20px",
  873. innerHTML: "x",
  874. })
  875. switch (f.hinttype) {
  876. case "html":
  877. closehintbutton.innerHTML = f.hint
  878. break
  879. case "text":
  880. closehintbutton.innerText = f.hint
  881. break
  882. default:
  883. throw new Error("invalid hint type", {
  884. hinttype: f.hinttype,
  885. obj,
  886. format: f,
  887. })
  888. }
  889. closehintbutton.innerHTML =
  890. '<button style="width:20px; height:20px">x</button><br>' +
  891. closehintbutton.innerHTML
  892. a("button").qs(closehintbutton).val.onclick = () =>
  893. hintbg.remove()
  894. },
  895. })
  896. thisrow.appendChild(showhint)
  897. }
  898. if (userliboptions.alligninputs) test.shift()
  899. if (test?.[0] && test[0]?.settovalue !== false && f.type !== "none") {
  900. resettodefaultbutton = e("div", {
  901. marginLeft: "4px",
  902. marginRight: "4px",
  903. innerHTML: "\u21B6" ?? "\u21B0",
  904. title: "reset to default",
  905. width: "10px",
  906. height: "10px",
  907. color: "black",
  908. fontWeight: "bold",
  909. display: "inline-block",
  910. visiblity: "hidden",
  911. textAlign: "center",
  912. fontSize: "100%",
  913. translate: "0 -50%",
  914. ...JSON.tryparse(userliboptions.resettodefaultbuttonstyle),
  915. onclick() {
  916. test.map((e) => e.settovalue(f.default))
  917. },
  918. })
  919. test.forEach((e) => {
  920. if (e.settovalue) {
  921. e.settovalue(f.key in options ? options[f.key] : f.default)
  922. if (canrunonsavefunction) onsave()
  923. } else warn(e)
  924. })
  925. canrunonsavefunction = true
  926. thisrow.appendChild(resettodefaultbutton)
  927. }
  928. })
  929. this.#main = main
  930. }
  931. hide() {
  932. this.#globalopts.onhide?.()
  933. if (this.#main) this.#main.remove()
  934. this.#main = undefined
  935. }
  936. },
  937. {
  938. showrangenumberbelowrange: {
  939. type: "boolean",
  940. text: "show range value below the range instead of above it",
  941. default: false,
  942. },
  943. keepnumberdisplaysamesize: {
  944. type: "boolean",
  945. text: "force number display to allways have the same number of digits",
  946. default: false,
  947. },
  948. tabsize: { type: "number", default: 10, text: "tab size" },
  949. escapecancelskeypicker: {
  950. default: false,
  951. type: "boolean",
  952. text: "escape cancels key picker",
  953. },
  954. noshadows: {
  955. default: false,
  956. type: "boolean",
  957. text: "don't create shadow doms",
  958. hint: "only useful to allow eruda to see it",
  959. hinttype: "text",
  960. },
  961. escapeunbindskeypicker: {
  962. default: true,
  963. type: "boolean",
  964. text: "escape unbinds key in key picker",
  965. },
  966. resettodefaultbuttonstyle: {
  967. default: "{}",
  968. type: "text",
  969. text: "reset to default button style",
  970. },
  971. dimmeropacity: {
  972. text: "dimmer opacity",
  973. default: 0.8,
  974. type: "range",
  975. min: 0,
  976. max: 1,
  977. step: 0.05,
  978. },
  979. popupdimmeropacity: {
  980. text: "popup dimmer opacity",
  981. default: 0.8,
  982. type: "range",
  983. min: 0,
  984. max: 1,
  985. step: 0.05,
  986. },
  987. menuposition1: {
  988. text: "menu position",
  989. default: "top",
  990. type: "select",
  991. options: ["top", "center", "bottom", "full screen"],
  992. },
  993. menuposition2: {
  994. text: "menu position",
  995. default: "right",
  996. type: "select",
  997. options: ["left", "center", "right"],
  998. },
  999. // aligninputs: {
  1000. // text: "align inputs",
  1001. // default: true,
  1002. // type: "boolean",
  1003. // },
  1004. clickoffclosesmenu: {
  1005. text: "clicking off the menu closes it",
  1006. default: true,
  1007. type: "boolean",
  1008. },
  1009. alligninputs: {
  1010. text: "allign inputs",
  1011. default: true,
  1012. type: "boolean",
  1013. },
  1014. }
  1015. )
  1016. })()