├── .gitignore
├── DEVELOPERS.md
├── README.md
├── auto-updater.js
├── build
├── background.png
├── icon.icns
├── icon.ico
└── icon.png
├── css
├── common.css
├── main-window.css
└── welcome-window.css
├── data
├── values-test.txt
└── values.txt
├── images
├── add-character-plus.svg
├── favorite-selected.svg
├── favorite-unselected.svg
├── icons.ai
└── menu-indicator.svg
├── js
├── battle-pairer.js
├── battle-timekeeper.js
├── database-init.js
├── prefs.js
├── utils.js
└── views
│ ├── background-view.js
│ ├── character-comparison-base-view.js
│ ├── character-comparison-conflict-view.js
│ ├── character-comparison-value-difference-view.js
│ ├── character-comparison-view.js
│ ├── character-favorites-view.js
│ ├── character-selector-multiple.js
│ ├── character-selector-single.js
│ ├── character-trainer-view.js
│ ├── characters-view.js
│ ├── common-ground-view.js
│ ├── favorite-button.js
│ ├── internal-conflict-view.js
│ ├── main-base-view.js
│ ├── main-view-selector.js
│ ├── main-window.js
│ ├── value-list-view.js
│ └── welcome-window.js
├── loading-status.html
├── main-window.html
├── main.js
├── migrations
├── 001-setup.js
└── 002-favorites.js
├── package-lock.json
├── package.json
├── update.html
└── welcome-window.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # VSCode directory
61 | .vscode
62 |
63 | # Database file.
64 | characterizer.sqlite
65 |
66 | # built app
67 | dist/
68 |
69 | yarn.lock
70 |
--------------------------------------------------------------------------------
/DEVELOPERS.md:
--------------------------------------------------------------------------------
1 | ### Building for Linux and Windows with Docker for Mac
2 |
3 | Install [Docker for Mac](https://www.docker.com/docker-mac), then:
4 |
5 | ```
6 | git clone git@github.com:wonderunit/characterizer.git characterizer
7 | docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine
8 | cd characterizer
9 | npm install
10 | npm prune
11 | npm run dist:win
12 | ```
13 |
14 |
15 | ## Testing Auto-Update
16 |
17 | - `latest.yml` (for Windows) and `latest-mac.yml` (for Mac) must be published in the GitHub Release along with the other release files.
18 |
19 | To test auto update, create a file called `dev-app-update.yml` in the root source folder with contents like:
20 |
21 | ```
22 | owner: wonderunit
23 | repo: storyboarder
24 | provider: github
25 | ```
26 |
27 | ... then decrement the current version in `package.json`. You will be notified that the app is out-of-date (although in dev mode, when unsigned, Squirrel.Mac will throw `Error: Could not get code signature for running application`)
28 |
29 | ## Publishing
30 |
31 | Be sure to have a local `GH_TOKEN` environment variable. For the value:
32 |
33 | Go to: https://github.com/settings/tokens/new
34 | Token description: Storyboarder Publishing
35 | Select scopes: [x] public_repo
36 |
37 | Create a Draft in GitHub Releases as the target for publishing
38 |
39 | Then, publish:
40 |
41 | GH_TOKEN={...} npm run dist:mac -- --publish onTagOrDraft
42 | GH_TOKEN={...} npm run dist:win -- --publish onTagOrDraft
43 | GH_TOKEN={...} npm run dist:linux -- --publish onTagOrDraft
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # characterizer
2 | The best app to train character values and generate conflicts, dilemmas, and common ground between characters.
3 |
--------------------------------------------------------------------------------
/auto-updater.js:
--------------------------------------------------------------------------------
1 | // REFERENCE
2 | // https://github.com/iffy/electron-updater-example/blob/master/main.js
3 | // https://github.com/wulkano/kap/blob/b326a5a398affb3652650ddc70d3a95724e755db/app/src/main/auto-updater.js
4 |
5 | const { BrowserWindow, dialog, app } = electron = require('electron')
6 |
7 | const autoUpdater = require('electron-updater').autoUpdater
8 |
9 | const init = () => {
10 | // autoUpdater.on('checking-for-update', () => {
11 | // dialog.showMessageBox(null, { message: 'Checking for update...' })
12 | // })
13 | autoUpdater.on('update-available', (ev, info) => {
14 | dialog.showMessageBox(
15 | null,
16 | {
17 | type: 'question',
18 | message: `An update is available to version ${ev.version}. Update now? There will be a short delay while we download the update and install it for you.`,
19 | buttons: ['Later', 'Download and Install Now']
20 | },
21 | index => {
22 | if (index) {
23 | // On Windows, this causes an error. Skipping for now.
24 | // BrowserWindow.getAllWindows().forEach(w => w.close())
25 |
26 | let win
27 | win = new BrowserWindow({
28 | width: 600,
29 | height: 720,
30 | show: false,
31 | center: true,
32 | resizable: false,
33 | backgroundColor: '#E5E5E5',
34 | webPreferences: {
35 | devTools: true
36 | }
37 | })
38 | win.on('closed', () => {
39 | win = null
40 | })
41 | win.loadURL(`file://${__dirname}/update.html`)
42 | win.once('ready-to-show', () => {
43 | win.webContents.send('release-notes', ev.releaseNotes)
44 | win.show()
45 | })
46 |
47 | autoUpdater.on('download-progress', (progressObj) => {
48 | win.webContents.send('progress', progressObj)
49 | })
50 |
51 | autoUpdater.on('update-downloaded', (ev, info) => {
52 | dialog.showMessageBox(null, { message: 'Update downloaded; will install in 5 seconds' })
53 | // Wait 5 seconds, then quit and install
54 | // In your application, you don't need to wait 5 seconds.
55 | // You could call autoUpdater.quitAndInstall(); immediately
56 | setTimeout(function() {
57 | autoUpdater.quitAndInstall()
58 | }, 5000)
59 | })
60 |
61 | // fail gracelessly if we can't update properly
62 | autoUpdater.on('error', (ev, err) => {
63 | dialog.showMessageBox(null, { message: 'Update failed. Quitting.' })
64 | win.close()
65 | app.quit()
66 | })
67 |
68 | // Download and Install
69 | autoUpdater.downloadUpdate()
70 | }
71 | }
72 | )
73 | })
74 | // autoUpdater.on('update-not-available', (ev, info) => {
75 | // dialog.showMessageBox(null, { message: 'Update not available.' })
76 | // })
77 | autoUpdater.on('error', (ev, err) => {
78 | console.error('Error in auto-updater.')
79 | // dialog.showMessageBox(null, { message: 'Error in auto-updater.' })
80 | })
81 |
82 | autoUpdater.autoDownload = false
83 | autoUpdater.checkForUpdates()
84 | }
85 |
86 | exports.init = init
87 |
--------------------------------------------------------------------------------
/build/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wonderunit/characterizer/3c2be13aa09534f68f5598e250e14fbe0e4b5448/build/background.png
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wonderunit/characterizer/3c2be13aa09534f68f5598e250e14fbe0e4b5448/build/icon.icns
--------------------------------------------------------------------------------
/build/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wonderunit/characterizer/3c2be13aa09534f68f5598e250e14fbe0e4b5448/build/icon.ico
--------------------------------------------------------------------------------
/build/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wonderunit/characterizer/3c2be13aa09534f68f5598e250e14fbe0e4b5448/build/icon.png
--------------------------------------------------------------------------------
/css/common.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 |
5 | .button {
6 | border-width: 1px;
7 | margin: 10px;
8 | padding: 20px;
9 | border-style: solid;
10 | border-color: #CCC;
11 | cursor: pointer;
12 | }
13 |
14 | .button-selected {
15 | background-color: #CCC;
16 | }
17 |
18 | .button:hover {
19 | background-color: #EEE;
20 | }
21 |
22 | .hidden {
23 | visibility: hidden;
24 | }
25 |
26 | .flex-wrap {
27 | display: flex;
28 | flex-wrap: wrap;
29 | }
30 |
31 | .align-items-center {
32 | align-items: center;
33 | }
34 |
35 | .text-align-right {
36 | text-align: right;
37 | }
38 |
39 | .text-align-center {
40 | text-align: center;
41 | }
--------------------------------------------------------------------------------
/css/main-window.css:
--------------------------------------------------------------------------------
1 | /** Reset **/
2 | h2 {
3 | -webkit-margin-before: 0;
4 | -webkit-margin-after: 0;
5 | }
6 |
7 | body {
8 | padding: 0;
9 | margin: 0;
10 | }
11 |
12 | /** Top Level Components */
13 |
14 | #message {
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | }
19 |
20 | #error {
21 | display: flex;
22 | align-items: center;
23 | justify-content: center;
24 | color: red;
25 | }
26 |
27 | #navigation {
28 | max-width: 20%;
29 | flex-grow: 1;
30 | height: 100vh;
31 | overflow-y: scroll;
32 | border-right: solid;
33 | border-right-width: 1px;
34 | border-right-color: #CCC;
35 | background: #ffffff;
36 | }
37 |
38 | #container {
39 | display: flex;
40 | flex-direction: row;
41 | height: 100vh;
42 | overflow: hidden;
43 | }
44 |
45 | #content-container {
46 | max-width: 80%;
47 | flex-grow: 4;
48 | padding-top: 2vh;
49 | height: 100vh;
50 | overflow: scroll;
51 | color: white;
52 | }
53 |
54 | /** Background **/
55 |
56 | .background-view {
57 | width: 100vw;
58 | height: 100vh;
59 | position: fixed;
60 | z-index: -1;
61 | transition: all;
62 | background: linear-gradient(135deg, #52edff, #0d057e)
63 | }
64 |
65 | .background-darkblue {
66 | opacity: 1;
67 | width: 100vw;
68 | height: 100vh;
69 | position: fixed;
70 | transition: all;
71 | transition-duration: 0.5s;
72 | background: linear-gradient(135deg, #0d057e, #52edff)
73 | }
74 |
75 | .hidden-background {
76 | opacity: 0;
77 | }
78 |
79 | /** Navigation */
80 |
81 | .main-view-selector-option {
82 | border-bottom-width: 1px;
83 | border-color: #CCC;
84 | border-bottom-style: solid;
85 | padding: 20px;
86 | position: relative;
87 | cursor: pointer;
88 | transition-property: all;
89 | transition-duration: 0.25s;
90 | }
91 |
92 | .main-view-selector-option:hover {
93 | background-color: #EEE;
94 | }
95 |
96 | .main-view-selector-selected {
97 | background-color: #CCC;
98 | }
99 |
100 | /************************/
101 | /** Main Content Views **/
102 | /************************/
103 |
104 | /** Base View **/
105 | .main-content-view {
106 | margin-left: 2vw;
107 | margin-right: 2vw;
108 | height: 98vh;
109 | position: relative;
110 | }
111 |
112 | /** Characters **/
113 |
114 | #characters-container {
115 |
116 | }
117 |
118 | #character-input-add-button {
119 | cursor: pointer;
120 | text-align: center;
121 | }
122 |
123 | #input-add-character-name {
124 | width: 75px;
125 | margin-bottom: 10px;
126 | }
127 |
128 | .manage-character-container div {
129 | margin-bottom: 10px;
130 | }
131 |
132 | .manage-character-container {
133 | display: flex;
134 | flex-direction: column;
135 | padding: 25px;
136 | border-style: solid;
137 | margin: 10px;
138 | border-width: 1px;
139 | }
140 |
141 | .manage-character-button {
142 | padding: 10px;
143 | background-color: #AAA;
144 | cursor: pointer;
145 | }
146 |
147 | .manage-characters-add-button-image-container {
148 | position: absolute;
149 | width: 100%;
150 | height: 100%;
151 | display: flex;
152 | flex-direction: column;
153 | justify-content: center;
154 | align-items: center;
155 | margin-left: -25px;
156 | margin-top: -25px;
157 | padding: 0;
158 | }
159 | .manage-characters-add-button-image {
160 | width: 35px;
161 | height: 35px;
162 | }
163 | .manage-characters-add-container {
164 | display: flex;
165 | flex-direction: column;
166 | padding: 25px;
167 | border-style: solid;
168 | margin: 10px;
169 | border-width: 1px;
170 | cursor: pointer;
171 | position: relative;
172 | }
173 |
174 |
175 | /** Value Training View */
176 | #character-trainer-container {
177 | height: 88vh;
178 | }
179 |
180 | #character-trainer-view-header {
181 | display: flex;
182 | flex-direction: row;
183 | align-items: center;
184 | justify-content: center;
185 | margin-top: 10vh;
186 | font-weight: normal;
187 | }
188 |
189 | .character-trainer-view-character-selector {
190 | font-size: 1.5em;
191 | font-weight: normal;
192 | text-align: right;
193 | border: none;
194 | outline: 0;
195 | color: white;
196 | -webkit-appearance: none;
197 | background: url(../images/menu-indicator.svg) no-repeat;
198 | background-size: 15px;
199 | background-position: 100%;
200 | padding-right: 25px;
201 | direction: rtl;
202 | }
203 |
204 | #character-trainer-view-header-text {
205 | margin-left: 10px;
206 | }
207 |
208 | #character-trainer-view-battle-container {
209 | outline: none;
210 | display: flex;
211 | flex-direction: row;
212 | max-width: 100%;
213 | margin-top: 10vh;
214 | justify-content: space-around;
215 | }
216 |
217 | .battle-view-timer-container {
218 | width: 100%;
219 | display: flex;
220 | }
221 |
222 | #battle-view-button-container {
223 | display: flex;
224 | margin-top: 45px;
225 | justify-content: space-around;
226 | }
227 |
228 | #character-trainer-view-center-container {
229 | display: flex;
230 | flex-direction: column;
231 | justify-content: space-around;
232 | }
233 |
234 | .battle-view-choice-button {
235 | margin: 10px;
236 | background-color: rgba(255, 255, 255, 0.2);
237 | padding: 20px;
238 | cursor: pointer;
239 | width: 24vw;
240 | height: 16vh;
241 | text-align: center;
242 | display: flex;
243 | align-items: center;
244 | justify-content: center;
245 | font-size: 2em;
246 | }
247 |
248 | #character-trainer-view-center-text {
249 | font-size: 1.5em;
250 | }
251 |
252 | .countdown-progress-bar {
253 | height: 20px;
254 | background-color: #CCC;
255 | }
256 |
257 | .battle-view-button {
258 | border-width: 1px;
259 | border-color: black;
260 | border-radius: 8px;
261 | cursor: pointer;
262 | text-align: center;
263 | }
264 |
265 | #value-list-footer {
266 | display: flex;
267 | flex-direction: row;
268 | width: 100%;
269 | justify-content: space-around;
270 | bottom: 4vh;
271 | position: absolute;
272 | }
273 |
274 | #value-list-session {
275 | display: flex;
276 | justify-content: space-around;
277 | }
278 |
279 | .battle-type-view {
280 | text-align: center;
281 | }
282 |
283 | #session-time-view {
284 | margin-left: .4em;
285 | }
286 |
287 | /** Value List */
288 |
289 | #values-view {
290 | padding-bottom: 5vh;
291 | }
292 |
293 | .value-list-container {
294 | border-width: 1px;
295 | border-color: black;
296 | margin: 10px 0px;
297 | background-color: #AAA;
298 | padding: 20px;
299 | position: relative;
300 | display: flex;
301 | flex-direction: row;
302 | }
303 |
304 | .favorite-button-container-value-list-view {
305 | cursor: pointer;
306 | z-index: 999;
307 | padding-right: 10px;
308 | max-height: 100%;
309 | width: 1em;
310 | height: 1em;
311 | }
312 |
313 | .value-list-label {
314 | position: relative;
315 | z-index: 999999;
316 | }
317 |
318 | .value-list-progress {
319 | position: absolute;
320 | top: 0;
321 | left: 0;
322 | width: 25px;
323 | background-color: #70cc70;
324 | height: 100%;
325 | float: left;
326 | z-index: 1;
327 | }
328 |
329 | /** Character Comparison View */
330 |
331 | #character-comparison-view {
332 | width: 100%;
333 | display: flex;
334 | flex-direction: row;
335 | position: relative;
336 | }
337 |
338 | .comparison-character-view-container {
339 | margin-right: 10px;
340 | }
341 |
342 | /** Character Favorites **/
343 |
344 | #favorite-pairs-holder {
345 | margin: 20px 0px 20px 0px;
346 | }
347 |
348 | #favorite-values-holder {
349 | margin: 20px 0px 20px 0px;
350 | }
351 |
352 | /** Value Difference **/
353 |
354 | #value-difference-view {
355 | margin: 10px 0px 10px 0px;
356 | border-bottom: solid;
357 | border-bottom-width: 1px;
358 | padding-bottom: 1em;
359 | width: 100%;
360 | }
361 |
362 | .value-difference-view-value {
363 | font-size: 1.5em;
364 | font-weight: bold;
365 | text-align: center;
366 | }
367 |
368 | .value-difference-comparison-view {
369 | align-items: center;
370 | }
371 |
372 | .value-difference-view-score-container {
373 | font-size: 0.6em;
374 | align-items: center;
375 | display: flex;
376 | flex-direction: column;
377 | margin-top: 1em;
378 | }
379 |
380 | /** Conflict Comparison **/
381 |
382 |
383 | /** Common Ground **/
384 |
385 | .common-ground-view-container {
386 | display: flex;
387 | flex-direction: column;
388 | justify-content: center;
389 | align-items: center;
390 | padding: 20px 0px;
391 | border-bottom-style: solid;
392 | border-bottom-width: 1px;
393 | width: 75vw;
394 | }
395 |
396 | .common-ground-view-name {
397 | font-size: 1.5em;
398 | font-weight: bold;
399 | }
400 |
401 | .common-ground-view-scores {
402 | margin-top: 10px;
403 | font-size: 0.6em;
404 | }
405 |
406 | /** Character Selector **/
407 | .character-selector-button {
408 | margin: 10px 10px 0px 0px;
409 | border-width: 1px;
410 | padding: 20px;
411 | border-style: solid;
412 | border-color: #CCC;
413 | cursor: pointer;
414 | }
415 |
416 | /** Generics */
417 |
418 | .comparison-view-selector {
419 | margin-top: 5vh;
420 | }
421 |
422 | .favorite-button-container {
423 | width: 20px;
424 | height: 20px;
425 | cursor: pointer;
426 | z-index: 999;
427 | }
428 |
429 |
430 | .character-selector-button-selected {
431 | background-color: #CCC;
432 | }
433 |
434 | .character-selector-button:hover {
435 | background-color: #EEE;
436 | }
437 |
438 | .favorites-filter-button {
439 | padding: 20px 0px;
440 | cursor: pointer;
441 | }
442 |
443 | .comparison-view-conflicts-header-container {
444 | display: flex;
445 | align-items: center;
446 | justify-content: space-around;
447 | }
448 |
449 | .comparison-view-conflict-container {
450 | display: flex;
451 | align-items: center;
452 | border-bottom-style: solid;
453 | padding: 10px 0px;
454 | border-bottom-width: thin;
455 | }
456 |
457 | .comparison-view-value-view {
458 | flex: 4;
459 | font-size: 1.5em;
460 | font-weight: bold;
461 | }
462 |
463 | .conflict-container-center {
464 | flex: 1;
465 | align-items: center;
466 | display: flex;
467 | flex-direction: column;
468 | }
469 |
470 | .comparison-view {
471 | width: 100%;
472 | display: flex;
473 | flex-direction: column;
474 | position: relative;
475 | margin: 5vh 0vh;
476 | }
--------------------------------------------------------------------------------
/css/welcome-window.css:
--------------------------------------------------------------------------------
1 | .button {
2 | cursor: pointer;
3 | }
4 |
5 | #button-container {
6 | display: flex;
7 | flex-direction: row;
8 | width: 100%;
9 | align-items: center;
10 | }
11 |
12 | .recent-documents-header {
13 | font-size: 15px;
14 | opacity: 0.5;
15 | color: #3A3A3A;
16 | }
17 |
18 | .recent-documents-list {
19 | border-bottom-style: solid;
20 | border-bottom-color: #CCC;
21 | border-bottom-width: 1px;
22 | }
23 |
24 | .recent-document {
25 | max-width: 300px;
26 | }
--------------------------------------------------------------------------------
/data/values-test.txt:
--------------------------------------------------------------------------------
1 | Abundance
2 | Balance
3 | Challenge
4 | Democracy
5 | Eating
6 | Fame
7 | Generosity
8 | Harmony
9 | Innovation
10 | Justice
11 | Kindness
12 | Love
13 | Making Love
14 | Nature
15 | Optimism
16 | Passion
17 | Quality
18 | Recognition
19 | Success
20 | Tradition
21 | Understanding
22 | Variety
23 | Vitality
24 | Wealth
25 | You
--------------------------------------------------------------------------------
/data/values.txt:
--------------------------------------------------------------------------------
1 | A good story
2 | A lost love
3 | A mystery
4 | A special hobby
5 | A special thing
6 | A wonderful journey
7 | Abundance
8 | Accepting help
9 | Accomplishment
10 | Accountability
11 | Accuracy
12 | Achievement
13 | Adventure
14 | Agency
15 | An easy journey
16 | An easy love
17 | An easy score
18 | Analyzing
19 | Animals
20 | Appreciation of objects
21 | Approval
22 | Approval of a close friend
23 | Approval of family
24 | Approval of friends
25 | Approval of parents
26 | Approval of partner
27 | Approval of sibling
28 | Approval of spouse
29 | Art
30 | Authenticity
31 | Authority
32 | Autonomy
33 | Balance
34 | Beauty
35 | Being able to go anywhere
36 | Being alone
37 | Being at parties
38 | Being at workshops
39 | Being in a community
40 | Being in a social place
41 | Being in an audience
42 | Being in an intimate space of togetherness
43 | Being in associations
44 | Being in school
45 | Being on time
46 | Being right
47 | Being someone who matters
48 | Being with family
49 | Belief
50 | Belonging to neighborhoods
51 | Benevolence
52 | Boating
53 | Boldness
54 | Breaking the rules for the right reason
55 | Building
56 | Challenge
57 | Change
58 | Citizenship
59 | Clarity
60 | Cleanliness, orderliness
61 | Collaboration
62 | Collecting
63 | Comfort
64 | Commitment
65 | Committing oneself
66 | Common sense
67 | Communication
68 | Community
69 | Compassion
70 | Competence
71 | Competency
72 | Competing
73 | Competition
74 | Composing
75 | Concern for others
76 | Confidence
77 | Conformity
78 | Connection
79 | Conservation
80 | Consistency
81 | Contribution
82 | Control
83 | Conventional success
84 | Cooking
85 | Cooperation
86 | Coordination
87 | Courage
88 | Creativity
89 | Credibility
90 | Curiosity
91 | Dancing
92 | Day Dreaming
93 | Decisiveness
94 | Defending yourself
95 | Democracy
96 | Designing
97 | Determination
98 | Developing awareness
99 | Discipline
100 | Discovery
101 | Dissenting
102 | Diversity
103 | Drawing
104 | Driving
105 | Ease
106 | Eating
107 | Education
108 | Efficiency
109 | Empathy
110 | Employment
111 | Environment
112 | Equality
113 | Excellence
114 | Exercise
115 | Exploration
116 | Expressing Emotions
117 | Expressing Opinions
118 | Fairness
119 | Faith
120 | Faithfulness
121 | Fame
122 | Family
123 | Fashion
124 | Fight for what you believe
125 | Fitting in
126 | Flair
127 | Flexibility
128 | Forgiveness
129 | Freedom
130 | Friendship
131 | Frugality
132 | Fulfillment
133 | Full transparency
134 | Fun
135 | Generosity
136 | Genuineness
137 | Getting to know oneself
138 | Good will
139 | Goodness
140 | Gratitude
141 | Great Content
142 | Great design
143 | Growing
144 | Growth
145 | Guaranteed now
146 | Happiness
147 | Happiness of a close friend
148 | Happiness of family
149 | Happiness of friends
150 | Happiness of parents
151 | Happiness of partner
152 | Happiness of sibling
153 | Happiness of spouse
154 | Hard won moment of joy
155 | Hard work
156 | Harmony
157 | Having Abilities
158 | Having Customs
159 | Having Duties
160 | Having Education
161 | Having Equal Rights
162 | Having Family
163 | Having Food
164 | Having Friendships
165 | Having Fun
166 | Having Games
167 | Having Literature
168 | Having Norms
169 | Having Parties
170 | Having Peace of mind
171 | Having Policies
172 | Having Relationship to nature
173 | Having Religion
174 | Having Responsibilities
175 | Having Rights
176 | Having Shelter
177 | Having Skills
178 | Having Social Security
179 | Having Teachers
180 | Having Techniques
181 | Having Values
182 | Having Work
183 | Having a place to belong to
184 | Having a place to live
185 | Having a space for expression
186 | Healing
187 | Hedonism
188 | Helping
189 | High risk path
190 | High risk shortcut
191 | Holistic Living
192 | Honesty
193 | Honor
194 | Humor
195 | Imagination
196 | Improvement
197 | Independence
198 | Individuality
199 | Infamy
200 | Influence
201 | Initiative
202 | Inner Harmony
203 | Inner peace
204 | Innovation
205 | Integrity
206 | Intelligence
207 | Intensity
208 | Internal satisfaction
209 | Interpreting
210 | Intimacy
211 | Intuition
212 | Inventiveness
213 | Investigating
214 | Investing
215 | Joy
216 | Justice
217 | Keeping the family together
218 | Kindness
219 | Knowledge
220 | Leadership
221 | Learning
222 | Love
223 | Low risk guaranteed arrival
224 | Loyalty
225 | Lust
226 | Magic
227 | Making Love
228 | Making an impact
229 | Making new friends
230 | Making peace
231 | Making real connections
232 | Manipulation
233 | Me
234 | Meaning
235 | Meaningful Work
236 | Meaningful relationships
237 | Mediating
238 | Mental health
239 | Merit
240 | Moderation
241 | Modesty
242 | Money
243 | Music
244 | Nature
245 | Nurturing
246 | Obedience
247 | Obligation to family
248 | Open-mindedness
249 | Openness
250 | Optimism
251 | Passion
252 | Patriotism
253 | Peace
254 | Peace, Non-violence
255 | Perfection
256 | Perseverance
257 | Persistence
258 | Personal Growth
259 | Personal desire
260 | Personal health
261 | Pets
262 | Planning
263 | Pleasure
264 | Poise
265 | Popularity
266 | Possession of objects
267 | Potential future
268 | Power
269 | Practicality
270 | Preservation
271 | Pride
272 | Privacy
273 | Problem solving
274 | Professionalism
275 | Progress
276 | Prosperity
277 | Punctuality
278 | Purpose
279 | Quality
280 | Quantity
281 | Random act of kindness
282 | Reading
283 | Reciprocity
284 | Recognition
285 | Regularity
286 | Relaxation
287 | Relaxing
288 | Reliability
289 | Religion
290 | Remembering
291 | Reputation
292 | Resourcefulness
293 | Respect
294 | Respect for others
295 | Responsibility
296 | Responsiveness
297 | Resting
298 | Results
299 | Rich solitude
300 | Romance
301 | Rule of Law
302 | Sacrifice
303 | Safety
304 | Safety of a close friend
305 | Safety of family
306 | Safety of friends
307 | Safety of parents
308 | Safety of partner
309 | Safety of sibling
310 | Safety of spouse
311 | Satisfying others
312 | Security
313 | Self-Respect
314 | Self-awareness
315 | Self-confidence
316 | Self-direction
317 | Self-esteem
318 | Self-expression
319 | Self-improvement
320 | Self-love
321 | Self-mastery
322 | Self-reliance
323 | Self-trust
324 | Sense of belonging
325 | Sense of history
326 | Sensuality
327 | Service
328 | Sex
329 | Sexual Intimacy
330 | Sharing
331 | Shopping
332 | Short term dreams
333 | Simplicity
334 | Sincerity
335 | Singing
336 | Skill
337 | Sleeping
338 | Social Status
339 | Social acceptance
340 | Solitude
341 | Solving Puzzles
342 | Speed
343 | Spirituality
344 | Spontaneity
345 | Sports
346 | Stability
347 | Standardization
348 | Status
349 | Stimulation
350 | Straightforwardness
351 | Strength
352 | Studying
353 | Success
354 | Superficial social fun
355 | Swimming
356 | Sympathy
357 | Systemization
358 | Taking care of
359 | Taking risks
360 | Teamwork
361 | The beaten path
362 | The memory of: Family
363 | The memory of: a special friend
364 | The memory of: a special thing
365 | The memory of: history
366 | The promise of low effort high reward
367 | The wonders of the unknown
368 | Timeliness
369 | Tolerance
370 | Tradition
371 | Tranquility
372 | Trust
373 | Trustworthiness
374 | Truth
375 | Understanding
376 | Unity
377 | Value of the team
378 | Variety
379 | Vitality
380 | Wealth
381 | Winning
382 | Wisdom
383 | Working
384 | Writing
385 | You
--------------------------------------------------------------------------------
/images/add-character-plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/favorite-selected.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/favorite-unselected.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icons.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wonderunit/characterizer/3c2be13aa09534f68f5598e250e14fbe0e4b5448/images/icons.ai
--------------------------------------------------------------------------------
/images/menu-indicator.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/battle-pairer.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events').EventEmitter
2 | const {remote} = require('electron')
3 | const knex = remote.getGlobal('knex')
4 | const MIN_BATTTLE_COUNT = 2
5 | const TOP_PERCENT_CUTOFF = .1
6 | const TOP_RANK_CUTOFF = 20
7 |
8 | module.exports = class BattlePairer extends EventEmitter {
9 | constructor(properties) {
10 | super()
11 | if(!properties.characterID) throw new Error("Missing characterID")
12 | this.characterID = properties.characterID
13 |
14 | if(!properties.choices) throw new Error("Missing choices")
15 | this.choices = properties.choices
16 |
17 | if(!properties.values) throw new Error("Missing values")
18 | this.values = properties.values
19 |
20 | if(!properties.battlePairs) throw new Error("Missing battlePairs")
21 | this.previousBattlePairs = properties.battlePairs
22 |
23 | if(!properties.favorites) throw new Error("Missing favorites")
24 | this.favorites = properties.favorites
25 |
26 | this.battleModes = [
27 | {
28 | "function": this.getTopPercentileBattle.bind(this),
29 | "weight": 40
30 | },
31 | {
32 | "function": this.getTopPercentileAndRandomBattle.bind(this),
33 | "weight": 10
34 | },
35 | {
36 | "function": this.getTopRankBattle.bind(this),
37 | "weight": 40
38 | },
39 | {
40 | "function": this.getRandomBattle.bind(this),
41 | "weight": 10
42 | },
43 | {
44 | "function": this.getFavoritesAndTopPercentileBattle.bind(this),
45 | "weight": 40
46 | },
47 | {
48 | "function": this.getFavoritesAndRandomBattle.bind(this),
49 | "weight": 40
50 | },
51 | {
52 | "function": this.getFavoritesBattle.bind(this),
53 | "weight": 40
54 | }
55 | ]
56 |
57 | this.valuesMap = properties.valuesMap
58 |
59 | this.valuesPercentRank = []
60 |
61 | // keeps the values separated by battle count.
62 | this.battleChoiceTiers = []
63 | for(var i = 0; i {
70 | let self = this
71 | for(let i=0; i 1) {
86 | if(this.isDuplicate(result)) {
87 | console.log(`Found duplicate!`)
88 | return this.getBattle(++rechooseCount)
89 | }
90 | console.log(`getFillDataBattle`)
91 | this.emit('battle-type-try', "Fill Data Battle")
92 | } else {
93 | let battleMode = this.chooseRandomBattleMode()
94 | result = battleMode()
95 | }
96 | if(!result) {
97 | console.log(`Didn't find valid candidates for battle mode.`)
98 | return this.getBattle(rechooseCount)
99 | }
100 | // TODO: check for the case where all matches have played out, and message it.
101 | // For now, let's just let it try to re-choose
102 | if(this.isDuplicate(result) && rechooseCount < 4) {
103 | console.log(`Found duplicate!`)
104 | return this.getBattle(++rechooseCount)
105 | }
106 |
107 | let randomizedResult = []
108 | let firstIndex = Math.floor(Math.random()*2)
109 | if(firstIndex === 0) {
110 | randomizedResult = [result[0], result[1]]
111 | } else {
112 | randomizedResult = [result[1], result[0]]
113 | }
114 | return randomizedResult
115 | }
116 |
117 | isDuplicate(battlePair) {
118 | let result = false
119 | if(!battlePair || battlePair.length < 2) {
120 | throw new Error("checkDuplicate expects an array of length >= 2")
121 | }
122 | var self = this
123 | var checkForPair = (contender1, contender2) => {
124 | if(!contender1 || !contender2) {
125 | throw new Error("missing contender")
126 | }
127 | if(self.previousBattlePairs.hasOwnProperty(contender1.id)) {
128 | let contender1Pairs = self.previousBattlePairs[contender1.id]
129 | if(contender1Pairs.hasOwnProperty(contender2.id) && contender1Pairs[contender2.id] > 0) {
130 | return true
131 | }
132 | }
133 | return false
134 | }
135 |
136 | result = checkForPair(battlePair[0], battlePair[1]) || checkForPair(battlePair[1], battlePair[0])
137 | return result
138 | }
139 |
140 | onBattleOutcome(battleOutcome) {
141 | let i = 0;
142 | let isWinnerFound = false
143 | let isLoserFound = false
144 | let self = this
145 | function moveToNextBucket(battleChoiceTier, i, j) {
146 | let target = battleChoiceTier.splice(j, 1)[0]
147 | if(i+1 {
7 | return new Promise((fulfill, reject)=>{
8 | knex('Values').where({name: value}).select('id')
9 | .then(result => {
10 | // check for existing value
11 | if(!result || result.length === 0) {
12 | knex('Values').insert({name: value, uuid: generateUUID()})
13 | .then(value => {
14 | // console.log(`Added Value: ${JSON.stringify(value)}`)
15 | fulfill()
16 | })
17 | } else {
18 | fulfill()
19 | }
20 | })
21 | .catch(reject)
22 | })
23 | })
24 | return Promise.all(writes)
25 | }
26 |
27 | function generateUUID() {
28 | return Math.floor(Math.random() * Math.pow(2, 32)).toString(32)
29 | }
30 |
31 | module.exports = {
32 | seedDB
33 | }
34 |
--------------------------------------------------------------------------------
/js/prefs.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const { app } = require('electron')
4 | const os = require("os");
5 |
6 | const pkg = require('../package.json')
7 | const util = require('./utils.js') // for Object.equals
8 |
9 | // TODO pref specifics shouldnt be in this module.
10 | const prefFile = path.join(app.getPath('userData'), 'characterizer-preferences.json')
11 |
12 | const defaultPrefs = {
13 | version: pkg.version,
14 | skipTimerLength: 10000
15 | }
16 |
17 | let prefs
18 |
19 | const load = () => {
20 | try {
21 | // load existing prefs
22 | // console.log("READING FROM DISK")
23 | prefs = JSON.parse(fs.readFileSync(prefFile))
24 | } catch (e) {
25 | prefs = defaultPrefs
26 | try {
27 | savePrefs(prefs)
28 | } catch (e) {
29 | //console.log(e)
30 | }
31 | }
32 | }
33 |
34 | const savePrefs = (newPref) => {
35 | // console.log('SAVEPREFS')
36 | if (!newPref) return
37 | if (Object.equals(newPref,prefs)) {
38 | // console.log("IM THE SAME!!!!")
39 | } else {
40 | prefs = newPref
41 | // console.log("SAVING TO DISK")
42 | fs.writeFileSync(prefFile, JSON.stringify(newPref, null, 2))
43 | }
44 | }
45 |
46 | const set = (keyPath, value, sync) => {
47 | // console.log('SETTING')
48 | const keys = keyPath.split(/\./)
49 | let obj = prefs
50 | while (keys.length > 1) {
51 | const key = keys.shift()
52 | if (!Object.prototype.hasOwnProperty.call(obj, key)) {
53 | obj[key] = {}
54 | }
55 | obj = obj[key]
56 | }
57 | let keyProp = keys.shift()
58 | let prevValue = obj[keyProp]
59 | if (Object.equals(prevValue,value)) {
60 | console.log("IM THE SAME!!!!")
61 | } else {
62 | obj[keyProp] = value
63 | console.log("SAVING TO DISK")
64 | console.log(prefs)
65 | if (sync) {
66 | fs.writeFileSync(prefFile, JSON.stringify(prefs, null, 2))
67 | } else {
68 | fs.writeFile(prefFile, JSON.stringify(prefs, null, 2), (err) => {
69 | console.log("SAVED ASYNC")
70 | })
71 | }
72 | }
73 | }
74 |
75 | const getPrefs = (from) => {
76 | // console.log("GETTING PREFS!!!", from)
77 | return prefs
78 | }
79 |
80 | const migrate = () => {
81 | prefs = Object.assign({}, defaultPrefs, prefs)
82 | prefs.version = defaultPrefs.version
83 | }
84 |
85 | const init = () => {
86 | //console.log("I AM INIT")
87 | load()
88 | if (prefs.version !== defaultPrefs.version) {
89 | migrate()
90 | savePrefs(prefs)
91 | }
92 | }
93 |
94 | init()
95 |
96 | module.exports = {
97 | savePrefs,
98 | getPrefs,
99 | set
100 | }
101 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | Object.equals = function( x, y ) {
2 | if ( x === y ) return true;
3 | // if both x and y are null or undefined and exactly the same
4 |
5 | if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
6 | // if they are not strictly equal, they both need to be Objects
7 |
8 | if ( x.constructor !== y.constructor ) return false;
9 | // they must have the exact same prototype chain, the closest we can do is
10 | // test there constructor.
11 |
12 | for ( var p in x ) {
13 | if ( ! x.hasOwnProperty( p ) ) continue;
14 | // other properties were tested using x.constructor === y.constructor
15 |
16 | if ( ! y.hasOwnProperty( p ) ) return false;
17 | // allows to compare x[ p ] and y[ p ] when set to undefined
18 |
19 | if ( x[ p ] === y[ p ] ) continue;
20 | // if they have the same strict value or identity then they are equal
21 |
22 | if ( typeof( x[ p ] ) !== "object" ) return false;
23 | // Numbers, Strings, Functions, Booleans must be strictly equal
24 |
25 | if ( ! Object.equals( x[ p ], y[ p ] ) ) return false;
26 | // Objects and Arrays must be tested recursively
27 | }
28 |
29 | for ( p in y ) {
30 | if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
31 | // allows x[ p ] to be set to undefined
32 | }
33 | return true;
34 | }
35 |
36 | function convertMS(ms) {
37 | var d, h, m, s
38 | s = Math.floor(ms / 1000)
39 | m = Math.floor(s / 60)
40 | s = s % 60
41 | h = Math.floor(m / 60)
42 | m = m % 60
43 | d = Math.floor(h / 24)
44 | h = h % 24
45 | return { d: d, h: h, m: m, s: s }
46 | }
47 |
48 | function getFriendlyMS(ms) {
49 | let converted = convertMS(ms)
50 | converted.h = converted.h < 10 ? '0'+converted.h : converted.h
51 | converted.m = converted.m < 10 ? '0'+converted.m : converted.m
52 | converted.s = converted.s < 10 ? '0'+converted.s : converted.s
53 | return converted
54 | }
55 |
56 | function semiRandomShuffle(array, factor) {
57 | let f=factor
58 | return array.map((e,i)=>{return [e,i+Math.random()*(f*2-f)]}).sort((a,b)=>{return a[1]-b[1]}).map(e=>{return e[0]})
59 | }
60 |
61 | // via https://stackoverflow.com/questions/5723154/truncate-a-string-in-the-middle-with-javascript
62 | // https://stackoverflow.com/questions/831552/ellipsis-in-the-middle-of-a-text-mac-style/36470401#36470401
63 | const truncateMiddle = (string, maxLength = 30, separator = '…') => {
64 | if (!string) return string
65 | if (maxLength < 1) return string
66 | if (string.length <= maxLength) return string
67 | if (maxLength == 1) return string.substring(0, 1) + separator
68 |
69 | var midpoint = Math.ceil(string.length / 2)
70 | var toremove = string.length - maxLength
71 | var lstrip = Math.ceil(toremove / 2)
72 | var rstrip = toremove - lstrip
73 |
74 | return string.substring(0, midpoint - lstrip) +
75 | separator +
76 | string.substring(midpoint + rstrip)
77 | }
78 |
79 | function checkObjectPath(keys, object) {
80 | let curObject = object
81 | for(let key of keys) {
82 | if(!curObject[key]) {
83 | return false
84 | }
85 | curObject = curObject[key]
86 | }
87 | return true
88 | }
89 |
90 | module.exports = {
91 | convertMS,
92 | getFriendlyMS,
93 | semiRandomShuffle,
94 | checkObjectPath,
95 | truncateMiddle
96 | }
--------------------------------------------------------------------------------
/js/views/background-view.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events').EventEmitter
2 |
3 | module.exports = class MainBaseView extends EventEmitter {
4 | constructor(properties) {
5 | super()
6 | this.isLight = true
7 |
8 | this.root = document.createElement("div")
9 | this.root.setAttribute("id", "background-view")
10 | this.root.classList.add("background-view")
11 |
12 | this.darkBackground = document.createElement("div")
13 | this.darkBackground.classList.add("background-darkblue")
14 | this.root.appendChild(this.darkBackground)
15 | }
16 |
17 | getView() {
18 | return this.root
19 | }
20 |
21 | nextBackground() {
22 | this.isLight = !this.isLight
23 | if(this.isLight) {
24 | this.darkBackground.classList.remove("hidden-background")
25 | } else {
26 | this.darkBackground.classList.add("hidden-background")
27 | }
28 |
29 | }
30 |
31 | updateView() {
32 |
33 | }
34 |
35 | viewWillDisappear() {
36 |
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/js/views/character-comparison-base-view.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 | const CharacterView = require('./character-selector-multiple.js')
3 |
4 | module.exports = class CharacterComparisonBaseView extends MainBaseView {
5 | constructor(properties) {
6 | super(properties)
7 | this.selectionsAllowedCount = 2 // the allowed number of selections
8 | this.selectedCharacters = this.getSelectedCharacters()
9 | this.valuesViewType = "table"
10 |
11 | this.characterView = new CharacterView(properties)
12 | this.root.appendChild(this.characterView.getView())
13 |
14 | this.charactersMap = {}
15 | this.getCharacters()
16 | .then(inCharacters => {
17 | this.characters = inCharacters
18 | this.characterView.on('select-character', data => {
19 | this.onSelectCharacter(data.characterID)
20 | })
21 | this.characterView.on('add-character', data => {
22 | this.emit('add-character', data)
23 | })
24 | this.characterView.updateView()
25 | for(let character of inCharacters) {
26 | this.charactersMap[character.id] = character
27 | if(this.selectedCharacters.length<2 && this.selectedCharacters.indexOf(character) < 0) {
28 | this.onSelectCharacter(character.id)
29 | }
30 | }
31 | })
32 | .catch(console.error)
33 | }
34 |
35 | onSelectCharacter(characterID) {
36 | let isExisting = false
37 | for(let i = 0; i this.selectionsAllowedCount) {
56 | this.selectedCharacters.splice(0, this.selectedCharacters.length - this.selectionsAllowedCount)
57 | }
58 |
59 | this.emit('selected-characters', this.selectedCharacters)
60 |
61 | this.characterView.updateView()
62 | this.updateView()
63 | }
64 |
65 | getSelectedCharacterValues() {
66 | let characterValuePromises = []
67 | for(let aCharacter of this.selectedCharacters) {
68 | characterValuePromises.push(this.getCharacterValues(aCharacter.id))
69 | }
70 |
71 | return Promise.all(characterValuePromises)
72 | }
73 |
74 | updateView() {
75 |
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/js/views/character-comparison-conflict-view.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils.js')
2 | const CharacterComparisonBaseView = require('./character-comparison-base-view.js')
3 | const FavoriteButton = require('./favorite-button.js')
4 | const { semiRandomShuffle } = require('../utils.js')
5 | const NUM_COMPARISON_ITEMS = 30
6 | const RANDOM_SHUFFLE_FACTOR = 4
7 |
8 | module.exports = class CharacterComparisonConflictView extends CharacterComparisonBaseView {
9 | constructor(properties) {
10 | super(properties)
11 | this.viewType = "all"
12 |
13 | this.viewTypeSelector = document.createElement("select")
14 | this.viewTypeSelector.classList.add("comparison-view-selector")
15 | for(let type of ["all", "favorites, paired", "favorited pairs"]) {
16 | let option = document.createElement("option")
17 | option.setAttribute("value", type)
18 | option.innerHTML = type
19 | this.viewTypeSelector.appendChild(option)
20 | }
21 | this.viewTypeSelector.addEventListener('change', (event)=>{
22 | this.viewType = event.target.value
23 | this.updateView()
24 | })
25 | this.root.appendChild(this.viewTypeSelector)
26 |
27 | this.comparisonView = document.createElement("div")
28 | this.comparisonView.classList.add("comparison-view")
29 | this.root.appendChild(this.comparisonView)
30 | this.updateView()
31 | }
32 |
33 | updateView() {
34 | this.comparisonView.innerHTML = ``
35 | let headersContainer = document.createElement("div")
36 | headersContainer.classList.add("comparison-view-conflicts-header-container")
37 | this.comparisonView.appendChild(headersContainer)
38 |
39 | for(let i = 0; i 0) {
41 | let vsView = document.createElement("div")
42 | vsView.classList.add("text-align-center")
43 | vsView.innerHTML = `vs`
44 | headersContainer.appendChild(vsView)
45 | }
46 | let characterName = document.createElement("h2")
47 | characterName.classList.add("comparison-view-conflicts-header")
48 | characterName.innerHTML = this.selectedCharacters[i].name
49 | headersContainer.appendChild(characterName)
50 | }
51 |
52 | let conflictContainer
53 | switch(this.viewType) {
54 | case "favorites, paired":
55 | conflictContainer = this.getFavoriteValuesView()
56 | break
57 | case "favorited pairs":
58 | conflictContainer = this.getFavoritePairedValuesView()
59 | break
60 | case "all":
61 | default:
62 | conflictContainer = this.getValuesView()
63 | break
64 | }
65 | this.comparisonView.appendChild(conflictContainer)
66 | }
67 |
68 | getValuesView() {
69 | let result = document.createElement("div")
70 |
71 | this.getSelectedCharacterValues()
72 | .then(inCharacterValueResults => {
73 | if(inCharacterValueResults.length < 2) {
74 | return
75 | }
76 |
77 | let characterValueResults = []
78 | for(let i = 0; i < inCharacterValueResults.length; i++) {
79 | let values = inCharacterValueResults[i].slice(0, NUM_COMPARISON_ITEMS)
80 | characterValueResults.push(semiRandomShuffle(values, RANDOM_SHUFFLE_FACTOR))
81 | }
82 |
83 | for(let valueIndex = 0; valueIndex < NUM_COMPARISON_ITEMS; valueIndex++) {
84 |
85 | var self = this
86 | let favButton = new FavoriteButton()
87 |
88 | // we use this in the add favorite event.
89 | let favoriteData = {}
90 | // we use this to check if the favorite pair exists.
91 | let favoritesPaths = []
92 |
93 | let character1Values = characterValueResults[0]
94 | let value1 = character1Values[valueIndex]
95 | let character2Values = characterValueResults[1]
96 | let value2 = character2Values[valueIndex]
97 |
98 | let conflictContainer = this.getComparisonView(value1, value2)
99 |
100 | let isFavorite = false
101 | if(favoritesPaths.length > 1) {
102 | isFavorite = utils.checkObjectPath(favoritesPaths[0].concat(favoritesPaths[1]), this.valueComparisonFavorites)
103 | || utils.checkObjectPath(favoritesPaths[1].concat(favoritesPaths[0]), this.valueComparisonFavorites)
104 | }
105 | if(isFavorite) {
106 | favButton.setChecked(true)
107 | favButton.setEnabled(false)
108 | } else {
109 | var self = this
110 | favButton.setHandler(function(event) {
111 | self.emit('add-comparison-favorite', favoriteData)
112 | favButton.setChecked(true)
113 | favButton.setEnabled(false)
114 | })
115 | favButton.setChecked(false)
116 | favButton.setEnabled(true)
117 | }
118 |
119 | result.appendChild(conflictContainer)
120 | }
121 | })
122 | .catch(console.error)
123 |
124 | return result
125 | }
126 |
127 | getFavoriteValuesView() {
128 | let result = document.createElement("div")
129 | let character1ID = this.selectedCharacters[0].id
130 | let character2ID = this.selectedCharacters[1].id
131 | let favoritePairs = []
132 | Promise.all([
133 | this.getCharacterValueFavorites(character1ID),
134 | this.getCharacterValueFavorites(character2ID),
135 | this.getCharacterValuesMap(character1ID),
136 | this.getCharacterValuesMap(character2ID)
137 | ]).then(results => {
138 | let characterValueFavorites1 = results[0].values
139 | let characterValueFavorites2 = results[1].values
140 | let characterValueMap1 = results[2]
141 | let characterValueMap2 = results[3]
142 |
143 | let characterValues1 = []
144 | for(let characterValueFavorite of characterValueFavorites1) {
145 | characterValues1.push(characterValueMap1[characterValueFavorite])
146 | }
147 | characterValues1.sort((a, b) => {
148 | return b.score - a.score
149 | })
150 |
151 | let characterValues2 = []
152 | for(let characterValueFavorite of characterValueFavorites2) {
153 | characterValues2.push(characterValueMap1[characterValueFavorite])
154 | }
155 | characterValues2.sort((a, b) => {
156 | return b.score - a.score
157 | })
158 |
159 | let favoritePairLength = Math.min(characterValues1.length, characterValues2.length)
160 | let favoritePairs = []
161 | for(let i = 0; i {
183 | let characterValueMap1 = results[0]
184 | let characterValueMap2 = results[1]
185 |
186 | let favoritePairs = []
187 | if(this.characterComparisonFavorites[character1ID]
188 | && this.characterComparisonFavorites[character1ID][character2ID]) {
189 |
190 | favoritePairs = this.characterComparisonFavorites[character1ID][character2ID]
191 | }
192 |
193 | for(let j = 0; j{
239 | let name = this.valuesMap[value.valueID].name
240 | let nameView = document.createElement("div")
241 | nameView.classList.add("comparison-view-value-view")
242 | nameView.innerHTML = name
243 | return nameView
244 | }
245 |
246 | let nameView1 = getValueView(value1)
247 | nameView1.classList.add("text-align-right")
248 | conflictContainer.appendChild(nameView1)
249 |
250 | let centerContainer = document.createElement("div")
251 | centerContainer.classList.add('conflict-container-center')
252 | conflictContainer.appendChild(centerContainer)
253 |
254 | centerContainer.appendChild(favButton.getView())
255 | let vsView = document.createElement("div")
256 | vsView.innerHTML = `vs`
257 | centerContainer.appendChild(vsView)
258 |
259 | let nameView2 = getValueView(value2)
260 | conflictContainer.appendChild(nameView2)
261 |
262 | return conflictContainer
263 | }
264 |
265 |
266 | }
267 |
268 |
--------------------------------------------------------------------------------
/js/views/character-comparison-value-difference-view.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils.js')
2 | const CharacterComparisonBaseView = require('./character-comparison-base-view.js')
3 | const FavoriteButton = require('./favorite-button.js')
4 |
5 | module.exports = class CharacterComparisonValueDifferencesView extends CharacterComparisonBaseView {
6 | constructor(properties) {
7 | super(properties)
8 |
9 | this.comparisonView = document.createElement("div")
10 | this.comparisonView.classList.add("comparison-view")
11 | this.comparisonView.classList.add("align-items-center")
12 | this.root.appendChild(this.comparisonView)
13 | this.updateView()
14 | }
15 |
16 | updateView() {
17 | this.comparisonView.innerHTML = ``
18 |
19 | if(this.selectedCharacters.length < 2) {
20 | return
21 | }
22 | this.getSelectedCharacterValues()
23 | .then(charactersValues => {
24 |
25 | let valuesGrouped = []
26 | for(let curCharacterValue of charactersValues[0]) {
27 | let groupID = curCharacterValue.valueID
28 | let group = {
29 | groupID: groupID
30 | }
31 | let groupValues = [curCharacterValue]
32 |
33 | let max = curCharacterValue.score, min = curCharacterValue.score, diff = 0
34 | for(let i = 1; i max) {
38 | max = characterValue.score
39 | }
40 | if(characterValue.score < min) {
41 | min = characterValue.score
42 | }
43 | group.diff = max - min
44 | groupValues.push(characterValue)
45 | break
46 | }
47 |
48 | }
49 | }
50 | group.values = groupValues
51 | valuesGrouped.push(group)
52 | }
53 |
54 | valuesGrouped.sort((a, b) => {
55 | return b.diff - a.diff
56 | })
57 |
58 | for(let valueGrouped of valuesGrouped) {
59 | let view = document.createElement('div')
60 | view.setAttribute("id", "value-difference-view")
61 |
62 | let valueView = document.createElement('div')
63 | valueView.classList.add("value-difference-view-value")
64 | valueView.innerHTML = `${this.valuesMap[valueGrouped.groupID].name}`
65 | view.appendChild(valueView)
66 |
67 | let scoreContainer = document.createElement("div")
68 | scoreContainer.classList.add("value-difference-view-score-container")
69 | view.appendChild(scoreContainer)
70 |
71 | var self = this
72 | let favButton = new FavoriteButton()
73 | scoreContainer.appendChild(favButton.getView())
74 |
75 | let scoreView = document.createElement('div')
76 | scoreView.innerHTML = `Difference: ${valueGrouped.diff}`
77 | scoreContainer.appendChild(scoreView)
78 |
79 | let index = 0
80 | let favoriteData = {}
81 | let favoritesPaths = []
82 | for(let characterValue of valueGrouped.values) {
83 | let characterScoreView = document.createElement('div')
84 | characterScoreView.innerHTML = `${this.selectedCharacters[index].name} score: ${characterValue.score}`
85 | scoreContainer.appendChild(characterScoreView)
86 | favoriteData[`character${index+1}ID`] = characterValue.characterID
87 | favoriteData[`value${index+1}ID`] = characterValue.valueID
88 | favoritesPaths.push([characterValue.characterID, characterValue.valueID])
89 | index++
90 | }
91 |
92 | let isFavorite = false
93 | if(favoritesPaths.length > 1) {
94 | isFavorite = utils.checkObjectPath(favoritesPaths[0].concat(favoritesPaths[1]), this.valueComparisonFavorites)
95 | || utils.checkObjectPath(favoritesPaths[1].concat(favoritesPaths[0]), this.valueComparisonFavorites)
96 | }
97 |
98 | if(isFavorite) {
99 | favButton.setChecked(true)
100 | favButton.setEnabled(false)
101 | } else {
102 | var self = this
103 | favButton.setHandler(function(event) {
104 | self.emit('add-comparison-favorite', favoriteData)
105 | favButton.setChecked(true)
106 | favButton.setEnabled(false)
107 | })
108 | favButton.setChecked(false)
109 | favButton.setEnabled(true)
110 | }
111 |
112 | this.comparisonView.appendChild(view)
113 | }
114 | })
115 | .catch(console.error)
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/js/views/character-comparison-view.js:
--------------------------------------------------------------------------------
1 | const CharacterComparisonBaseView = require('./character-comparison-base-view.js')
2 | const CharacterView = require('./character-selector-multiple.js')
3 | const FavoriteButton = require('./favorite-button.js')
4 |
5 | module.exports = class CharacterComparisonView extends CharacterComparisonBaseView {
6 | constructor(properties) {
7 | super(properties)
8 | this.valuesViewType = "average"
9 |
10 | this.isFiltering = false
11 | this.favoritesFilter = document.createElement("div")
12 | this.favoritesFilter.classList.add("favorites-filter-button")
13 | this.favoritesFilter.innerHTML = `Show Favorites`
14 | this.favoritesFilter.addEventListener("click", (event) => {
15 | this.isFiltering = !this.isFiltering
16 | if(this.isFiltering) {
17 | this.favoritesFilter.innerHTML = `Show All`
18 | } else {
19 | this.favoritesFilter.innerHTML = `Show Favorites`
20 | }
21 | this.updateView()
22 | })
23 | this.root.appendChild(this.favoritesFilter)
24 |
25 | this.comparisonView = document.createElement("div")
26 | this.comparisonView.setAttribute("id", "character-comparison-view")
27 | this.root.appendChild(this.comparisonView)
28 |
29 | this.updateView()
30 | }
31 |
32 | updateView() {
33 | this.getSelectedCharacterValues()
34 | .then(characterValueResults => {
35 | if(!characterValueResults || !characterValueResults.length) {
36 | return
37 | }
38 |
39 | this.comparisonView.innerHTML = ``
40 | let comparisonView = document.createElement("div")
41 | comparisonView.setAttribute("id", "character-comparison-view")
42 |
43 | let targetWidth = 100/this.characters.length
44 | let index = 0
45 |
46 | for(let characterValueResult of characterValueResults) {
47 | let character = this.selectedCharacters[index]
48 | let containerDiv = document.createElement("div")
49 | containerDiv.classList.add("comparison-character-view-container")
50 | let nameContainer = document.createElement("h2")
51 | nameContainer.innerHTML = character.name
52 | containerDiv.appendChild(nameContainer)
53 | let curValuesView = this.getScoresView(character.id, characterValueResults[index])
54 | containerDiv.appendChild(curValuesView)
55 | comparisonView.appendChild(containerDiv)
56 | index++
57 | }
58 |
59 | this.root.replaceChild(comparisonView, this.comparisonView)
60 | this.comparisonView = comparisonView
61 | })
62 |
63 | }
64 |
65 | getScoresView(characterID, values) {
66 | if(!values) {
67 | throw new Error("missing values")
68 | }
69 | let result = document.createElement('div')
70 | result.setAttribute("id", "values-view")
71 |
72 | for(let value of values) {
73 | this.getCharacterValueFavorites(characterID).then(characterValueFavorites => {
74 | let valueView = document.createElement('div')
75 | valueView.setAttribute("class", "value-list-container")
76 |
77 |
78 | let favoriteValues = characterValueFavorites.values
79 | let isFavorite = favoriteValues.indexOf(value.valueID) >= 0
80 | let favButtonProperties = {
81 | checked: isFavorite,
82 | enabled: !isFavorite,
83 | className: "favorite-button-container-value-list-view"
84 | }
85 |
86 | var self = this
87 | let favButton = new FavoriteButton(favButtonProperties)
88 | favButton.setHandler(function(event) {
89 | self.emit('add-character-value-favorite', {valueID: value.valueID, characterID: characterID})
90 | favButton.setChecked(true)
91 | favButton.setEnabled(false)
92 | })
93 | valueView.appendChild(favButton.getView())
94 |
95 | let progressView = document.createElement('div')
96 | progressView.setAttribute("class", "value-list-progress")
97 | progressView.setAttribute("style", `width: ${value.score*100}%`)
98 | valueView.appendChild(progressView)
99 | let nameView = document.createElement('div')
100 | nameView.setAttribute("class", "value-list-label")
101 | nameView.innerHTML = `${this.valuesMap[value.valueID.toString()].name} | ${value.score} | Wins: ${value.wins}, Losses: ${value.losses} | Battles: ${value.battleCount}`
102 | valueView.appendChild(nameView)
103 |
104 | if(this.isFiltering && isFavorite) {
105 | result.appendChild(valueView)
106 | } else if(!this.isFiltering) {
107 | result.appendChild(valueView)
108 | }
109 |
110 | })
111 |
112 | }
113 |
114 | return result
115 | }
116 |
117 |
118 | }
--------------------------------------------------------------------------------
/js/views/character-favorites-view.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 |
3 | module.exports = class CharacterFavoritesView extends MainBaseView {
4 | constructor(properties) {
5 | super(properties)
6 |
7 | this.root.setAttribute("id", "value-list-container")
8 |
9 | this.characterSelector = document.createElement("select")
10 | this.characterSelector.setAttribute("id", "character-selector")
11 | this.characterSelector.addEventListener('change', (event)=>{
12 | this.currentCharacterID = parseInt(event.target.value)
13 | this.onSelectCharacter(this.currentCharacterID)
14 | })
15 | this.root.appendChild(this.characterSelector)
16 |
17 | this.valuesViewContainer = document.createElement("div")
18 | this.valuesViewContainer.setAttribute("id", "values-view-container")
19 | this.root.appendChild(this.valuesViewContainer)
20 |
21 | var selectedCharacters = this.getSelectedCharacters()
22 | if(selectedCharacters && selectedCharacters.length > 0) {
23 | this.currentCharacterID = selectedCharacters[0].id
24 | }
25 |
26 | this.characters = []
27 | this.getCharacters()
28 | .then(inCharacters => {
29 | this.characters = inCharacters
30 |
31 | if(!this.currentCharacterID && this.characters && this.characters.length) {
32 | this.currentCharacterID = this.characters[0].id
33 | }
34 |
35 | this.characterSelector.innerHTML = ``
36 | for(let character of this.characters) {
37 | let option = document.createElement("option")
38 | option.setAttribute("value", character.id)
39 | if(character.id == this.currentCharacterID) {
40 | option.setAttribute("selected", true)
41 | }
42 | option.innerHTML = character.name
43 | this.characterSelector.appendChild(option)
44 | }
45 |
46 | this.onSelectCharacter(this.currentCharacterID)
47 | })
48 | .catch(console.error)
49 | }
50 |
51 | updateView() {
52 |
53 | }
54 |
55 | onSelectCharacter(characterID) {
56 | this.currentCharacterID = characterID
57 | let character
58 | for(let aCharacter of this.characters) {
59 | if(aCharacter.id === this.currentCharacterID) {
60 | character = aCharacter
61 | break
62 | }
63 | }
64 |
65 | if(!character) {
66 | return
67 | }
68 |
69 | this.valuesViewContainer.innerHTML = ``
70 |
71 | this.getCharacterValueFavorites(characterID).then(characterBattleFavorites => {
72 | if(!characterBattleFavorites) {
73 | return
74 | }
75 | this.valuesViewContainer.innerHTML = ``
76 |
77 | let values = characterBattleFavorites.values
78 | let favoriteValuesHolder = document.createElement("div")
79 | favoriteValuesHolder.setAttribute("id", "favorite-values-holder")
80 | let valueNames = []
81 | for(let valueID of values) {
82 | let value = this.valuesMap[valueID]
83 | if(value && value.name) {
84 | valueNames.push(value.name)
85 | }
86 | }
87 | valueNames = valueNames.sort()
88 | for(let valueName of valueNames) {
89 | let view = document.createElement("div")
90 | view.innerHTML = `${valueName}`
91 | favoriteValuesHolder.appendChild(view)
92 | }
93 | this.valuesViewContainer.appendChild(favoriteValuesHolder)
94 |
95 | let pairs = characterBattleFavorites.pairs
96 | let pairsHolder = document.createElement("div")
97 | pairsHolder.setAttribute("id", "favorite-pairs-holder")
98 | for(var value1ID in pairs) {
99 | let value1 = this.valuesMap[value1ID]
100 | for(var value2ID in pairs[value1ID]) {
101 | let value2 = this.valuesMap[value2ID]
102 | let view = document.createElement("div")
103 | view.innerHTML = `${value1.name} vs ${value2.name}`
104 | pairsHolder.appendChild(view)
105 | }
106 | }
107 | this.valuesViewContainer.appendChild(pairsHolder)
108 | })
109 |
110 | this.emit('selected-characters', [character])
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/js/views/character-selector-multiple.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 |
3 | module.exports = class CharacterView extends MainBaseView {
4 | constructor(properties) {
5 | super(properties)
6 |
7 | this.root = document.createElement('div')
8 | this.root.setAttribute("id", "characters-container")
9 |
10 | this.characterList = document.createElement("div")
11 | this.characterList.setAttribute("id", "character-list")
12 | this.characterList.classList.add("flex-wrap")
13 | this.root.appendChild(this.characterList)
14 |
15 | this.characters = []
16 | this.getCharacters().then(inValues => {
17 | this.characters = inValues
18 | this.updateView()
19 | })
20 | }
21 |
22 | addCharacterView(characterName, characterID, isSelected) {
23 | let characterView = document.createElement('div')
24 | characterView.classList.add("character-selector-button")
25 | if(isSelected) {
26 | characterView.classList.add("character-selector-button-selected")
27 | }
28 | characterView.setAttribute("data-id", characterID || 1)
29 | characterView.innerHTML = characterName
30 | characterView.addEventListener('click', this.onCharacterClick.bind(this));
31 | this.characterList.appendChild(characterView)
32 | }
33 |
34 | onCharacterClick(event) {
35 | this.emit('select-character', {characterID: parseInt(event.target.dataset.id)})
36 | }
37 |
38 | getView() {
39 | return this.root
40 | }
41 |
42 | updateView() {
43 | let selectedCharacters = this.getSelectedCharacters()
44 | this.characterList.innerHTML = ''
45 | for(let character of this.characters) {
46 | let isSelected = false
47 | for(let selectedCharacter of selectedCharacters) {
48 | if(selectedCharacter.id === character.id) {
49 | isSelected = true
50 | break
51 | }
52 | }
53 | this.addCharacterView(character.name, character.id, isSelected)
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/js/views/character-selector-single.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 |
3 | module.exports = class CharacterSelectorSingle extends MainBaseView {
4 | constructor(properties) {
5 | super(properties)
6 | this.characters = []
7 | this.root = document.createElement("select")
8 | this.root.setAttribute("id", "character-selector")
9 | this.root.addEventListener('change', (event)=>{
10 | this.emit('change', event)
11 | })
12 | }
13 |
14 | init() {
15 | this.getCharacters()
16 | .then(inCharacters => {
17 | this.root.innerHTML = ``
18 | if(!inCharacters || !inCharacters.length) {
19 | return
20 | }
21 |
22 | this.character = inCharacters[0]
23 | this.characters = inCharacters
24 | for(let character of inCharacters) {
25 | this.addCharacterView(character.name, character.id, this.character.id === character.id)
26 | }
27 | })
28 | .catch(console.error)
29 | }
30 |
31 | addCharacterView(characterName, characterID, isSelected) {
32 | let option = document.createElement("option")
33 | option.setAttribute("value", characterID)
34 | option.innerHTML = characterName
35 | if(isSelected) {
36 | option.setAttribute("selected", true)
37 | }
38 | this.root.appendChild(option)
39 | }
40 |
41 | getView() {
42 | return this.root
43 | }
44 |
45 | updateView() {
46 | this.root = ``
47 | let selectedCharacters = this.getSelectedCharacters()
48 | this.characterList.innerHTML = ''
49 | let isSelected = false
50 | for(let character of this.characters) {
51 | if(!isSelected) {
52 | for(let selectedCharacter of selectedCharacters) {
53 | if(selectedCharacter.id === character.id) {
54 | isSelected = true
55 | break
56 | }
57 | }
58 | }
59 | this.addCharacterView(character.name, character.id, isSelected)
60 | }
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/js/views/character-trainer-view.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 | const FavoriteButton = require('./favorite-button.js')
3 | const BattleTimeKeeper = require('../battle-timekeeper.js')
4 | const { getFriendlyMS } = require('../utils.js')
5 | const prefsModule = require('electron').remote.require('./js/prefs')
6 | const EXPIRE_TIME = prefsModule.getPrefs()['skipTimerLength'] || 10*1000
7 |
8 | module.exports = class CharacterTrainerView extends MainBaseView {
9 | constructor(properties) {
10 | super(properties)
11 | this.showTimer = true
12 |
13 | this.root.setAttribute("id", "character-trainer-container")
14 | this.root.addEventListener('keypress', this.onKeyPress.bind(this))
15 |
16 | this.header = document.createElement("h2")
17 | this.header.setAttribute("id", "character-trainer-view-header")
18 | this.root.appendChild(this.header)
19 |
20 | this.characterSelector = document.createElement("select")
21 | this.characterSelector.classList.add("character-trainer-view-character-selector")
22 | this.characterSelector.addEventListener('change', (event)=>{
23 | this.currentCharacterID = parseInt(event.target.value)
24 | this.onSelectCharacter(this.currentCharacterID)
25 | })
26 | this.header.appendChild(this.characterSelector)
27 | let chooses = document.createElement("div")
28 | chooses.setAttribute("id", "character-trainer-view-header-text")
29 | chooses.innerHTML = `faces a difficult choice...`
30 | this.header.appendChild(chooses)
31 |
32 | let selectedCharacters = this.getSelectedCharacters()
33 | if(selectedCharacters.length > 0) {
34 | this.character = selectedCharacters[0]
35 | }
36 |
37 | this.characters = []
38 | this.getCharacters()
39 | .then(inCharacters => {
40 | this.characterSelector.innerHTML = ``
41 | if(!inCharacters || !inCharacters.length) {
42 | return
43 | }
44 |
45 | if(!this.character) {
46 | this.character = inCharacters[0]
47 | }
48 | this.characters = inCharacters
49 | for(let character of this.characters) {
50 | let option = document.createElement("option")
51 | option.setAttribute("value", character.id)
52 | option.innerHTML = character.name
53 | if(this.character.id === character.id) {
54 | option.setAttribute("selected", true)
55 | }
56 | this.characterSelector.appendChild(option)
57 | }
58 |
59 | this.onSelectCharacter(this.character.id)
60 | })
61 | .catch(console.error)
62 |
63 | this.battleView = document.createElement("div")
64 | this.battleView.setAttribute("id", "character-trainer-view-battle-container")
65 | this.battleView.setAttribute("tabindex", 1)
66 | this.root.appendChild(this.battleView)
67 |
68 | this.buttonContainer = document.createElement("div")
69 | this.buttonContainer.setAttribute("id", "battle-view-button-container")
70 | this.root.appendChild(this.buttonContainer)
71 |
72 | this.skipButton = document.createElement("div")
73 | this.skipButton.setAttribute("class", "battle-view-button")
74 | this.skipButton.addEventListener('click', this.onSkip.bind(this))
75 | this.skipButton.innerHTML = `Skip`
76 | this.buttonContainer.appendChild(this.skipButton)
77 |
78 | this.footerView = document.createElement("div")
79 | this.footerView.setAttribute("id", "value-list-footer")
80 | this.footerView.classList.add("footer")
81 | this.root.appendChild(this.footerView)
82 |
83 | this.battleCountView = document.createElement("div")
84 | this.battleCountView.setAttribute("id", "value-list-battle-count")
85 | this.footerView.appendChild(this.battleCountView)
86 |
87 | this.sessionView = document.createElement("div")
88 | this.sessionView.setAttribute("id", "value-list-session")
89 | this.sessionCountView = document.createElement("div")
90 | this.sessionView.appendChild(this.sessionCountView)
91 | this.sessionTimeView = document.createElement("div")
92 | this.sessionTimeView.setAttribute("id", "session-time-view")
93 | this.sessionView.appendChild(this.sessionTimeView)
94 | this.footerView.appendChild(this.sessionView)
95 |
96 | let battleTypeView = document.createElement("div")
97 | this.battleTypeView = battleTypeView
98 | this.battleTypeView.classList.add("battle-type-view")
99 | this.footerView.appendChild(this.battleTypeView)
100 |
101 | this.showTimerSwitch = document.createElement("div")
102 | this.showTimerSwitch.setAttribute("id", "battle-view-show-timer")
103 | this.showTimerSwitch.setAttribute("class", "battle-view-button")
104 | this.showTimerSwitch.innerHTML = "Hide Timer"
105 | this.showTimerSwitch.addEventListener("click", this.toggleTimerView.bind(this))
106 | this.footerView.appendChild(this.showTimerSwitch)
107 |
108 | this.characterSessionStartTime = Date.now()
109 | }
110 |
111 | setupBattleView() {
112 | this.getBattlePairer(this.currentCharacterID).then(battlePairer =>{
113 | this.battleView.innerHTML = ``
114 |
115 | this.battlePairer = battlePairer
116 |
117 | this.choiceContainer1 = document.createElement("div")
118 | this.choiceContainer1.classList.add("battle-view-choice-button")
119 | this.choiceContainer1.addEventListener('click', this.onChoiceClick.bind(this))
120 | this.battleView.appendChild(this.choiceContainer1)
121 |
122 | this.centerContainer = document.createElement("div")
123 | this.centerContainer.setAttribute("id", "character-trainer-view-center-container")
124 |
125 | this.favoriteButton = new FavoriteButton({handler: this.onFavorite.bind(this)})
126 | this.centerContainer.appendChild(this.favoriteButton.getView())
127 |
128 | let centerText = document.createElement("div")
129 | centerText.setAttribute("id", "character-trainer-view-center-text")
130 | centerText.innerHTML = `or`
131 | this.centerContainer.appendChild(centerText)
132 |
133 | this.timerContainer = document.createElement("div")
134 | this.timerContainer.setAttribute("id", "battle-view-timer-container")
135 | this.timerContainer.classList.add("battle-view-timer-container")
136 | this.countdownTimer = document.createElement("div")
137 | this.countdownTimer.classList.add("countdown-progress-bar")
138 | this.timerContainer.appendChild(this.countdownTimer)
139 | this.centerContainer.appendChild(this.timerContainer)
140 |
141 | this.battleView.appendChild(this.centerContainer)
142 |
143 | this.choiceContainer2 = document.createElement("div")
144 | this.choiceContainer2.classList.add("battle-view-choice-button")
145 | this.choiceContainer2.addEventListener('click', this.onChoiceClick.bind(this))
146 | this.battleView.appendChild(this.choiceContainer2)
147 |
148 | this.battlePaiererTypeHandler = (battleType)=>{
149 | this.battleTypeView.innerHTML = battleType
150 | }
151 | this.battlePairer.on("battle-type-try", this.battlePaiererTypeHandler)
152 |
153 | this.setupBattle()
154 | })
155 | }
156 |
157 | onSelectCharacter(characterID) {
158 | this.clearSessionTimer()
159 | this.characterSessionStartTime = Date.now()
160 | this.curBattleTimeKeeper = new BattleTimeKeeper()
161 | this.currentCharacterID = characterID
162 | for(let aCharacter of this.characters) {
163 | if(aCharacter.id === this.currentCharacterID) {
164 | this.character = aCharacter
165 | break
166 | }
167 | }
168 | this.setupBattleView()
169 | this.updateView()
170 |
171 | this.emit('selected-characters', [this.character])
172 | }
173 |
174 | getView() {
175 | return this.root
176 | }
177 |
178 | updateView() {
179 | if(this.timerID) {
180 | clearInterval(this.timerID)
181 | this.timerID = null
182 | }
183 |
184 | this.battleCountView.innerHTML = `${this.character.name} // ${this.getCharacterBattleCount(this.character.id)} Questions`
185 |
186 | let session = this.getCharacterSession(this.character.id)
187 | if(this.characterSessionStartTime && this.showTimer) {
188 | this.startSessionTimerView()
189 | this.sessionCountView.innerHTML = `${session.battleCount} Questions in `
190 | } else {
191 | this.sessionTimeView.innerHTML = ``
192 | this.sessionCountView.innerHTML = `${session.battleCount} Questions`
193 | }
194 | }
195 |
196 | startSessionTimerView() {
197 | this.clearSessionTimer()
198 | this.timerID = setInterval( () => {
199 | this.updateTimerView()
200 | }, 500)
201 | this.updateTimerView()
202 | }
203 |
204 | updateTimerView() {
205 | let now = Date.now()
206 | let elapsed = getFriendlyMS(now - this.characterSessionStartTime)
207 | this.sessionTimeView.innerHTML = `${elapsed.h ? elapsed.h+':' : ''}${elapsed.m}:${elapsed.s}`
208 | }
209 |
210 | clearSessionTimer() {
211 | if(this.timerID) {
212 | clearInterval(this.timerID)
213 | this.timerID = null
214 | this.sessionTimeView.innerHTML = ``
215 | }
216 | }
217 |
218 | viewWillDisappear() {
219 | this.clearBattleTimer()
220 | this.battlePairer.removeListener("battle-type-try", this.battlePaiererTypeHandler)
221 |
222 | if(this.timerID) {
223 | clearInterval(this.timerID)
224 | this.timerID = null
225 | }
226 | }
227 |
228 | setupBattle() {
229 | this.clearBattleTimer()
230 | this.favoriteButton.innerHTML = `Favorite`
231 |
232 | this.choiceContainer1.innerHTML = ""
233 | this.choiceContainer2.innerHTML = ""
234 | let battleData = this.battlePairer.getBattle()
235 |
236 | this.choiceDataOne = battleData[0]
237 | this.choiceContainer1.setAttribute("data-id", this.choiceDataOne.id)
238 | this.choiceContainer1.innerHTML = this.choiceDataOne.name
239 |
240 | this.choiceDataTwo = battleData[1]
241 | this.choiceContainer2.setAttribute("data-id", this.choiceDataTwo.id)
242 | this.choiceContainer2.innerHTML = this.choiceDataTwo.name
243 |
244 | if(this.showTimer) {
245 | this.startBattleTimerView()
246 | }
247 |
248 | this.emit('battle-start', battleData)
249 | this.curBattleTimeKeeper.onBattleStart()
250 | }
251 |
252 | onChoiceClick(event) {
253 | let winnerID = parseInt(event.target.dataset.id)
254 | this.handleChoice(winnerID)
255 | }
256 |
257 | handleChoice(winnerID) {
258 | let winner, loser
259 | let battleOutcome = {
260 | characterID: this.character.id
261 | }
262 | if(winnerID === this.choiceDataOne.id) {
263 | battleOutcome.winner = this.choiceDataOne.id
264 | battleOutcome.loser = this.choiceDataTwo.id
265 | } else {
266 | battleOutcome.winner = this.choiceDataTwo.id
267 | battleOutcome.loser = this.choiceDataOne.id
268 | }
269 | this.emit('battle-update', battleOutcome)
270 | this.setupBattle()
271 | this.curBattleTimeKeeper.onBattleOutcome(battleOutcome)
272 | this.updateView()
273 | }
274 |
275 | onKeyPress(event) {
276 | if(event.key == "1") {
277 | this.handleChoice(this.choiceDataOne.id)
278 | }
279 | if(event.key == "2") {
280 | this.handleChoice(this.choiceDataTwo.id)
281 | }
282 | }
283 |
284 | onSkip(event) {
285 | this.emit('battle-skip')
286 | this.setupBattle()
287 | }
288 |
289 | onFavorite(event) {
290 | this.emit('battle-favorite', {value1: this.choiceDataOne, value2: this.choiceDataTwo, character: this.character})
291 | this.setupBattle()
292 | }
293 |
294 | clearBattleTimer() {
295 | if(this.battleTimerID) {
296 | clearInterval(this.battleTimerID)
297 | this.battleTimerID = null
298 | }
299 | }
300 |
301 | startBattleTimerView() {
302 | this.battleStartTime = Date.now()
303 | this.battleTimerID = setInterval(() => {
304 | let now = Date.now()
305 | let elapsed = now - this.battleStartTime
306 | if(elapsed > (EXPIRE_TIME)) {
307 | this.onSkip()
308 | }
309 | this.countdownTimer.setAttribute("style", `width: ${((EXPIRE_TIME-elapsed)/EXPIRE_TIME)*100}%`)
310 | }, 1000/120)
311 | }
312 |
313 | toggleTimerView(event) {
314 |
315 | let session = this.getCharacterSession(this.character.id)
316 | this.showTimer = !this.showTimer
317 | if(this.showTimer) {
318 | this.showTimerSwitch.innerHTML = "Hide Timer"
319 | this.timerContainer.classList.remove("hidden")
320 | this.clearBattleTimer()
321 | this.startBattleTimerView()
322 | this.emit("show-timer")
323 | this.startSessionTimerView()
324 | this.sessionCountView.innerHTML = `${session.battleCount} Questions in `
325 | } else {
326 | this.showTimerSwitch.innerHTML = "Show Timer"
327 | this.timerContainer.classList.add("hidden")
328 | this.clearBattleTimer()
329 | this.emit("hide-timer")
330 | this.clearSessionTimer()
331 | this.sessionCountView.innerHTML = `${session.battleCount} Questions`
332 | }
333 | }
334 | }
--------------------------------------------------------------------------------
/js/views/characters-view.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 |
3 | module.exports = class CharactersView extends MainBaseView {
4 | constructor(properties) {
5 | super(properties)
6 | this.root.setAttribute("id", "characters-container")
7 |
8 | this.characterList = document.createElement("div")
9 | this.characterList.setAttribute("id", "character-list")
10 | this.characterList.classList.add("flex-wrap")
11 | this.root.appendChild(this.characterList)
12 |
13 | this.characters = []
14 | this.getCharacters().then(inValues => {
15 | this.characters = inValues
16 | this.updateView()
17 | })
18 | }
19 |
20 | addCharacterView(character) {
21 | let self = this
22 | let characterName = character.name
23 | let characterID = character.id
24 |
25 | let characterView = document.createElement('div')
26 | characterView.classList.add("manage-character-container")
27 | characterView.setAttribute("data-id", characterID || 1)
28 |
29 | let nameView = document.createElement("div")
30 | nameView.innerHTML = characterName
31 | characterView.appendChild(nameView)
32 |
33 | let battleCountView = document.createElement("div")
34 | battleCountView.innerHTML = `${this.getCharacterBattleCount(characterID)} Battles`
35 | characterView.appendChild(battleCountView)
36 |
37 | let trainButton = document.createElement("div")
38 | trainButton.classList.add("manage-character-button")
39 | trainButton.innerHTML = "Train"
40 | trainButton.addEventListener("click", function(event) {
41 | self.emit("train", [character])
42 | })
43 | characterView.appendChild(trainButton)
44 |
45 | let viewValuesButton = document.createElement("div")
46 | viewValuesButton.classList.add("manage-character-button")
47 | viewValuesButton.innerHTML = "Values"
48 | viewValuesButton.addEventListener("click", function(event) {
49 | self.emit("viewValues", [character])
50 | })
51 | characterView.appendChild(viewValuesButton)
52 |
53 | characterView.addEventListener('click', this.onCharacterClick.bind(this));
54 | this.characterList.appendChild(characterView)
55 | }
56 |
57 | addInputCharacterView() {
58 | this.addCharacterViewContainer = document.createElement('div')
59 | this.addCharacterViewContainer.classList.add("manage-characters-add-container")
60 | this.addCharacterViewContainer.dataset.mode = "button"
61 |
62 | this.characterInput = document.createElement("input")
63 | this.characterInput.setAttribute("id", "input-add-character-name")
64 | this.characterInput.setAttribute("type", "text")
65 | this.characterInput.classList.add("hidden")
66 | this.characterInput.addEventListener('keydown', (event)=>{
67 | if(event.keyCode === 13) {
68 | this.addCharacterFronInput()
69 | }
70 | })
71 | this.addCharacterViewContainer.appendChild(this.characterInput)
72 | this.characterInputAddButton = document.createElement("div")
73 | this.characterInputAddButton.setAttribute("id", "character-input-add-button")
74 | this.characterInputAddButton.classList.add("hidden")
75 | this.characterInputAddButton.innerHTML = `Add`
76 | this.characterInputAddButton.addEventListener('click', this.addCharacterFronInput.bind(this))
77 | this.addCharacterViewContainer.appendChild(this.characterInputAddButton)
78 |
79 | // plus biew
80 | let plusContainer = document.createElement("div")
81 | plusContainer.classList.add("manage-characters-add-button-image-container")
82 | this.addCharacterViewContainer.appendChild(plusContainer)
83 |
84 | let plus = document.createElement("img")
85 | plus.setAttribute("src", "images/add-character-plus.svg")
86 | plus.classList.add("manage-characters-add-button-image")
87 | plusContainer.appendChild(plus)
88 |
89 | this.addCharacterViewContainer.addEventListener('click', (event)=>{
90 | if(this.addCharacterViewContainer.dataset.mode === "button") {
91 | this.characterInput.classList.remove("hidden")
92 | this.characterInput.focus()
93 | this.characterInputAddButton.classList.remove("hidden")
94 | plus.classList.add("hidden")
95 | this.addCharacterViewContainer.dataset.mode = "input"
96 | } else {
97 | this.characterInput.classList.add("hidden")
98 | this.characterInputAddButton.classList.add("hidden")
99 | plus.classList.remove("hidden")
100 | this.addCharacterViewContainer.dataset.mode = "button"
101 | }
102 |
103 | });
104 |
105 | this.characterList.appendChild(this.addCharacterViewContainer)
106 | }
107 |
108 | addCharacterFronInput(event) {
109 | this.characterList.removeChild(this.addCharacterViewContainer)
110 | let newName = this.characterInput.value
111 | this.addCharacterView({name: newName}, 13371337)
112 | if(newName) {
113 | this.emit('add-character', {name: newName})
114 | }
115 | this.addInputCharacterView()
116 | }
117 |
118 | onCharacterClick(event) {
119 | this.emit('select-character', {characterID: parseInt(event.target.dataset.id)})
120 | }
121 |
122 | getView() {
123 | return this.root
124 | }
125 |
126 | updateView() {
127 | let selectedCharacters = this.getSelectedCharacters()
128 | this.characterList.innerHTML = ''
129 | for(let character of this.characters) {
130 | this.addCharacterView(character)
131 | }
132 |
133 | this.addInputCharacterView()
134 | }
135 | }
--------------------------------------------------------------------------------
/js/views/common-ground-view.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils.js')
2 | const CharacterComparisonBaseView = require('./character-comparison-base-view.js')
3 | const COMMONNESS_THRESHOLD = .10
4 |
5 | module.exports = class CommonGroundView extends CharacterComparisonBaseView {
6 | constructor(properties) {
7 | super(properties)
8 | this.viewType = "all"
9 |
10 | this.viewTypeSelector = document.createElement("select")
11 | this.viewTypeSelector.classList.add("comparison-view-selector")
12 | for(let type of ["all", "favorites"]) {
13 | let option = document.createElement("option")
14 | option.setAttribute("value", type)
15 | option.innerHTML = type
16 | this.viewTypeSelector.appendChild(option)
17 | }
18 | this.viewTypeSelector.addEventListener('change', (event)=>{
19 | this.viewType = event.target.value
20 | this.updateView()
21 | })
22 | this.root.appendChild(this.viewTypeSelector)
23 |
24 | this.comparisonView = document.createElement("div")
25 | this.comparisonView.classList.add("comparison-view")
26 | this.root.appendChild(this.comparisonView)
27 | this.updateView()
28 | }
29 |
30 | updateView() {
31 | this.comparisonView.innerHTML = ``
32 | let headersContainer = document.createElement("div")
33 | headersContainer.classList.add("comparison-view-conflicts-header-container")
34 | this.comparisonView.appendChild(headersContainer)
35 |
36 | for(let i = 0; i 0) {
38 | let vsView = document.createElement("div")
39 | vsView.innerHTML = `and`
40 | headersContainer.appendChild(vsView)
41 | }
42 | let characterName = document.createElement("h2")
43 | characterName.classList.add("comparison-view-conflicts-header")
44 | characterName.innerHTML = this.selectedCharacters[i].name
45 | headersContainer.appendChild(characterName)
46 | }
47 |
48 | if(this.selectedCharacters.length < 2) {
49 | let selectView = document.createElement("div")
50 | selectView.innerHTML = `Please select two characters to show their common ground.`
51 | this.comparisonView.appendChild(selectView)
52 | return
53 | }
54 |
55 | let valuesContainer
56 | switch(this.viewType) {
57 | case "favorites":
58 | valuesContainer = this.getFavoriteValuesView()
59 | break
60 | case "all":
61 | default:
62 | valuesContainer = this.getAllValuesView()
63 | break
64 | }
65 |
66 | this.comparisonView.appendChild(valuesContainer)
67 | }
68 |
69 | getAllValuesView() {
70 | let valuesContainer = document.createElement("div")
71 |
72 | let character1ID = this.selectedCharacters[0].id
73 | let character2ID = this.selectedCharacters[1].id
74 | let favoritePairs = []
75 | Promise.all([
76 | this.getCharacterValuesMap(character1ID),
77 | this.getCharacterValuesMap(character2ID)
78 | ])
79 | .then(results => {
80 | let characterValueMap1 = results[0]
81 | let characterValueMap2 = results[1]
82 |
83 | let commonValues = []
84 | for(let valueID in characterValueMap1) {
85 | let character1Value = characterValueMap1[valueID]
86 | let character2Value = characterValueMap2[valueID]
87 |
88 | if(Math.abs(character1Value.score - character2Value.score) < COMMONNESS_THRESHOLD
89 | && character1Value.score > 0 && character2Value.score > 0) {
90 |
91 | commonValues.push([character1Value, character2Value])
92 | }
93 | }
94 |
95 | commonValues.sort((a, b) => {
96 | let value1 = a[0]
97 | let value2 = b[0]
98 | return value2.score - value1.score
99 | })
100 |
101 | for(let commonValue of commonValues) {
102 | let character1Value = commonValue[0]
103 | let character2Value = commonValue[1]
104 |
105 | let valueContainer = document.createElement("div")
106 | valueContainer.classList.add("common-ground-view-container")
107 |
108 | let name = document.createElement("div")
109 | name.classList.add("common-ground-view-name")
110 | name.innerHTML = this.valuesMap[character1Value.valueID].name
111 | valueContainer.appendChild(name)
112 |
113 | let scoresContainer = document.createElement("div")
114 | scoresContainer.classList.add("common-ground-view-scores")
115 | valueContainer.appendChild(scoresContainer)
116 |
117 | let character1Container = document.createElement("div")
118 | character1Container.classList.add("common-ground-view-character-container")
119 | character1Container.innerHTML = `${this.selectedCharacters[0].name}: ${character1Value.score}`
120 | scoresContainer.appendChild(character1Container)
121 |
122 | let character2Container = document.createElement("div")
123 | character2Container.classList.add("common-ground-view-character-container")
124 | character2Container.innerHTML = `${this.selectedCharacters[1].name}: ${character2Value.score}`
125 | scoresContainer.appendChild(character2Container)
126 |
127 | valuesContainer.appendChild(valueContainer)
128 | }
129 | })
130 |
131 | return valuesContainer
132 | }
133 |
134 | getFavoriteValuesView() {
135 | let valuesContainer = document.createElement("div")
136 |
137 | let character1ID = this.selectedCharacters[0].id
138 | let character2ID = this.selectedCharacters[1].id
139 | let favoritePairs = []
140 | Promise.all([
141 | this.getCharacterValueFavorites(character1ID),
142 | this.getCharacterValueFavorites(character2ID),
143 | this.getCharacterValuesMap(character1ID),
144 | this.getCharacterValuesMap(character2ID)
145 | ]).then(results => {
146 | let characterValueFavorites1 = results[0].values
147 | let characterValueFavorites2 = results[1].values
148 | let characterValueMap1 = results[2]
149 | let characterValueMap2 = results[3]
150 |
151 | let commonValues = []
152 |
153 | // combine favorites, and make unique
154 | let s = new Set(characterValueFavorites1.concat(characterValueFavorites2))
155 | let it = s.values()
156 | let favoriteValueIDs = Array.from(it)
157 |
158 | for(let valueID of favoriteValueIDs) {
159 | let character1Value = characterValueMap1[valueID]
160 | let character2Value = characterValueMap2[valueID]
161 |
162 | if(character1Value && character2Value
163 | && Math.abs(character1Value.score - character2Value.score) < COMMONNESS_THRESHOLD
164 | && character1Value.score > 0 && character2Value.score > 0) {
165 |
166 | commonValues.push([character1Value, character2Value])
167 | }
168 | }
169 |
170 | commonValues.sort((a, b) => {
171 | let value1 = a[0]
172 | let value2 = b[0]
173 | return value2.score - value1.score
174 | })
175 |
176 | for(let commonValue of commonValues) {
177 | let character1Value = commonValue[0]
178 | let character2Value = commonValue[1]
179 |
180 | let valueContainer = document.createElement("div")
181 | valueContainer.classList.add("common-ground-view-container")
182 |
183 | let name = document.createElement("div")
184 | name.classList.add("common-ground-view-name")
185 | name.innerHTML = this.valuesMap[character1Value.valueID].name
186 | valueContainer.appendChild(name)
187 |
188 | let scoresContainer = document.createElement("div")
189 | scoresContainer.classList.add("common-ground-view-scores")
190 | valueContainer.appendChild(scoresContainer)
191 |
192 | let character1Container = document.createElement("div")
193 | character1Container.classList.add("common-ground-view-character-container")
194 | character1Container.innerHTML = `${this.selectedCharacters[0].name}: ${character1Value.score}`
195 | scoresContainer.appendChild(character1Container)
196 |
197 | let character2Container = document.createElement("div")
198 | character2Container.classList.add("common-ground-view-character-container")
199 | character2Container.innerHTML = `${this.selectedCharacters[1].name}: ${character2Value.score}`
200 | scoresContainer.appendChild(character2Container)
201 |
202 | valuesContainer.appendChild(valueContainer)
203 | }
204 | })
205 |
206 | return valuesContainer
207 | }
208 | }
--------------------------------------------------------------------------------
/js/views/favorite-button.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events').EventEmitter
2 |
3 | const ON_ICON_PATH = "images/favorite-selected.svg"
4 | const OFF_ICON_PATH = "images/favorite-unselected.svg"
5 |
6 | module.exports = class MainBaseView extends EventEmitter {
7 | constructor(properties={}) {
8 | super()
9 | this.handleClick = this.handleClick.bind(this)
10 |
11 | this.root = document.createElement("div")
12 | let className = properties.className ? properties.className : "favorite-button-container"
13 | this.root.classList.add(className)
14 |
15 | this.icon = document.createElement("img")
16 | this.icon.setAttribute("src", OFF_ICON_PATH)
17 | this.root.appendChild(this.icon)
18 | this.handler = properties.handler
19 | this.setChecked(properties.hasOwnProperty("checked") ? properties.checked : false)
20 | this.setEnabled(properties.hasOwnProperty("enabled") ? properties.enabled : true)
21 | }
22 |
23 | setEnabled(enabled) {
24 | this.enabled = enabled
25 | if(!this.enabled) {
26 | this.root.removeEventListener("click", this.handleClick)
27 | } else {
28 | this.root.addEventListener("click", this.handleClick)
29 | }
30 | }
31 |
32 | handleClick(event) {
33 | if(this.handler) {
34 | this.handler()
35 | }
36 | }
37 |
38 | setHandler(handler) {
39 | this.handler = handler
40 | }
41 |
42 | setChecked(checked) {
43 | this.checked = checked
44 | this.updateView()
45 | }
46 |
47 | getView() {
48 | return this.root
49 | }
50 |
51 | updateView() {
52 | if(this.checked) {
53 | this.icon.setAttribute("src", ON_ICON_PATH)
54 | } else {
55 | this.icon.setAttribute("src", OFF_ICON_PATH)
56 | }
57 | }
58 |
59 | viewWillDisappear() {
60 | this.root.removeEventListener(this.handler)
61 | }
62 | }
--------------------------------------------------------------------------------
/js/views/internal-conflict-view.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils.js')
2 | const MainBaseView = require('./main-base-view.js')
3 | const FavoriteButton = require('./favorite-button.js')
4 | const { semiRandomShuffle } = require('../utils.js')
5 | const NUM_COMPARISON_ITEMS = 60
6 | const RANDOM_SHUFFLE_FACTOR = 4
7 |
8 | module.exports = class InternalConflictView extends MainBaseView {
9 | constructor(properties) {
10 | super(properties)
11 |
12 | this.root.setAttribute("id", "value-list-container")
13 |
14 | this.characterSelector = document.createElement("select")
15 | this.characterSelector.setAttribute("id", "character-selector")
16 | this.characterSelector.addEventListener('change', (event)=>{
17 | this.currentCharacterID = parseInt(event.target.value)
18 | this.onSelectCharacter(this.currentCharacterID)
19 | })
20 | this.root.appendChild(this.characterSelector)
21 |
22 | this.isFiltering = false
23 | this.favoritesFilter = document.createElement("div")
24 | this.favoritesFilter.classList.add("favorites-filter-button")
25 | this.favoritesFilter.innerHTML = `Show Favorites`
26 | this.favoritesFilter.addEventListener("click", (event) => {
27 | this.isFiltering = !this.isFiltering
28 | if(this.isFiltering) {
29 | this.favoritesFilter.innerHTML = `Show All`
30 | } else {
31 | this.favoritesFilter.innerHTML = `Show Favorites`
32 | }
33 | this.updateView()
34 | })
35 | this.root.appendChild(this.favoritesFilter)
36 |
37 | this.valuesViewContainer = document.createElement("div")
38 | this.valuesViewContainer.setAttribute("id", "values-view-container")
39 |
40 | this.comparisonView = document.createElement("div")
41 | this.comparisonView.classList.add("comparison-view")
42 | this.valuesViewContainer.appendChild(this.comparisonView)
43 | this.root.appendChild(this.valuesViewContainer)
44 |
45 | this.characters = []
46 | this.getCharacters()
47 | .then(inCharacters => {
48 | this.characters = inCharacters
49 | this.characterSelector.innerHTML = ``
50 | for(let character of this.characters) {
51 | let option = document.createElement("option")
52 | option.setAttribute("value", character.id)
53 | option.innerHTML = character.name
54 | this.characterSelector.appendChild(option)
55 | }
56 | if(this.characters && this.characters.length) {
57 | this.onSelectCharacter(this.characters[0].id)
58 | }
59 | })
60 | .catch(console.error)
61 | }
62 |
63 | updateView() {
64 | if(!this.character) {
65 | return
66 | }
67 | this.comparisonView.innerHTML = ``
68 |
69 | if(this.isFiltering) {
70 | this.getFavoriteValuesView()
71 | } else {
72 | this.getAllValuesView()
73 | }
74 | }
75 |
76 | getComparisonView(value1, value2) {
77 | let conflictContainer = document.createElement("div")
78 | conflictContainer.classList.add("comparison-view-conflict-container")
79 |
80 | let favButton = new FavoriteButton()
81 |
82 | let favoriteData = {}
83 | favoriteData[`character1ID`] = value1.characterID
84 | favoriteData[`value1ID`] = value1.valueID
85 | favoriteData[`character2ID`] = value2.characterID
86 | favoriteData[`value2ID`] = value2.valueID
87 | let isFavorite = utils.checkObjectPath([value1.characterID, value1.valueID, value2.characterID, value2.valueID], this.valueComparisonFavorites)
88 | || utils.checkObjectPath([value2.characterID, value2.valueID, value1.characterID, value1.valueID], this.valueComparisonFavorites)
89 |
90 | if(isFavorite) {
91 | favButton.setChecked(true)
92 | favButton.setEnabled(false)
93 | } else {
94 | var self = this
95 | favButton.setHandler(function(event) {
96 | self.emit('add-comparison-favorite', favoriteData)
97 | favButton.setChecked(true)
98 | favButton.setEnabled(false)
99 | })
100 | favButton.setChecked(false)
101 | favButton.setEnabled(true)
102 | }
103 |
104 | let getValueView = (value)=>{
105 | let name = this.valuesMap[value.valueID].name
106 | let nameView = document.createElement("div")
107 | nameView.classList.add("comparison-view-value-view")
108 | nameView.innerHTML = name
109 | return nameView
110 | }
111 |
112 | let nameView1 = getValueView(value1)
113 | nameView1.classList.add("text-align-right")
114 | conflictContainer.appendChild(nameView1)
115 |
116 | let centerContainer = document.createElement("div")
117 | centerContainer.classList.add('conflict-container-center')
118 | conflictContainer.appendChild(centerContainer)
119 |
120 | centerContainer.appendChild(favButton.getView())
121 | let vsView = document.createElement("div")
122 | vsView.innerHTML = `vs`
123 | centerContainer.appendChild(vsView)
124 |
125 | let nameView2 = getValueView(value2)
126 | conflictContainer.appendChild(nameView2)
127 | this.comparisonView.appendChild(conflictContainer)
128 | }
129 |
130 | getFavoriteValuesView() {
131 |
132 | let values = []
133 |
134 | if(this.valueComparisonFavorites[this.currentCharacterID]) {
135 | this.getCharacterValuesMap(this.currentCharacterID)
136 | .then(characterValueMap => {
137 | for(let key in this.valueComparisonFavorites[this.currentCharacterID]) {
138 | if(this.valueComparisonFavorites[this.currentCharacterID][key][this.currentCharacterID]) {
139 | let key2
140 | for(let value2ID in this.valueComparisonFavorites[this.currentCharacterID][key][this.currentCharacterID]) {
141 | key2 = value2ID
142 | break
143 | }
144 | let value1 = characterValueMap[key]
145 | let value2 = characterValueMap[key2]
146 | this.getComparisonView(value1, value2)
147 | }
148 | }
149 | })
150 | .catch(console.error)
151 | }
152 |
153 | let getValueView = (value)=>{
154 | let name = this.valuesMap[value.valueID].name
155 | let nameView = document.createElement("div")
156 | nameView.innerHTML = name
157 | return nameView
158 | }
159 | }
160 |
161 | getAllValuesView() {
162 | this.getCharacterValues(this.currentCharacterID).then(inCharacterValues => {
163 | let valuesCopy = inCharacterValues.slice(0, NUM_COMPARISON_ITEMS)
164 | let values = semiRandomShuffle(valuesCopy, RANDOM_SHUFFLE_FACTOR)
165 |
166 | for(let i = 0; i {
103 | // convert these to normal json objects (instead of knex objects)
104 | values = JSON.parse(JSON.stringify(values))
105 | let end = Date.now()
106 | valuesLib = values
107 | for(let value of valuesLib) {
108 | valuesMap[value.id.toString()] = value
109 | }
110 | remote.valuesMap = valuesMap
111 | })
112 |
113 |
114 | var backgroundView = new BackgroundView()
115 | container.insertBefore(backgroundView.getView(), container.firstChild)
116 |
117 | var mainViewSelector = new MainViewSelector({type: curViewType, mainViews: mainViews})
118 | document.getElementById("navigation").appendChild(mainViewSelector.getView())
119 | mainViewSelector.on('select-view', viewType => {
120 | console.log(viewType)
121 | curViewType = viewType
122 | onSelectView()
123 | })
124 |
125 | // Initialize character data.
126 | ipcRenderer.send('log', {type: 'progress', message: `Initializing Project`})
127 | displayMessage(`Initializing Project`)
128 | loadValueBattleFavorites()
129 | .then(()=>{
130 | return loadCharactersValueFavorites()
131 | })
132 | .then(()=> {
133 | return loadValueComparisonFavorites()
134 | })
135 | .then(()=>{
136 | return getCharacters()
137 | })
138 | .then(characters => {
139 |
140 | var finishSetup = () => {
141 | displayMessage(``)
142 | ipcRenderer.send('workspace-ready')
143 | onSelectView()
144 | }
145 | if(characters && characters.length) {
146 | // do the character data loads in series so we
147 | // can update the loading screen on a per character basis.
148 | // from: https://stackoverflow.com/a/36672042/1535171
149 | var sequence = Promise.resolve()
150 | characters.forEach(function(character) {
151 | sequence = sequence.then(function() {
152 | displayMessage(`Initializing ${character.name}`)
153 | ipcRenderer.send('log', {type: 'progress', message: `Initializing ${character.name}`})
154 | return getBattlePairer(character.id)
155 | }).then(()=>{});
156 | })
157 | sequence.then(()=>{
158 | finishSetup()
159 | })
160 | } else {
161 | finishSetup()
162 | }
163 |
164 | })
165 | .catch(error => {
166 | ipcRenderer.send('log', {type: 'progress', message: `Error: ${error}`})
167 | })
168 |
169 | /**
170 | * Switch
171 | */
172 | function onSelectView() {
173 | if(currentContentView) {
174 | currentContentView.viewWillDisappear()
175 | }
176 | var ContentView = getContentView()
177 | currentContentView = new ContentView(viewProperties)
178 | document.getElementById("content-container").innerHTML = ``
179 | document.getElementById("content-container").appendChild(currentContentView.getView())
180 | currentContentView.on('battle-update', battleOutcome => {
181 | handleBattleUpdate(battleOutcome)
182 | })
183 | currentContentView.on('battle-favorite', battleData => {
184 | updateCharacterBattleFavorites(battleData)
185 | })
186 | currentContentView.on('add-character', data => {
187 | addCharacter(data)
188 | })
189 | currentContentView.on('selected-characters', data => {
190 | selectedCharacters = data
191 | })
192 | currentContentView.on('add-comparison-favorite', data => {
193 | addValueComparisonFavorite(data)
194 | })
195 | currentContentView.on('add-character-value-favorite', data => {
196 | addCharacterValueFavorite(data)
197 | })
198 | currentContentView.on('train', data => {
199 | curViewType = "characterTrainer"
200 | mainViewSelector.setSelectedViewType(curViewType)
201 | selectedCharacters = data
202 | onSelectView()
203 | })
204 | currentContentView.on('viewValues', data => {
205 | curViewType = "valueList"
206 | mainViewSelector.setSelectedViewType(curViewType)
207 | selectedCharacters = data
208 | onSelectView()
209 | })
210 |
211 | backgroundView.nextBackground()
212 | }
213 |
214 | /**
215 | * @return The class for the currently selected main view.
216 | */
217 | function getContentView() {
218 | switch(curViewType) {
219 | case "characterComparison":
220 | return CharacterComparisonView
221 | case "valueList":
222 | return ValueListView
223 | case "characterFavorites":
224 | return CharacterFavoritesView
225 | case "characterTrainer":
226 | return CharacterTrainerView
227 | case "characterConflictComparison":
228 | return CharacterComparisonConflictView
229 | case "internalConflict":
230 | return InternalConflictView
231 | case "valueDifference":
232 | return CharacterComparisonValueDifferenceView
233 | case "commonGround":
234 | return CommonGroundView
235 | case "manageCharacters":
236 | default:
237 | return CharactersView
238 | }
239 | }
240 |
241 | function updateView() {
242 | currentContentView.updateView()
243 | }
244 |
245 | function addCharacter(record) {
246 | knex('Characters').returning('id').insert(record)
247 | .then((result) => {
248 | let newID = result && result[0]
249 | record.id = newID
250 | characters.push(record)
251 |
252 | let newCharacterValueInserts = valuesLib.map((value)=>{
253 | return knex('CharacterValues').insert({characterID: newID, valueID: value.id, score: 0.0, wins: 0, losses: 0, battleCount: 0})
254 | })
255 | Promise.all(newCharacterValueInserts)
256 | .then(()=>{
257 | updateView()
258 | if(currentContentView && typeof currentContentView.onSelectCharacter === "function") {
259 | currentContentView.onSelectCharacter(newID)
260 | }
261 | })
262 | .catch(error => {
263 | displayError(`Error adding character value: ${error}`)
264 | knex.raw(`delete from Characters where "id" = ?`, [newID])
265 | .then(()=>{})
266 | .catch(console.error)
267 | })
268 | })
269 | .catch(error => {
270 | displayError(`Error adding character value: ${error}`)
271 | })
272 | }
273 |
274 | /**
275 | *
276 | * @param {Object} battleOutcome
277 | */
278 | function handleBattleUpdate(battleOutcome) {
279 | let curCharacterValues = characterValues[battleOutcome.characterID]
280 | let isWinnerUpdated, isLoserUpdated
281 | let tailPromises = []
282 | for(let curCharacterValue of curCharacterValues) {
283 | if(curCharacterValue.valueID == battleOutcome.winner) {
284 | curCharacterValue.wins += 1
285 | curCharacterValue.battleCount = curCharacterValue.wins + curCharacterValue.losses
286 | curCharacterValue.score = curCharacterValue.wins / (curCharacterValue.wins + curCharacterValue.losses)
287 | tailPromises.push(knex('CharacterValues').where({id: curCharacterValue.id}).update(curCharacterValue))
288 | isWinnerUpdated = true
289 | } else if(curCharacterValue.valueID == battleOutcome.loser) {
290 | curCharacterValue.losses += 1
291 | curCharacterValue.battleCount = curCharacterValue.wins + curCharacterValue.losses
292 | curCharacterValue.score = curCharacterValue.wins / (curCharacterValue.wins + curCharacterValue.losses)
293 | tailPromises.push(knex('CharacterValues').where({id: curCharacterValue.id}).update(curCharacterValue))
294 | isLoserUpdated = true
295 | }
296 | if(isWinnerUpdated && isLoserUpdated) {
297 | break
298 | }
299 | }
300 | curCharacterValues.sort((a, b) => {
301 | if(a.score === b.score) {
302 | return b.battleCount - a.battleCount
303 | }
304 | return b.score - a.score
305 | })
306 |
307 | updateBattlePairs(battleOutcome)
308 | battlePairers[battleOutcome.characterID].onBattleOutcome(battleOutcome)
309 |
310 | let characterSession = getCharacterSession(battleOutcome.characterID)
311 | characterSessions[battleOutcome.characterID].battleCount += 1
312 | if(!characterSessions[battleOutcome.characterID].battleStart) {
313 | characterSessions[battleOutcome.characterID].battleStart = Date.now()
314 | }
315 |
316 | tailPromises.push(knex('ValuesBattleOutcomes').insert(battleOutcome))
317 | setImmediate(()=>{
318 | Promise.all(tailPromises)
319 | .then(()=> {})
320 | .catch(console.error)
321 | })
322 |
323 | backgroundView.nextBackground()
324 | }
325 |
326 | /**
327 | *
328 | * @param {String} characterID
329 | */
330 | function getCharacterValues(characterID) {
331 | characterID = characterID.toString()
332 | if(characterValues[characterID]) {
333 | return Promise.resolve(characterValues[characterID])
334 | } else {
335 | return new Promise((resolve, reject) => {
336 | knex.raw(`select * from CharacterValues where "characterID" = ?`, [characterID])
337 | .then(queryResult => {
338 | queryResult = JSON.parse(JSON.stringify(queryResult))
339 | let result = queryResult.sort((a, b)=>{
340 | if(a.score === b.score) {
341 | return b.battleCount - a.battleCount
342 | }
343 | return b.score - a.score
344 | })
345 | resolve(result)
346 | characterValues[characterID] = result
347 | })
348 | .catch(console.error)
349 | })
350 | }
351 | }
352 |
353 | /**
354 | *
355 | * @param {String} characterID
356 | */
357 | function getCharacterValuesMap(characterID) {
358 | characterID = characterID.toString()
359 | if(characterValuesMap[characterID]) {
360 | return Promise.resolve(characterValuesMap[characterID])
361 | } else {
362 | return new Promise((fulfill, reject) => {
363 | getCharacterValues(characterID)
364 | .then(values => {
365 | characterValuesMap[characterID] = {}
366 | for(let value of values) {
367 | characterValuesMap[characterID][value.valueID] = value
368 | }
369 | fulfill(characterValuesMap[characterID])
370 | })
371 | .catch(console.error)
372 | })
373 | }
374 | }
375 |
376 | /**
377 | * @return array of characters
378 | */
379 | function getCharacters() {
380 | if(characters) {
381 | return Promise.resolve(characters)
382 | } else {
383 | return new Promise((resolve, reject)=>{
384 | knex.select().table('Characters')
385 | .then(result => {
386 | characters = JSON.parse(JSON.stringify(result))
387 | characters = result
388 | resolve(characters)
389 | })
390 | .catch(reject)
391 | })
392 | }
393 | }
394 |
395 | /**
396 | *
397 | * @param {Number} characterID
398 | */
399 | function getBattlePairer(characterID) {
400 | if(battlePairers[characterID]) {
401 | return Promise.resolve(battlePairers[characterID])
402 | } else {
403 | return new Promise((fulfill, reject) =>{
404 | getCharacterValues(characterID).then(characterValues => {
405 | getCharacterBattlePairs(characterID).then(characterBattlePairs => {
406 | getCharacterValueFavorites(characterID).then(favorites =>{
407 | let properties = {
408 | choices: valuesLib,
409 | characterID: characterID,
410 | values:characterValues,
411 | valuesMap: valuesMap,
412 | favorites: favorites,
413 | battlePairs: characterBattlePairs
414 | }
415 | let battlePairer = new BattlePairer(properties)
416 | battlePairer.init()
417 | .then(()=>{
418 | battlePairers[characterID] = battlePairer
419 | fulfill(battlePairers[characterID])
420 | })
421 | .catch(console.error)
422 | })
423 | })
424 | })
425 | })
426 | }
427 |
428 | return battlePairers[characterID]
429 | }
430 |
431 |
432 | /**
433 | *
434 | * @param {Number} characterID
435 | * @return {Object} has the form objects of the form { winnerID: { loserID: count }, ... }
436 | */
437 | function getCharacterBattlePairs(characterID) {
438 | if(characterBattlePairs[characterID]) {
439 | return Promise.resolve(characterBattlePairs[characterID])
440 | } else {
441 | return new Promise((fulfill, reject) => {
442 | knex.raw(`select * from ValuesBattleOutcomes where "characterID" = ?`, [characterID])
443 | .then((result) => {
444 | if(!result || result.length < 1 || !characterBattlePairs[characterID]) {
445 | characterBattlePairs[characterID] = {}
446 | }
447 | for(let battleOutcome of result) {
448 | updateBattlePairs(battleOutcome)
449 | }
450 | fulfill(characterBattlePairs[characterID])
451 | })
452 | .catch(console.error)
453 | })
454 | }
455 | }
456 |
457 | /**
458 | *
459 | * @param {Object} battleOutcome
460 | */
461 | function updateBattlePairs(battleOutcome) {
462 | // update the pair count.
463 | if(!characterBattlePairs[battleOutcome.characterID]) {
464 | characterBattlePairs[battleOutcome.characterID] = {}
465 | }
466 | let battlePairs = characterBattlePairs[battleOutcome.characterID]
467 | if(!battlePairs.hasOwnProperty(battleOutcome.winner)) {
468 | battlePairs[battleOutcome.winner] = {}
469 | }
470 | let winnerPairs = battlePairs[battleOutcome.winner]
471 | if(!winnerPairs.hasOwnProperty(battleOutcome.loser)) {
472 | winnerPairs[battleOutcome.loser] = 0
473 | }
474 | winnerPairs[battleOutcome.loser] += 1
475 |
476 | // update the total count.
477 | if(!characterBattleCounts[battleOutcome.characterID]) {
478 | characterBattleCounts[battleOutcome.characterID] = 0
479 | }
480 | characterBattleCounts[battleOutcome.characterID] += 1
481 | }
482 |
483 | /**
484 | *
485 | * @param {Number} characterID
486 | * @return {Object} objects have the form { value1ID: { value2ID: true, value2ID: true }, value1ID: {value2ID: true } }
487 | */
488 | function getCharacterValueFavorites(characterID) {
489 | if(characterValueFavorites[characterID]) {
490 | return Promise.resolve(characterValueFavorites[characterID])
491 | } else {
492 | characterValueFavorites[characterID] = {
493 | pairs: {},
494 | values: []
495 | }
496 | return Promise.resolve(characterValueFavorites[characterID])
497 | }
498 | }
499 |
500 | /**
501 | * Updates both the local cache and database of character battle favorites.
502 | * Note that this is a toggle. If the pair exists, it is deleted.
503 | *
504 | * @param {Object} battleData has the shape { character: character, value1: value, value2: value }
505 | */
506 | function updateCharacterBattleFavorites(battleData) {
507 | let record = {
508 | value1ID: battleData.value1.id,
509 | value2ID: battleData.value2.id,
510 | characterID: battleData.character.id
511 | }
512 | if(!characterValueFavorites[record.characterID]) {
513 | characterValueFavorites[record.characterID] = {
514 | pairs: {},
515 | values: []
516 | }
517 | }
518 |
519 | let battlePairs = characterValueFavorites[record.characterID].pairs
520 | let favoriteValues = characterValueFavorites[record.characterID].values
521 | let value1Pairs
522 | let value2Pairs
523 | if(battlePairs.hasOwnProperty(record.value1ID)) {
524 | value1Pairs = battlePairs[record.value1ID]
525 | }
526 | if(battlePairs.hasOwnProperty(record.value2ID)) {
527 | value2Pairs = battlePairs[record.value2ID]
528 | }
529 |
530 | function removeFromFavoriteValues(valueID) {
531 | let index = favoriteValues.indexOf(valueID)
532 | if(index >= 0) {
533 | favoriteValues.splice(index, 1)
534 | }
535 | }
536 |
537 | let replacements = [record.characterID, record.value1ID, record.value2ID]
538 | if(value1Pairs && value1Pairs.hasOwnProperty(record.value2ID)) {
539 | delete value1Pairs[record.value2ID]
540 | removeFromFavoriteValues(record.value1ID)
541 | removeFromFavoriteValues(record.value2ID)
542 | knex.raw(`delete from ValuesBattleFavorites where "characterID" = ? and "value1ID" = ? and "value2ID" = ?`, replacements)
543 | .then(()=>{})
544 | .catch(console.error)
545 | } else if(value2Pairs && value2Pairs.hasOwnProperty(record.value1ID)) {
546 | delete value2Pairs[record.value1ID]
547 | knex.raw(`delete from ValuesBattleFavorites where "characterID" = ? and "value2ID" = ? and "value1ID" = ?`, replacements)
548 | .then(()=>{})
549 | .catch(console.error)
550 | removeFromFavoriteValues(record.value1ID)
551 | removeFromFavoriteValues(record.value2ID)
552 | } else {
553 | if(!value1Pairs) {
554 | battlePairs[record.value1ID] = {}
555 | }
556 | battlePairs[record.value1ID][record.value2ID] = true
557 |
558 | if(favoriteValues.indexOf(record.value1ID) < 0) {
559 | favoriteValues.push(record.value1ID)
560 | }
561 | if(favoriteValues.indexOf(record.value2ID) < 0) {
562 | favoriteValues.push(record.value2ID)
563 | }
564 |
565 | knex('ValuesBattleFavorites').insert(record)
566 | .then(()=>{})
567 | .catch(console.error)
568 | }
569 | }
570 |
571 | /**
572 | *
573 | * @param {Number} characterID
574 | * @return {Number} Number of battles the character has been trained on.
575 | */
576 | function getCharacterBattleCount(characterID) {
577 | if(!characterBattleCounts[characterID]) {
578 | characterBattleCounts[characterID] = 0
579 | }
580 | return characterBattleCounts[characterID]
581 | }
582 |
583 | /**
584 | *
585 | * @param {Number} characterID
586 | * @return {Object} Object of the shape { battleCount: count, battleStart: timestamp }
587 | */
588 | function getCharacterSession(characterID) {
589 | if(!characterSessions[characterID]) {
590 | characterSessions[characterID] = {
591 | battleCount: 0,
592 | battleStart: 0
593 | }
594 | }
595 | return characterSessions[characterID]
596 | }
597 |
598 | function getSelectedCharacters() {
599 | return selectedCharacters
600 | }
601 |
602 | function cacheValueBattleFavorite(record) {
603 | if(!characterValueFavorites[record.characterID]) {
604 | characterValueFavorites[record.characterID] = {
605 | pairs: {},
606 | values: []
607 | }
608 | }
609 |
610 | var favoritePairs = characterValueFavorites[record.characterID].pairs
611 | var favoriteValues = characterValueFavorites[record.characterID].values
612 | if(!favoritePairs[record.value1ID]) {
613 | favoritePairs[record.value1ID] = {}
614 | }
615 | favoritePairs[record.value1ID][record.value2ID] = true
616 |
617 | if(favoriteValues.indexOf(record.value1ID) < 0) {
618 | favoriteValues.push(record.value1ID)
619 | }
620 | if(favoriteValues.indexOf(record.value2ID) < 0) {
621 | favoriteValues.push(record.value2ID)
622 | }
623 | }
624 |
625 | function loadValueBattleFavorites() {
626 | return new Promise((fulfill, reject) => {
627 | knex.raw(`select * from ValuesBattleFavorites`)
628 | .then((records) => {
629 | if(!records) {
630 | fulfill([])
631 | }
632 | for(let record of records) {
633 | cacheValueBattleFavorite(record)
634 | }
635 | fulfill(records)
636 | })
637 | .catch(reject)
638 | })
639 | }
640 |
641 | function loadValueComparisonFavorites() {
642 | return new Promise((fulfill, reject) => {
643 | knex.raw(`select * from ValueComparisonFavorites`)
644 | .then(records => {
645 | if(!records) {
646 | return fulfill([])
647 | }
648 | for(let record of records) {
649 | cacheValueComparisonFavorite(record)
650 | }
651 | fulfill(records)
652 | })
653 | .catch(reject)
654 | })
655 | }
656 |
657 | function cacheValueComparisonFavorite(record) {
658 | if(!valueComparisonFavorites[record.character1ID]) {
659 | valueComparisonFavorites[record.character1ID] = {}
660 | }
661 | if(!valueComparisonFavorites[record.character1ID][record.value1ID]) {
662 | valueComparisonFavorites[record.character1ID][record.value1ID] = {}
663 | }
664 | if(!valueComparisonFavorites[record.character1ID][record.value1ID][record.character2ID]) {
665 | valueComparisonFavorites[record.character1ID][record.value1ID][record.character2ID] = {}
666 | }
667 | if(!valueComparisonFavorites[record.character1ID][record.value1ID][record.character2ID][record.value2ID]) {
668 | valueComparisonFavorites[record.character1ID][record.value1ID][record.character2ID][record.value2ID] = {}
669 | }
670 |
671 | if(!characterComparisonFavorites[record.character1ID]) {
672 | characterComparisonFavorites[record.character1ID] = {}
673 | }
674 | if(!characterComparisonFavorites[record.character1ID][record.character2ID]) {
675 | characterComparisonFavorites[record.character1ID][record.character2ID] = []
676 | }
677 |
678 | let comparisonRecord = {}
679 | comparisonRecord[record.character1ID] = record.value1ID
680 | comparisonRecord[record.character2ID] = record.value2ID
681 | characterComparisonFavorites[record.character1ID][record.character2ID].push(comparisonRecord)
682 |
683 | getCharacterValueFavorites(record.character1ID)
684 | .then((characterValueFavorites) => {
685 | var favoriteValues = characterValueFavorites.values
686 | if(favoriteValues.indexOf(record.value1ID) < 0) {
687 | favoriteValues.push(record.value1ID)
688 | }
689 |
690 | if(record.character1ID === record.character2ID) {
691 | var favoritePairs = characterValueFavorites.pairs
692 | if(!favoritePairs[record.value1ID]) {
693 | favoritePairs[record.value1ID] = {}
694 | }
695 | favoritePairs[record.value1ID][record.value2ID] = true
696 | }
697 | })
698 |
699 | getCharacterValueFavorites(record.character2ID)
700 | .then((characterValueFavorites) => {
701 | var favoriteValues = characterValueFavorites.values
702 | if(favoriteValues.indexOf(record.value2ID) < 0) {
703 | favoriteValues.push(record.value2ID)
704 | }
705 | })
706 | }
707 |
708 | function addValueComparisonFavorite(data) {
709 | cacheValueComparisonFavorite(data)
710 | knex('ValueComparisonFavorites').returning('id').insert(data)
711 | .then((result) => {
712 | console.log(`added: ${JSON.stringify(data)}`)
713 | })
714 | .catch(console.error)
715 | }
716 |
717 | function cacheCharacterValueFavorite(record) {
718 | getCharacterValueFavorites(record.characterID)
719 | .then((characterValueFavorites) => {
720 | var favoriteValues = characterValueFavorites.values
721 | if(favoriteValues.indexOf(record.valueID) < 0) {
722 | favoriteValues.push(record.valueID)
723 | }
724 | })
725 | }
726 |
727 | function addCharacterValueFavorite(data) {
728 | cacheCharacterValueFavorite(data)
729 | knex('CharacterValueFavorites').returning('id').insert(data)
730 | .then((result) => {
731 | console.log(`added: ${JSON.stringify(data)}`)
732 | })
733 | .catch(console.error)
734 | }
735 |
736 | function loadCharactersValueFavorites() {
737 | return new Promise((fulfill, reject) => {
738 | knex.raw(`select * from CharacterValueFavorites`)
739 | .then(records => {
740 | for(let record of records) {
741 | cacheCharacterValueFavorite(record)
742 | }
743 | fulfill(records)
744 | })
745 | .catch(reject)
746 | })
747 | }
748 |
749 | function displayMessage(message) {
750 | let messageDiv = document.getElementById("message")
751 | messageDiv.innerHTML = message
752 | }
753 |
754 | function displayError(message) {
755 | let messageDiv = document.getElementById("error")
756 | messageDiv.innerHTML = message
757 | }
--------------------------------------------------------------------------------
/js/views/value-list-view.js:
--------------------------------------------------------------------------------
1 | const MainBaseView = require('./main-base-view.js')
2 | const FavoriteButton = require('./favorite-button.js')
3 |
4 | module.exports = class ValueListView extends MainBaseView {
5 | constructor(properties) {
6 | super(properties)
7 |
8 | this.root.setAttribute("id", "value-list-container")
9 |
10 | this.characterSelector = document.createElement("select")
11 | this.characterSelector.setAttribute("id", "character-selector")
12 | this.characterSelector.addEventListener('change', (event)=>{
13 | this.currentCharacterID = parseInt(event.target.value)
14 | this.onSelectCharacter(this.currentCharacterID)
15 | })
16 | this.root.appendChild(this.characterSelector)
17 |
18 | this.isFiltering = false
19 | this.favoritesFilter = document.createElement("div")
20 | this.favoritesFilter.classList.add("favorites-filter-button")
21 | this.favoritesFilter.innerHTML = `Show Favorites`
22 | this.favoritesFilter.addEventListener("click", (event) => {
23 | this.isFiltering = !this.isFiltering
24 | if(this.isFiltering) {
25 | this.favoritesFilter.innerHTML = `Show All`
26 | } else {
27 | this.favoritesFilter.innerHTML = `Show Favorites`
28 | }
29 | this.updateView()
30 | })
31 | this.root.appendChild(this.favoritesFilter)
32 |
33 | this.valuesViewContainer = document.createElement("div")
34 | this.valuesViewContainer.setAttribute("id", "values-view-container")
35 | this.root.appendChild(this.valuesViewContainer)
36 |
37 | this.characters = []
38 | this.getCharacters()
39 | .then(inCharacters => {
40 | this.characters = inCharacters
41 |
42 | let selectedCharacter
43 | let selectedCharacters = this.getSelectedCharacters()
44 | if(selectedCharacters && selectedCharacters.length) {
45 | selectedCharacter = selectedCharacters[0]
46 | } else if(this.characters && this.characters.length) {
47 | selectedCharacter = this.characters[0]
48 | }
49 |
50 | this.characterSelector.innerHTML = ``
51 | for(let character of this.characters) {
52 |
53 | let option = document.createElement("option")
54 | option.setAttribute("value", character.id)
55 | option.innerHTML = character.name
56 | this.characterSelector.appendChild(option)
57 | if(selectedCharacter && selectedCharacter.id === character.id) {
58 | option.setAttribute("selected", true)
59 | }
60 | }
61 | this.onSelectCharacter(selectedCharacter.id)
62 | })
63 | .catch(console.error)
64 | }
65 |
66 | updateView() {
67 | if(!this.currentCharacterID) {
68 | return
69 | }
70 |
71 | this.valuesViewContainer.innerHTML = ``
72 |
73 | this.getCharacterValues(this.currentCharacterID)
74 | .then(characterValues => {
75 | this.valuesView = this.getValuesView(characterValues)
76 | this.valuesViewContainer.appendChild(this.valuesView)
77 | })
78 | .catch(console.error)
79 | }
80 |
81 | onSelectCharacter(characterID) {
82 | this.currentCharacterID = characterID
83 | this.updateView()
84 | }
85 |
86 | getValuesView(values) {
87 | let result = document.createElement('div')
88 | result.setAttribute("id", "values-view")
89 |
90 | for(let value of values) {
91 | this.getCharacterValueFavorites(this.currentCharacterID)
92 | .then(characterValueFavorites => {
93 | let favoriteValues = characterValueFavorites.values
94 | let isFavorite = favoriteValues.indexOf(value.valueID) >= 0
95 |
96 | let valueView = document.createElement('div')
97 | valueView.classList.add("value-list-container")
98 |
99 | let favButtonProperties = {
100 | checked: isFavorite,
101 | enabled: !isFavorite,
102 | className: "favorite-button-container-value-list-view"
103 | }
104 |
105 | var self = this
106 | let favButton = new FavoriteButton(favButtonProperties)
107 | favButton.setHandler(function(event) {
108 | self.emit('add-character-value-favorite', {valueID: value.valueID, characterID: self.currentCharacterID})
109 | favButton.setChecked(true)
110 | favButton.setEnabled(false)
111 | })
112 | valueView.appendChild(favButton.getView())
113 |
114 | let progressView = document.createElement('div')
115 | progressView.setAttribute("class", "value-list-progress")
116 | progressView.setAttribute("style", `width: ${value.score*100}%`)
117 | valueView.appendChild(progressView)
118 | let nameView = document.createElement('div')
119 | nameView.setAttribute("class", "value-list-label")
120 | nameView.innerHTML = `${this.valuesMap[value.valueID.toString()].name} | ${value.score} | Wins: ${value.wins}, Losses: ${value.losses} | Battles: ${value.battleCount}`
121 | valueView.appendChild(nameView)
122 | if(this.isFiltering && isFavorite) {
123 | result.appendChild(valueView)
124 | } else if(!this.isFiltering) {
125 | result.appendChild(valueView)
126 | }
127 | })
128 | .catch(console.error)
129 | }
130 | return result
131 | }
132 |
133 | }
--------------------------------------------------------------------------------
/js/views/welcome-window.js:
--------------------------------------------------------------------------------
1 | const {ipcRenderer, shell, remote} = require('electron')
2 | const knex = remote.getGlobal('knex')
3 | const prefsModule = require('electron').remote.require('./js/prefs.js')
4 | const fs = require('fs')
5 |
6 | let container = document.getElementById("container")
7 | let recentContainer = document.createElement("div")
8 | recentContainer.setAttribute("id", "recent-container")
9 | let recentHeader = document.createElement("h2")
10 | recentHeader.classList.add("recent-documents-header")
11 | recentHeader.innerHTML = `Recent Documents`
12 | recentContainer.appendChild(recentHeader)
13 | container.appendChild(recentContainer)
14 |
15 | let recentDocumentsList = document.createElement("div")
16 | recentDocumentsList.classList.add("flex-wrap")
17 | recentDocumentsList.classList.add("recent-documents-list")
18 | recentContainer.appendChild(recentDocumentsList)
19 |
20 | function updateRecentDocuments() {
21 | recentDocumentsList.innerHTML = ``
22 | let recentDocuments = prefsModule.getPrefs('welcome')['recentDocuments']
23 | if (recentDocuments && recentDocuments.length>0) {
24 | for (var recentDocument of recentDocuments) {
25 | try {
26 | fs.accessSync(recentDocument.filename, fs.R_OK)
27 | } catch (e) {
28 | // It isn't accessible
29 | continue
30 | }
31 | let recent = document.createElement("div")
32 | recent.classList.add("recent-document")
33 | recent.classList.add("button")
34 | recent.innerHTML = recentDocument.title
35 | recent.setAttribute("data-filename", recentDocument.filename)
36 | recent.addEventListener("click", (event)=>{
37 | event.target.setAttribute("disabled", true)
38 | ipcRenderer.send("open-project", event.target.dataset.filename)
39 | })
40 | recentDocumentsList.appendChild(recent)
41 | }
42 | }
43 | }
44 |
45 | let buttonContainer = document.createElement("div")
46 | buttonContainer.setAttribute("id", "button-container")
47 | container.appendChild(buttonContainer)
48 |
49 | let newButton = document.createElement("div")
50 | newButton.innerHTML = "New Project"
51 | newButton.classList.add("button")
52 | newButton.addEventListener('click', event => {
53 | ipcRenderer.send('new-project')
54 | })
55 | buttonContainer.appendChild(newButton)
56 |
57 | let openButton = document.createElement("div")
58 | openButton.innerText = "Open Project"
59 | openButton.classList.add("button")
60 | openButton.addEventListener('click', event => {
61 | ipcRenderer.send('browse-for-project')
62 | })
63 | buttonContainer.appendChild(openButton)
64 |
65 | ipcRenderer.on('update-recent-documents', (event, args)=>{
66 | updateRecentDocuments()
67 | })
68 |
69 | updateRecentDocuments()
70 |
71 |
--------------------------------------------------------------------------------
/loading-status.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
45 |
46 |
47 |
48 |
49 |
Loading
50 |
Initializing …
51 |
52 |
53 |
54 |
73 |
74 |
--------------------------------------------------------------------------------
/main-window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Characterizer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const {app, ipcMain, BrowserWindow, dialog} = require('electron')
2 | const path = require('path')
3 | const url = require('url')
4 | const fs = require('fs')
5 | const userDataPath = app.getPath('userData')
6 | const valuesSeedDataPath = path.join(__dirname, "data", "values.txt")
7 | const {seedDB} = require('./js/database-init.js')
8 | const prefModule = require('./js/prefs.js')
9 | const utils = require('./js/utils.js')
10 | const trash = require('trash')
11 | const autoUpdater = require('./auto-updater')
12 | const isDev = require('electron-is-dev')
13 |
14 | let welcomeWindow
15 | let loadingStatusWindow
16 | let mainWindow
17 |
18 | function createWelcomeWindow () {
19 | welcomeWindow = new BrowserWindow({width: 800, height: 600})
20 | welcomeWindow.loadURL(url.format({
21 | pathname: path.join(__dirname, 'welcome-window.html'),
22 | title: "Characterizer",
23 | protocol: 'file:',
24 | slashes: true
25 | }))
26 |
27 | welcomeWindow.on('closed', () => {
28 | mainWindow = null
29 | })
30 |
31 | if (!isDev) {
32 | autoUpdater.init()
33 | }
34 | }
35 |
36 | app.on('ready', createWelcomeWindow)
37 |
38 | app.on('window-all-closed', () => {
39 | if (process.platform !== 'darwin') {
40 | app.quit()
41 | }
42 | })
43 |
44 | ipcMain.on('open-project', (e, arg)=>{
45 | showMainWindow(arg)
46 | addToRecentDocs(arg)
47 | })
48 |
49 | ipcMain.on('browse-for-project', (e, arg)=> {
50 | browseForProject()
51 | })
52 |
53 | function browseForProject() {
54 | let properties = {
55 | title:"Open Script",
56 | filters:[
57 | {
58 | name: 'Database File',
59 | extensions: ['sqlite']
60 | }
61 | ]
62 | }
63 | dialog.showOpenDialog(properties, (filenames)=>{
64 | if (filenames) {
65 | showMainWindow(filenames[0])
66 | addToRecentDocs(filenames[0])
67 | }
68 | })
69 | }
70 |
71 | ipcMain.on('new-project', (e, arg)=> {
72 | createNewProject()
73 | })
74 |
75 | ipcMain.on('log', (event, opt) => {
76 | !loadingStatusWindow.isDestroyed() && loadingStatusWindow.webContents.send('log', opt)
77 | })
78 |
79 | ipcMain.on('workspace-ready', event => {
80 | mainWindow && mainWindow.show()
81 | !loadingStatusWindow.isDestroyed() && loadingStatusWindow.hide()
82 | })
83 |
84 |
85 | function createNewProject() {
86 | let properties = {
87 | title: "New Project",
88 | buttonLabel: "Create",
89 | }
90 | dialog.showSaveDialog(properties, filename => {
91 | if(!filename) {
92 | return
93 | }
94 | function openWindow() {
95 | fs.mkdirSync(filename)
96 | let projectName = path.basename(filename)
97 | let dbFileName = path.join(filename, projectName + '.sqlite')
98 | showMainWindow(dbFileName)
99 | addToRecentDocs(dbFileName)
100 | }
101 |
102 | if(fs.existsSync(filename)) {
103 | if(fs.lstatSync(filename).isDirectory()) {
104 | console.log('\ttrash existing folder', filename)
105 | trash(filename)
106 | .then(openWindow)
107 | .catch(console.error)
108 | } else {
109 | dialog.showMessageBox(null, {
110 | message: "Could not overwrite file " + path.basename(filename) + ". Only folders can be overwritten."
111 | })
112 | return
113 | }
114 | } else {
115 | openWindow()
116 | }
117 | })
118 | }
119 |
120 | function showMainWindow(dbFile) {
121 | let basename = path.basename(dbFile)
122 | if(!loadingStatusWindow) {
123 | loadingStatusWindow = new BrowserWindow({
124 | width: 450,
125 | height: 150,
126 | backgroundColor: '#333333',
127 | show: false,
128 | frame: false,
129 | resizable: false
130 | })
131 | }
132 |
133 | loadingStatusWindow.loadURL(`file://${__dirname}/loading-status.html?name=${basename}`)
134 | loadingStatusWindow.once('ready-to-show', () => {
135 | loadingStatusWindow.show()
136 | })
137 |
138 | try {
139 | var knex = require('knex')({
140 | client: 'sqlite3',
141 | connection: {
142 | filename: dbFile
143 | }
144 | })
145 | global.knex = knex
146 |
147 | let migrationsPath = path.join(__dirname, `migrations`)
148 |
149 | knex.migrate.latest({directory: migrationsPath})
150 | .then(()=> {
151 | return seedDB(knex, {valuesSeedDataPath})
152 | })
153 | .then(()=>{
154 | if(mainWindow) {
155 | mainWindow.close()
156 | }
157 | mainWindow = new BrowserWindow({
158 | width: 800,
159 | height: 600,
160 | show: false,
161 | title: basename,
162 | })
163 | mainWindow.loadURL(url.format({
164 | pathname: path.join(__dirname, 'main-window.html'),
165 | protocol: 'file:',
166 | slashes: true
167 | }))
168 | mainWindow.on('closed', () => {
169 | mainWindow = null
170 | })
171 | })
172 | .catch(error => {
173 | // The loading status window doesn't receive the message unless it's delayed a little
174 | setTimeout(()=>{
175 | loadingStatusWindow.webContents.send('log', { type: "progress", message: error.toString()})
176 | }, 500)
177 | })
178 | } catch(error) {
179 | setTimeout(()=>{
180 | loadingStatusWindow.webContents.send('log', { type: "progress", message: error.toString()})
181 | }, 500)
182 | }
183 |
184 | }
185 |
186 | let addToRecentDocs = (filename, metadata={}) => {
187 | let prefs = prefModule.getPrefs('add to recent')
188 |
189 | let recentDocuments
190 | if (!prefs.recentDocuments) {
191 | recentDocuments = []
192 | } else {
193 | recentDocuments = JSON.parse(JSON.stringify(prefs.recentDocuments))
194 | }
195 |
196 | let currPos = 0
197 |
198 | for (var document of recentDocuments) {
199 | if (document.filename == filename) {
200 | recentDocuments.splice(currPos, 1)
201 | break
202 | }
203 | currPos++
204 | }
205 |
206 | let recentDocument = metadata
207 |
208 | if (!recentDocument.title) {
209 | let title = filename.split(path.sep)
210 | title = title[title.length-1]
211 | title = title.split('.')
212 | title.splice(-1,1)
213 | title = title.join('.')
214 | recentDocument.title = title
215 | }
216 |
217 | recentDocument.filename = filename
218 | recentDocument.time = Date.now()
219 | recentDocuments.unshift(recentDocument)
220 | // save
221 | prefModule.set('recentDocuments', recentDocuments)
222 |
223 | if(welcomeWindow) {
224 | welcomeWindow.webContents.send('update-recent-documents')
225 | }
226 | }
--------------------------------------------------------------------------------
/migrations/001-setup.js:
--------------------------------------------------------------------------------
1 | module.exports.up = function(knex, Promise) {
2 | let characterQuery = knex.schema.hasTable('Characters').then((exists) => {
3 | if(!exists) {
4 | return knex.schema.createTable('Characters', function(table) {
5 | table.string('name')
6 | table.increments('id').primary()
7 | table.timestamps(false, true)
8 | })
9 | } else {
10 | return Promise.resolve(true)
11 | }
12 | })
13 |
14 | let valuesQuery = knex.schema.hasTable('Values').then((exists) => {
15 | if(!exists) {
16 | return knex.schema.createTable('Values', function(table) {
17 | table.string('name')
18 | table.increments('id').primary()
19 | table.string('uuid')
20 | table.timestamps(false, true)
21 | })
22 | } else {
23 | return Promise.resolve(true)
24 | }
25 | })
26 |
27 | let collectionsQuery = knex.schema.hasTable('Collections').then((exists) => {
28 | if(!exists) {
29 | return knex.schema.createTable('Collections', function(table) {
30 | table.string('name')
31 | table.increments('id').primary()
32 | table.timestamps(false, true)
33 | })
34 | } else {
35 | return Promise.resolve(true)
36 | }
37 | })
38 |
39 | let characterValuesQuery = knex.schema.hasTable('CharacterValues').then((exists) => {
40 | if(!exists) {
41 | return knex.schema.createTable('CharacterValues',function(table){
42 | table.increments('id').primary()
43 | table.integer('characterID').references('id').inTable('Characters')
44 | table.integer('valueID').references('id').inTable('Values')
45 | table.integer('wins')
46 | table.integer('losses')
47 | table.integer('battleCount')
48 | table.float('score')
49 | table.timestamps(false, true)
50 | })
51 | } else {
52 | return Promise.resolve(true)
53 | }
54 | })
55 |
56 | let valuesCollectionsQuery = knex.schema.hasTable('ValuesCollections').then((exists) => {
57 | if(!exists) {
58 | return knex.schema.createTable('ValuesCollections',function(table){
59 | table.increments('id').primary()
60 | table.string('name')
61 | table.integer('valueID').references('id').inTable('Values')
62 | table.timestamps(false, true)
63 | })
64 | } else {
65 | return Promise.resolve(true)
66 | }
67 | })
68 |
69 | let valuesBattleOutcomesQuery = knex.schema.hasTable('ValuesBattleOutcomes').then((exists) => {
70 | if(!exists) {
71 | return knex.schema.createTable('ValuesBattleOutcomes',function(table){
72 | table.increments('id').primary()
73 | table.integer('characterID').references('id').inTable('Characters')
74 | table.integer('loser').references('id').inTable('Values')
75 | table.integer('winner').references('id').inTable('Values')
76 | table.timestamps(false, true)
77 | })
78 | } else {
79 | return Promise.resolve(true)
80 | }
81 | })
82 |
83 | let valuesBattleFavorites = knex.schema.hasTable('ValuesBattleFavorites').then((exists) => {
84 | if(!exists) {
85 | return knex.schema.createTable('ValuesBattleFavorites',function(table){
86 | table.increments('id').primary()
87 | table.integer('characterID').references('id').inTable('Characters')
88 | table.integer('value1ID').references('id').inTable('Values')
89 | table.integer('value2ID').references('id').inTable('Values')
90 | table.timestamps(false, true)
91 | })
92 | } else {
93 | return Promise.resolve(true)
94 | }
95 | })
96 |
97 | return Promise.all([
98 | characterQuery,
99 | valuesQuery,
100 | collectionsQuery,
101 | characterValuesQuery,
102 | valuesCollectionsQuery,
103 | valuesBattleOutcomesQuery
104 | ])
105 | }
106 |
107 | module.exports.down = function(knex, Promise) {
108 |
109 | }
--------------------------------------------------------------------------------
/migrations/002-favorites.js:
--------------------------------------------------------------------------------
1 | module.exports.up = function(knex, Promise) {
2 | let ValueComparisonFavorites = knex.schema.hasTable('ValueComparisonFavorites').then((exists) => {
3 | if(!exists) {
4 | return knex.schema.createTable('ValueComparisonFavorites', function(table) {
5 | table.integer('character1ID').references('id').inTable('Characters')
6 | table.integer('value1ID').references('id').inTable('Values')
7 |
8 | table.integer('character2ID').references('id').inTable('Characters')
9 | table.integer('value2ID').references('id').inTable('Values')
10 |
11 | table.timestamps(false, true)
12 | table.increments('id').primary()
13 | })
14 | } else {
15 | return Promise.resolve(true)
16 | }
17 | })
18 |
19 | let CharacterValueFavorites = knex.schema.hasTable('CharacterValueFavorites').then((exists) => {
20 | if(!exists) {
21 | return knex.schema.createTable('CharacterValueFavorites', function(table) {
22 | table.integer('characterID').references('id').inTable('Characters')
23 | table.integer('valueID').references('id').inTable('Values')
24 | table.timestamps(false, true)
25 | table.increments('id').primary()
26 | })
27 | } else {
28 | return Promise.resolve(true)
29 | }
30 | })
31 |
32 | return Promise.all([
33 | ValueComparisonFavorites,
34 | CharacterValueFavorites
35 | ])
36 | }
37 |
38 | module.exports.down = function(knex, Promise) {
39 |
40 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "characterizer",
3 | "version": "0.4.0",
4 | "description": "The best app to train character values and generate conflicts, dilemmas, and common ground between characters.",
5 | "main": "main.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/wonderunit/characterizer.git"
9 | },
10 | "scripts": {
11 | "start": "electron .",
12 | "postinstall": "install-app-deps",
13 | "pack": "electron-builder --dir",
14 | "dist": "electron-builder",
15 | "dist:mac": "build -m",
16 | "dist:win": "build -w",
17 | "dist:linux": "build -l"
18 | },
19 | "devDependencies": {
20 | "electron": "4.0.0-beta.7",
21 | "electron-builder": "20.36.2"
22 | },
23 | "dependencies": {
24 | "electron-updater": "4.0.4",
25 | "knex": "^0.15.2",
26 | "nan": "^2.7.0",
27 | "sqlite3": "4.0.4",
28 | "trash": "^4.0.1",
29 | "electron-is-dev": "1.0.1"
30 | },
31 | "build": {
32 | "asar": true,
33 | "appId": "com.wonderunit.characterizer",
34 | "mac": {
35 | "icon": "build/icon.icns"
36 | },
37 | "dmg": {
38 | "background": "build/background.png",
39 | "icon": "build/icon.icns",
40 | "iconSize": 140,
41 | "contents": [
42 | {
43 | "x": 120,
44 | "y": 250
45 | },
46 | {
47 | "x": 420,
48 | "y": 250,
49 | "type": "link",
50 | "path": "/Applications"
51 | }
52 | ]
53 | },
54 | "win": {
55 | "icon": "build/icon.ico"
56 | },
57 | "files": [
58 | "**/*",
59 | "!*.md",
60 | "!README.md"
61 | ],
62 | "nsis": {
63 | "perMachine": true
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/update.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Auto Update
6 |
7 |
8 | Downloading …
9 |
10 |
11 |
14 |
15 | 0%
16 |
17 |
18 |
19 | Release Notes
20 |
21 |
22 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/welcome-window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Characterizer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
--------------------------------------------------------------------------------