diff --git a/css/main.css b/css/main.css
index c93c1aa..a024266 100644
--- a/css/main.css
+++ b/css/main.css
@@ -10,6 +10,37 @@ body {
font-size: 16px;
}
+button {
+ padding: 8px 16px;
+ font-size: 14px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ color: #fff;
+ background-color: #555;
+ transition: background-color 0.2s;
+}
+
+button:hover {
+ background-color: #333;
+}
+
+button#download {
+ background-color: #2b7a2b;
+}
+
+button#download:hover {
+ background-color: #216121;
+}
+
+button#reset {
+ background-color: #b33a3a;
+}
+
+button#reset:hover {
+ background-color: #902d2d;
+}
+
p {
text-align: center;
}
diff --git a/index.html b/index.html
index 53680e6..b423994 100644
--- a/index.html
+++ b/index.html
@@ -8,7 +8,7 @@
Thumbnail Generator
Generating YouTube video thumbnails for GitHub READMEs.
-
![]()
+
@@ -44,6 +44,10 @@
Thumbnail Generator
+
+
+
+
diff --git a/js/index.js b/js/index.js
index ead6ed2..4c1d784 100644
--- a/js/index.js
+++ b/js/index.js
@@ -2,7 +2,7 @@
function getElementByIdOrDie(elementId) {
var element = document.getElementById(elementId);
if (element === null) {
- throw new Error("Could not find element with id '" + elementId + "'");
+ throw new Error("Could not find element with id '".concat(elementId, "'"));
}
return element;
}
@@ -17,7 +17,7 @@ function renderThumbnail(ctx, ytThumb, state) {
gradient.addColorStop(1.0, '#00000000');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, state.width, height);
- ctx.font = state.fontSize + "px LibreBaskerville";
+ ctx.font = "".concat(state.fontSize, "px LibreBaskerville");
ctx.fillStyle = 'white';
ctx.fillText(state.title, state.pad, height - state.pad);
}
@@ -31,6 +31,26 @@ function updateUrl(state, defaultState) {
}
window.history.replaceState(null, "", "?" + new URLSearchParams(diff).toString());
}
+function slugify(str) {
+ return str
+ .toLowerCase()
+ .trim()
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)/g, "");
+}
+function downloadThumbnail(title, canvas) {
+ canvas.toBlob(function (blob) {
+ if (!blob)
+ throw new Error("Could not convert the canvas to Blob");
+ var link = document.createElement("a");
+ link.href = URL.createObjectURL(blob);
+ link.download = slugify(title);
+ link.click();
+ URL.revokeObjectURL(link.href);
+ });
+}
window.onload = function () {
var ytLink = getElementByIdOrDie("yt-link");
var ytError = getElementByIdOrDie("yt-error");
@@ -43,6 +63,8 @@ window.onload = function () {
var ytFontDisplay = getElementByIdOrDie("yt-font-display");
var ytPad = getElementByIdOrDie("yt-pad");
var ytPadDisplay = getElementByIdOrDie("yt-pad-display");
+ var downloadBtn = getElementByIdOrDie("download");
+ var resetBtn = getElementByIdOrDie("reset");
var ctx = ytCanvas.getContext('2d');
if (ctx === null)
throw new Error("Could not initialize 2d context");
@@ -70,18 +92,26 @@ window.onload = function () {
if (link)
ytLink.value = link;
ytWidthDisplay.value = ytWidth.value;
- ytFontDisplay.value = ytFont.value + "px";
+ ytFontDisplay.value = "".concat(ytFont.value, "px");
ytPadDisplay.value = ytPad.value;
- var state = {
- title: ytTitle.value,
- width: Number(ytWidth.value),
- fontSize: Number(ytFont.value),
- pad: Number(ytPad.value),
- link: ytLink.value,
- };
+ var state = Object.assign({}, defaultState);
var json = JSON.stringify(state);
console.log(json);
console.log(btoa(json));
+ downloadBtn.addEventListener("click", function () { return downloadThumbnail(ytTitle.value, ytCanvas); });
+ resetBtn.addEventListener("click", function () {
+ ytTitle.value = defaultState.title;
+ ytWidth.value = defaultState.width.toString();
+ ytFont.value = defaultState.fontSize.toString();
+ ytPad.value = defaultState.pad.toString();
+ ytLink.value = defaultState.link;
+ ytWidthDisplay.value = defaultState.width.toString();
+ ytFontDisplay.value = "".concat(defaultState.fontSize.toString(), "px");
+ ytPadDisplay.value = ytPad.value;
+ state = Object.assign({}, defaultState);
+ updateUrl(state, defaultState);
+ renderThumbnail(ctx, ytThumb, state);
+ });
ytWidth.onchange = function () { return updateUrl(state, defaultState); };
ytWidth.oninput = function () {
ytWidthDisplay.value = ytWidth.value;
@@ -90,7 +120,7 @@ window.onload = function () {
};
ytFont.onchange = function () { return updateUrl(state, defaultState); };
ytFont.oninput = function () {
- ytFontDisplay.value = ytFont.value + "px";
+ ytFontDisplay.value = "".concat(ytFont.value, "px");
state.fontSize = Number(ytFont.value);
renderThumbnail(ctx, ytThumb, state);
};
@@ -116,7 +146,7 @@ window.onload = function () {
var ytHostRegexp = new RegExp('^(.+\.)?youtube\.com$');
if (ytHostRegexp.test(url.hostname)) {
var ytVideoId = url.searchParams.getAll('v').join('');
- ytThumb.src = "http://i3.ytimg.com/vi/" + ytVideoId + "/maxresdefault.jpg";
+ ytThumb.src = "http://i3.ytimg.com/vi/".concat(ytVideoId, "/maxresdefault.jpg");
}
else {
throw new Error('Only YouTube Links are supported');
diff --git a/ts/index.ts b/ts/index.ts
index 5147072..1378f0a 100644
--- a/ts/index.ts
+++ b/ts/index.ts
@@ -19,7 +19,7 @@ function renderThumbnail(ctx: CanvasRenderingContext2D, ytThumb: HTMLImageElemen
const aspect = ytThumb.height / ytThumb.width;
const height = aspect * state.width;
- ctx.canvas.width = state.width;
+ ctx.canvas.width = state.width;
ctx.canvas.height = height;
ctx.drawImage(ytThumb, 0, 0, state.width, height);
@@ -46,18 +46,42 @@ function updateUrl(state: State, defaultState: State) {
window.history.replaceState(null, "", "?" + new URLSearchParams(diff).toString());
}
+function slugify(str: string) {
+ return str
+ .toLowerCase()
+ .trim()
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)/g, "");
+}
+
+function downloadThumbnail(title: string, canvas: HTMLCanvasElement): void {
+ canvas.toBlob(blob => {
+ if (!blob) throw new Error("Could not convert the canvas to Blob");
+ const link = document.createElement("a");
+ link.href = URL.createObjectURL(blob);
+ link.download = slugify(title);
+ link.click();
+ URL.revokeObjectURL(link.href);
+ });
+}
+
+
window.onload = () => {
- const ytLink = getElementByIdOrDie("yt-link") as HTMLInputElement;
- const ytError = getElementByIdOrDie("yt-error") as HTMLElement;
- const ytThumb = getElementByIdOrDie("yt-thumb") as HTMLImageElement;
- const ytCanvas = getElementByIdOrDie("yt-canvas") as HTMLCanvasElement;
- const ytTitle = getElementByIdOrDie("yt-title") as HTMLInputElement;
- const ytWidth = getElementByIdOrDie("yt-width") as HTMLInputElement;
+ const ytLink = getElementByIdOrDie("yt-link") as HTMLInputElement;
+ const ytError = getElementByIdOrDie("yt-error") as HTMLElement;
+ const ytThumb = getElementByIdOrDie("yt-thumb") as HTMLImageElement;
+ const ytCanvas = getElementByIdOrDie("yt-canvas") as HTMLCanvasElement;
+ const ytTitle = getElementByIdOrDie("yt-title") as HTMLInputElement;
+ const ytWidth = getElementByIdOrDie("yt-width") as HTMLInputElement;
const ytWidthDisplay = getElementByIdOrDie("yt-width-display") as HTMLOutputElement;
- const ytFont = getElementByIdOrDie("yt-font") as HTMLInputElement;
- const ytFontDisplay = getElementByIdOrDie("yt-font-display") as HTMLOutputElement;
- const ytPad = getElementByIdOrDie("yt-pad") as HTMLInputElement;
- const ytPadDisplay = getElementByIdOrDie("yt-pad-display") as HTMLOutputElement;
+ const ytFont = getElementByIdOrDie("yt-font") as HTMLInputElement;
+ const ytFontDisplay = getElementByIdOrDie("yt-font-display") as HTMLOutputElement;
+ const ytPad = getElementByIdOrDie("yt-pad") as HTMLInputElement;
+ const ytPadDisplay = getElementByIdOrDie("yt-pad-display") as HTMLOutputElement;
+ const downloadBtn = getElementByIdOrDie("download") as HTMLButtonElement;
+ const resetBtn = getElementByIdOrDie("reset") as HTMLButtonElement;
const ctx = ytCanvas.getContext('2d');
if (ctx === null) throw new Error(`Could not initialize 2d context`);
@@ -71,28 +95,40 @@ window.onload = () => {
};
const params = new URLSearchParams(window.location.search);
- const title = params.get("title"); if (title) ytTitle.value = title;
- const width = params.get("width"); if (width) ytWidth.value = width;
- const fontSize = params.get("fontSize"); if (fontSize) ytFont.value = fontSize;
- const pad = params.get("pad"); if (pad) ytPad.value = pad;
- const link = params.get("link"); if (link) ytLink.value = link;
+ const title = params.get("title"); if (title) ytTitle.value = title;
+ const width = params.get("width"); if (width) ytWidth.value = width;
+ const fontSize = params.get("fontSize"); if (fontSize) ytFont.value = fontSize;
+ const pad = params.get("pad"); if (pad) ytPad.value = pad;
+ const link = params.get("link"); if (link) ytLink.value = link;
ytWidthDisplay.value = ytWidth.value;
- ytFontDisplay.value = `${ytFont.value}px`;
- ytPadDisplay.value = ytPad.value;
+ ytFontDisplay.value = `${ytFont.value}px`;
+ ytPadDisplay.value = ytPad.value;
- const state = {
- title: ytTitle.value,
- width: Number(ytWidth.value),
- fontSize: Number(ytFont.value),
- pad: Number(ytPad.value),
- link: ytLink.value,
- };
+ let state = Object.assign({}, defaultState);
const json = JSON.stringify(state);
console.log(json)
console.log(btoa(json));
+ downloadBtn.addEventListener("click", () => downloadThumbnail(ytTitle.value, ytCanvas));
+
+ resetBtn.addEventListener("click", () => {
+ ytTitle.value = defaultState.title;
+ ytWidth.value = defaultState.width.toString();
+ ytFont.value = defaultState.fontSize.toString();
+ ytPad.value = defaultState.pad.toString();
+ ytLink.value = defaultState.link;
+
+ ytWidthDisplay.value = defaultState.width.toString();
+ ytFontDisplay.value = `${defaultState.fontSize.toString()}px`;
+ ytPadDisplay.value = ytPad.value;
+
+ state = Object.assign({}, defaultState)
+ updateUrl(state, defaultState);
+ renderThumbnail(ctx, ytThumb, state);
+ });
+
ytWidth.onchange = () => updateUrl(state, defaultState);
ytWidth.oninput = () => {
ytWidthDisplay.value = ytWidth.value;
@@ -131,7 +167,7 @@ window.onload = () => {
} else {
throw new Error('Only YouTube Links are supported');
}
- } catch(e) {
+ } catch (e) {
ytError.innerText = (e as Error).message;
}
};