├── .gitignore
├── favicon.png
├── favicon_localhost.png
├── js
├── lib
│ ├── jquery-ui
│ │ ├── images
│ │ │ ├── ui-icons_444444_256x240.png
│ │ │ ├── ui-icons_555555_256x240.png
│ │ │ ├── ui-icons_777620_256x240.png
│ │ │ ├── ui-icons_777777_256x240.png
│ │ │ ├── ui-icons_cc0000_256x240.png
│ │ │ └── ui-icons_ffffff_256x240.png
│ │ ├── LICENSE.txt
│ │ ├── jquery-ui.theme.min.css
│ │ ├── AUTHORS.txt
│ │ └── jquery-ui.structure.min.css
│ ├── chartjs
│ │ ├── Chart.min.css
│ │ └── Chart.css
│ ├── awesomplete
│ │ └── awesomplete.css
│ └── FileSaver.js
├── env.js.map
├── env.js
├── env.ts
├── navbar.js
├── navbar.ts
├── navbar.js.map
├── src
│ ├── listManager.js.map
│ ├── listManager.js
│ ├── util.js.map
│ ├── util.js
│ ├── listManager.ts
│ ├── util.ts
│ ├── heatmap.js.map
│ ├── heatmap.js
│ ├── heatmap.ts
│ ├── MAL.js.map
│ ├── MAL.js
│ ├── MAL.ts
│ └── animelistTL.js.map
└── lullPage.js.map
├── tsconfig.json
├── scripts
└── unwrap_labels.py
├── LICENSE
├── README.md
├── time-to-watch
├── duration.css
└── index.html
├── lull
├── lull.css
└── index.html
├── res
├── logo.svg
└── root.css
└── chart.html
/.gitignore:
--------------------------------------------------------------------------------
1 | #
2 | .idea/
3 | .vscode
4 |
5 | favicon.png
6 |
7 | # EOF
8 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/favicon.png
--------------------------------------------------------------------------------
/favicon_localhost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/favicon_localhost.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_444444_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_444444_256x240.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_555555_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_555555_256x240.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_777620_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_777620_256x240.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_777777_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_777777_256x240.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_cc0000_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_cc0000_256x240.png
--------------------------------------------------------------------------------
/js/lib/jquery-ui/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkviii/js-animelist-timeline/HEAD/js/lib/jquery-ui/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "sourceMap": true,
5 | "module": "es6",
6 | "baseUrl": "./js"
7 | },
8 | "exclude": [
9 | "node_modules"
10 | ],
11 |
12 | }
--------------------------------------------------------------------------------
/js/env.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"env.js","sourceRoot":"","sources":["env.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,MAAM,CAAC,IAAI,KAAK,GAAY,KAAK,CAAC;AAClC,mCAAmC;AAEnC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAY,KAAK,CAAC;AAC5C,8CAA8C;AAG9C,0DAA0D;AAC1D,IAAI,KAAK,IAAI,aAAa,EAAE,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AACxC,CAAC"}
--------------------------------------------------------------------------------
/js/lib/chartjs/Chart.min.css:
--------------------------------------------------------------------------------
1 | @keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}
--------------------------------------------------------------------------------
/js/env.js:
--------------------------------------------------------------------------------
1 | /** Enable extra features that aren't generally useful, Might be able to change in the console. */
2 | export var debug = false;
3 | // export var debug: boolean = true
4 | /** Use a local file instead of asking anilist's servers */
5 | export const usingTestData = false;
6 | // export const usingTestData: boolean = true;
7 | // Should probably figure out something to enforce that...
8 | if (debug || usingTestData) {
9 | console.log("Using debug settings.");
10 | console.warn("Don't commit debug!");
11 | }
12 | //# sourceMappingURL=env.js.map
--------------------------------------------------------------------------------
/js/env.ts:
--------------------------------------------------------------------------------
1 | /** Enable extra features that aren't generally useful, Might be able to change in the console. */
2 | export var debug: boolean = false;
3 | // export var debug: boolean = true
4 |
5 | /** Use a local file instead of asking anilist's servers */
6 | export const usingTestData: boolean = false;
7 | // export const usingTestData: boolean = true;
8 |
9 |
10 | // Should probably figure out something to enforce that...
11 | if (debug || usingTestData) {
12 | console.log("Using debug settings.");
13 | console.warn("Don't commit debug!");
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/unwrap_labels.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import sys
3 | from bs4 import BeautifulSoup
4 |
5 |
6 | def main():
7 | if len(sys.argv) != 2:
8 | print("asdf")
9 | sys.exit(1)
10 | filename = Path(sys.argv[1])
11 |
12 | soup = BeautifulSoup(filename.read_text(), "html5lib")
13 |
14 | label_list = soup.find_all("label")
15 |
16 | for label in label_list:
17 | inp = label.find("input")
18 | if not inp:
19 | continue
20 |
21 | label["for"] = inp["id"]
22 | ch = label.findChildren()
23 | for c in ch:
24 | label.insert_after(c.extract())
25 | label.string = label.text.strip()
26 |
27 |
28 | # out = soup.prettify()
29 | out = soup.encode(formatter="html5").decode("utf8")
30 |
31 | print(out)
32 |
33 |
34 | main()
--------------------------------------------------------------------------------
/js/lib/chartjs/Chart.css:
--------------------------------------------------------------------------------
1 | /*
2 | * DOM element rendering detection
3 | * https://davidwalsh.name/detect-node-insertion
4 | */
5 | @keyframes chartjs-render-animation {
6 | from { opacity: 0.99; }
7 | to { opacity: 1; }
8 | }
9 |
10 | .chartjs-render-monitor {
11 | animation: chartjs-render-animation 0.001s;
12 | }
13 |
14 | /*
15 | * DOM element resizing detection
16 | * https://github.com/marcj/css-element-queries
17 | */
18 | .chartjs-size-monitor,
19 | .chartjs-size-monitor-expand,
20 | .chartjs-size-monitor-shrink {
21 | position: absolute;
22 | direction: ltr;
23 | left: 0;
24 | top: 0;
25 | right: 0;
26 | bottom: 0;
27 | overflow: hidden;
28 | pointer-events: none;
29 | visibility: hidden;
30 | z-index: -1;
31 | }
32 |
33 | .chartjs-size-monitor-expand > div {
34 | position: absolute;
35 | width: 1000000px;
36 | height: 1000000px;
37 | left: 0;
38 | top: 0;
39 | }
40 |
41 | .chartjs-size-monitor-shrink > div {
42 | position: absolute;
43 | width: 200%;
44 | height: 200%;
45 | left: 0;
46 | top: 0;
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-animelist-timeline
2 |
3 | Generate an SVG timeline chronologizing when you watched anime. https://linkviii.github.io/js-animelist-timeline/
4 |
5 | Test it with Username: linkviii, Width: 2000, From: 2016-01-01, To: 2016-06-01. https://linkviii.github.io/js-animelist-timeline/?n=linkviii&w=1400&dtS=2020-10-01&dtF=2020-11-07&lang=english&era=false&kind=ANIME&fs=8
6 |
7 | Anilist Forum thread: https://anilist.co/forum/thread/18603/1
8 |
9 |
10 | ## Build
11 | ```Bash
12 | npm install -g typescript
13 | git clone https://github.com/Linkviii/js-animelist-timeline.git
14 | # Build
15 | tsc
16 | ```
17 |
18 | ## Dependencies
19 | * https://github.com/Linkviii/js-timeline - MIT license
20 | * [svg.js](http://svgjs.com/): © 2012 - 2016 Wout Fierens - svg.js is released under the terms of the MIT license.
21 | * [strftime](https://github.com/samsonjs/strftime): Copyright 2010 - 2016 Sami Samhuri sami@samhuri.net - MIT license
22 | * https://github.com/eligrey/FileSaver.js/ - MIT license
23 | * jquery - MIT license
24 | * Chart.js - MIT license
25 |
26 | Timeline layout issues are probably for js-timeline.
27 |
28 | ## License
29 | Project is MIT licensed. Help would be cool but lol.
30 |
31 | # Dev notes:
32 |
33 |
34 |
--------------------------------------------------------------------------------
/js/navbar.js:
--------------------------------------------------------------------------------
1 | const PAGES = [
2 | { name: "Timeline", path: "" },
3 | { name: "Time to Watch", path: "time-to-watch/" },
4 | { name: "Watch Lulls", path: "lull/" },
5 | // {name:"",path:""},
6 | ];
7 | function initNavBar() {
8 | const nav = document.getElementsByTagName("nav")[0];
9 | let here = window.location.pathname;
10 | if (!here.endsWith("/")) {
11 | here += "/";
12 | }
13 | /* Site is hosted on github pages so home isn't just `/`. */
14 | let isHome = true;
15 | for (let page of PAGES.slice(1)) {
16 | if (here.endsWith(page.path)) {
17 | isHome = false;
18 | break;
19 | }
20 | }
21 | for (let i = 0; i < PAGES.length; ++i) {
22 | const { path, name } = PAGES[i];
23 | const isThisPage = (i === 0 && isHome) || (i !== 0 && here.endsWith(path));
24 | if (isThisPage) {
25 | const tag = document.createElement("span");
26 | tag.textContent = name;
27 | tag.className = "nav-active";
28 | nav.append(tag);
29 | }
30 | else {
31 | const tag = document.createElement("a");
32 | tag.textContent = name;
33 | tag.href = path;
34 | nav.append(tag);
35 | }
36 | if (i !== PAGES.length - 1) {
37 | nav.append(" | ");
38 | }
39 | }
40 | }
41 | initNavBar();
42 | //# sourceMappingURL=navbar.js.map
--------------------------------------------------------------------------------
/js/navbar.ts:
--------------------------------------------------------------------------------
1 | interface Page {
2 | name: string;
3 | path: string;
4 | }
5 |
6 | const PAGES: Page[] = [
7 | { name: "Timeline", path: "" },
8 | { name: "Time to Watch", path: "time-to-watch/" },
9 | { name: "Watch Lulls", path: "lull/" },
10 | // {name:"",path:""},
11 | ];
12 |
13 | function initNavBar() {
14 | const nav = document.getElementsByTagName("nav")[0];
15 | let here = window.location.pathname;
16 | if (!here.endsWith("/")) {
17 | here += "/";
18 | }
19 |
20 | /* Site is hosted on github pages so home isn't just `/`. */
21 | let isHome = true;
22 | for (let page of PAGES.slice(1)) {
23 | if (here.endsWith(page.path)) {
24 | isHome = false;
25 | break;
26 | }
27 | }
28 |
29 | for (let i = 0; i < PAGES.length; ++i) {
30 | const { path, name } = PAGES[i];
31 | const isThisPage = (i === 0 && isHome) || (i !== 0 && here.endsWith(path));
32 |
33 | if (isThisPage) {
34 | const tag = document.createElement("span");
35 | tag.textContent = name;
36 | tag.className = "nav-active";
37 | nav.append(tag);
38 | } else {
39 | const tag = document.createElement("a");
40 | tag.textContent = name;
41 | tag.href = path;
42 | nav.append(tag);
43 | }
44 | if (i !== PAGES.length - 1) {
45 | nav.append(" | ");
46 | }
47 | }
48 | }
49 | initNavBar();
--------------------------------------------------------------------------------
/js/navbar.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"navbar.js","sourceRoot":"","sources":["navbar.ts"],"names":[],"mappings":"AAKA,MAAM,KAAK,GAAW;IAClB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE;IAC9B,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACjD,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE;IACtC,qBAAqB;CACxB,CAAC;AAEF,SAAS,UAAU;IACf,MAAM,GAAG,GAAG,QAAQ,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,IAAI,IAAI,GAAG,CAAC;IAChB,CAAC;IAED,6DAA6D;IAC7D,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,KAAK,CAAC;YACf,MAAM;QACV,CAAC;IACL,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QACpC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAE3E,IAAI,UAAU,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAC3C,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC;YACvB,GAAG,CAAC,SAAS,GAAG,YAAY,CAAC;YAC7B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACJ,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YACxC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC;YACvB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACL,CAAC;AACL,CAAC;AACD,UAAU,EAAE,CAAC"}
--------------------------------------------------------------------------------
/js/lib/jquery-ui/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright jQuery Foundation and other contributors, https://jquery.org/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery-ui
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | Copyright and related rights for sample code are waived via CC0. Sample
34 | code is defined as all source code contained within the demos directory.
35 |
36 | CC0: http://creativecommons.org/publicdomain/zero/1.0/
37 |
38 | ====
39 |
40 | All files located in the node_modules and external directories are
41 | externally maintained libraries used by this software which have their
42 | own licenses; we recommend you read them, as their terms may differ from
43 | the terms above.
44 |
--------------------------------------------------------------------------------
/time-to-watch/duration.css:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | */
4 |
5 | #list-box {
6 | /* width: ; */
7 | }
8 | #list-box>fieldset {
9 | width: 45%;
10 | display: inline-block;
11 | }
12 |
13 | /* ----------- */
14 | table {
15 | border-collapse: separate;
16 | /* Not collapse for sticky header */
17 | border-spacing: 0;
18 | /* table-layout: fixed; */
19 | }
20 |
21 | :root {
22 | --table-border: 1px solid black;
23 | --table-border-2: 2px solid black;
24 | }
25 |
26 | table th {
27 | border-top: var(--table-border-2);
28 | border-right: var(--table-border);
29 | border-bottom: var(--table-border-2);
30 | top: 0;
31 | position: sticky;
32 | }
33 |
34 | table td {
35 | border-right: var(--table-border);
36 | border-bottom: var(--table-border);
37 | }
38 |
39 | table th:first-child,
40 | table td:first-child {
41 | border-left: var(--table-border);
42 | }
43 |
44 | thead th,
45 | tfoot td {
46 | background-color: white;
47 | background-clip: padding-box;
48 | }
49 |
50 | tbody tr:nth-child(odd) {
51 | background-color: #dddddd;
52 | /* color: #fff; */
53 | }
54 |
55 | .col-num {
56 | text-align: right;
57 | }
58 |
59 | .col-title {
60 | /* That didn't work... I don't care enough */
61 | /* display: block; */
62 | /* max-width: 20%; */
63 | }
64 |
65 | .col-title-inner :nth-child(2){
66 |
67 | float: right;
68 | }
69 |
70 | /*
71 | * ----------------------------
72 | */
73 |
74 | /* https://stackoverflow.com/a/66550060/1993919 */
75 | label.label-checkbox {
76 | cursor: pointer;
77 | }
78 |
79 |
80 | label.label-checkbox input {
81 | position: absolute;
82 | top: 0;
83 | left: 0;
84 | visibility: hidden;
85 | pointer-events: none;
86 | }
87 |
88 | label.label-checkbox span {
89 | padding: 11px 21px;
90 | border: 1px solid #ccc;
91 | display: inline-block;
92 | color: #202020;
93 | border-radius: 6px;
94 | margin: 7px;
95 | background: #f5f5f5;
96 | user-select: none;
97 | }
98 |
99 | label.label-checkbox input:checked+span {
100 | box-shadow: inset 1px 2px 5px #777;
101 | transform: translateY(1px);
102 | background: #e5e5e5;
103 | }
--------------------------------------------------------------------------------
/js/lib/awesomplete/awesomplete.css:
--------------------------------------------------------------------------------
1 | .awesomplete [hidden] {
2 | display: none;
3 | }
4 |
5 | .awesomplete .visually-hidden {
6 | position: absolute;
7 | clip: rect(0, 0, 0, 0);
8 | }
9 |
10 | .awesomplete {
11 | display: inline-block;
12 | position: relative;
13 | }
14 |
15 | .awesomplete > input {
16 | display: block;
17 | }
18 |
19 | .awesomplete > ul {
20 | position: absolute;
21 | left: 0;
22 | z-index: 1;
23 | min-width: 100%;
24 | box-sizing: border-box;
25 | list-style: none;
26 | padding: 0;
27 | margin: 0;
28 | background: #fff;
29 | }
30 |
31 | .awesomplete > ul:empty {
32 | display: none;
33 | }
34 |
35 | .awesomplete > ul {
36 | border-radius: .3em;
37 | margin: .2em 0 0;
38 | background: hsla(0,0%,100%,.9);
39 | background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));
40 | border: 1px solid rgba(0,0,0,.3);
41 | box-shadow: .05em .2em .6em rgba(0,0,0,.2);
42 | text-shadow: none;
43 | }
44 |
45 | @supports (transform: scale(0)) {
46 | .awesomplete > ul {
47 | transition: .3s cubic-bezier(.4,.2,.5,1.4);
48 | transform-origin: 1.43em -.43em;
49 | }
50 |
51 | .awesomplete > ul[hidden],
52 | .awesomplete > ul:empty {
53 | opacity: 0;
54 | transform: scale(0);
55 | display: block;
56 | transition-timing-function: ease;
57 | }
58 | }
59 |
60 | /* Pointer */
61 | .awesomplete > ul:before {
62 | content: "";
63 | position: absolute;
64 | top: -.43em;
65 | left: 1em;
66 | width: 0; height: 0;
67 | padding: .4em;
68 | background: white;
69 | border: inherit;
70 | border-right: 0;
71 | border-bottom: 0;
72 | -webkit-transform: rotate(45deg);
73 | transform: rotate(45deg);
74 | }
75 |
76 | .awesomplete > ul > li {
77 | position: relative;
78 | padding: .2em .5em;
79 | cursor: pointer;
80 | }
81 |
82 | .awesomplete > ul > li:hover {
83 | background: hsl(200, 40%, 80%);
84 | color: black;
85 | }
86 |
87 | .awesomplete > ul > li[aria-selected="true"] {
88 | background: hsl(205, 40%, 40%);
89 | color: white;
90 | }
91 |
92 | .awesomplete mark {
93 | background: hsl(65, 100%, 50%);
94 | }
95 |
96 | .awesomplete li:hover mark {
97 | background: hsl(68, 100%, 41%);
98 | }
99 |
100 | .awesomplete li[aria-selected="true"] mark {
101 | background: hsl(86, 100%, 21%);
102 | color: inherit;
103 | }
104 | /*# sourceMappingURL=awesomplete.css.map */
105 |
--------------------------------------------------------------------------------
/lull/lull.css:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | */
4 |
5 | #console-pane {
6 | font-family: monospace;
7 | white-space: pre;
8 | }
9 |
10 | #list-box {
11 | /* width: ; */
12 | }
13 |
14 | #list-box>fieldset {
15 | width: 45%;
16 | display: inline-block;
17 | }
18 |
19 | /* ----------- */
20 | table {
21 | border-collapse: separate;
22 | /* Not collapse for sticky header */
23 | border-spacing: 0;
24 | /* table-layout: fixed; */
25 | }
26 |
27 | :root {
28 | --table-border: 1px solid black;
29 | --table-border-2: 2px solid black;
30 | }
31 |
32 | table th {
33 | border-top: var(--table-border-2);
34 | border-right: var(--table-border);
35 | border-bottom: var(--table-border-2);
36 | top: 0;
37 | position: sticky;
38 | }
39 |
40 | table td {
41 | border-right: var(--table-border);
42 | border-bottom: var(--table-border);
43 | }
44 |
45 | table th:first-child,
46 | table td:first-child {
47 | border-left: var(--table-border);
48 | }
49 |
50 | thead th,
51 | tfoot td {
52 | background-color: white;
53 | background-clip: padding-box;
54 | }
55 |
56 | tbody tr:nth-child(odd) {
57 | background-color: #dddddd;
58 | /* color: #fff; */
59 | }
60 |
61 | .col-num {
62 | text-align: right;
63 | }
64 |
65 | .col-title {
66 | /* That didn't work... I don't care enough */
67 | /* display: block; */
68 | /* max-width: 20%; */
69 | }
70 |
71 | .title-score {
72 | background-color: aqua;
73 | border-radius: 20px;
74 | padding: 5px;
75 | margin-top: 25px;
76 | margin-bottom: 25px;
77 | }
78 |
79 | .title-links {
80 | float: right;
81 | }
82 |
83 |
84 |
85 | /*
86 | * ----------------------------
87 | */
88 |
89 | /* https://stackoverflow.com/a/66550060/1993919 */
90 | label.label-checkbox {
91 | cursor: pointer;
92 | }
93 |
94 |
95 | label.label-checkbox input {
96 | position: absolute;
97 | top: 0;
98 | left: 0;
99 | visibility: hidden;
100 | pointer-events: none;
101 | }
102 |
103 | label.label-checkbox span {
104 | padding: 11px 21px;
105 | border: 1px solid #ccc;
106 | display: inline-block;
107 | color: #202020;
108 | border-radius: 6px;
109 | margin: 7px;
110 | background: #f5f5f5;
111 | user-select: none;
112 | }
113 |
114 | label.label-checkbox input:checked+span {
115 | box-shadow: inset 1px 2px 5px #777;
116 | transform: translateY(1px);
117 | background: #e5e5e5;
118 | }
--------------------------------------------------------------------------------
/res/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/src/listManager.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"listManager.js","sourceRoot":"","sources":["listManager.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/chart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
64 |
65 |
66 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/res/root.css:
--------------------------------------------------------------------------------
1 | body {
2 | /* Slightly off white. I guess. */
3 | background-color: azure;
4 | margin: 0px;
5 | padding: 0px;
6 | }
7 |
8 | .required:before {
9 | content: " *";
10 | color: red;
11 | }
12 |
13 | input:invalid {
14 | border: 1px solid red;
15 | }
16 |
17 | input:valid {
18 | border: 1px solid green;
19 | }
20 |
21 | div#top {
22 | padding: 20px;
23 | }
24 |
25 | span#feedback {
26 | background-color: plum;
27 | margin-left: 20px;
28 | }
29 |
30 | .float-left {
31 | float: left;
32 | }
33 |
34 | .float-right {
35 | float: right
36 | }
37 |
38 | .align-left {
39 | text-align: left;
40 | }
41 | .align-right {
42 | text-align: right;
43 | }
44 | .flex-start {
45 | /* justify-content: flex-start; */
46 | flex-grow: 1;
47 | }
48 | /* .flex-end {
49 | justify-content: flex-end;
50 | } */
51 |
52 | header {
53 | background-color: #3db4f2;
54 | padding: 5px;
55 | display: flex;
56 | }
57 |
58 | .nav-active {
59 | background-color: azure;
60 | border-radius: 15px;
61 | padding: 3px;
62 | }
63 |
64 | #filter-list {
65 | list-style-type: none;
66 | padding-left: 0;
67 | }
68 |
69 | ul.buttonList {
70 | /* position: absolute; */
71 | /* left: 0; */
72 | float: right;
73 | margin-top: 0;
74 | padding: 0;
75 | list-style-type: none;
76 | display: block;
77 | }
78 |
79 | ul.buttonList>li {
80 | display: inline-block;
81 | }
82 |
83 | div.timeline {
84 | overflow-x: scroll;
85 | width: 100%;
86 | }
87 |
88 |
89 | .check-grid>div {
90 | display: inline-block;
91 | padding-right: 10px;
92 | text-align: center;
93 |
94 | }
95 |
96 | .check-grid label {
97 | display: block;
98 | }
99 |
100 | /* Maybe should just use jquery ui sliders. */
101 | input[type="range"]::-moz-range-progress {
102 | background-color: rgb(162, 162, 253);
103 | }
104 |
105 | label.before::after {
106 | content: ":";
107 | }
108 |
109 | .mainGroup {
110 | padding: 10px;
111 | margin: 10px;
112 | }
113 |
114 | .fieldContainer {
115 | background: lightgray;
116 | padding: 5px;
117 | margin-bottom: 15px;
118 | border-radius: 10px;
119 | }
120 |
121 |
122 |
123 | input {
124 | color: inherit;
125 | border-width: 1px;
126 | padding: 3px;
127 | border-radius: 3px;
128 | }
129 |
130 | button {
131 | align-items: center;
132 | background: #3db4f2;
133 | border-radius: 4px;
134 | color: rgb(237, 241, 245);
135 | cursor: pointer;
136 | display: inline-flex;
137 | margin-right: 10px;
138 | margin-top: 15px;
139 | padding: 10px 15px;
140 | transition: .2s;
141 | border-width: 0px;
142 | }
143 |
144 | button.danger {
145 | background: rgba(232, 93, 117, .8);
146 | }
147 |
148 | .smallClose {
149 | margin: 2px;
150 | padding: 3px 15px;
151 | }
152 |
153 | .nlist {
154 | list-style-type: none;
155 | margin-left: 0ch;
156 | padding-left: 0ch;
157 | }
158 |
159 | .nlist-label {
160 | width: 5ch;
161 | text-align: right;
162 | display: inline-block;
163 | margin-right: 1ch;
164 |
165 | }
166 |
167 | .nlist-label::after {
168 | content: " ";
169 | }
170 |
171 |
172 |
173 | th {
174 | min-width: 5ch;
175 | }
176 |
177 | .heat-table-cell {
178 |
179 | display: block;
180 | box-sizing: border-box;
181 | border-style: solid;
182 | border-width: 2px;
183 | border-color: transparent;
184 |
185 | min-width: 4ch;
186 | min-height: 1em;
187 | /* width: max-content; */
188 | width: 100%;
189 | height: 100%;
190 | color: transparent;
191 |
192 |
193 | }
194 |
195 | .heat-table-cell span {
196 | display: inline-block;
197 | /* width: 50%; */
198 | min-width: 2ch;
199 | /* border: 1px solid black; */
200 | }
201 |
202 | .heat-table-cell :nth-child(1) {
203 | border-right: 1px solid white;
204 | }
205 |
206 | .heat-table-cell:hover {
207 | border-color: rebeccapurple;
208 | cursor: pointer;
209 | }
210 |
211 | /* https://stackoverflow.com/a/25813336/1993919 */
212 | [data-tooltip]:before {
213 | position: absolute;
214 | content: attr(data-tooltip);
215 | opacity: 0;
216 |
217 | /* transition: all 0.15s ease; */
218 | }
219 |
220 | [data-tooltip]:hover:before {
221 | opacity: 1;
222 | background: yellow;
223 | /* Super janky positioning in the table */
224 | /* margin-top: -50px; */
225 | /* margin-left: -50px; */
226 | padding: 5px;
227 | border-radius: 10px;
228 | color: black;
229 | }
230 |
231 | #heatmap-container {
232 | background-color: white;
233 | width: fit-content;
234 |
235 | }
236 |
237 | div>#filter-list {
238 | background: #0000001c;
239 | border: 1px solid black;
240 | min-height: 5em;
241 | }
242 |
243 | #filter-list {
244 | display: grid;
245 | grid-auto-flow: row;
246 | grid-template: repeat(2, 1fr) / repeat(2, 1fr);
247 | }
248 |
249 | .display-block {
250 | display: block;
251 | }
252 | .display-inline-block {
253 | display: inline-block;
254 | }
--------------------------------------------------------------------------------
/js/src/listManager.js:
--------------------------------------------------------------------------------
1 | import * as env from "../env.js";
2 | import * as MAL from "./MAL.js";
3 | export async function getAnilistAnimeList(userName) {
4 | if (env.usingTestData) {
5 | const url = "res/anilist_example.json";
6 | let job = await fetch(url).then(response => response.json());
7 | return job;
8 | }
9 | const query = `
10 | query ($userName: String) {
11 | MediaListCollection(userName: $userName, type: ANIME) {
12 | hasNextChunk
13 | user {
14 | id
15 | }
16 | lists {
17 | name
18 | status
19 | entries {
20 | mediaId
21 | score
22 | progress
23 | startedAt { year month day }
24 | completedAt { year month day }
25 | media {
26 | idMal
27 | duration
28 | episodes
29 | format
30 | title {
31 | romaji english native userPreferred
32 | }
33 | startDate { year month day }
34 | endDate { year month day }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | `; // Could probably munch the whitespace with a regex but no real need to
41 | const variables = {
42 | userName: userName
43 | };
44 | // Define the config we'll need for our Api request
45 | const url = 'https://graphql.anilist.co', options = {
46 | method: 'POST',
47 | headers: {
48 | 'Content-Type': 'application/json',
49 | 'Accept': 'application/json',
50 | },
51 | body: JSON.stringify({
52 | query: query,
53 | variables: variables
54 | })
55 | };
56 | const response = await fetch(url, options);
57 | const foo = await response.json();
58 | if (foo.errors) {
59 | console.error(foo.errors);
60 | return new MAL.BadUsernameError();
61 | }
62 | const data = foo.data.MediaListCollection;
63 | if (data.hasNextChunk) {
64 | console.warn("TODO: next chunk not implemented yet.");
65 | }
66 | if (env.debug) {
67 | console.info("\n" + JSON.stringify(data) + "\n");
68 | }
69 | return data;
70 | }
71 | export async function getAnilistMangaList(userName) {
72 | if (env.usingTestData) {
73 | console.warn("Using test manga list data.");
74 | const url = "res/TODO.json";
75 | let job = await fetch(url).then(response => response.json());
76 | return job;
77 | }
78 | const query = `
79 | query ($userName: String) {
80 | MediaListCollection(userName: $userName, type: MANGA) {
81 | hasNextChunk
82 | user {
83 | id
84 | }
85 | lists {
86 | name
87 | status
88 | entries {
89 | mediaId
90 | score
91 | progress
92 | startedAt { year month day }
93 | completedAt { year month day }
94 | media {
95 | idMal
96 | duration
97 | episodes
98 | format
99 | title {
100 | romaji english native userPreferred
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 | `; // Could probably munch the whitespace with a regex but no real need to
108 | const variables = {
109 | userName: userName
110 | };
111 | // Define the config we'll need for our Api request
112 | const url = 'https://graphql.anilist.co', options = {
113 | method: 'POST',
114 | headers: {
115 | 'Content-Type': 'application/json',
116 | 'Accept': 'application/json',
117 | },
118 | body: JSON.stringify({
119 | query: query,
120 | variables: variables
121 | })
122 | };
123 | const response = await fetch(url, options);
124 | const foo = await response.json();
125 | if (foo.errors) {
126 | console.error(foo.errors);
127 | return new MAL.BadUsernameError();
128 | }
129 | const data = foo.data.MediaListCollection;
130 | if (data.hasNextChunk) {
131 | console.warn("TODO: next chunk not implemented yet.");
132 | }
133 | return data;
134 | }
135 | export class ListManager {
136 | userAnimeCache = new Map();
137 | userMangaCache = new Map();
138 | constructor() {
139 | }
140 | async getAnimeList(username) {
141 | const data = this.userAnimeCache.get(username);
142 | if (data) {
143 | if (!(data instanceof MAL.BadUsernameError)) {
144 | console.info([username, "'s data loaded from cache."].join(""));
145 | data.cached = true;
146 | }
147 | return data;
148 | }
149 | const aniList = await getAnilistAnimeList(username);
150 | window["lastAnilist"] = aniList;
151 | if (aniList instanceof MAL.BadUsernameError) {
152 | this.userAnimeCache.set(username, aniList);
153 | return aniList;
154 | }
155 | const animeList = MAL.animeListFromAniList(aniList, username);
156 | this.userAnimeCache.set(username, animeList);
157 | return animeList;
158 | }
159 | async getMangaList(username) {
160 | const data = this.userAnimeCache.get(username);
161 | if (data) {
162 | if (!(data instanceof MAL.BadUsernameError)) {
163 | console.info([username, "'s data loaded from cache."].join(""));
164 | data.cached = true;
165 | }
166 | return data;
167 | }
168 | const aniList = await getAnilistMangaList(username);
169 | if (aniList instanceof MAL.BadUsernameError) {
170 | this.userAnimeCache.set(username, aniList);
171 | return aniList;
172 | }
173 | const animeList = MAL.mangaListFromAniList(aniList, username);
174 | this.userMangaCache.set(username, animeList);
175 | return animeList;
176 | }
177 | }
178 | //# sourceMappingURL=listManager.js.map
--------------------------------------------------------------------------------
/js/src/util.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"util.js","sourceRoot":"","sources":["util.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/js/lib/FileSaver.js:
--------------------------------------------------------------------------------
1 | /* FileSaver.js
2 | * A saveAs() FileSaver implementation.
3 | * 1.3.2
4 | * 2016-06-16 18:25:19
5 | *
6 | * By Eli Grey, http://eligrey.com
7 | * License: MIT
8 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
9 | */
10 |
11 | /*global self */
12 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
13 |
14 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
15 |
16 | var saveAs = saveAs || (function(view) {
17 | "use strict";
18 | // IE <10 is explicitly unsupported
19 | if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
20 | return;
21 | }
22 | var
23 | doc = view.document
24 | // only get URL when necessary in case Blob.js hasn't overridden it yet
25 | , get_URL = function() {
26 | return view.URL || view.webkitURL || view;
27 | }
28 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
29 | , can_use_save_link = "download" in save_link
30 | , click = function(node) {
31 | var event = new MouseEvent("click");
32 | node.dispatchEvent(event);
33 | }
34 | , is_safari = /constructor/i.test(view.HTMLElement) || view.safari
35 | , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
36 | , throw_outside = function(ex) {
37 | (view.setImmediate || view.setTimeout)(function() {
38 | throw ex;
39 | }, 0);
40 | }
41 | , force_saveable_type = "application/octet-stream"
42 | // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
43 | , arbitrary_revoke_timeout = 1000 * 40 // in ms
44 | , revoke = function(file) {
45 | var revoker = function() {
46 | if (typeof file === "string") { // file is an object URL
47 | get_URL().revokeObjectURL(file);
48 | } else { // file is a File
49 | file.remove();
50 | }
51 | };
52 | setTimeout(revoker, arbitrary_revoke_timeout);
53 | }
54 | , dispatch = function(filesaver, event_types, event) {
55 | event_types = [].concat(event_types);
56 | var i = event_types.length;
57 | while (i--) {
58 | var listener = filesaver["on" + event_types[i]];
59 | if (typeof listener === "function") {
60 | try {
61 | listener.call(filesaver, event || filesaver);
62 | } catch (ex) {
63 | throw_outside(ex);
64 | }
65 | }
66 | }
67 | }
68 | , auto_bom = function(blob) {
69 | // prepend BOM for UTF-8 XML and text/* types (including HTML)
70 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
71 | if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
72 | return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
73 | }
74 | return blob;
75 | }
76 | , FileSaver = function(blob, name, no_auto_bom) {
77 | if (!no_auto_bom) {
78 | blob = auto_bom(blob);
79 | }
80 | // First try a.download, then web filesystem, then object URLs
81 | var
82 | filesaver = this
83 | , type = blob.type
84 | , force = type === force_saveable_type
85 | , object_url
86 | , dispatch_all = function() {
87 | dispatch(filesaver, "writestart progress write writeend".split(" "));
88 | }
89 | // on any filesys errors revert to saving with object URLs
90 | , fs_error = function() {
91 | if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
92 | // Safari doesn't allow downloading of blob urls
93 | var reader = new FileReader();
94 | reader.onloadend = function() {
95 | var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
96 | var popup = view.open(url, '_blank');
97 | if(!popup) view.location.href = url;
98 | url=undefined; // release reference before dispatching
99 | filesaver.readyState = filesaver.DONE;
100 | dispatch_all();
101 | };
102 | reader.readAsDataURL(blob);
103 | filesaver.readyState = filesaver.INIT;
104 | return;
105 | }
106 | // don't create more object URLs than needed
107 | if (!object_url) {
108 | object_url = get_URL().createObjectURL(blob);
109 | }
110 | if (force) {
111 | view.location.href = object_url;
112 | } else {
113 | var opened = view.open(object_url, "_blank");
114 | if (!opened) {
115 | // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
116 | view.location.href = object_url;
117 | }
118 | }
119 | filesaver.readyState = filesaver.DONE;
120 | dispatch_all();
121 | revoke(object_url);
122 | }
123 | ;
124 | filesaver.readyState = filesaver.INIT;
125 |
126 | if (can_use_save_link) {
127 | object_url = get_URL().createObjectURL(blob);
128 | setTimeout(function() {
129 | save_link.href = object_url;
130 | save_link.download = name;
131 | click(save_link);
132 | dispatch_all();
133 | revoke(object_url);
134 | filesaver.readyState = filesaver.DONE;
135 | });
136 | return;
137 | }
138 |
139 | fs_error();
140 | }
141 | , FS_proto = FileSaver.prototype
142 | , saveAs = function(blob, name, no_auto_bom) {
143 | return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
144 | }
145 | ;
146 | // IE 10+ (native saveAs)
147 | if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
148 | return function(blob, name, no_auto_bom) {
149 | name = name || blob.name || "download";
150 |
151 | if (!no_auto_bom) {
152 | blob = auto_bom(blob);
153 | }
154 | return navigator.msSaveOrOpenBlob(blob, name);
155 | };
156 | }
157 |
158 | FS_proto.abort = function(){};
159 | FS_proto.readyState = FS_proto.INIT = 0;
160 | FS_proto.WRITING = 1;
161 | FS_proto.DONE = 2;
162 |
163 | FS_proto.error =
164 | FS_proto.onwritestart =
165 | FS_proto.onprogress =
166 | FS_proto.onwrite =
167 | FS_proto.onabort =
168 | FS_proto.onerror =
169 | FS_proto.onwriteend =
170 | null;
171 |
172 | return saveAs;
173 | }(
174 | typeof self !== "undefined" && self
175 | || typeof window !== "undefined" && window
176 | || this.content
177 | ));
178 | // `self` is undefined in Firefox for Android content script context
179 | // while `this` is nsIContentFrameMessageManager
180 | // with an attribute `content` that corresponds to the window
181 |
182 | if (typeof module !== "undefined" && module.exports) {
183 | module.exports.saveAs = saveAs;
184 | } else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
185 | define("FileSaver.js", function() {
186 | return saveAs;
187 | });
188 | }
189 |
--------------------------------------------------------------------------------
/js/src/util.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | */
4 | import * as MAL from "./MAL.js";
5 | //
6 | export function assertUnreachable(x) { }
7 | const dateRegex = /^\d\d\d\d[\-\/.]\d\d[\-\/\.]\d\d$|^\d\d\d\d\d\d\d\d$/;
8 | export function wrapListItem(elm) {
9 | const li = document.createElement("li");
10 | li.appendChild(elm);
11 | return li;
12 | }
13 | export function textNode(tag, txt, classes) {
14 | const elm = document.createElement(tag);
15 | elm.textContent = txt;
16 | if (classes) {
17 | elm.className = classes;
18 | }
19 | return elm;
20 | }
21 | export function minutesToString(min) {
22 | min = Math.round(min);
23 | let h = Math.floor(min / 60);
24 | const d = Math.floor(h / 24);
25 | h = h % 24;
26 | const m = min % 60;
27 | if (h > 0 || d > 0) {
28 | if (d > 0)
29 | return `${d}D ${h}H ${m}M`;
30 | else
31 | return `${h}H ${m}M`;
32 | }
33 | return `${m} minutes`;
34 | }
35 | export function daysToYMD(n) {
36 | let s = "";
37 | if (n >= 365) {
38 | const y = Math.floor(n / 365);
39 | n = n % 365;
40 | s += `${y}Y `;
41 | }
42 | if (n >= 30) {
43 | const m = Math.floor(n / 30);
44 | n = n % 30;
45 | s += `${m}M `;
46 | }
47 | if (n !== 0) {
48 | s += `${n}D`;
49 | }
50 | return s;
51 | }
52 | export function daysToYWD(n) {
53 | let s = "";
54 | if (n >= 365) {
55 | const y = Math.floor(n / 365);
56 | n = n % 365;
57 | s += `${y}Y `;
58 | }
59 | if (n >= 7) {
60 | const w = Math.floor(n / 7);
61 | n = n % 7;
62 | s += `${w}W `;
63 | }
64 | if (true || n !== 0) {
65 | s += `${n}D`;
66 | }
67 | return s;
68 | }
69 | export function updateKey(map, key, value) {
70 | map.set(key, map.get(key) + value);
71 | }
72 | export function daysBetween(first, second) {
73 | if (typeof first === 'string') {
74 | first = new Date(first);
75 | }
76 | if (typeof second === 'string') {
77 | second = new Date(second);
78 | }
79 | // Take the difference between the dates and divide by milliseconds per day.
80 | // Round to nearest whole number to deal with DST.
81 | const diff = (second.valueOf() - first.valueOf());
82 | const milliInDay = (1000 * 60 * 60 * 24);
83 | return Math.abs(Math.round(diff / milliInDay));
84 | }
85 | //
86 | // Data cleaning
87 | //
88 | // I don't remember why I wanted this, but I might of had a good reason.
89 | // Could probably find this on SO
90 | /**
91 | * Returns if the string represents a non negative integer.
92 | * @param str
93 | * @returns {boolean}
94 | */
95 | export function isNormalInteger(str) {
96 | const n = ~~Number(str);
97 | return (String(n) === str) && (n >= 0);
98 | }
99 | /**
100 | * Returns if the string represents a non negative integer.
101 | * @param str
102 | * @returns {boolean}
103 | */
104 | export function isPositiveInteger(str) {
105 | const n = ~~Number(str);
106 | return (String(n) === str) && (n > 0);
107 | }
108 | //make user input suitable for anime timeline
109 | /**
110 | * Clamps date into a useful value
111 | * @param date May be rawNullDate
112 | * @param minmax -1: clamp min; 1 clamp max
113 | * @returns YYYY-MM-DD str
114 | */
115 | export function fixDate(date, minmax) {
116 | const minYear = 1980; //Nerds can change this in the future
117 | const maxYear = 2030; //For now its sane
118 | const test = dateRegex.test(date);
119 | if (!test) {
120 | // Maybe should return or throw an error?
121 | if (null !== date && "" !== date)
122 | console.error("Unexpected date format from:", date);
123 | // Pretend all invalid input is equivalent to null date
124 | date = MAL.rawNullDate;
125 | }
126 | let ys;
127 | let ms;
128 | let ds;
129 | if (/^\d\d\d\d\d\d\d\d$/.test(date)) {
130 | ys = date.slice(0, 4);
131 | ms = date.slice(4, 6);
132 | ds = date.slice(6, 8);
133 | }
134 | else {
135 | ys = date.slice(0, 4);
136 | ms = date.slice(5, 7);
137 | ds = date.slice(8, 10);
138 | }
139 | const y = parseInt(ys);
140 | const m = parseInt(ms);
141 | const d = parseInt(ds);
142 | //A date needs at least a sane year
143 | if (y < minYear || y > maxYear) {
144 | if (minmax == -1)
145 | ys = minYear.toString();
146 | else // (minmax == 1)
147 | ys = maxYear.toString();
148 | }
149 | if (m < 0 || m > 12) {
150 | ms = "00";
151 | }
152 | if (d < 0 || d > 32) {
153 | ds = "00";
154 | }
155 | return [ys, ms, ds].join("-");
156 | }
157 | /*
158 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
159 | */
160 | export function esSetEq(a, b) {
161 | // Why do I have to write this :( :( :(
162 | // js sucks
163 | if (a.size !== b.size)
164 | return false;
165 | for (const v of a) {
166 | if (!b.has(v))
167 | return false;
168 | }
169 | return true;
170 | }
171 | export function esSetIntersection(a, b) {
172 | const _intersection = new Set();
173 | for (const elem of b) {
174 | if (a.has(elem)) {
175 | _intersection.add(elem);
176 | }
177 | }
178 | return _intersection;
179 | }
180 | export function esSetDifference(a, b) {
181 | const _difference = new Set(a);
182 | for (const elem of b) {
183 | _difference.delete(elem);
184 | }
185 | return _difference;
186 | }
187 | /* --------------------------------------------------------------------------- */
188 | export function validateSelect(select, options) {
189 | const name = select[0].id;
190 | const htmlValues = new Set(select.children().map((i, opt) => opt.value));
191 | const jsValues = new Set(options);
192 | // console.log(name, htmlValues);
193 | const isGood = esSetEq(htmlValues, jsValues);
194 | if (!isGood) {
195 | const fromHTML = esSetDifference(htmlValues, jsValues);
196 | const fromJS = esSetDifference(jsValues, htmlValues);
197 | console.warn(`${name}:\tInvalid select`);
198 | console.log("HTML Extra:", fromHTML);
199 | console.log("HTML Missing:", fromJS);
200 | }
201 | }
202 | /* --------------------------------------------------------------------------- */
203 | export class LabelCheckbox_PushButton {
204 | /* https://stackoverflow.com/a/66550060/1993919 */
205 | topElm = document.createElement("label");
206 | inputElm = document.createElement("input");
207 | textElm = document.createElement("span");
208 | constructor(parent, text) {
209 | this.topElm.className = "label-checkbox";
210 | this.inputElm.type = "checkbox";
211 | this.textElm.textContent = text;
212 | this.topElm.append(this.inputElm);
213 | this.topElm.append(this.textElm);
214 | parent.append(this.topElm);
215 | }
216 | }
217 | //# sourceMappingURL=util.js.map
--------------------------------------------------------------------------------
/lull/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Watch Lulls
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
34 |
35 |
36 |
37 |
Find your biggest gaps in starting new anime.
38 |
39 |
44 |
45 |
46 |
47 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/js/src/listManager.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as env from "../env.js";
3 | import * as MAL from "./MAL.js";
4 |
5 |
6 |
7 |
8 | export async function getAnilistAnimeList(userName: string): Promise {
9 |
10 | if (env.usingTestData) {
11 |
12 |
13 | const url = "res/anilist_example.json";
14 |
15 | let job = await fetch(url).then(response => response.json());
16 | return job;
17 |
18 | }
19 |
20 | const query = `
21 | query ($userName: String) {
22 | MediaListCollection(userName: $userName, type: ANIME) {
23 | hasNextChunk
24 | user {
25 | id
26 | }
27 | lists {
28 | name
29 | status
30 | entries {
31 | mediaId
32 | score
33 | progress
34 | startedAt { year month day }
35 | completedAt { year month day }
36 | media {
37 | idMal
38 | duration
39 | episodes
40 | format
41 | title {
42 | romaji english native userPreferred
43 | }
44 | startDate { year month day }
45 | endDate { year month day }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | `; // Could probably munch the whitespace with a regex but no real need to
52 |
53 | const variables = {
54 | userName: userName
55 | };
56 |
57 |
58 | // Define the config we'll need for our Api request
59 | const url = 'https://graphql.anilist.co',
60 | options = {
61 | method: 'POST',
62 | headers: {
63 | 'Content-Type': 'application/json',
64 | 'Accept': 'application/json',
65 | },
66 | body: JSON.stringify({
67 | query: query,
68 | variables: variables
69 | })
70 | };
71 |
72 |
73 | const response = await fetch(url, options);
74 | const foo = await response.json();
75 |
76 | if (foo.errors) {
77 | console.error(foo.errors);
78 | return new MAL.BadUsernameError();
79 | }
80 |
81 | const data = foo.data.MediaListCollection;
82 |
83 | if (data.hasNextChunk) {
84 | console.warn("TODO: next chunk not implemented yet.");
85 | }
86 |
87 |
88 | if (env.debug) {
89 | console.info("\n" + JSON.stringify(data) + "\n");
90 | }
91 |
92 |
93 | return data;
94 |
95 | }
96 |
97 |
98 |
99 |
100 |
101 | export async function getAnilistMangaList(userName: string): Promise {
102 |
103 | if (env.usingTestData) {
104 | console.warn("Using test manga list data.");
105 |
106 | const url = "res/TODO.json";
107 |
108 | let job = await fetch(url).then(response => response.json());
109 | return job;
110 |
111 | }
112 |
113 | const query = `
114 | query ($userName: String) {
115 | MediaListCollection(userName: $userName, type: MANGA) {
116 | hasNextChunk
117 | user {
118 | id
119 | }
120 | lists {
121 | name
122 | status
123 | entries {
124 | mediaId
125 | score
126 | progress
127 | startedAt { year month day }
128 | completedAt { year month day }
129 | media {
130 | idMal
131 | duration
132 | episodes
133 | format
134 | title {
135 | romaji english native userPreferred
136 | }
137 | }
138 | }
139 | }
140 | }
141 | }
142 | `; // Could probably munch the whitespace with a regex but no real need to
143 |
144 | const variables = {
145 | userName: userName
146 | };
147 |
148 |
149 | // Define the config we'll need for our Api request
150 | const url = 'https://graphql.anilist.co',
151 | options = {
152 | method: 'POST',
153 | headers: {
154 | 'Content-Type': 'application/json',
155 | 'Accept': 'application/json',
156 | },
157 | body: JSON.stringify({
158 | query: query,
159 | variables: variables
160 | })
161 | };
162 |
163 |
164 | const response = await fetch(url, options);
165 | const foo = await response.json();
166 |
167 | if (foo.errors) {
168 | console.error(foo.errors);
169 | return new MAL.BadUsernameError();
170 | }
171 |
172 | const data = foo.data.MediaListCollection;
173 |
174 | if (data.hasNextChunk) {
175 | console.warn("TODO: next chunk not implemented yet.");
176 | }
177 |
178 |
179 | return data;
180 |
181 | }
182 |
183 | export class ListManager {
184 |
185 | userAnimeCache: Map = new Map();
186 | userMangaCache: Map = new Map();
187 |
188 | constructor() {
189 |
190 | }
191 |
192 | async getAnimeList(username: string) {
193 | const data: MAL.AnimeList | MAL.BadUsernameError = this.userAnimeCache.get(username);
194 | if (data) {
195 | if (!(data instanceof MAL.BadUsernameError)) {
196 | console.info([username, "'s data loaded from cache."].join(""));
197 |
198 | data.cached = true;
199 | }
200 | return data;
201 | }
202 |
203 | const aniList = await getAnilistAnimeList(username);
204 | window["lastAnilist"] = aniList;
205 | if (aniList instanceof MAL.BadUsernameError) {
206 | this.userAnimeCache.set(username, aniList);
207 | return aniList;
208 | }
209 |
210 | const animeList = MAL.animeListFromAniList(aniList, username);
211 | this.userAnimeCache.set(username, animeList);
212 |
213 | return animeList;
214 |
215 | }
216 | async getMangaList(username: string) {
217 | const data: MAL.MangaList | MAL.BadUsernameError = this.userAnimeCache.get(username);
218 | if (data) {
219 | if (!(data instanceof MAL.BadUsernameError)) {
220 | console.info([username, "'s data loaded from cache."].join(""));
221 | data.cached = true;
222 | }
223 | return data;
224 | }
225 |
226 | const aniList = await getAnilistMangaList(username);
227 | if (aniList instanceof MAL.BadUsernameError) {
228 | this.userAnimeCache.set(username, aniList);
229 | return aniList;
230 | }
231 |
232 | const animeList = MAL.mangaListFromAniList(aniList, username);
233 | this.userMangaCache.set(username, animeList);
234 |
235 | return animeList;
236 |
237 | }
238 | }
--------------------------------------------------------------------------------
/time-to-watch/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Time to Watch List
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
35 |
36 |
37 |
38 |
View your list with focus on how long it took.
39 |
40 |
45 |
46 |
47 |
48 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/js/src/util.ts:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | */
4 |
5 | import * as MAL from "./MAL.js";
6 |
7 | //
8 |
9 | export function assertUnreachable(x: never): void { }
10 |
11 |
12 | const dateRegex = /^\d\d\d\d[\-\/.]\d\d[\-\/\.]\d\d$|^\d\d\d\d\d\d\d\d$/;
13 |
14 |
15 | export function wrapListItem(elm: Element) {
16 | const li = document.createElement("li");
17 | li.appendChild(elm);
18 | return li;
19 | }
20 |
21 | export function textNode(tag, txt: string, classes?: string) {
22 | const elm = document.createElement(tag) as HTMLElement;
23 | elm.textContent = txt;
24 | if (classes) {
25 |
26 | elm.className = classes;
27 | }
28 | return elm;
29 | }
30 |
31 | export function minutesToString(min: number): string {
32 | min = Math.round(min);
33 |
34 | let h = Math.floor(min / 60);
35 | const d = Math.floor(h / 24);
36 | h = h % 24;
37 |
38 | const m = min % 60;
39 |
40 | if (h > 0 || d > 0) {
41 | if (d > 0)
42 | return `${d}D ${h}H ${m}M`;
43 | else
44 | return `${h}H ${m}M`;
45 | }
46 | return `${m} minutes`;
47 |
48 | }
49 |
50 | export function daysToYMD(n: number) {
51 | let s = "";
52 |
53 | if (n >= 365) {
54 | const y = Math.floor(n / 365);
55 | n = n % 365;
56 | s += `${y}Y `;
57 | }
58 | if (n >= 30) {
59 | const m = Math.floor(n / 30);
60 | n = n % 30;
61 | s += `${m}M `;
62 | }
63 | if (n !== 0) {
64 | s += `${n}D`;
65 | }
66 | return s;
67 | }
68 |
69 | export function daysToYWD(n: number) {
70 | let s = "";
71 |
72 | if (n >= 365) {
73 | const y = Math.floor(n / 365);
74 | n = n % 365;
75 | s += `${y}Y `;
76 | }
77 | if (n >= 7) {
78 | const w = Math.floor(n / 7);
79 | n = n % 7;
80 | s += `${w}W `;
81 | }
82 | if (true || n !== 0) {
83 | s += `${n}D`;
84 | }
85 | return s;
86 | }
87 |
88 | export function updateKey(map: Map, key: K, value: number) {
89 | map.set(key, map.get(key) + value);
90 | }
91 |
92 | export function daysBetween(first: Date | string, second: Date | string): number {
93 |
94 | if (typeof first === 'string') {
95 | first = new Date(first);
96 | }
97 | if (typeof second === 'string') {
98 | second = new Date(second);
99 | }
100 |
101 | // Take the difference between the dates and divide by milliseconds per day.
102 | // Round to nearest whole number to deal with DST.
103 |
104 | const diff = (second.valueOf() - first.valueOf());
105 | const milliInDay = (1000 * 60 * 60 * 24);
106 | return Math.abs(Math.round(diff / milliInDay));
107 | }
108 |
109 |
110 | //
111 | // Data cleaning
112 | //
113 |
114 | // I don't remember why I wanted this, but I might of had a good reason.
115 | // Could probably find this on SO
116 |
117 | /**
118 | * Returns if the string represents a non negative integer.
119 | * @param str
120 | * @returns {boolean}
121 | */
122 | export function isNormalInteger(str: string): boolean {
123 | const n: number = ~~Number(str);
124 | return (String(n) === str) && (n >= 0);
125 | }
126 |
127 | /**
128 | * Returns if the string represents a non negative integer.
129 | * @param str
130 | * @returns {boolean}
131 | */
132 | export function isPositiveInteger(str: string): boolean {
133 | const n: number = ~~Number(str);
134 | return (String(n) === str) && (n > 0);
135 | }
136 |
137 |
138 | //make user input suitable for anime timeline
139 |
140 | /**
141 | * Clamps date into a useful value
142 | * @param date May be rawNullDate
143 | * @param minmax -1: clamp min; 1 clamp max
144 | * @returns YYYY-MM-DD str
145 | */
146 | export function fixDate(date: string, minmax: -1 | 1): string {
147 |
148 | const minYear = 1980;//Nerds can change this in the future
149 | const maxYear = 2030;//For now its sane
150 |
151 | const test: boolean = dateRegex.test(date);
152 | if (!test) {
153 | // Maybe should return or throw an error?
154 | if (null !== date && "" !== date)
155 | console.error("Unexpected date format from:", date);
156 | // Pretend all invalid input is equivalent to null date
157 | date = MAL.rawNullDate;
158 | }
159 | let ys: string;
160 | let ms: string;
161 | let ds: string;
162 | if (/^\d\d\d\d\d\d\d\d$/.test(date)) {
163 | ys = date.slice(0, 4);
164 | ms = date.slice(4, 6);
165 | ds = date.slice(6, 8);
166 | } else {
167 | ys = date.slice(0, 4);
168 | ms = date.slice(5, 7);
169 | ds = date.slice(8, 10);
170 | }
171 | const y: number = parseInt(ys);
172 | const m: number = parseInt(ms);
173 | const d: number = parseInt(ds);
174 |
175 | //A date needs at least a sane year
176 | if (y < minYear || y > maxYear) {
177 | if (minmax == -1)
178 | ys = minYear.toString();
179 | else // (minmax == 1)
180 | ys = maxYear.toString();
181 |
182 | }
183 | if (m < 0 || m > 12) {
184 | ms = "00";
185 | }
186 | if (d < 0 || d > 32) {
187 | ds = "00";
188 | }
189 |
190 | return [ys, ms, ds].join("-");
191 | }
192 |
193 | /*
194 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
195 | */
196 | export function esSetEq(a: Set, b: Set) {
197 | // Why do I have to write this :( :( :(
198 | // js sucks
199 | if (a.size !== b.size) return false;
200 | for (const v of a) {
201 | if (!b.has(v)) return false;
202 | }
203 | return true;
204 |
205 | }
206 |
207 | export function esSetIntersection(a: Set, b: Set) {
208 | const _intersection = new Set();
209 | for (const elem of b) {
210 | if (a.has(elem)) {
211 | _intersection.add(elem);
212 | }
213 | }
214 | return _intersection;
215 | }
216 |
217 | export function esSetDifference(a: Set, b: Set) {
218 | const _difference = new Set(a);
219 | for (const elem of b) {
220 | _difference.delete(elem);
221 | }
222 | return _difference;
223 | }
224 |
225 | /* --------------------------------------------------------------------------- */
226 |
227 | export function validateSelect(select: JQuery, options: Readonly) {
228 | const name = select[0].id;
229 | const htmlValues = new Set(select.children().map((i, opt) => opt.value));
230 | const jsValues = new Set(options);
231 | // console.log(name, htmlValues);
232 | const isGood = esSetEq(htmlValues, jsValues);
233 | if (!isGood) {
234 | const fromHTML = esSetDifference(htmlValues, jsValues);
235 | const fromJS = esSetDifference(jsValues, htmlValues);
236 |
237 | console.warn(`${name}:\tInvalid select`);
238 | console.log("HTML Extra:", fromHTML);
239 | console.log("HTML Missing:", fromJS);
240 |
241 | }
242 | }
243 | /* --------------------------------------------------------------------------- */
244 |
245 | export class LabelCheckbox_PushButton {
246 | /* https://stackoverflow.com/a/66550060/1993919 */
247 |
248 | topElm = document.createElement("label");
249 |
250 | inputElm = document.createElement("input");
251 |
252 | textElm = document.createElement("span");
253 |
254 | constructor(parent: HTMLElement, text: string) {
255 | this.topElm.className = "label-checkbox";
256 |
257 | this.inputElm.type = "checkbox";
258 |
259 | this.textElm.textContent = text;
260 |
261 | this.topElm.append(this.inputElm);
262 | this.topElm.append(this.textElm);
263 |
264 | parent.append(this.topElm);
265 | }
266 | }
--------------------------------------------------------------------------------
/js/src/heatmap.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"heatmap.js","sourceRoot":"","sources":["heatmap.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/js/src/heatmap.js:
--------------------------------------------------------------------------------
1 | import * as ATL from "./animelistTL.js";
2 | const monthNames = new Map();
3 | for (let i = 0; i < 12; i++) {
4 | let name = strftime("%b", new Date(2020, i, 5));
5 | monthNames.set(i + 1, name);
6 | }
7 | const FULL_YEAR = 0;
8 | const Q1 = 13;
9 | const Q2 = 14;
10 | const Q3 = 15;
11 | const Q4 = 16;
12 | const LAST = Q4;
13 | function seasonQ(date) {
14 | const season = ATL.seasonOf(date);
15 | switch (season) {
16 | case ATL.allSeasons[0]: return Q1;
17 | case ATL.allSeasons[1]: return Q2;
18 | case ATL.allSeasons[2]: return Q3;
19 | case ATL.allSeasons[3]: return Q4;
20 | }
21 | }
22 | export class WatchHeatMap {
23 | spanHandler;
24 | data = new Map();
25 | minYear;
26 | maxYear;
27 | maxMonthCount = { startCount: 0, finishCount: 0 };
28 | maxSeasonCount = { startCount: 0, finishCount: 0 };
29 | maxYearCount = { startCount: 0, finishCount: 0 };
30 | // spanHandler: SpanHandler;
31 | constructor(tln, spanHandler) {
32 | this.spanHandler = spanHandler;
33 | this.minYear = tln.firstDate.year();
34 | this.maxYear = tln.lastDate.year();
35 | for (let y = this.minYear; y <= this.maxYear; y++) {
36 | const year = [];
37 | for (let i = 0; i <= LAST; ++i) {
38 | year.push({ startCount: 0, finishCount: 0 });
39 | }
40 | this.data.set(y, year);
41 | }
42 | for (let anime of tln.mediaSet) {
43 | let date = anime.userStartDate;
44 | if (!date.isNullDate()) {
45 | let y = date.year();
46 | let m = date.month();
47 | const year = this.data.get(y);
48 | const season = seasonQ(date);
49 | year[FULL_YEAR].startCount++;
50 | year[m].startCount++;
51 | year[season].startCount++;
52 | }
53 | date = anime.userFinishDate;
54 | if (!date.isNullDate()) {
55 | let y = date.year();
56 | let m = date.month();
57 | const year = this.data.get(y);
58 | const season = seasonQ(date);
59 | year[FULL_YEAR].finishCount++;
60 | year[m].finishCount++;
61 | year[season].finishCount++;
62 | }
63 | }
64 | //---
65 | const maxGram = (a, b) => {
66 | return {
67 | startCount: Math.max(a.startCount, b.startCount),
68 | finishCount: Math.max(a.finishCount, b.finishCount)
69 | };
70 | };
71 | for (let y = this.minYear; y <= this.maxYear; y++) {
72 | const year = this.data.get(y);
73 | for (let m = 1; m <= 12; ++m) {
74 | const gram = year[m];
75 | this.maxMonthCount = maxGram(this.maxMonthCount, gram);
76 | }
77 | for (let s = Q1; s <= Q4; s++) {
78 | const gram = year[s];
79 | this.maxSeasonCount = maxGram(this.maxSeasonCount, gram);
80 | }
81 | this.maxYearCount = maxGram(this.maxYearCount, year[FULL_YEAR]);
82 | }
83 | }
84 | render() {
85 | const table = document.createElement("table");
86 | const head = document.createElement("thead");
87 | table.append(head);
88 | const headRow = document.createElement("tr");
89 | head.append(headRow);
90 | // ------
91 | { // Spacer cell
92 | const cell = document.createElement("th");
93 | headRow.append(cell);
94 | }
95 | for (let m = 1; m <= 12; ++m) {
96 | const cell = document.createElement("th");
97 | headRow.append(cell);
98 | cell.textContent = monthNames.get(m);
99 | }
100 | for (let s = Q1; s <= Q4; s++) {
101 | const cell = document.createElement("th");
102 | headRow.append(cell);
103 | cell.textContent = ATL.allSeasons[s - Q1];
104 | }
105 | {
106 | const cell = document.createElement("th");
107 | headRow.append(cell);
108 | cell.textContent = "Year";
109 | }
110 | // ------
111 | const returnDateSpan = (d0, d1) => {
112 | return () => {
113 | // console.log(d0);
114 | // console.log(d1);
115 | this.spanHandler(d0, d1);
116 | };
117 | };
118 | function fill(percent) {
119 | // const value =0xff * percent;
120 | const scaleMax = 0xEE;
121 | const value = (scaleMax * percent) + (0xff - scaleMax);
122 | return Math.floor(value).toString(16);
123 | }
124 | function styleBox(box0, gram, maxgram) {
125 | const box = document.createElement("span");
126 | box0.append(box);
127 | box.classList.add("heat-table-cell");
128 | if (gram.startCount === 0 && gram.finishCount === 0) {
129 | box.style.backgroundColor = "black";
130 | box.textContent = "____";
131 | }
132 | else {
133 | const tooltip = `Started ${gram.startCount} and Finished ${gram.finishCount}`;
134 | // box.title = tooltip;
135 | // https://stackoverflow.com/a/25813336/1993919
136 | box.setAttribute("data-tooltip", tooltip);
137 | {
138 | let span = document.createElement("span");
139 | box.append(span);
140 | // span.textContent = gram.startCount.toString();
141 | span.textContent = "__";
142 | let trans = fill(gram.startCount / maxgram.startCount);
143 | span.style.backgroundColor = ATL.startColor1 + trans;
144 | }
145 | {
146 | let span = document.createElement("span");
147 | box.append(span);
148 | span.textContent = "__";
149 | // span.textContent = gram.finishCount.toString();
150 | let trans = fill(gram.finishCount / maxgram.finishCount);
151 | span.style.backgroundColor = ATL.endColor + trans;
152 | }
153 | }
154 | }
155 | for (let y = this.minYear; y <= this.maxYear; y++) {
156 | const row = document.createElement("tr");
157 | table.append(row);
158 | const box = document.createElement("th");
159 | row.append(box);
160 | box.textContent = y.toString();
161 | const year = this.data.get(y);
162 | for (let m = 1; m <= 12; ++m) {
163 | let gram = year[m];
164 | const box = document.createElement("td");
165 | row.append(box);
166 | box.onclick = returnDateSpan(new Date(y, m - 1, 1), new Date(y, m, 0));
167 | styleBox(box, gram, this.maxMonthCount);
168 | }
169 | //
170 | for (let s = Q1; s <= Q4; s++) {
171 | const gram = year[s];
172 | const box = document.createElement("td");
173 | row.append(box);
174 | let dates = ATL.seasonBounds(ATL.allSeasons[s - Q1], y);
175 | box.onclick = returnDateSpan(dates[0].date, dates[1].date);
176 | styleBox(box, gram, this.maxSeasonCount);
177 | }
178 | {
179 | const gram = year[FULL_YEAR];
180 | const box = document.createElement("td");
181 | box.onclick = returnDateSpan(new Date(y, 0, 1), new Date(y + 1, 0, 0));
182 | row.append(box);
183 | styleBox(box, gram, this.maxYearCount);
184 | }
185 | }
186 | return table;
187 | }
188 | }
189 | //# sourceMappingURL=heatmap.js.map
--------------------------------------------------------------------------------
/js/src/heatmap.ts:
--------------------------------------------------------------------------------
1 | import * as MAL from "./MAL.js";
2 | import * as ATL from "./animelistTL.js";
3 |
4 |
5 | declare function strftime(format: string, date: Date): string;
6 |
7 | const monthNames = new Map();
8 | for (let i = 0; i < 12; i++) {
9 | let name = strftime("%b", new Date(2020, i, 5));
10 | monthNames.set(i + 1, name);
11 | }
12 |
13 | interface Datagram {
14 | startCount: number;
15 | finishCount: number;
16 | }
17 | const FULL_YEAR = 0;
18 | const Q1 = 13;
19 | const Q2 = 14;
20 | const Q3 = 15;
21 | const Q4 = 16;
22 | const LAST = Q4;
23 |
24 | function seasonQ(date: MAL.Mdate): number {
25 | const season = ATL.seasonOf(date);
26 | switch (season) {
27 | case ATL.allSeasons[0]: return Q1;
28 | case ATL.allSeasons[1]: return Q2;
29 | case ATL.allSeasons[2]: return Q3;
30 | case ATL.allSeasons[3]: return Q4;
31 | }
32 | }
33 |
34 | type SpanHandler = (d1: Date, d2: Date) => void;
35 |
36 | export class WatchHeatMap {
37 | data = new Map();
38 | minYear: number;
39 | maxYear: number;
40 |
41 | maxMonthCount: Datagram = { startCount: 0, finishCount: 0 };
42 | maxSeasonCount: Datagram = { startCount: 0, finishCount: 0 };
43 | maxYearCount: Datagram = { startCount: 0, finishCount: 0 };
44 |
45 | // spanHandler: SpanHandler;
46 |
47 | constructor(tln: ATL.AnimeListTimeline, public spanHandler: SpanHandler) {
48 | this.minYear = tln.firstDate.year();
49 | this.maxYear = tln.lastDate.year();
50 |
51 | for (let y = this.minYear; y <= this.maxYear; y++) {
52 | const year: Datagram[] = [];
53 | for (let i = 0; i <= LAST; ++i) {
54 | year.push({ startCount: 0, finishCount: 0 });
55 | }
56 | this.data.set(y, year);
57 | }
58 | for (let anime of tln.mediaSet) {
59 | let date = anime.userStartDate;
60 | if (!date.isNullDate()) {
61 | let y = date.year();
62 | let m = date.month();
63 | const year = this.data.get(y);
64 |
65 | const season = seasonQ(date);
66 |
67 | year[FULL_YEAR].startCount++;
68 | year[m].startCount++;
69 | year[season].startCount++;
70 | }
71 | date = anime.userFinishDate;
72 | if (!date.isNullDate()) {
73 | let y = date.year();
74 | let m = date.month();
75 | const year = this.data.get(y);
76 |
77 | const season = seasonQ(date);
78 |
79 | year[FULL_YEAR].finishCount++;
80 | year[m].finishCount++;
81 | year[season].finishCount++;
82 | }
83 | }
84 | //---
85 | const maxGram = (a: Datagram, b: Datagram) => {
86 | return {
87 | startCount: Math.max(a.startCount, b.startCount),
88 | finishCount: Math.max(a.finishCount, b.finishCount)
89 | };
90 | };
91 |
92 | for (let y = this.minYear; y <= this.maxYear; y++) {
93 | const year = this.data.get(y);
94 | for (let m = 1; m <= 12; ++m) {
95 | const gram = year[m];
96 | this.maxMonthCount = maxGram(this.maxMonthCount, gram);
97 | }
98 | for (let s = Q1; s <= Q4; s++) {
99 | const gram = year[s];
100 | this.maxSeasonCount = maxGram(this.maxSeasonCount, gram);
101 |
102 | }
103 | this.maxYearCount = maxGram(this.maxYearCount, year[FULL_YEAR]);
104 |
105 | }
106 | }
107 |
108 | render(): HTMLElement {
109 | const table = document.createElement("table");
110 |
111 | const head = document.createElement("thead");
112 | table.append(head);
113 |
114 | const headRow = document.createElement("tr");
115 | head.append(headRow);
116 |
117 | // ------
118 | { // Spacer cell
119 | const cell = document.createElement("th");
120 | headRow.append(cell);
121 | }
122 | for (let m = 1; m <= 12; ++m) {
123 | const cell = document.createElement("th");
124 | headRow.append(cell);
125 | cell.textContent = monthNames.get(m);
126 | }
127 | for (let s = Q1; s <= Q4; s++) {
128 | const cell = document.createElement("th");
129 | headRow.append(cell);
130 | cell.textContent = ATL.allSeasons[s - Q1];
131 | }
132 | {
133 | const cell = document.createElement("th");
134 | headRow.append(cell);
135 | cell.textContent = "Year";
136 | }
137 |
138 | // ------
139 |
140 | const returnDateSpan = (d0: Date, d1: Date) => {
141 | return () => {
142 | // console.log(d0);
143 | // console.log(d1);
144 | this.spanHandler(d0, d1);
145 | };
146 | };
147 |
148 | function fill(percent: number) {
149 | // const value =0xff * percent;
150 |
151 | const scaleMax = 0xEE;
152 | const value = (scaleMax * percent) + (0xff - scaleMax);
153 |
154 | return Math.floor(value).toString(16);
155 | }
156 |
157 | function styleBox(box0: HTMLElement, gram: Datagram, maxgram: Datagram) {
158 | const box = document.createElement("span");
159 | box0.append(box);
160 |
161 |
162 | box.classList.add("heat-table-cell");
163 | if (gram.startCount === 0 && gram.finishCount === 0) {
164 | box.style.backgroundColor = "black";
165 | box.textContent = "____";
166 | } else {
167 | const tooltip = `Started ${gram.startCount} and Finished ${gram.finishCount}`;
168 | // box.title = tooltip;
169 | // https://stackoverflow.com/a/25813336/1993919
170 | box.setAttribute("data-tooltip", tooltip);
171 | {
172 | let span = document.createElement("span");
173 | box.append(span);
174 |
175 | // span.textContent = gram.startCount.toString();
176 | span.textContent = "__";
177 | let trans = fill(gram.startCount / maxgram.startCount);
178 | span.style.backgroundColor = ATL.startColor1 + trans;
179 | }
180 | {
181 | let span = document.createElement("span");
182 | box.append(span);
183 |
184 | span.textContent = "__";
185 | // span.textContent = gram.finishCount.toString();
186 | let trans = fill(gram.finishCount / maxgram.finishCount);
187 | span.style.backgroundColor = ATL.endColor + trans;
188 | }
189 | }
190 | }
191 |
192 |
193 | for (let y = this.minYear; y <= this.maxYear; y++) {
194 | const row = document.createElement("tr");
195 | table.append(row);
196 | const box = document.createElement("th");
197 | row.append(box);
198 | box.textContent = y.toString();
199 |
200 | const year = this.data.get(y);
201 |
202 | for (let m = 1; m <= 12; ++m) {
203 | let gram = year[m];
204 | const box = document.createElement("td");
205 | row.append(box);
206 |
207 |
208 | box.onclick = returnDateSpan(new Date(y, m - 1, 1), new Date(y, m, 0));
209 |
210 | styleBox(box, gram, this.maxMonthCount);
211 |
212 | }
213 | //
214 | for (let s = Q1; s <= Q4; s++) {
215 | const gram = year[s];
216 | const box = document.createElement("td");
217 | row.append(box);
218 |
219 | let dates = ATL.seasonBounds(ATL.allSeasons[s - Q1], y);
220 | box.onclick = returnDateSpan(dates[0].date, dates[1].date);
221 |
222 | styleBox(box, gram, this.maxSeasonCount);
223 |
224 | }
225 | {
226 | const gram = year[FULL_YEAR];
227 | const box = document.createElement("td");
228 | box.onclick = returnDateSpan(new Date(y, 0, 1), new Date(y + 1, 0, 0));
229 |
230 | row.append(box);
231 |
232 | styleBox(box, gram, this.maxYearCount);
233 |
234 | }
235 |
236 |
237 | }
238 |
239 | return table;
240 | }
241 | }
242 |
243 |
--------------------------------------------------------------------------------
/js/src/MAL.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"MAL.js","sourceRoot":"","sources":["MAL.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/js/src/MAL.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | *
4 | */
5 | import { assertUnreachable } from "./util.js";
6 | /**
7 | *Exported list gave status as a string.
8 | */
9 | export const STATUSES = {
10 | watching: "Watching",
11 | completed: "Completed",
12 | onHold: "On-Hold",
13 | dropped: "Dropped",
14 | planToWatch: "Plan to Watch"
15 | };
16 | /**
17 | * MAL API gives a number. What ever happened to 5?
18 | */
19 | export var Status;
20 | (function (Status) {
21 | Status[Status["Watching"] = 1] = "Watching";
22 | Status[Status["Completed"] = 2] = "Completed";
23 | Status[Status["OnHold"] = 3] = "OnHold";
24 | Status[Status["Dropped"] = 4] = "Dropped";
25 | Status[Status["PlanToWatch"] = 6] = "PlanToWatch";
26 | })(Status || (Status = {}));
27 | function statusFromAniList(status) {
28 | switch (status) {
29 | case "CURRENT": return Status.Watching;
30 | case "PLANNING": return Status.PlanToWatch;
31 | case "COMPLETED": return Status.Completed;
32 | case "DROPPED": return Status.Dropped;
33 | case "PAUSED": return Status.OnHold;
34 | // Idk
35 | case "REPEATING": return Status.Completed;
36 | }
37 | return null;
38 | }
39 | function statusFromMALExport(status) {
40 | switch (status) {
41 | case STATUSES.watching: return Status.Watching;
42 | case STATUSES.planToWatch: return Status.PlanToWatch;
43 | case STATUSES.completed: return Status.Completed;
44 | case STATUSES.dropped: return Status.Dropped;
45 | case STATUSES.onHold: return Status.OnHold;
46 | default:
47 | console.warn(`Unexpected MAL status ${status}`);
48 | assertUnreachable(status);
49 | }
50 | }
51 | export class BadUsernameError extends Error {
52 | }
53 | export class AnimeList {
54 | user;
55 | anime;
56 | namedLists;
57 | cached = false;
58 | constructor(user, anime, namedLists) {
59 | this.user = user;
60 | this.anime = anime;
61 | this.namedLists = namedLists;
62 | }
63 | }
64 | export class MangaList {
65 | user;
66 | anime;
67 | cached = false;
68 | constructor(user, anime) {
69 | this.user = user;
70 | this.anime = anime;
71 | }
72 | }
73 | export function mangaListFromAniList(obj, userName) {
74 | const user = userFromAniList(obj.user, userName);
75 | const userLists = obj.lists;
76 | const allAnime = [];
77 | for (let list of userLists) {
78 | const status = statusFromAniList(list.status);
79 | for (let anime of list.entries) {
80 | allAnime.push(mangaFromAniList(anime, status));
81 | }
82 | }
83 | return new MangaList(user, allAnime);
84 | }
85 | function tagTxt(parent, tag) {
86 | return parent.getElementsByTagName(tag)[0].textContent;
87 | }
88 | function userFromMALExport(myinfo) {
89 | return {
90 | userName: tagTxt(myinfo, "user_name"),
91 | userId: parseInt(tagTxt(myinfo, "user_id"))
92 | };
93 | }
94 | function animeFromMALExport(tag) {
95 | const title = new Title({ userPreferred: tagTxt(tag, "series_title") });
96 | const status = statusFromMALExport(tagTxt(tag, "my_status"));
97 | return {
98 | idAniList: null,
99 | idMAL: parseInt(tagTxt(tag, "series_animedb_id")),
100 | seriesTitle: title,
101 | seriesType: tagTxt(tag, "series_type"),
102 | seriesEpisodes: parseInt(tagTxt(tag, "series_episodes")),
103 | seriesEpisodesDuration: 0,
104 | seriesStart: nullDate,
105 | seriesEnd: nullDate,
106 | userStartDate: new Mdate(tagTxt(tag, "my_start_date")),
107 | userFinishDate: new Mdate(tagTxt(tag, "my_finish_date")),
108 | userScore: parseInt(tagTxt(tag, "my_score")),
109 | userWatchedEpisodes: parseInt(tagTxt(tag, "my_watched_episodes")),
110 | userStatus: status
111 | };
112 | }
113 | export function animeListFromMALExport(xml) {
114 | const user = userFromMALExport(xml.getElementsByTagName("myinfo")[0]);
115 | const animeList = [];
116 | for (const animeTag of xml.getElementsByTagName("anime")) {
117 | const anime = animeFromMALExport(animeTag);
118 | animeList.push(anime);
119 | }
120 | const namedLists = {};
121 | return new AnimeList(user, animeList, namedLists);
122 | }
123 | export function animeListFromAniList(obj, userName) {
124 | const user = userFromAniList(obj.user, userName);
125 | const userLists = obj.lists;
126 | const allAnime = [];
127 | const otherLists = {};
128 | for (let list of userLists) {
129 | const status = statusFromAniList(list.status);
130 | if (null != status) {
131 | for (let anime of list.entries) {
132 | allAnime.push(animeFromAniList(anime, status));
133 | }
134 | }
135 | else {
136 | const tmp = [];
137 | for (let anime of list.entries) {
138 | tmp.push(anime.mediaId);
139 | }
140 | otherLists[list.name] = tmp;
141 | }
142 | }
143 | return new AnimeList(user, allAnime, otherLists);
144 | }
145 | function userFromAniList(obj, name) {
146 | return { userId: obj.id, userName: name, };
147 | }
148 | export class Title {
149 | english;
150 | userPreferred;
151 | romaji;
152 | native;
153 | constructor(it) {
154 | this.english = it.english;
155 | this.userPreferred = it.userPreferred;
156 | this.romaji = it.romaji;
157 | this.native = it.native;
158 | }
159 | preferredEnglish() {
160 | const order = [this.english, this.userPreferred, this.romaji, this.native];
161 | return order.filter(x => x)[0];
162 | }
163 | preferredRomaji() {
164 | const order = [this.romaji, this.userPreferred, this.english, this.native];
165 | return order.filter(x => x)[0];
166 | }
167 | preferredNative() {
168 | const order = [this.native, this.romaji, this.userPreferred, this.english];
169 | return order.filter(x => x)[0];
170 | }
171 | preferred(key) {
172 | switch (key) {
173 | case "english": return this.preferredEnglish();
174 | case "romaji": return this.preferredRomaji();
175 | case "native": return this.preferredNative();
176 | default: throw "Key error";
177 | }
178 | }
179 | }
180 | function mediaFromAniList(obj, status) {
181 | const titleObj = new Title(obj.media.title);
182 | return {
183 | seriesTitle: titleObj,
184 | seriesType: obj.media.format,
185 | seriesStart: dateFromAniList(obj.media.startDate),
186 | seriesEnd: dateFromAniList(obj.media.endDate),
187 | idAniList: obj.mediaId,
188 | idMAL: obj.media.idMal,
189 | userStartDate: dateFromAniList(obj.startedAt),
190 | userFinishDate: dateFromAniList(obj.completedAt),
191 | userStatus: status,
192 | userScore: obj.score,
193 | };
194 | }
195 | function mangaFromAniList(obj, status) {
196 | const base = mediaFromAniList(obj, status);
197 | return base;
198 | }
199 | function animeFromAniList(anime, status) {
200 | const base = mediaFromAniList(anime, status);
201 | const tmp = {
202 | seriesEpisodes: anime.media.episodes,
203 | seriesEpisodesDuration: anime.media.duration,
204 | userWatchedEpisodes: anime.progress,
205 | };
206 | const it = { ...base, ...tmp };
207 | if (it.userStartDate.isNullDate() && it.seriesEpisodes == 1 && !it.userFinishDate.isNullDate()) {
208 | it.userStartDate = it.userFinishDate;
209 | }
210 | return it;
211 | }
212 | export function dateFromYMD(year, month, day) {
213 | const fmt = x => x ? x.toString().padStart(2, "0") : "00";
214 | const ys = year || "0000";
215 | const dstring = `${ys}-${fmt(month)}-${fmt(day)}`;
216 | return new Mdate(dstring);
217 | }
218 | function dateFromAniList(obj) {
219 | return dateFromYMD(obj.year, obj.month, obj.day);
220 | }
221 | export class Mdate {
222 | /*
223 | * YYYY-MM-DD
224 | * MM and DD can be 00 but YYYY must be a year
225 | */
226 | rawDateStr;
227 | fixedDateStr;
228 | /**
229 | * Available only if not nullDate
230 | */
231 | date;
232 | constructor(date) {
233 | //@assume valid string
234 | this.rawDateStr = date;
235 | this.fixedDateStr = Mdate.fixDate(date);
236 | if (this.rawDateStr != rawNullDate) {
237 | this.date = new Date(this.fixedDateStr);
238 | }
239 | }
240 | isNullDate() {
241 | return this.rawDateStr == rawNullDate;
242 | }
243 | static fixDate(dateStr) {
244 | if (dateStr == rawNullDate) {
245 | return rawNullDate;
246 | }
247 | let m = dateStr.slice(5, 7);
248 | if (m == '00')
249 | m = '01';
250 | let d = dateStr.slice(8);
251 | if (d == '00')
252 | d = '01';
253 | return dateStr.slice(0, 5) + m + '-' + d;
254 | }
255 | year() {
256 | // return this.date.getFullYear();
257 | return parseInt(this.fixedDateStr.slice(0, 4));
258 | }
259 | month() {
260 | return parseInt(this.fixedDateStr.slice(5, 7));
261 | }
262 | /**
263 | * this > other → +
264 | * this < other → -
265 | *
266 | * can't use on null dates (?)
267 | *
268 | * @param other
269 | * @returns {number}
270 | */
271 | compare(other) {
272 | let d2;
273 | if (typeof other === "string") {
274 | d2 = new Mdate(other);
275 | }
276 | else {
277 | d2 = other;
278 | }
279 | //assert not null?
280 | if (this.isNullDate() || d2.isNullDate()) {
281 | throw "Can't compare null dates";
282 | }
283 | if (this.fixedDateStr == d2.fixedDateStr) {
284 | return 0;
285 | }
286 | return this.date.valueOf() - d2.date.valueOf();
287 | }
288 | // Select max/min of possibly null dates
289 | extremeOfDates(d2, findMax = true) {
290 | if (this.isNullDate() && d2.isNullDate()) {
291 | return nullDate;
292 | }
293 | else if (this.isNullDate()) {
294 | return d2;
295 | }
296 | else if (d2.isNullDate()) {
297 | return this;
298 | }
299 | let val = this.compare(d2);
300 | if (val == 0) {
301 | return this;
302 | }
303 | if (!findMax) {
304 | val = -val;
305 | }
306 | if (val > 0) {
307 | return this;
308 | }
309 | else { //if (val < 0){
310 | return d2;
311 | }
312 | }
313 | inBounds(lb, rb) {
314 | const date = this;
315 | if (date.isNullDate())
316 | return false;
317 | return date.compare(lb) >= 0 && date.compare(rb) <= 0;
318 | }
319 | }
320 | export function bestMediaID(media) {
321 | return media.idAniList ?? media.idMAL;
322 | }
323 | export const rawNullDate = "0000-00-00";
324 | export const nullDate = new Mdate(rawNullDate);
325 | //# sourceMappingURL=MAL.js.map
--------------------------------------------------------------------------------
/js/src/MAL.ts:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | *
4 | */
5 |
6 | import { assertUnreachable } from "./util.js";
7 |
8 | type BaseObject = Record;
9 |
10 |
11 | type ValuesAsKeys = {
12 | [K in keyof T as T[K]]: number;
13 | };
14 |
15 | type Values = T[keyof T];
16 |
17 |
18 | /**
19 | *Exported list gave status as a string.
20 | */
21 | export const STATUSES = {
22 |
23 | watching: "Watching",
24 | completed: "Completed",
25 | onHold: "On-Hold",
26 | dropped: "Dropped",
27 | planToWatch: "Plan to Watch"
28 |
29 | } as const;
30 |
31 | /**
32 | * MAL API gives a number. What ever happened to 5?
33 | */
34 | export enum Status {
35 | Watching = 1,
36 | Completed = 2,
37 | OnHold = 3,
38 | Dropped = 4,
39 | PlanToWatch = 6
40 | }
41 |
42 | function statusFromAniList(status: string): Status {
43 | switch (status) {
44 | case "CURRENT": return Status.Watching;
45 | case "PLANNING": return Status.PlanToWatch;
46 | case "COMPLETED": return Status.Completed;
47 | case "DROPPED": return Status.Dropped;
48 | case "PAUSED": return Status.OnHold;
49 | // Idk
50 | case "REPEATING": return Status.Completed;
51 |
52 | }
53 | return null;
54 | }
55 | function statusFromMALExport(status: Values): Status {
56 | switch (status) {
57 | case STATUSES.watching: return Status.Watching;
58 | case STATUSES.planToWatch: return Status.PlanToWatch;
59 | case STATUSES.completed: return Status.Completed;
60 | case STATUSES.dropped: return Status.Dropped;
61 | case STATUSES.onHold: return Status.OnHold;
62 | default:
63 | console.warn(`Unexpected MAL status ${status}`);
64 | assertUnreachable(status);
65 | }
66 | }
67 |
68 | export class BadUsernameError extends Error {
69 | }
70 |
71 | export class AnimeList {
72 |
73 | cached = false;
74 |
75 | constructor(
76 | public user: User,
77 | public anime: Anime[],
78 | public namedLists: Record
79 | ) {
80 |
81 | }
82 | }
83 |
84 | export class MangaList {
85 | cached = false;
86 | constructor(
87 | public user: User,
88 | public anime: Manga[],
89 | ) { }
90 | }
91 |
92 |
93 |
94 |
95 |
96 | export function mangaListFromAniList(obj, userName: string): MangaList {
97 | const user = userFromAniList(obj.user, userName);
98 |
99 | const userLists = obj.lists;
100 | const allAnime = [];
101 |
102 | for (let list of userLists) {
103 | const status = statusFromAniList(list.status);
104 | for (let anime of list.entries) {
105 | allAnime.push(mangaFromAniList(anime, status));
106 | }
107 | }
108 | return new MangaList(user, allAnime);
109 | }
110 |
111 | function tagTxt(parent: Element, tag: string): string {
112 | return parent.getElementsByTagName(tag)[0].textContent;
113 | }
114 |
115 | function userFromMALExport(myinfo: Element): User {
116 | return {
117 | userName: tagTxt(myinfo, "user_name"),
118 | userId: parseInt(tagTxt(myinfo, "user_id"))
119 | };
120 | }
121 | function animeFromMALExport(tag: Element): Anime {
122 | const title = new Title({ userPreferred: tagTxt(tag, "series_title") });
123 | const status = statusFromMALExport(tagTxt(tag, "my_status") as any);
124 | return {
125 | idAniList: null,
126 | idMAL: parseInt(tagTxt(tag, "series_animedb_id")),
127 | seriesTitle: title,
128 | seriesType: tagTxt(tag, "series_type"),
129 | seriesEpisodes: parseInt(tagTxt(tag, "series_episodes")),
130 | seriesEpisodesDuration: 0,
131 | seriesStart: nullDate,
132 | seriesEnd: nullDate,
133 | userStartDate: new Mdate(tagTxt(tag, "my_start_date")),
134 | userFinishDate: new Mdate(tagTxt(tag, "my_finish_date")),
135 | userScore: parseInt(tagTxt(tag, "my_score")),
136 | userWatchedEpisodes: parseInt(tagTxt(tag, "my_watched_episodes")),
137 | userStatus: status
138 |
139 | };
140 | }
141 |
142 | export function animeListFromMALExport(xml: Document): AnimeList {
143 | const user = userFromMALExport(xml.getElementsByTagName("myinfo")[0]);
144 | const animeList = [];
145 | for (const animeTag of xml.getElementsByTagName("anime")) {
146 | const anime = animeFromMALExport(animeTag);
147 | animeList.push(anime);
148 | }
149 | const namedLists = {};
150 |
151 | return new AnimeList(user, animeList, namedLists);
152 | }
153 |
154 | export function animeListFromAniList(obj, userName: string): AnimeList {
155 |
156 |
157 | const user = userFromAniList(obj.user, userName);
158 |
159 | const userLists = obj.lists;
160 | const allAnime = [];
161 |
162 | const otherLists = {};
163 |
164 | for (let list of userLists) {
165 | const status = statusFromAniList(list.status);
166 | if (null != status) {
167 | for (let anime of list.entries) {
168 | allAnime.push(animeFromAniList(anime, status));
169 | }
170 | } else {
171 | const tmp = [];
172 | for (let anime of list.entries) {
173 | tmp.push(anime.mediaId);
174 | }
175 | otherLists[list.name] = tmp;
176 |
177 | }
178 | }
179 | return new AnimeList(user, allAnime, otherLists);
180 | }
181 |
182 | export interface User {
183 | userId: number;
184 | userName: string;
185 | }
186 |
187 |
188 |
189 | function userFromAniList(obj, name: string): User {
190 | return { userId: obj.id, userName: name, };
191 | }
192 |
193 | interface ITitle {
194 | english?: string;
195 | userPreferred?: string;
196 | romaji?: string;
197 | native?: string;
198 | }
199 |
200 | export class Title implements ITitle {
201 | public english?: string;
202 | public userPreferred?: string;
203 | public romaji?: string;
204 | public native?: string;
205 |
206 | constructor(it: ITitle) {
207 | this.english = it.english;
208 | this.userPreferred = it.userPreferred;
209 | this.romaji = it.romaji;
210 | this.native = it.native;
211 | }
212 |
213 | preferredEnglish(): string {
214 | const order = [this.english, this.userPreferred, this.romaji, this.native];
215 | return order.filter(x => x)[0];
216 | }
217 |
218 | preferredRomaji(): string {
219 | const order = [this.romaji, this.userPreferred, this.english, this.native];
220 | return order.filter(x => x)[0];
221 | }
222 |
223 | preferredNative(): string {
224 | const order = [this.native, this.romaji, this.userPreferred, this.english];
225 | return order.filter(x => x)[0];
226 | }
227 |
228 | preferred(key: string | "english" | "romaji" | "native"): string {
229 | switch (key) {
230 | case "english": return this.preferredEnglish();
231 | case "romaji": return this.preferredRomaji();
232 | case "native": return this.preferredNative();
233 | default: throw "Key error";
234 | }
235 | }
236 |
237 | }
238 |
239 | interface IMedia {
240 | seriesTitle: Title;
241 | seriesType: string;
242 | idAniList: number | null;
243 | idMAL: number;
244 | seriesStart: Mdate;
245 | seriesEnd: Mdate;
246 |
247 | userStartDate: Mdate;
248 | userFinishDate: Mdate;
249 | userStatus: number;
250 | userScore: number;
251 | }
252 |
253 | export interface Anime extends IMedia {
254 |
255 | seriesEpisodes: number;
256 | seriesEpisodesDuration: number;
257 |
258 | userWatchedEpisodes: number;
259 |
260 | }
261 |
262 | export interface Manga extends IMedia {
263 |
264 |
265 | }
266 |
267 | type GraphMedia = any;
268 |
269 | function mediaFromAniList(obj: GraphMedia, status: Status): IMedia {
270 | const titleObj = new Title(obj.media.title);
271 |
272 |
273 | return {
274 | seriesTitle: titleObj,
275 | seriesType: obj.media.format,
276 | seriesStart: dateFromAniList(obj.media.startDate),
277 | seriesEnd: dateFromAniList(obj.media.endDate),
278 | idAniList: obj.mediaId,
279 | idMAL: obj.media.idMal,
280 | userStartDate: dateFromAniList(obj.startedAt),
281 | userFinishDate: dateFromAniList(obj.completedAt),
282 | userStatus: status,
283 | userScore: obj.score,
284 | };
285 | }
286 |
287 | function mangaFromAniList(obj: GraphMedia, status: Status): Manga {
288 | const base = mediaFromAniList(obj, status);
289 | return base;
290 | }
291 |
292 | function animeFromAniList(anime: GraphMedia, status: Status): Anime {
293 |
294 | const base = mediaFromAniList(anime, status);
295 |
296 | const tmp: Omit = {
297 | seriesEpisodes: anime.media.episodes,
298 | seriesEpisodesDuration: anime.media.duration,
299 |
300 | userWatchedEpisodes: anime.progress,
301 |
302 | };
303 |
304 | const it: Anime = { ...base, ...tmp };
305 |
306 | if (it.userStartDate.isNullDate() && it.seriesEpisodes == 1 && !it.userFinishDate.isNullDate()) {
307 | it.userStartDate = it.userFinishDate;
308 | }
309 |
310 | return it;
311 | }
312 |
313 |
314 |
315 | export function dateFromYMD(year: null | number, month: null | number, day: null | number): Mdate {
316 | const fmt = x => x ? x.toString().padStart(2, "0") : "00";
317 | const ys = year || "0000";
318 | const dstring = `${ys}-${fmt(month)}-${fmt(day)}`;
319 | return new Mdate(dstring);
320 | }
321 |
322 | function dateFromAniList(obj): Mdate {
323 | return dateFromYMD(obj.year, obj.month, obj.day);
324 | }
325 |
326 | export class Mdate {
327 | /*
328 | * YYYY-MM-DD
329 | * MM and DD can be 00 but YYYY must be a year
330 | */
331 |
332 |
333 | public readonly rawDateStr: string;
334 | public readonly fixedDateStr: string;
335 |
336 | /**
337 | * Available only if not nullDate
338 | */
339 | public readonly date: Date;
340 |
341 | constructor(date: string) {
342 |
343 | //@assume valid string
344 |
345 | this.rawDateStr = date;
346 | this.fixedDateStr = Mdate.fixDate(date);
347 | if (this.rawDateStr != rawNullDate) {
348 | this.date = new Date(this.fixedDateStr);
349 | }
350 | }
351 |
352 | isNullDate(): boolean {
353 | return this.rawDateStr == rawNullDate;
354 | }
355 |
356 | static fixDate(dateStr: string): string {
357 | if (dateStr == rawNullDate) {
358 | return rawNullDate;
359 | }
360 |
361 | let m: string = dateStr.slice(5, 7);
362 | if (m == '00') m = '01';
363 | let d: string = dateStr.slice(8);
364 | if (d == '00') d = '01';
365 |
366 | return dateStr.slice(0, 5) + m + '-' + d;
367 | }
368 |
369 | year(): number {
370 | // return this.date.getFullYear();
371 | return parseInt(this.fixedDateStr.slice(0, 4));
372 | }
373 |
374 | month(): number {
375 | return parseInt(this.fixedDateStr.slice(5, 7));
376 | }
377 |
378 | /**
379 | * this > other → +
380 | * this < other → -
381 | *
382 | * can't use on null dates (?)
383 | *
384 | * @param other
385 | * @returns {number}
386 | */
387 | compare(other: string | Mdate): number {
388 | let d2: Mdate;
389 | if (typeof other === "string") {
390 | d2 = new Mdate(other);
391 | } else {
392 | d2 = other;
393 | }
394 |
395 | //assert not null?
396 | if (this.isNullDate() || d2.isNullDate()) {
397 | throw "Can't compare null dates";
398 | }
399 |
400 |
401 | if (this.fixedDateStr == d2.fixedDateStr) {
402 | return 0;
403 | }
404 |
405 | return this.date.valueOf() - d2.date.valueOf();
406 |
407 | }
408 |
409 |
410 | // Select max/min of possibly null dates
411 | extremeOfDates(d2: Mdate, findMax: boolean = true): Mdate {
412 |
413 | if (this.isNullDate() && d2.isNullDate()) {
414 | return nullDate;
415 | } else if (this.isNullDate()) {
416 | return d2;
417 | } else if (d2.isNullDate()) {
418 | return this;
419 | }
420 |
421 | let val: number = this.compare(d2);
422 |
423 | if (val == 0) {
424 | return this;
425 | }
426 |
427 | if (!findMax) {
428 | val = -val;
429 | }
430 |
431 | if (val > 0) {
432 | return this;
433 | } else {//if (val < 0){
434 | return d2;
435 | }
436 |
437 | }
438 |
439 | inBounds(lb: Mdate, rb: Mdate): boolean {
440 | const date = this;
441 | if (date.isNullDate())
442 | return false;
443 | return date.compare(lb) >= 0 && date.compare(rb) <= 0;
444 | }
445 |
446 | }
447 |
448 | export function bestMediaID(media:Media){
449 | return media.idAniList?? media.idMAL;
450 | }
451 |
452 | export const rawNullDate: string = "0000-00-00";
453 | export const nullDate: Mdate = new Mdate(rawNullDate);
454 |
455 |
456 | export type Media = Anime | Manga;
457 | export type MediaArray = Anime[] | Manga[];
458 | export type MediaList = AnimeList | MangaList;
--------------------------------------------------------------------------------
/js/lib/jquery-ui/jquery-ui.theme.min.css:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.12.1 - 2016-09-14
2 | * http://jqueryui.com
3 | * Copyright jQuery Foundation and other contributors; Licensed MIT */
4 |
5 | .ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.003;filter:Alpha(Opacity=.3)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666}
--------------------------------------------------------------------------------
/js/lib/jquery-ui/AUTHORS.txt:
--------------------------------------------------------------------------------
1 | Authors ordered by first contribution
2 | A list of current team members is available at http://jqueryui.com/about
3 |
4 | Paul Bakaus
5 | Richard Worth
6 | Yehuda Katz
7 | Sean Catchpole
8 | John Resig
9 | Tane Piper
10 | Dmitri Gaskin
11 | Klaus Hartl
12 | Stefan Petre
13 | Gilles van den Hoven
14 | Micheil Bryan Smith
15 | Jörn Zaefferer
16 | Marc Grabanski
17 | Keith Wood
18 | Brandon Aaron
19 | Scott González
20 | Eduardo Lundgren
21 | Aaron Eisenberger
22 | Joan Piedra
23 | Bruno Basto
24 | Remy Sharp
25 | Bohdan Ganicky
26 | David Bolter
27 | Chi Cheng
28 | Ca-Phun Ung
29 | Ariel Flesler
30 | Maggie Wachs
31 | Scott Jehl
32 | Todd Parker
33 | Andrew Powell
34 | Brant Burnett
35 | Douglas Neiner
36 | Paul Irish
37 | Ralph Whitbeck
38 | Thibault Duplessis
39 | Dominique Vincent
40 | Jack Hsu
41 | Adam Sontag
42 | Carl Fürstenberg
43 | Kevin Dalman
44 | Alberto Fernández Capel
45 | Jacek Jędrzejewski (http://jacek.jedrzejewski.name)
46 | Ting Kuei
47 | Samuel Cormier-Iijima
48 | Jon Palmer
49 | Ben Hollis
50 | Justin MacCarthy
51 | Eyal Kobrigo
52 | Tiago Freire
53 | Diego Tres
54 | Holger Rüprich
55 | Ziling Zhao
56 | Mike Alsup
57 | Robson Braga Araujo
58 | Pierre-Henri Ausseil
59 | Christopher McCulloh
60 | Andrew Newcomb
61 | Lim Chee Aun
62 | Jorge Barreiro
63 | Daniel Steigerwald
64 | John Firebaugh
65 | John Enters
66 | Andrey Kapitcyn
67 | Dmitry Petrov
68 | Eric Hynds
69 | Chairat Sunthornwiphat
70 | Josh Varner
71 | Stéphane Raimbault
72 | Jay Merrifield
73 | J. Ryan Stinnett
74 | Peter Heiberg
75 | Alex Dovenmuehle
76 | Jamie Gegerson
77 | Raymond Schwartz
78 | Phillip Barnes
79 | Kyle Wilkinson
80 | Khaled AlHourani
81 | Marian Rudzynski
82 | Jean-Francois Remy
83 | Doug Blood
84 | Filippo Cavallarin
85 | Heiko Henning
86 | Aliaksandr Rahalevich
87 | Mario Visic
88 | Xavi Ramirez
89 | Max Schnur
90 | Saji Nediyanchath
91 | Corey Frang
92 | Aaron Peterson
93 | Ivan Peters
94 | Mohamed Cherif Bouchelaghem
95 | Marcos Sousa
96 | Michael DellaNoce
97 | George Marshall
98 | Tobias Brunner
99 | Martin Solli
100 | David Petersen
101 | Dan Heberden
102 | William Kevin Manire
103 | Gilmore Davidson
104 | Michael Wu
105 | Adam Parod
106 | Guillaume Gautreau
107 | Marcel Toele
108 | Dan Streetman
109 | Matt Hoskins
110 | Giovanni Giacobbi
111 | Kyle Florence
112 | Pavol Hluchý
113 | Hans Hillen
114 | Mark Johnson
115 | Trey Hunner
116 | Shane Whittet
117 | Edward A Faulkner
118 | Adam Baratz
119 | Kato Kazuyoshi
120 | Eike Send
121 | Kris Borchers
122 | Eddie Monge
123 | Israel Tsadok
124 | Carson McDonald
125 | Jason Davies
126 | Garrison Locke
127 | David Murdoch
128 | Benjamin Scott Boyle
129 | Jesse Baird
130 | Jonathan Vingiano
131 | Dylan Just
132 | Hiroshi Tomita
133 | Glenn Goodrich
134 | Tarafder Ashek-E-Elahi
135 | Ryan Neufeld
136 | Marc Neuwirth
137 | Philip Graham
138 | Benjamin Sterling
139 | Wesley Walser
140 | Kouhei Sutou
141 | Karl Kirch
142 | Chris Kelly
143 | Jason Oster
144 | Felix Nagel
145 | Alexander Polomoshnov
146 | David Leal
147 | Igor Milla
148 | Dave Methvin
149 | Florian Gutmann
150 | Marwan Al Jubeh
151 | Milan Broum
152 | Sebastian Sauer
153 | Gaëtan Muller
154 | Michel Weimerskirch
155 | William Griffiths
156 | Stojce Slavkovski
157 | David Soms
158 | David De Sloovere
159 | Michael P. Jung
160 | Shannon Pekary
161 | Dan Wellman
162 | Matthew Edward Hutton
163 | James Khoury
164 | Rob Loach
165 | Alberto Monteiro
166 | Alex Rhea
167 | Krzysztof Rosiński
168 | Ryan Olton
169 | Genie <386@mail.com>
170 | Rick Waldron
171 | Ian Simpson
172 | Lev Kitsis
173 | TJ VanToll
174 | Justin Domnitz
175 | Douglas Cerna
176 | Bert ter Heide
177 | Jasvir Nagra
178 | Yuriy Khabarov <13real008@gmail.com>
179 | Harri Kilpiö
180 | Lado Lomidze
181 | Amir E. Aharoni
182 | Simon Sattes
183 | Jo Liss
184 | Guntupalli Karunakar
185 | Shahyar Ghobadpour
186 | Lukasz Lipinski
187 | Timo Tijhof
188 | Jason Moon
189 | Martin Frost
190 | Eneko Illarramendi
191 | EungJun Yi
192 | Courtland Allen
193 | Viktar Varvanovich
194 | Danny Trunk
195 | Pavel Stetina
196 | Michael Stay
197 | Steven Roussey
198 | Michael Hollis
199 | Lee Rowlands
200 | Timmy Willison
201 | Karl Swedberg
202 | Baoju Yuan
203 | Maciej Mroziński
204 | Luis Dalmolin
205 | Mark Aaron Shirley
206 | Martin Hoch
207 | Jiayi Yang
208 | Philipp Benjamin Köppchen
209 | Sindre Sorhus
210 | Bernhard Sirlinger
211 | Jared A. Scheel
212 | Rafael Xavier de Souza
213 | John Chen
214 | Robert Beuligmann
215 | Dale Kocian
216 | Mike Sherov
217 | Andrew Couch
218 | Marc-Andre Lafortune
219 | Nate Eagle
220 | David Souther
221 | Mathias Stenbom
222 | Sergey Kartashov
223 | Avinash R
224 | Ethan Romba
225 | Cory Gackenheimer
226 | Juan Pablo Kaniefsky
227 | Roman Salnikov
228 | Anika Henke
229 | Samuel Bovée
230 | Fabrício Matté
231 | Viktor Kojouharov
232 | Pawel Maruszczyk (http://hrabstwo.net)
233 | Pavel Selitskas
234 | Bjørn Johansen
235 | Matthieu Penant
236 | Dominic Barnes