-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
405 lines (361 loc) · 14.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EverQuest Purple Links Creator</title>
<script src="https://cdn.tailwindcss.com"></script>
<!-- Temporary styles-->
<style>
body {
background-color: grey;
font-size: 14px;
}
h1 {
margin-left: 20px;
}
</style>
</head>
<body>
<div>
<h1>EverQuest purple links creator</h1>
<label for="input-files">Inventory file(s):</label>
<input type="file" id="input-files" name="input-files" accept=".txt" multiple class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
<label for="input-list">Input list:</label>
<textarea id="input-list" name="input-list" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></textarea>
<label for="prices-list">Input prices list:</label>
<textarea id="prices-list" name="prices-list" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></textarea>
<label for="prefix">Prefix:</label>
<input id="prefix" value="/auc WTS " class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></input>
<label for="delimiter">Delimiter:</label>
<input id="delimiter" value=" | " class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></input>
<label for="buttonNumberStart">Button number start (Starts on page 2, button 1-12):</label>
<input id="buttonNumberStart" value="5" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></input>
<label for="output-list">Output list:</label>
<textarea id="output-list" class="w-full py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></textarea>
<label for="default-ignores">Ignore default bags:</label>
<input id="default-ignores" type="checkbox"></input>
</div>
<div>
<div>
Ignores: Prices:
<ul id="ignores">
</ul>
</div>
<div id="prices"></div>
</div>
<script src="jquery-3.7.1.min.js"></script>
<script>
const delimiter = document.getElementById('delimiter');
const inputList = document.getElementById('input-list');
const pricesList = document.getElementById('prices-list');
const outputList = document.getElementById('output-list');
const prefix = document.getElementById('prefix');
const buttonNumberStart = document.getElementById('buttonNumberStart');
const ignores = $('#ignores');
const prices = $('#prices');
const selectedFiles = document.getElementById("input-files");
const defaultIgnoresCheckbox = document.getElementById("default-ignores");
const defaults = {
ignoreIsDefault: true,
ignores: [
"17502", // Elemental Grimoire
"17005", // Backpack
"17969", // Hand Made Backpack
"17007", // Large Box
"17901", // Small Box
"17006" // Medicine Bag
],
}
// Local storage keys
const DEFAULT_IGNORES = "defaultIgnores"
const IGNORE_LIST = "ignoreList"
const ITEM_LIST = "itemList"
const FILES_LIST = "inputFiles"
// Storage actions
const updateLocalStore = list => {
localStorage.setItem(ITEM_LIST, JSON.stringify(Array.from(list)))
}
const getItemList = () => {
return JSON.parse(localStorage.getItem(ITEM_LIST))
}
const getItemById = id => getItemList()?.filter(item => item.id == id)[0]
const getItemPrice = id => {
return getItemById(id)?.price
}
const setItemPrice = (id, price) => {
const list = getItemList()
const itemIndex = list.findIndex(item => item.id == id)
list[itemIndex].price = price
updateLocalStore(list)
}
const getIgnores = () => {
const dbIgnores = JSON.parse(localStorage.getItem(IGNORE_LIST)) || []
return [ ...dbIgnores, ...defaults.ignores]
}
const toggleIgnoreId = id => {
let ignoreList = getIgnores();
if (ignoreList.includes(id)) {
ignoreList = ignoreList.filter(i => i != id);
} else {
ignoreList.push(id);
}
localStorage.setItem(IGNORE_LIST, JSON.stringify(ignoreList))
}
// Methods
function unique(a) {
var seen = {};
return a.filter(function(item) {
if (!seen[item.id]) {
seen[item.id] = true
return true
}
return false
});
}
const updateList = () => {
const inputListText = inputList.value
let items = parseInput(inputListText) // creates [{name:x, id:1, hex:x, price:1}]
updateLocalStore(items)
createIgnoresAndPricesLists(items)
items = removeIgnores(items).sort(sortPrice)
const finalLines = generateOutput(items)
// Separate with newlines and place in DOM
outputList.value = finalLines?.join("\n") || ""
}
function createIgnoresAndPricesLists(items) {
// Add to both ignores and prices lists
ignores.empty();
items.forEach(({name, id}) => {
const isChecked = getIgnores()?.includes(id) ? "checked" : "";
const isDisabled = getIgnores()?.includes(id) ? "disabled" : "";
const ignoreCheckbox = $(`<input type="checkbox" id="${name}" ${isChecked} ">`)
const label = $(`<label for="${name}">${name}</label>`)
const price = $(`<input id="price-${id}" value="${getItemPrice(id) || ""}" ${isDisabled} class="rounded-md border-0 py-1.5 pl-7 pr-20 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-red-600 placeholder:font-black focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" placeholder="0" ></input>`)
const item = $(`<li></li>`);
item.append(ignoreCheckbox, label, price);
ignores.append(item);
price.on('input', event => {
setItemPrice(id, event.target.value)
});
price.on('blur', updateList)
ignoreCheckbox.on('click', event => {
toggleIgnoreId(id)
updateList()
});
});
}
const createFinalLines = items => {
const finalLines = []
let line;
items.forEach(({link, price}, i) => {
const linkedItem = price ? `${link} ${price}` : link
if (i == 0) {
return line = prefix.value + linkedItem;
}
const lineAddition = delimiter.value + linkedItem
if (line.length + lineAddition.length > 255) {
finalLines.push(line);
line = prefix.value + linkedItem;
} else {
line += lineAddition;
}
})
// Add the final line to the list, since we are always working on a incomplete line, and adding a line
// to the list only when we are working on a linked item that is going onto a new line.
finalLines.push(line);
return finalLines;
}
// Converts input list from a string to a list of objects
// items = [
// {
// name: "identifier",
// id: "1235",
// hex: "a012",
// price: "1k",
// }
// ]
// ignored = ['1235', ...]
const parseInput = inputList => {
const items = []
// Remove garbage lines: header, empty slots, empty lines
const inventory = inputList.split("\n").filter(value => !value.includes("Location ") && !value.includes("\tEmpty") && value.trim() !== "");
// Remove unnecessary values, keeping just Name and ID
inventory.forEach(row => {
columns = row.split('\t');
const name = columns[1]
const id = columns[2]
const hex = Number(id).toString(16).padStart(4, 0)
const price = getItemPrice(id)
// Add special characters around its hex value and name, with some strange padding.
// 47 chars long + name = ~50ish chars + delimiter
// E.g.: 002fe3000000000000000000000000000000000000000Leatherfoot Raider Skullcap
// TODO: Figure out the reason for this padding...
const link = "00" + hex + "000000000000000000000000000000000000000" + name + ""
items.push({
id,
name,
price,
link,
});
})
// Dedupe
return unique(items);
}
const generateOutput = items => {
if (items.length == 0) {
console.warn("Warning: items is empty")
return
}
// Generate amount of items that will fit on each line
let finalLines = createFinalLines(items)
// Failure path:
finalLines.forEach(value => {
if (value?.length > 255) {
console.warn("Warning: length of the following line is above 255 chars (" + value.length + "):")
console.warn(value)
}
})
// Add button lines
// Page2Button10Line5=
// Bug: Does not go to next page, just increments upwards in buttons, even though button 12 is the highest in the game.
let buttonLineNumber = 1;
let buttonNumber = buttonNumberStart.value;
let pageNumber = 2;
finalLines = finalLines.map(value => {
if (buttonLineNumber > 5) {
buttonLineNumber = 1;
buttonNumber++;
}
if (buttonNumber > 12) {
buttonNumber = 1;
pageNumber++;
}
return `Page${pageNumber}Button${buttonNumber}Line${buttonLineNumber++}=${value}`
})
return finalLines
}
const removeIgnores = items => {
const ignoreList = JSON.parse(localStorage.getItem(IGNORE_LIST)) || []
return items.filter(value => !ignoreList.includes(value.id));
}
const sortPrice = (item, prev) => Number(item.price || 0) > Number(prev.price || 0) ? -1 : 1
const handleFiles = () => {
const fileList = selectedFiles.files
if (fileList.length > 0) {
for (file of fileList) {
const reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (event) {
inputList.value += event.target.result;
updateList()
}
reader.onerror = function (e) {
console.error(e)
}
}
}
}
const handleSavedFiles = () => {
if (fileList.length > 0) {
for (file of fileList) {
const reader = new FileReader()
reader.read(new File(file))
reader.onload = function (event) {
inputList.value += event.target.result;
updateList()
}
reader.onerror = function (e) {
console.error(e)
}
}
}
}
// saveFiles - This will attempt to save the same file names to local storage
// We need this + the user path to load the same files each time on load (Electron-app).
const saveFilesLocation = () => {
const {files} = selectedFiles
const fileNames = []
for (let file of files) {
fileNames.push(file.path)
}
localStorage.setItem(FILES_LIST, JSON.stringify(fileNames))
}
// Parse the prices from:
// A Sword 1234
// To:
// [{"A Sword": "1234"}, ...]
//
// First column name, Last column price.
const parsePrices = list => {
const items = {}
// Remove garbage lines: empty lines
const inventory = list.split("\n").filter(value => value.trim() !== "")
// Remove unnecessary values, keeping just Name and ID
inventory.forEach(row => {
columns = row.split('\t')
// Remove outside whitespace from name/price input
const name = columns[0].trim()
const price = columns[1].trim()
items[name] = price
})
return items
}
const updatePrices = () => {
const inputListText = pricesList.value
const newPrices = parsePrices(inputListText)
let oldPrices = getItemList()
for (name in newPrices) {
const price = newPrices[name]
oldPrices = oldPrices.map(item => {
if (item.name == name) {
item.price = price
}
return item
})
}
updateLocalStore(oldPrices)
}
const toggleDefaultIgnores = () => {
let value
if (localStorage.getItem(DEFAULT_IGNORES) === null) {
value = defaults.ignoreIsDefault
} else {
value = localStorage.getItem(DEFAULT_IGNORES) === 'true'
}
localStorage.setItem(DEFAULT_IGNORES, !value)
setDefaultIgnoresCheckbox(!value)
}
const setDefaultIgnoresCheckbox = value => {
if (value) {
defaultIgnoresCheckbox.setAttribute("checked", "")
} else {
defaultIgnoresCheckbox.removeAttribute("checked")
}
}
const onLoad = () => {
if (localStorage.getItem(DEFAULT_IGNORES) === null) {
setDefaultIgnoresCheckbox(defaults.ignoreIsDefault)
} else {
setDefaultIgnoresCheckbox(localStorage.getItem(DEFAULT_IGNORES) === 'true')
}
}
// Events
inputList.addEventListener('input', updateList);
prefix.addEventListener('input', updateList);
delimiter.addEventListener('input', updateList);
selectedFiles.addEventListener('change', handleFiles);
selectedFiles.addEventListener('change', saveFilesLocation);
pricesList.addEventListener('input', () => {
updatePrices()
updateList()
})
defaultIgnoresCheckbox.addEventListener('change', () => {
toggleDefaultIgnores()
updateList()
})
// Actions
onLoad()
</script>
</body>
</html>