diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2deaabd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.crx +*.pem \ No newline at end of file diff --git a/userscript-client.js b/client/extension/StadiaAvatar.js similarity index 100% rename from userscript-client.js rename to client/extension/StadiaAvatar.js diff --git a/client/extension/_locales/en/messages.json b/client/extension/_locales/en/messages.json new file mode 100644 index 0000000..4ac2f45 --- /dev/null +++ b/client/extension/_locales/en/messages.json @@ -0,0 +1,18 @@ +{ + "extension_description": { + "message": "Custom Stadia avatars for you and your friends", + "description": "Short summary of this extension's function." + }, + "avatar_set": { + "message": "Set avatar from", + "description": "List of different options to set a users's avatar" + }, + "avatar_set_url_support": { + "message": "All image formats supported by Chrome except SVG can be added", + "description": "Disclaimer about supported image formats" + }, + "page_return": { + "message": "Go back", + "description": "Tooltip when hovering the back button on a page" + } +} \ No newline at end of file diff --git a/client/extension/assets/css/popup.css b/client/extension/assets/css/popup.css new file mode 100644 index 0000000..e8491cb --- /dev/null +++ b/client/extension/assets/css/popup.css @@ -0,0 +1,163 @@ +:root { + /* color components */ + --palette-background: 33,33,33; + --palette-contrast: 255,255,255; + --palette-header: 45,45,45; + --palette-accent-high: 253,74,24; + --palette-accent-low: 170,3,88; + + /* compiled colors */ + --color-background: rgb(var(--palette-background)); + --color-contrast: rgb(var(--palette-contrast)); + --color-header: rgb(var(--palette-header)); + --color-accent: linear-gradient(145deg, var(--palette-accent-high) 0%, var(--palette-accent-low) 100%); +} + +* { + margin: 0; + color: var(--color-contrast); +} + +*::-webkit-scrollbar { + display: none; +} + +html, +body { + background-color: var(--color-background); + display: flex; + flex-direction: column; + align-items: center; +} + +body { + --page-depth: 0; + --animation-speed: 400ms; + + width: 400px; + transition: var(--animation-speed) transform cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(calc((100% * var(--page-depth)) * -1)); +} + +.page .header, +header { + width: 100%; + background-color: var(--color-header); + border-bottom: solid 1px black; +} + +header { + --header-padding: 30px; + --pfp-size: 80px; + --pfp-stroke-offset: 5px; + --pfp-stroke-width: 4px; + --pfp-stroke-size: calc(var(--pfp-stroke-offset) + var(--pfp-stroke-width)); + + height: calc(var(--pfp-size) + (var(--pfp-stroke-size) * 2)); + padding: var(--header-padding) 0 var(--header-padding) 0; + display: flex; + justify-content: center; + align-items: center; +} + +#myAvatar { + width: var(--pfp-size); + height: var(--pfp-size); + box-shadow: + 0 0 0 var(--pfp-stroke-offset) var(--color-header), + 0 0 0 var(--pfp-stroke-size) rgb(var(--palette-accent-high)); + border-radius: 100%; +} + +ol { + list-style-type: none; + width: 100%; + font-size: 15px; + padding-left: 0; +} + +ol li { + --padding: 25px; + + width: calc(100% - (var(--padding) * 2)); + height: 64px; + padding: 0 var(--padding) 0 var(--padding); + display: flex; + align-items: center; + cursor: pointer; +} + +.page .back:hover, +ol li:hover { + background: rgba(var(--palette-contrast),.1); +} + +ol li img { + height: 20px; + filter: brightness(0) invert(1); + margin-right: 20px; +} + +/* ---- */ + +.page { + transition: var(--animation-speed) opacity; + position: absolute; + top: 0; + left: 100%; + width: 100%; + opacity: 1; +} + +.page .header { + --height: 60px; + --button-size: 40px; + --padding: 10px; + + height: 60px; + box-sizing: border-box; + padding: 0 var(--padding) 0 var(--padding); + display: flex; + align-items: center; +} + +.page .back { + --chevron-size: 10px; + --chevron-stroke-width: 3px; + + width: var(--button-size); + height: var(--button-size); + border-radius: 4px; + border: solid 1px var(--color-background); + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; +} + +.page .back::before { + content: ''; + border-style: solid; + border-width: var(--chevron-stroke-width) var(--chevron-stroke-width) 0 0; + display: inline-block; + width: var(--chevron-size); + height: var(--chevron-size); + top: 0; + left: calc(var(--chevron-size) / 3); + transform: rotate(-135deg); + position: relative; + vertical-align: top; +} + +.page .header p { + width: calc(100% - (var(--button-size) * 2)); + font-size: 17px; + text-align: center; +} + +.page .body { + position: relative; + top: var(--height); + box-sizing: border-box; + padding: 20px; +} \ No newline at end of file diff --git a/client/extension/assets/img/icon_set_gravatar.svg b/client/extension/assets/img/icon_set_gravatar.svg new file mode 100644 index 0000000..b14cd67 --- /dev/null +++ b/client/extension/assets/img/icon_set_gravatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/extension/assets/img/icon_set_url.svg b/client/extension/assets/img/icon_set_url.svg new file mode 100644 index 0000000..633692c --- /dev/null +++ b/client/extension/assets/img/icon_set_url.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/extension/assets/img/logo.png b/client/extension/assets/img/logo.png new file mode 100644 index 0000000..89d3e4b Binary files /dev/null and b/client/extension/assets/img/logo.png differ diff --git a/client/extension/assets/img/logo_icon_128.png b/client/extension/assets/img/logo_icon_128.png new file mode 100644 index 0000000..eabf022 Binary files /dev/null and b/client/extension/assets/img/logo_icon_128.png differ diff --git a/client/extension/assets/img/logo_icon_16.png b/client/extension/assets/img/logo_icon_16.png new file mode 100644 index 0000000..e3983a9 Binary files /dev/null and b/client/extension/assets/img/logo_icon_16.png differ diff --git a/client/extension/assets/img/logo_icon_32.png b/client/extension/assets/img/logo_icon_32.png new file mode 100644 index 0000000..a16f8c5 Binary files /dev/null and b/client/extension/assets/img/logo_icon_32.png differ diff --git a/client/extension/assets/img/logo_icon_48.png b/client/extension/assets/img/logo_icon_48.png new file mode 100644 index 0000000..2a63616 Binary files /dev/null and b/client/extension/assets/img/logo_icon_48.png differ diff --git a/client/extension/assets/js/popup.js b/client/extension/assets/js/popup.js new file mode 100644 index 0000000..77a766c --- /dev/null +++ b/client/extension/assets/js/popup.js @@ -0,0 +1,33 @@ +import { AvatarURL } from "./popup_modules/ChangeAvatar.mjs"; + +// Localize by replacing __MSG_***__ strings in DOM +function localizePage() { + for (var i = 0; i < document.body.children.length; i++) { + const element = document.body.children[i]; + + let valStrH = element.innerHTML.toString(); + const valNewH = valStrH.replace(/__MSG_(\w+)__/g, (match,key) => { + return key ? chrome.i18n.getMessage(key) : ""; + }); + + if(valNewH != valStrH) { + element.innerHTML = valNewH; + } + } +} + +function eventHandler(event) { + const target = event.target.closest("[button]"); + switch(target.getAttribute("button")) { + case "avatar:url": new AvatarURL(); break; + } +} + +document.addEventListener("DOMContentLoaded", function () { + // Bind click listeners to all button attributes + for(const button of document.querySelectorAll("[button]")) { + button.addEventListener("click",event => eventHandler(event)); + } +}); + +localizePage(); \ No newline at end of file diff --git a/client/extension/assets/js/popup_modules/ChangeAvatar.mjs b/client/extension/assets/js/popup_modules/ChangeAvatar.mjs new file mode 100644 index 0000000..86752f4 --- /dev/null +++ b/client/extension/assets/js/popup_modules/ChangeAvatar.mjs @@ -0,0 +1,12 @@ +import { Page } from "./Page.mjs"; + +export class AvatarURL extends Page { + + constructor() { + super(chrome.i18n.getMessage("avatar_set") + " URL"); + + this.appendHTML(`
${chrome.i18n.getMessage("avatar_set_url_support")}
`); + this.open(); + } + +} diff --git a/client/extension/assets/js/popup_modules/Page.mjs b/client/extension/assets/js/popup_modules/Page.mjs new file mode 100644 index 0000000..fad30c9 --- /dev/null +++ b/client/extension/assets/js/popup_modules/Page.mjs @@ -0,0 +1,70 @@ +export class Page { + + constructor(title = "") { + this.body = null; + + this.pageDepth = () => { + return parseInt(getComputedStyle(document.body).getPropertyValue("--page-depth")); + } + + this.create(title); + } + + create(title) { + // Create elements + const wrapper = document.createElement("div"); + this.body = document.createElement("div"); + const header = document.createElement("div"); + const backButton = document.createElement("div"); + + const pageDepth = (this.pageDepth() + 1) * 100; + + // Add element attributes + wrapper.classList.add("page"); + wrapper.style.setProperty("left",pageDepth + "%"); + this.body.classList.add("body"); + header.classList.add("header"); + backButton.classList.add("back"); + backButton.setAttribute("title",chrome.i18n.getMessage("page_return")); + + // Attach interfaces + wrapper.close = () => this.close(); // Attach public interface to close this page + backButton.addEventListener("click",() => this.close()); + + // Append document subtree + header.appendChild(backButton); + header.insertAdjacentHTML("beforeend",`${title}
`); + wrapper.appendChild(header); + wrapper.appendChild(this.body); + document.body.appendChild(wrapper); + } + + destroy() { + const wrapper = this.body.closest(".page"); + + while(wrapper.firstChild) { + wrapper.removeChild(wrapper.lastChild); + } + wrapper.remove(); + } + + appendHTML(HTML) { + this.body.insertAdjacentHTML("beforeend",HTML); + } + + // ---- + + close() { + const delay = parseInt(getComputedStyle(document.body).getPropertyValue("--animation-speed")); + document.body.style.setProperty("--page-depth",this.pageDepth() - 1); + + setTimeout(() => { + this.destroy(); + },delay); + } + + open() { + document.body.style.setProperty("--page-depth",this.pageDepth() + 1); + } + +} diff --git a/client/extension/manifest.json b/client/extension/manifest.json new file mode 100644 index 0000000..8c55508 --- /dev/null +++ b/client/extension/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "Stadia Avatar", + "version": "1.0.1", + "author": "Victor Westerlund", + "default_locale": "en", + "homepage_url": "https://github.com/VictorWesterlund/stadia-avatar", + "description": "__MSG_extension_description__", + "icons": { + "16": "assets/img/logo_icon_16.png", + "32": "assets/img/logo_icon_32.png", + "48": "assets/img/logo_icon_48.png", + "128": "assets/img/logo_icon_128.png" + }, + "host_permissions": [ + "*://stadia.google.com/*" + ], + "action": { + "default_popup": "popup.html", + "default_title": "__MSG_extension_description__", + "default_icon": { + "16": "assets/img/logo_icon_16.png", + "32": "assets/img/logo_icon_32.png" + } + }, + "manifest_version": 3 +} \ No newline at end of file diff --git a/client/extension/popup.html b/client/extension/popup.html new file mode 100644 index 0000000..c74a023 --- /dev/null +++ b/client/extension/popup.html @@ -0,0 +1,22 @@ + + + + + + +__MSG_avatar_set__ URL
+__MSG_avatar_set__ Gravatar
+