@necrolab/dashboard 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/.eslintrc.js +24 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc +10 -0
  5. package/.vscode/extensions.json +3 -0
  6. package/ICONS.md +21 -0
  7. package/README.md +65 -0
  8. package/backend/api.js +430 -0
  9. package/backend/auth.js +62 -0
  10. package/backend/batching.js +43 -0
  11. package/backend/endpoints.js +343 -0
  12. package/backend/index.js +23 -0
  13. package/backend/mock-data.js +66 -0
  14. package/backend/mock-src/classes/logger.js +112 -0
  15. package/backend/mock-src/classes/utils.js +42 -0
  16. package/backend/mock-src/ticketmaster.js +92 -0
  17. package/backend/validator.js +62 -0
  18. package/config/configs.json +20 -0
  19. package/config/filter.json +3 -0
  20. package/config/presale.csv +3 -0
  21. package/config/proxies.txt +6 -0
  22. package/config/used-codes.json +4 -0
  23. package/index.html +114 -0
  24. package/index.js +2 -0
  25. package/jsconfig.json +16 -0
  26. package/package.json +48 -0
  27. package/postcss.config.js +6 -0
  28. package/postinstall.js +9 -0
  29. package/public/android-chrome-192x192.png +0 -0
  30. package/public/android-chrome-512x512.png +0 -0
  31. package/public/apple-touch-icon.png +0 -0
  32. package/public/favicon-16x16.png +0 -0
  33. package/public/favicon-32x32.png +0 -0
  34. package/public/favicon.ico +0 -0
  35. package/public/flags/ae.svg +1 -0
  36. package/public/flags/at.svg +1 -0
  37. package/public/flags/au.svg +1 -0
  38. package/public/flags/be.svg +1 -0
  39. package/public/flags/ch.svg +1 -0
  40. package/public/flags/cz.svg +1 -0
  41. package/public/flags/de.svg +1 -0
  42. package/public/flags/dk.svg +1 -0
  43. package/public/flags/es.svg +1 -0
  44. package/public/flags/nl.svg +1 -0
  45. package/public/flags/no.svg +1 -0
  46. package/public/flags/nz.svg +1 -0
  47. package/public/flags/pl.svg +1 -0
  48. package/public/flags/se.svg +1 -0
  49. package/public/flags/uk.svg +1 -0
  50. package/public/flags/us.svg +1 -0
  51. package/public/img/award.svg +3 -0
  52. package/public/img/background.svg +14 -0
  53. package/public/img/bag_w.svg +12 -0
  54. package/public/img/banks/amex.svg +4 -0
  55. package/public/img/banks/mastercard.svg +4 -0
  56. package/public/img/banks/visa.svg +4 -0
  57. package/public/img/camera.svg +3 -0
  58. package/public/img/close.svg +3 -0
  59. package/public/img/controls/disable.svg +5 -0
  60. package/public/img/controls/enable.svg +5 -0
  61. package/public/img/groups.svg +3 -0
  62. package/public/img/hand.svg +3 -0
  63. package/public/img/key.svg +3 -0
  64. package/public/img/logo.png +0 -0
  65. package/public/img/logo_icon.png +0 -0
  66. package/public/img/logo_icon_2.png +0 -0
  67. package/public/img/logo_trans.png +0 -0
  68. package/public/img/loyalty.svg +3 -0
  69. package/public/img/mail.svg +3 -0
  70. package/public/img/pencil.svg +3 -0
  71. package/public/img/profile.svg +4 -0
  72. package/public/img/reload.svg +3 -0
  73. package/public/img/sandclock.svg +25 -0
  74. package/public/img/save.svg +5 -0
  75. package/public/img/savings.svg +3 -0
  76. package/public/img/scanner.svg +3 -0
  77. package/public/img/sell.svg +3 -0
  78. package/public/img/shield.svg +3 -0
  79. package/public/img/ski.svg +3 -0
  80. package/public/img/stadium.svg +8 -0
  81. package/public/img/stadium_w.svg +8 -0
  82. package/public/img/timer.svg +3 -0
  83. package/public/manifest.json +27 -0
  84. package/public/robots.txt +2 -0
  85. package/run +10 -0
  86. package/src/App.vue +307 -0
  87. package/src/assets/css/_input.scss +197 -0
  88. package/src/assets/css/main.scss +269 -0
  89. package/src/assets/css/tailwind.css +3 -0
  90. package/src/assets/img/award.svg +3 -0
  91. package/src/assets/img/background.svg +11 -0
  92. package/src/assets/img/camera.svg +3 -0
  93. package/src/assets/img/close.svg +3 -0
  94. package/src/assets/img/eyes/closed.svg +13 -0
  95. package/src/assets/img/eyes/open.svg +12 -0
  96. package/src/assets/img/groups.svg +3 -0
  97. package/src/assets/img/hand.svg +3 -0
  98. package/src/assets/img/key.svg +3 -0
  99. package/src/assets/img/logo.png +0 -0
  100. package/src/assets/img/logo_icon.png +0 -0
  101. package/src/assets/img/logo_icon_2.png +0 -0
  102. package/src/assets/img/logo_trans.png +0 -0
  103. package/src/assets/img/loyalty.svg +3 -0
  104. package/src/assets/img/mail.svg +3 -0
  105. package/src/assets/img/pencil.svg +3 -0
  106. package/src/assets/img/reload.svg +3 -0
  107. package/src/assets/img/savings.svg +3 -0
  108. package/src/assets/img/scanner.svg +3 -0
  109. package/src/assets/img/sell.svg +3 -0
  110. package/src/assets/img/shield.svg +3 -0
  111. package/src/assets/img/ski.svg +3 -0
  112. package/src/assets/img/square_check.svg +5 -0
  113. package/src/assets/img/square_uncheck.svg +5 -0
  114. package/src/assets/img/stadium.svg +8 -0
  115. package/src/assets/img/timer.svg +3 -0
  116. package/src/assets/img/wildcard.svg +7 -0
  117. package/src/components/Auth/LoginForm.vue +48 -0
  118. package/src/components/Editors/Account/Account.vue +119 -0
  119. package/src/components/Editors/Account/AccountCreator.vue +147 -0
  120. package/src/components/Editors/Account/AccountView.vue +87 -0
  121. package/src/components/Editors/Account/CreateAccount.vue +106 -0
  122. package/src/components/Editors/Profile/CreateProfile.vue +321 -0
  123. package/src/components/Editors/Profile/Profile.vue +142 -0
  124. package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
  125. package/src/components/Editors/Profile/ProfileView.vue +96 -0
  126. package/src/components/Editors/TagLabel.vue +16 -0
  127. package/src/components/Editors/TagToggle.vue +41 -0
  128. package/src/components/Filter/Filter.vue +409 -0
  129. package/src/components/Filter/FilterPreview.vue +236 -0
  130. package/src/components/Filter/PriceSortToggle.vue +105 -0
  131. package/src/components/Table/Header.vue +5 -0
  132. package/src/components/Table/Row.vue +5 -0
  133. package/src/components/Table/Table.vue +14 -0
  134. package/src/components/Table/index.js +4 -0
  135. package/src/components/Tasks/CheckStock.vue +62 -0
  136. package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
  137. package/src/components/Tasks/Controls/MobileControls.vue +32 -0
  138. package/src/components/Tasks/Controls/index.js +3 -0
  139. package/src/components/Tasks/CreateTaskAXS.vue +339 -0
  140. package/src/components/Tasks/CreateTaskTM.vue +459 -0
  141. package/src/components/Tasks/MassEdit.vue +50 -0
  142. package/src/components/Tasks/QuickSettings.vue +167 -0
  143. package/src/components/Tasks/ScrapeVenue.vue +42 -0
  144. package/src/components/Tasks/Stats.vue +66 -0
  145. package/src/components/Tasks/Task.vue +296 -0
  146. package/src/components/Tasks/TaskLabel.vue +20 -0
  147. package/src/components/Tasks/TaskView.vue +126 -0
  148. package/src/components/Tasks/Utilities.vue +33 -0
  149. package/src/components/icons/Award.vue +8 -0
  150. package/src/components/icons/Bag.vue +8 -0
  151. package/src/components/icons/BagWhite.vue +8 -0
  152. package/src/components/icons/Box.vue +8 -0
  153. package/src/components/icons/Camera.vue +8 -0
  154. package/src/components/icons/Cart.vue +8 -0
  155. package/src/components/icons/Check.vue +5 -0
  156. package/src/components/icons/Checkmark.vue +11 -0
  157. package/src/components/icons/Click.vue +8 -0
  158. package/src/components/icons/Close.vue +21 -0
  159. package/src/components/icons/CloseX.vue +5 -0
  160. package/src/components/icons/Console.vue +13 -0
  161. package/src/components/icons/Down.vue +8 -0
  162. package/src/components/icons/Edit.vue +13 -0
  163. package/src/components/icons/Event.vue +8 -0
  164. package/src/components/icons/Expand.vue +8 -0
  165. package/src/components/icons/Filter.vue +13 -0
  166. package/src/components/icons/Gear.vue +8 -0
  167. package/src/components/icons/Group.vue +8 -0
  168. package/src/components/icons/Hand.vue +8 -0
  169. package/src/components/icons/Key.vue +21 -0
  170. package/src/components/icons/Logout.vue +13 -0
  171. package/src/components/icons/Loyalty.vue +8 -0
  172. package/src/components/icons/Mail.vue +8 -0
  173. package/src/components/icons/Menu.vue +8 -0
  174. package/src/components/icons/Pause.vue +5 -0
  175. package/src/components/icons/Pencil.vue +21 -0
  176. package/src/components/icons/Play.vue +8 -0
  177. package/src/components/icons/Plus.vue +8 -0
  178. package/src/components/icons/Profile.vue +18 -0
  179. package/src/components/icons/Reload.vue +7 -0
  180. package/src/components/icons/Sandclock.vue +33 -0
  181. package/src/components/icons/Savings.vue +8 -0
  182. package/src/components/icons/Scanner.vue +8 -0
  183. package/src/components/icons/Scrape.vue +8 -0
  184. package/src/components/icons/Sell.vue +21 -0
  185. package/src/components/icons/Shield.vue +8 -0
  186. package/src/components/icons/Shrink.vue +8 -0
  187. package/src/components/icons/Ski.vue +8 -0
  188. package/src/components/icons/Spinner.vue +42 -0
  189. package/src/components/icons/SquareCheck.vue +18 -0
  190. package/src/components/icons/SquareUncheck.vue +18 -0
  191. package/src/components/icons/Stadium.vue +13 -0
  192. package/src/components/icons/StadiumWhite.vue +13 -0
  193. package/src/components/icons/Status.vue +8 -0
  194. package/src/components/icons/Tag.vue +8 -0
  195. package/src/components/icons/Tasks.vue +13 -0
  196. package/src/components/icons/Ticket.vue +8 -0
  197. package/src/components/icons/Timer.vue +8 -0
  198. package/src/components/icons/Trash.vue +8 -0
  199. package/src/components/icons/Up.vue +10 -0
  200. package/src/components/icons/Wildcard.vue +18 -0
  201. package/src/components/icons/index.js +111 -0
  202. package/src/components/ui/Modal.vue +61 -0
  203. package/src/components/ui/Navbar.vue +207 -0
  204. package/src/components/ui/ReconnectIndicator.vue +90 -0
  205. package/src/components/ui/Splash.vue +24 -0
  206. package/src/components/ui/controls/CountryChooser.vue +87 -0
  207. package/src/components/ui/controls/EyeToggle.vue +11 -0
  208. package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
  209. package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
  210. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  211. package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
  212. package/src/components/ui/controls/atomic/Switch.vue +84 -0
  213. package/src/libs/Filter.js +593 -0
  214. package/src/libs/ansii.js +565 -0
  215. package/src/libs/panzoom.js +1413 -0
  216. package/src/main.js +23 -0
  217. package/src/registerServiceWorker.js +32 -0
  218. package/src/router/index.js +65 -0
  219. package/src/stores/cities.json +1 -0
  220. package/src/stores/connection.js +399 -0
  221. package/src/stores/countries.js +88 -0
  222. package/src/stores/logger.js +103 -0
  223. package/src/stores/requests.js +88 -0
  224. package/src/stores/sampleData.js +1034 -0
  225. package/src/stores/ui.js +584 -0
  226. package/src/stores/utils.js +554 -0
  227. package/src/types/index.js +42 -0
  228. package/src/utils/debug.js +1 -0
  229. package/src/views/Accounts.vue +191 -0
  230. package/src/views/Console.vue +224 -0
  231. package/src/views/Editor.vue +785 -0
  232. package/src/views/FilterBuilder.vue +785 -0
  233. package/src/views/Login.vue +27 -0
  234. package/src/views/Profiles.vue +209 -0
  235. package/src/views/Tasks.vue +157 -0
  236. package/static/offline.html +184 -0
  237. package/tailwind.config.js +57 -0
  238. package/vite.config.js +63 -0
  239. package/vue.config.js +32 -0
  240. package/workbox-config.js +66 -0
@@ -0,0 +1,584 @@
1
+ import { ref } from "vue";
2
+ import { defineStore } from "pinia";
3
+ import { ConnectionHandler } from "@/stores/connection.js";
4
+ import { toast } from "vue3-toastify";
5
+ import { createLogger } from "@/stores/logger";
6
+ import { timeDifference, betterSort, sortTaskIds } from "@/stores/utils";
7
+
8
+ import mockTaskData from "@/stores/sampleData.js";
9
+ import { useRouter } from "vue-router";
10
+ import { DEBUG } from "@/utils/debug";
11
+
12
+ const ALL_SELECTED = 0;
13
+ const SOME_SELECTED = 1;
14
+ const NO_SELECTED = 2;
15
+
16
+ export const useUIStore = defineStore("ui", () => {
17
+ const logger = createLogger("UI");
18
+ logger.DEBUG = DEBUG;
19
+
20
+ logger.Info("Initialized UI");
21
+
22
+ const activeModal = ref(null);
23
+ const accounts = ref([]);
24
+ const profiles = ref([]);
25
+ const proxies = ref("");
26
+ const tasks = ref({});
27
+
28
+ const showSpinner = ref(false);
29
+ const botVersion = ref("-");
30
+
31
+ const spinnerMessage = ref("");
32
+
33
+ const profile = ref({
34
+ profileTags: [],
35
+ accountTags: [],
36
+ admin: false,
37
+ name: ""
38
+ });
39
+
40
+ if (DEBUG) {
41
+ tasks.value = mockTaskData.Tasks;
42
+ profiles.value = mockTaskData.Profiles;
43
+ accounts.value = mockTaskData.Accounts;
44
+ profile.value = mockTaskData.Profile;
45
+ }
46
+ const mainCheckbox = ref({
47
+ profiles: false,
48
+ tasks: false,
49
+ accounts: false
50
+ });
51
+ const menuOpen = ref(false);
52
+ const sortData = ref({
53
+ sortBy: null,
54
+ reversed: false,
55
+ lastOrder: []
56
+ });
57
+ const taskIdOrder = ref(Object.keys(tasks.value));
58
+ const modalData = ref({});
59
+ const taskFilter = ref("All"); // 'All' or 'Checkout'
60
+ const connection = new ConnectionHandler();
61
+
62
+ const startPoint = ref(0);
63
+ const pullChange = ref(0);
64
+
65
+ const currentEvent = ref("");
66
+ const currentCountry = ref({ id: "US", siteId: "TM_US", url: "https://www.ticketmaster.com" });
67
+ const currentModule = ref("TM");
68
+
69
+ const savedEvent = localStorage.getItem("current-event");
70
+ const savedCountry = localStorage.getItem("current-country");
71
+ const savedModule = localStorage.getItem("current-module");
72
+ if (savedCountry) currentCountry.value = JSON.parse(savedCountry);
73
+ if (savedEvent) currentEvent.value = savedEvent;
74
+ if (savedModule) currentModule.value = savedModule;
75
+
76
+ const disabledButtons = ref({
77
+ "add-tasks": false,
78
+ "add-profiles": false,
79
+ "add-accounts": false,
80
+ "mass-edit": false,
81
+ "create-accounts": false
82
+ });
83
+
84
+ const queueStats = ref({
85
+ show: false,
86
+ total: 0,
87
+ queued: 0,
88
+ sleeping: 0,
89
+ nextQueuePasses: []
90
+ });
91
+
92
+ const router = useRouter();
93
+ const openContextMenu = ref("");
94
+ const currentlyEditing = ref({});
95
+ const search = ref({
96
+ profiles: { query: "", field: "Name", results: [], show: "All", tag: "Any" },
97
+ accounts: { query: "", results: [], show: "All", tag: "Any" }
98
+ });
99
+ const currentDropdown = ref("");
100
+
101
+ // Set the _timeLeftString property every second
102
+ setInterval(() => {
103
+ const t1 = new Date();
104
+ for (const [key, value] of Object.entries(tasks.value)) {
105
+ if (value.expirationTime)
106
+ tasks.value[key]._timeLeftString = timeDifference(Date.parse(value.expirationTime), t1.getTime());
107
+ else if (value.noCartholds) tasks.value[key]._timeLeftString = "No Cartholds";
108
+ else tasks.value[key]._timeLeftString = undefined;
109
+ }
110
+
111
+ if (t1.getSeconds() % 10 === 0) {
112
+ const allIds = new Set(Object.keys(tasks.value));
113
+ const orderIds = new Set(taskIdOrder.value);
114
+
115
+ const missingIds = [...allIds].filter((id) => !orderIds.has(id));
116
+ const extraIds = [...orderIds].filter((id) => !allIds.has(id));
117
+
118
+ if (missingIds.length > 0 || extraIds.length > 0) {
119
+ taskIdOrder.value = taskIdOrder.value.filter((id) => allIds.has(id));
120
+
121
+ if (missingIds.length > 0) {
122
+ missingIds.sort(sortTaskIds);
123
+ for (const id of missingIds) {
124
+ let pos = taskIdOrder.value.length;
125
+ for (let i = 0; i < taskIdOrder.value.length; i++) {
126
+ if (sortTaskIds(id, taskIdOrder.value[i]) < 0) {
127
+ pos = i;
128
+ break;
129
+ }
130
+ }
131
+ taskIdOrder.value.splice(pos, 0, id);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }, 1000);
137
+
138
+ connection.init("/api/updates?type=tasks");
139
+
140
+ const pd = (e) => {
141
+ if (!e.target.closest(".scrollable")) {
142
+ e.preventDefault();
143
+ }
144
+ };
145
+ const preventScroll = () => {
146
+ const scrollY = window.scrollY;
147
+ document.body.style.position = "fixed";
148
+ document.body.style.top = `-${scrollY}px`;
149
+ document.body.style.width = "100%";
150
+ document.body.style.height = "100%";
151
+ document.body.style.left = "0"; // Add left positioning
152
+
153
+ // iOS specific properties
154
+ document.body.style.overflow = "hidden";
155
+ document.body.style.overscrollBehavior = "none";
156
+ document.body.style["-webkit-overflow-scrolling"] = "touch";
157
+
158
+ // Prevent text selection issues
159
+ document.body.style["-webkit-user-select"] = "none";
160
+ document.body.style.userSelect = "none";
161
+
162
+ // Also apply to html element for better iOS compatibility
163
+ document.documentElement.style.overflow = "hidden";
164
+ document.documentElement.style.overscrollBehavior = "none";
165
+ document.documentElement.style["-webkit-overflow-scrolling"] = "touch";
166
+
167
+ // Add touch-action prevention
168
+ document.body.style.touchAction = "none";
169
+ document.documentElement.style.touchAction = "none";
170
+
171
+ // Re-enable text selection only for inputs and textareas
172
+ const inputs = document.querySelectorAll("input, textarea");
173
+ inputs.forEach((input) => {
174
+ input.style["-webkit-user-select"] = "auto";
175
+ input.style.userSelect = "auto";
176
+ input.style.touchAction = "pan-x pan-y";
177
+ });
178
+
179
+ // Prevent touch moves
180
+ document.addEventListener("touchmove", pd, { passive: false });
181
+ };
182
+
183
+ const enableScroll = () => {
184
+ if (showSpinner.value) return;
185
+
186
+ const scrollY = document.body.style.top;
187
+
188
+ // Reset body styles
189
+ document.body.style.position = "";
190
+ document.body.style.top = "";
191
+ document.body.style.width = "";
192
+ document.body.style.height = "";
193
+ document.body.style.left = "";
194
+ document.body.style.overflow = "";
195
+ document.body.style.overscrollBehavior = "";
196
+ document.body.style["-webkit-overflow-scrolling"] = "";
197
+ document.body.style["-webkit-user-select"] = "";
198
+ document.body.style.userSelect = "";
199
+ document.body.style.touchAction = "";
200
+
201
+ // Reset html element styles
202
+ document.documentElement.style.overflow = "";
203
+ document.documentElement.style.overscrollBehavior = "";
204
+ document.documentElement.style["-webkit-overflow-scrolling"] = "";
205
+ document.documentElement.style.touchAction = "";
206
+
207
+ // Reset input styles
208
+ const inputs = document.querySelectorAll("input, textarea");
209
+ inputs.forEach((input) => {
210
+ input.style["-webkit-user-select"] = "";
211
+ input.style.userSelect = "";
212
+ input.style.touchAction = "";
213
+ });
214
+
215
+ // Restore scroll position
216
+ window.scrollTo(0, parseInt(scrollY || "0") * -1);
217
+
218
+ // Remove event listener
219
+ document.removeEventListener("touchmove", pd);
220
+ };
221
+
222
+ const refreshQueueStats = () => {
223
+ queueStats.value.show = false;
224
+ queueStats.value.total = 0;
225
+ queueStats.value.queued = 0;
226
+ queueStats.value.sleeping = 0;
227
+ queueStats.value.nextQueuePasses = [];
228
+
229
+ let relevantTasks = Object.values(tasks.value).filter((t) => t.siteId === currentCountry.value.siteId);
230
+ if (currentEvent.value) relevantTasks = relevantTasks.filter((t) => t.eventId === currentEvent.value);
231
+
232
+ queueStats.value.total = 0;
233
+ queueStats.value.queued = relevantTasks.filter((t) => t.inQueue).length || 0;
234
+ queueStats.value.show = queueStats.value.total > 0;
235
+ const sleepingStatuses = ["sleeping in queue", "waiting for drop", "waiting for carting", "waiting for queue"];
236
+ queueStats.value.sleeping = relevantTasks.filter((t) =>
237
+ sleepingStatuses.includes(t.status.toLowerCase())
238
+ ).length;
239
+ const positions = relevantTasks.map((t) => t.queuePosition).filter((e) => !isNaN(e));
240
+ queueStats.value.nextQueuePasses = positions.sort((a, b) => a - b);
241
+ };
242
+
243
+ const toggleModal = (name, clearValue = false) => {
244
+ if (disabledButtons.value["add-tasks"] && name === "create-task") return;
245
+ if (clearValue) currentlyEditing.value = {};
246
+ activeModal.value = name;
247
+ if (!name) {
248
+ currentlyEditing.value = {};
249
+ enableScroll();
250
+ } else preventScroll();
251
+ };
252
+
253
+ const reverseTasks = () => {
254
+ logger.Info("Reversing", !sortData.value.reversed);
255
+ taskIdOrder.value.reverse();
256
+ };
257
+
258
+ const sortTasks = () => {
259
+ try {
260
+ if (!sortData.value.sortBy) {
261
+ // When no specific sort column is set, default to sorting by taskId
262
+ const taskArr = Object.entries(tasks.value).map(([taskId, task]) => ({ taskId, ...task }));
263
+ taskArr.sort((a, b) => sortTaskIds(a.taskId, b.taskId));
264
+ taskIdOrder.value = taskArr.map((task) => task.taskId);
265
+
266
+ if (sortData.value.reversed) {
267
+ taskIdOrder.value.reverse();
268
+ }
269
+ return;
270
+ }
271
+
272
+ logger.Info("Sorting", sortData.value.sortBy);
273
+ const taskArr = Object.entries(tasks.value).map(([taskId, task]) => ({ taskId, ...task }));
274
+
275
+ if (sortData.value.sortBy === "taskId") {
276
+ taskArr.sort((a, b) => sortTaskIds(a.taskId, b.taskId));
277
+ } else {
278
+ taskArr.sort((a, b) => betterSort(a[sortData.value.sortBy], b[sortData.value.sortBy]));
279
+ }
280
+
281
+ taskIdOrder.value = taskArr.map((task) => task.taskId);
282
+ if (sortData.value.reversed) {
283
+ taskIdOrder.value.reverse();
284
+ }
285
+ } catch (ex) {
286
+ logger.Error("Error sorting tasks:", ex);
287
+ }
288
+ };
289
+
290
+ const getTaskSelectionStatus = () => {
291
+ let selected = {};
292
+ for (const [key, value] of Object.entries(tasks.value)) if (value.selected) selected[key] = value;
293
+ if (Object.keys(selected).length === 0) return NO_SELECTED;
294
+ if (Object.keys(selected).length === Object.keys(tasks.value).length) return ALL_SELECTED;
295
+ return SOME_SELECTED;
296
+ };
297
+
298
+ const getSelectionStatus = (of) => {
299
+ let selected = of.filter((o) => o.selected);
300
+ if (selected.length === 0) return NO_SELECTED;
301
+ if (selected.length === of.length) return ALL_SELECTED;
302
+ return SOME_SELECTED;
303
+ };
304
+
305
+ const setMainCheckboxRightValue = (group = "tasks") => {
306
+ const status = getTaskSelectionStatus();
307
+ if (status === NO_SELECTED) {
308
+ mainCheckbox.value[group] = false;
309
+ } else if (status === SOME_SELECTED) {
310
+ mainCheckbox.value[group] = false;
311
+ } else if (status === ALL_SELECTED) {
312
+ mainCheckbox.value[group] = true;
313
+ }
314
+ };
315
+
316
+ const toggleMainCheckbox = (group) => {
317
+ let status;
318
+ if (group === "tasks") status = getTaskSelectionStatus();
319
+ else if (group === "profiles") status = getSelectionStatus(profiles.value);
320
+ else if (group === "accounts") status = getSelectionStatus(accounts.value);
321
+ const currentValue = mainCheckbox.value[group];
322
+ mainCheckbox.value[group] = !mainCheckbox.value[group];
323
+
324
+ if (status === ALL_SELECTED && !currentValue) return;
325
+ if (group === "tasks") {
326
+ for (const value of Object.values(tasks.value))
327
+ value.selected = status == NO_SELECTED || status == SOME_SELECTED;
328
+ } else if (group === "profiles")
329
+ profiles.value.forEach((p) => (p.selected = status == NO_SELECTED || status == SOME_SELECTED));
330
+ else if (group === "accounts")
331
+ accounts.value.forEach((p) => (p.selected = status == NO_SELECTED || status == SOME_SELECTED));
332
+ };
333
+
334
+ const getSelectedTasks = () => {
335
+ let selected = {};
336
+ for (const [key, value] of Object.entries(tasks.value)) {
337
+ if (currentCountry.value.siteId && value.siteId !== currentCountry.value.siteId) continue;
338
+ if (currentEvent.value && value.eventId !== currentEvent.value) continue;
339
+ if (value.selected) selected[key] = value;
340
+ }
341
+ if (Object.keys(selected).length === 0) {
342
+ const r = {};
343
+ for (const [key, value] of Object.entries(tasks.value)) {
344
+ if (currentCountry.value.siteId && value.siteId === currentCountry.value.siteId) r[key] = value;
345
+ }
346
+ return r;
347
+ }
348
+ return selected;
349
+ };
350
+
351
+ const getSelectedProfiles = () => {
352
+ const res = search.value.profiles.results.filter((p) => p.selected);
353
+ if (res.length === 0) return search.value.profiles.results;
354
+ else return res;
355
+ };
356
+ const getSelectedAccounts = () => {
357
+ const res = search.value.accounts.results.filter((p) => p.selected);
358
+ if (res.length === 0) return search.value.accounts.results;
359
+ else return res;
360
+ };
361
+
362
+ const startSpinner = (msg) => {
363
+ if (router.currentRoute.value.name == "login" || window.location.href.includes(":5173")) return;
364
+ showSpinner.value = true;
365
+ spinnerMessage.value = msg;
366
+ preventScroll();
367
+ };
368
+
369
+ if (!DEBUG) startSpinner("Connecting", true);
370
+
371
+ return {
372
+ // auth
373
+ profile,
374
+ setProfile: (pf) => (profile.value = pf),
375
+
376
+ // modal
377
+ activeModal,
378
+ toggleModal,
379
+ modalData,
380
+
381
+ // sorting
382
+ toggleSort: (sort = null) => {
383
+ const sameSortBy = sortData.value.sortBy === sort;
384
+
385
+ if (sort) {
386
+ if (sameSortBy) {
387
+ // If clicking on the same sort field, just reverse the current order
388
+ reverseTasks();
389
+ sortData.value.reversed = !sortData.value.reversed;
390
+ } else {
391
+ // If changing sort field, reset reversed state and sort
392
+ sortData.value.sortBy = sort;
393
+ sortData.value.reversed = false;
394
+ sortTasks();
395
+ }
396
+ } else if (sortData.value.sortBy) {
397
+ // If no sort specified but we have a current sort, just re-sort
398
+ sortTasks();
399
+ }
400
+
401
+ logger.Info(`toggleSort: sortBy=${sortData.value.sortBy}, reversed=${sortData.value.reversed}`);
402
+
403
+ // Update lastOrder after sorting
404
+ sortData.value.lastOrder = [...taskIdOrder.value];
405
+ },
406
+ sortData,
407
+ taskIdOrder,
408
+ reverseTasks,
409
+
410
+ // refresh
411
+ startPoint,
412
+ pullChange,
413
+ setStartPoint: (point) => (startPoint.value = point),
414
+ setPullChange: () => (pullChange.value = length),
415
+ tasks,
416
+
417
+ // top main checkbox
418
+ mainCheckbox,
419
+ toggleMainCheckbox,
420
+
421
+ createAcconts: (cnfg) => connection.sendCreateAccounts(cnfg),
422
+
423
+ // single
424
+ deleteTask: (taskId) => {
425
+ delete tasks.value[taskId];
426
+ taskIdOrder.value = taskIdOrder.value.filter((i) => i !== taskId);
427
+ connection.sendDeleteTask(taskId);
428
+ },
429
+ stopTask: (id) => connection.sendStopTask(id),
430
+ startTask: (id) => connection.sendStartTask(id),
431
+ continueTask: (id, type) => connection.sendContinueTask(id, type),
432
+ addNewTask: (task) => {
433
+ toggleModal("");
434
+ if (DEBUG) {
435
+ const tid = `T-${Object.keys(tasks.value).length}`;
436
+ const t = {
437
+ ...task,
438
+ siteId: currentCountry.value.siteId,
439
+ hidden: false,
440
+ active: true,
441
+ mode: "CHECKOUT",
442
+ taskId: tid,
443
+ email: "test@test.de",
444
+ password: "test"
445
+ };
446
+ tasks.value[tid] = t;
447
+ logger.Debug("Creating debug task", t);
448
+ taskIdOrder.value.push(tid);
449
+ if (sortData.value.sortBy) {
450
+ sortTasks(); // Re-sort after adding a new task
451
+ }
452
+ } else connection.sendAddTask(task);
453
+ },
454
+
455
+ // selected/all
456
+ getSelectedTasks,
457
+ deleteTasks: () => {
458
+ if (!confirm("Do you want do delete all selected tasks?")) return;
459
+ const selectedTasks = getSelectedTasks();
460
+ for (const value of Object.values(selectedTasks)) connection.sendDeleteTask(value.taskId);
461
+ },
462
+ foldTasks: () => {
463
+ const selectedTasks = getSelectedTasks();
464
+ for (const key of Object.keys(selectedTasks)) tasks.value[key].isExpanded = false;
465
+ },
466
+ expandTasks: () => {
467
+ const selectedTasks = getSelectedTasks();
468
+ for (const key of Object.keys(selectedTasks)) tasks.value[key].isExpanded = true;
469
+ },
470
+ startTasks: () => {
471
+ const selectedTasks = getSelectedTasks();
472
+ for (const value of Object.values(selectedTasks)) if (!value.active) connection.sendStartTask(value.taskId);
473
+ },
474
+ stopTasks: () => {
475
+ const selectedTasks = getSelectedTasks();
476
+ for (const value of Object.values(selectedTasks)) if (value.active) connection.sendStopTask(value.taskId);
477
+ },
478
+ toggleTaskSelected: (taskId) => {
479
+ tasks.value[taskId].selected = !tasks.value[taskId].selected;
480
+ setMainCheckboxRightValue();
481
+ },
482
+
483
+ toggleProfileSelected: (id) => {
484
+ const idx = profiles.value.findIndex((p) => p._id === id);
485
+ profiles.value[idx].selected = !profiles.value[idx].selected;
486
+ },
487
+ toggleAccountSelected: (id) => {
488
+ const idx = accounts.value.findIndex((p) => p._id === id);
489
+ accounts.value[idx].selected = !accounts.value[idx].selected;
490
+ },
491
+
492
+ // proxy for connection functions
493
+ checkStock: (eventId, presaleCode, eventDid, clOrigin) =>
494
+ connection.sendCheckStock(eventId, presaleCode, eventDid, clOrigin),
495
+ scrapeVenue: (eventId) => connection.sendScrapeMap(eventId),
496
+ massEditPresaleCode: (eventId, presaleCode) => connection.sendMassEditPresaleCode(eventId, presaleCode),
497
+
498
+ // alerts
499
+ showError: (err) => toast.error(err, { autoClose: 2000 }),
500
+ showSuccess: (msg) => toast.success(msg, { autoClose: 2000 }),
501
+ // Country
502
+ currentCountry,
503
+ setCurrentCountry: (country, closeModal, newModule) => {
504
+ currentCountry.value = country;
505
+ currentModule.value = newModule;
506
+ if (closeModal) menuOpen.value = false;
507
+ localStorage.setItem("current-country", JSON.stringify(currentCountry.value));
508
+ localStorage.setItem("current-module", newModule);
509
+ refreshQueueStats();
510
+ },
511
+ menuOpen,
512
+
513
+ // Event
514
+ currentEvent,
515
+ setCurrentEvent: (event) => {
516
+ logger.Info("Setting current event", event);
517
+ currentEvent.value = event;
518
+ localStorage.setItem("current-event", currentEvent.value);
519
+ refreshQueueStats();
520
+ },
521
+
522
+ disabledButtons,
523
+ queueStats,
524
+
525
+ refreshQueueStats,
526
+
527
+ openContextMenu,
528
+ setOpenContextMenu: (taskId) => (openContextMenu.value = taskId),
529
+
530
+ accounts,
531
+ addAccount: (acc) => {
532
+ if (!DEBUG) return connection.sendSaveAccount(acc);
533
+ const isEdit = accounts.value.findIndex((p) => p._id === acc._id);
534
+ if (DEBUG && isEdit !== -1) {
535
+ Object.keys(acc).forEach((k) => (accounts.value[isEdit][k] = acc[k]));
536
+ } else accounts.value.push({ ...acc, _id: Math.random() });
537
+ },
538
+ setAccounts: (accs) => accounts.value.push(...accs),
539
+ profiles,
540
+ addProfile: (profile) => {
541
+ if (!DEBUG) return connection.sendSaveProfile(profile);
542
+ const isEdit = profiles.value.findIndex((p) => p._id === profile._id);
543
+
544
+ if (DEBUG && isEdit !== -1) {
545
+ Object.keys(profile).forEach((k) => (accounts.value[isEdit][k] = profile[k]));
546
+ } else {
547
+ profiles.value.push({ ...profile, _id: Math.random() });
548
+ }
549
+ },
550
+ deleteProfile: (id) => {
551
+ if (!DEBUG) return connection.sendDeleteProfile(id);
552
+ else profiles.value = profiles.value.filter((p) => p._id != id);
553
+ },
554
+ setProfiles: (profs) => (profiles.value = profs),
555
+ proxies,
556
+ setProxies: (p) => (proxies.value = p),
557
+
558
+ currentlyEditing,
559
+ getSelectedProfiles,
560
+ getSelectedAccounts,
561
+ search,
562
+
563
+ currentDropdown,
564
+ setCurrentDropdown: (v) => (currentDropdown.value = v),
565
+
566
+ startSpinner,
567
+ hideSpinner: () => {
568
+ showSpinner.value = false;
569
+ enableScroll();
570
+ },
571
+ setBotVersion: (version) => {
572
+ botVersion.value = version;
573
+ },
574
+ botVersion,
575
+
576
+ spinnerMessage,
577
+ showSpinner,
578
+ taskFilter,
579
+ setTaskFilter: (e) => (taskFilter.value = e),
580
+ sortTasks,
581
+ logger,
582
+ currentModule
583
+ };
584
+ });