djledda.de main
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

2 месяцев назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import { defineComponent, computed, ref } from 'vue';
  2. /*
  3. class GrainInput {
  4. static inputs = [];
  5. name;
  6. unit;
  7. mpg;
  8. inputEl;
  9. reference;
  10. * @param {{
  11. * name: string,
  12. * mpg: number,
  13. * reference: GrainInput | 'self',
  14. * unit: string,
  15. * step?: number,
  16. * }} opts
  17. constructor(opts) {
  18. this.name = opts.name;
  19. this.mpg = opts.mpg;
  20. this.unit = opts.unit;
  21. this.reference = opts.reference === "self" ? this : opts.reference;
  22. this.inputEl = h("input", { type: "number", min: 0, step: opts.step ?? 1 });
  23. eventBus.addListener(this);
  24. }
  25. attachInput(insertionPoint) {
  26. insertionPoint.appendChild(this.inputEl);
  27. this.inputEl.valueAsNumber = this.mpg;
  28. this.inputEl.addEventListener("input", (e) => {
  29. const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
  30. if (this !== this.reference) {
  31. this.reference.inputEl.valueAsNumber = newVal / this.mpg;
  32. }
  33. eventBus.sendChange(this);
  34. });
  35. }
  36. onChange() {
  37. if (!this.reference || this === this.reference) return;
  38. this.inputEl.valueAsNumber = this.mpg * this.reference.inputEl.valueAsNumber;
  39. }
  40. getCurrentValue() {
  41. return this.inputEl.valueAsNumber;
  42. }
  43. }
  44. class RatiosController {
  45. t3Ratio = h("input", { type: "number", min: 0, step: 1 });
  46. t4Ratio = h("input", { type: "number", min: 0, step: 1 });
  47. t3Syn = h("input", { type: "number", min: 0 });
  48. t4Syn = h("input", { type: "number", min: 0 });
  49. t3 = 1;
  50. t4 = 4;
  51. reference;
  52. constructor(referenceInput) {
  53. this.reference = referenceInput;
  54. }
  55. attachInputs(inputs) {
  56. inputs.t3Ratio.replaceWith(this.t3Ratio);
  57. inputs.t4Ratio.replaceWith(this.t4Ratio);
  58. inputs.t3Syn.replaceWith(this.t3Syn);
  59. inputs.t4Syn.replaceWith(this.t4Syn);
  60. this.t3Ratio.addEventListener("input", (e) => {
  61. const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
  62. this.t3 = newVal;
  63. this.onChange();
  64. });
  65. this.t4Ratio.addEventListener("input", (e) => {
  66. const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
  67. this.t4 = newVal;
  68. this.onChange();
  69. });
  70. this.t3Syn.addEventListener("input", (e) => {
  71. const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
  72. this.reference.inputEl.valueAsNumber = newVal / MPG_T3_SYN + this.t4Syn.valueAsNumber / MPG_T4_SYN;
  73. this.t3 = newVal;
  74. this.t4 = this.t4Syn.valueAsNumber;
  75. this.updateRatio();
  76. this.updateSyn();
  77. eventBus.sendChange(this);
  78. });
  79. this.t4Syn.addEventListener("input", (e) => {
  80. const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
  81. this.reference.inputEl.valueAsNumber = newVal / MPG_T4_SYN + this.t3Syn.valueAsNumber / MPG_T3_SYN;
  82. this.t3 = this.t3Syn.valueAsNumber;
  83. this.t4 = newVal;
  84. this.updateRatio();
  85. this.updateSyn();
  86. eventBus.sendChange(this);
  87. });
  88. eventBus.addListener(this);
  89. this.onChange();
  90. }
  91. onChange() {
  92. this.updateRatio();
  93. this.updateSyn();
  94. }
  95. updateRatio() {
  96. this.t3Ratio.valueAsNumber = this.t3;
  97. this.t4Ratio.valueAsNumber = this.t4;
  98. }
  99. updateSyn() {
  100. const total = this.t3 + this.t4;
  101. const t3Proportion = this.t3 / total;
  102. const t4Proportion = this.t4 / total;
  103. const grainsSyn = t3Proportion / MPG_T3_SYN + t4Proportion / MPG_T4_SYN;
  104. const multiplierSyn = this.reference.getCurrentValue() / grainsSyn;
  105. this.t3Syn.valueAsNumber = t3Proportion * multiplierSyn;
  106. this.t4Syn.valueAsNumber = t4Proportion * multiplierSyn;
  107. }
  108. }
  109. class ThyroidConverter extends DJElement {
  110. static styles = css``;
  111. static template = html;
  112. constructor() {
  113. super();
  114. const mainGrainInput = new GrainInput({
  115. name: "Grains",
  116. mpg: 1,
  117. reference: "self",
  118. unit: "",
  119. step: 0.1,
  120. });
  121. const ratiosController = new RatiosController(mainGrainInput);
  122. const inputs = [
  123. mainGrainInput,
  124. new GrainInput({
  125. name: "Armour, Natural Dessicated Thyroid",
  126. mpg: 60,
  127. unit: "mg",
  128. reference: mainGrainInput,
  129. }),
  130. new GrainInput({
  131. name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)',
  132. mpg: MPG_T3_SYN,
  133. unit: "mcg",
  134. reference: mainGrainInput,
  135. }),
  136. new GrainInput({
  137. name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)',
  138. mpg: MPG_T4_SYN,
  139. unit: "mcg",
  140. reference: mainGrainInput,
  141. }),
  142. ];
  143. const tableBody = qs("#table-body", this.root);
  144. const compoundedStart = qs("#compounded-start", this.root);
  145. for (const field of inputs) {
  146. const newRow = h("tr", {}, [
  147. h("td", { textContent: field.name }),
  148. h("td", {}, [
  149. h("div", { className: "input", style: "display: inline-block;" }),
  150. h("span", { className: "breathe", textContent: field.unit }),
  151. ]),
  152. ]);
  153. field.attachInput(qs("div.input", newRow));
  154. tableBody.insertBefore(newRow, compoundedStart);
  155. }
  156. ratiosController.attachInputs({
  157. t3Ratio: qs(".ratios .t3", tableBody),
  158. t4Ratio: qs(".ratios .t4", tableBody),
  159. t3Syn: qs(".synthetic .t3", tableBody),
  160. t4Syn: qs(".synthetic .t4", tableBody),
  161. });
  162. }
  163. }
  164. */
  165. export default defineComponent({
  166. name: 'ge-calculator',
  167. setup() {
  168. const t3Ratio = ref(1);
  169. const t4Ratio = ref(4);
  170. const MPG_T3_SYN = 25;
  171. const MPG_T4_SYN = 100;
  172. const inputDefs = [
  173. {
  174. name: "Grains",
  175. mpg: 1,
  176. unit: "",
  177. step: 0.1,
  178. },
  179. {
  180. name: "Armour, Natural Dessicated Thyroid",
  181. mpg: 60,
  182. unit: "mg",
  183. },
  184. {
  185. name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)',
  186. mpg: MPG_T3_SYN,
  187. unit: "mcg",
  188. },
  189. {
  190. name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)',
  191. mpg: MPG_T4_SYN,
  192. unit: "mcg",
  193. },
  194. ];
  195. const numGrains = ref(2);
  196. const ratio = computed(() => t3Ratio.value + t4Ratio.value);
  197. const compounded = computed(() => {
  198. const total = t3Ratio.value + t4Ratio.value;
  199. const t3Proportion = t3Ratio.value / total;
  200. const t4Proportion = t4Ratio.value / total;
  201. const grainsSyn = t3Proportion / MPG_T3_SYN + t4Proportion / MPG_T4_SYN;
  202. const multiplierSyn = numGrains.value / grainsSyn;
  203. return {
  204. t3Syn: t3Proportion * multiplierSyn,
  205. t4Syn: t4Proportion * multiplierSyn,
  206. };
  207. });
  208. return () => <>
  209. <header>
  210. <h1>Thyroid Calculator</h1>
  211. </header>
  212. <div class="text-slab">
  213. <p>
  214. Use this calculator to convert between different forms and units of thyroid hormone. Common
  215. synthetic, pure and natural dessicated forms are listed. The last section of the table provides
  216. input fields for a specified ratio of T3 to T4, and will give the dosages required for both the
  217. synthetic and pure forms. All dosages given are based on the "Grains" field at the beginning of the
  218. table, but editing any field will automatically update the rest to keep them in sync.
  219. </p>
  220. </div>
  221. <div class="text-slab ge-calculator">
  222. <table>
  223. <tbody>
  224. { inputDefs.map((_, i) => (
  225. <tr key={_.name}>
  226. <td>
  227. { _.name }
  228. </td>
  229. <td class="right">
  230. <div style="display: inline-block;">
  231. <input
  232. value={_.name === 'Grains' ? numGrains.value : _.mpg * numGrains.value}
  233. onInput={(e) => {
  234. const val = (e.target as HTMLInputElement).valueAsNumber;
  235. if (_.name === 'Grains') {
  236. numGrains.value = val;
  237. } else {
  238. numGrains.value = _.mpg / val;
  239. }
  240. }}
  241. min="0"
  242. step={ _.step ?? 1 }
  243. type="number" />
  244. </div>
  245. <span class="breathe">{ _.unit }</span>
  246. </td>
  247. </tr>
  248. ))}
  249. <tr><td colspan="2"><strong>Compounded (T3 and T4, "Cynpolus")</strong></td></tr>
  250. <tr class="ratios">
  251. <td>
  252. Desired Ratio (T3:T4)
  253. </td>
  254. <td class="right">
  255. <div>
  256. <input
  257. value={t3Ratio.value}
  258. onInput={(e) => {
  259. t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
  260. }}
  261. min="0"
  262. step="1"
  263. type="number" />
  264. </div> : <div>
  265. <input
  266. value={t4Ratio.value}
  267. onInput={(e) => {
  268. t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
  269. }}
  270. min="0"
  271. step="1"
  272. type="number" />
  273. </div>
  274. </td>
  275. </tr>
  276. <tr class="synthetic">
  277. <td>
  278. Synthetic T3/T4 Combo
  279. </td>
  280. <td class="right">
  281. <div>
  282. <input
  283. value={compounded.value.t3Syn}
  284. onInput={(e) => {
  285. t4Ratio.value = compounded.value.t4Syn;
  286. t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
  287. numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN;
  288. }}
  289. min="0"
  290. step="1"
  291. type="number" />
  292. </div> : <div>
  293. <input
  294. value={compounded.value.t4Syn}
  295. onInput={(e) => {
  296. t3Ratio.value = compounded.value.t3Syn;
  297. t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
  298. numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN;
  299. }}
  300. min="0"
  301. step="1"
  302. type="number" />
  303. </div>
  304. </td>
  305. </tr>
  306. </tbody>
  307. </table>
  308. </div>
  309. <div class="text-slab">
  310. <strong>Changelog:</strong>
  311. <p>
  312. <ul>
  313. <li>
  314. November 2024: Migrated to new web framework and fixed some buggy input.
  315. </li>
  316. <li>
  317. 13th March 2024: Removed the synthetic/pure distinction as it was confusing and unnecessary
  318. bloat.
  319. </li>
  320. </ul>
  321. </p>
  322. </div>
  323. </>;
  324. }
  325. });