Center text instead of doing weird calculation
[brackets.git] / frontend / index.js
1 const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
2
3 /**
4 * Safari iOS pls
5 */
6 const formSubmitPolyfill = (form, callback) => {
7 if (form.requestSubmit) {
8 form.requestSubmit();
9 } else {
10 callback();
11 }
12 }
13
14 const createGenreListWithClickEvent = (genreList, genres, callback) => {
15 genreList.append(...genres.map((genre) => {
16 const name = genre["name"];
17 const li = document.createElement("li");
18 li.setAttribute("data-name", name);
19 li.addEventListener("click", callback);
20 li.appendChild(document.createTextNode(name));
21 return li;
22 }));
23 }
24
25 const relativeToCtx = (x1, x2, y1, y2, ctx) => {
26 const newX = x1 + x2;
27 const newY = y1 + y2;
28 ctx.lineTo(newX, newY);
29 return [newX, newY];
30 }
31
32 /**
33 * Return (x,y) of canvas
34 */
35 const getDimensions = (canvas) => [canvas.width, canvas.height];
36
37 /**
38 * Return (x,y) midpoint of canvas
39 */
40 const getCenter = (canvas) => getDimensions(canvas).map((dim) => dim / 2);
41
42 const getRectangleDimensionsUnbound = (canvas, xScale, yScale) => {
43 [width, height] = getDimensions(canvas);
44 return [width * xScale, height * yScale];
45 }
46
47 const getRectangleDimensions = (canvas) => getRectangleDimensionsUnbound(canvas, .5, .1);
48
49 /**
50 * Draw champion box and clear background.
51 */
52 const drawWinner = (canvas) => {
53 const ctx = canvas.getContext("2d");
54 const [width, height] = getDimensions(canvas);
55 const [mid_x, mid_y] = getCenter(canvas);
56 const [rect_width, rect_height] = getRectangleDimensions(canvas);
57 ctx.strokeRect(mid_x - rect_width / 2, mid_y - rect_height / 2, rect_width, rect_height);
58 ctx.clearRect(mid_x - rect_width / 2, mid_y - rect_height / 2, rect_width, rect_height);
59 }
60
61 /**
62 * Draws a path in context from the current location to a point xDist * left, yDist * up away from it.
63 * @param ctx RenderingContext
64 * @param x, y float current location
65 * @param x, y float distance away
66 * @param up, left (-1|1) directions
67 * @return [newNodeX, newNodeY]
68 */
69 const drawBranchFrom = (ctx, x, y, xDist, yDist, left, up) => {
70 const newX = x + (xDist * left)
71 const newY = y + (yDist * up)
72 ctx.lineTo(x, newY);
73 ctx.lineTo(newX, newY);
74 ctx.stroke();
75 return [newX, newY];
76 }
77
78 const drawArtistOnCtx = (ctx, artistName, x, y) => {
79 ctx.font = "20px sans serif";
80 ctx.strokeText(artistName, x, y);
81 }
82
83 /**
84 * Draws paths to the terminal nodes of a round
85 * @param x, y the point representation of the start of the branch
86 * @param baseCallback a callback that needs the terminal x,y context
87 */
88 const drawMatchup = (canvas, x, y, iter, maxIter, left, artists, baseCallback) => {
89 if (iter === maxIter) {
90 return baseCallback(x, y);
91 }
92 const ctx = canvas.getContext("2d");
93 ctx.direction = left === -1 ? "ltr" : "rtl";
94 const [width, height] = getDimensions(canvas);
95
96 const drawBranchUp = (xDist, yDist) => drawBranchFrom(ctx, x, y, xDist, yDist, left, -1);
97 const drawBranchDown = (xDist, yDist) => drawBranchFrom(ctx, x, y, xDist, yDist, left, 1);
98 const drawArtist = (artistName, x, y) => drawArtistOnCtx(ctx, artistName, x, y);
99 const drawArtist1 = (x, y) => drawArtist(artists.shift()["name"], x, y);
100 const drawArtist2 = (x, y) => drawArtist(artists.pop()["name"], x, y);
101
102 ctx.beginPath();
103 ctx.moveTo(x, y);
104 const branchDistances = [width / (5 * (iter + 1)), height / (9 * (iter + 1))];
105 drawMatchup(
106 canvas,
107 ...drawBranchUp(...branchDistances),
108 iter + 1,
109 maxIter,
110 left,
111 artists,
112 drawArtist1,
113 );
114
115 ctx.moveTo(x, y);
116 drawMatchup(
117 canvas,
118 ...drawBranchDown(...branchDistances),
119 iter + 1,
120 maxIter,
121 left,
122 artists,
123 drawArtist2,
124 );
125 }
126
127 const drawBracket = (canvas, artists, genre) => {
128 const context = canvas.getContext("2d");
129 context.clearRect(0, 0, canvas.width, canvas.height);
130 drawWinner(canvas);
131 const [mid_x, mid_y] = getCenter(canvas);
132 context.font = '28px sans serif'
133 context.textAlign = "center";
134 context.strokeText(genre.toUpperCase(), mid_x, 40);
135 context.textAlign = "start";
136 const [rect_width, rect_height] = getRectangleDimensions(canvas);
137 const groups = 4;
138 const rounds = Math.floor(Math.log2(artists.length / groups));
139 for (let group = 1; group <= groups; group++) {
140 drawMatchup(
141 canvas,
142 mid_x + (Math.pow(-1, group) * (rect_width / 6)),
143 mid_y + (Math.pow(-1, Math.floor(group / 2)) * (rect_height * 2.5)),
144 0,
145 rounds,
146 Math.pow(-1, group),
147 artists,
148 (x, y) => console.log("hello")
149 );
150 }
151 }
152
153 window.onload = () => {
154 const lStorage = window.localStorage;
155 const genreList = document.getElementById("genre-list");
156 const genreInput = document.getElementById("genre-input");
157 const genreForm = document.getElementById("genre-form");
158 const canvas = document.getElementById("bracket");
159
160 const formSubmitAction = () => {
161 fetch(encodeURI(`http://api.brackets.jacobcasper.com/artist/genre?genre_name=${genreInput.value}`))
162 .then((response) => response.json())
163 .then((data) => drawBracket(canvas, data.slice(0, 33), genreInput.value));
164 }
165
166 const createGenreList = (genreList, genres) => {
167 return createGenreListWithClickEvent(genreList, genres, (e) => {
168 genreInput.value = e.target.innerText;
169 formSubmitPolyfill(genreForm, formSubmitAction);
170 })
171 }
172 let genres = JSON.parse(lStorage.getItem("genres"))
173 if (genres === null) {
174 fetch("http://api.brackets.jacobcasper.com/genre")
175 .then((response) => response.text())
176 .then((text) => {
177 window.localStorage.setItem("genres", text);
178 genres = JSON.parse(text);
179 createGenreList(genreList, genres);
180 });
181 } else {
182 createGenreList(genreList, genres);
183 }
184
185 genreForm.addEventListener("submit", (e) => {
186 e.preventDefault();
187 formSubmitAction()
188 });
189
190 genreInput.addEventListener("input", (e) => {
191 const input = e.target;
192 Array.from(genreList.children).forEach((item) => item.style.display= item.dataset.name.includes(input.value.toLowerCase()) ? "block" : "none");
193 });
194 genreInput.addEventListener("focus", (e) => {
195 genreList.style.display = "block";
196 });
197 genreInput.addEventListener("blur", (e) => {
198 sleep(150).then(() => genreList.style.display = "none");
199 });
200
201
202 canvas.width = window.innerWidth;
203 canvas.height = window.innerHeight;
204 drawBracket(canvas, ['dummy', 'dummy', 'dum' ,'dumhy'], "");
205
206 const bgImg = new Image();
207 bgImg.onload = () => {
208 const ctx = canvas.getContext("2d");
209 //ctx.mozImageSmoothingEnabled = false;
210 //ctx.webkitImageSmoothingEnabled = false;
211 //ctx.msImageSmoothingEnabled = false;
212 //ctx.imageSmoothingEnabled = false;
213 ctx.drawImage(
214 bgImg,
215 0,
216 0,
217 (canvas.width / bgImg.width) * bgImg.width,
218 (canvas.height / bgImg.height) * bgImg.height
219 );
220 }
221
222 const upload = document.getElementById("image-upload");
223 upload.addEventListener("change", (e) => {
224 bgImg.src = URL.createObjectURL(e.target.files[0]);
225 });
226 }