« get me outta code hell

interactive-bgm - Browser extension that adds background music based on the site you're browsing
about summary refs log tree commit diff
path: root/extension/popup/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'extension/popup/main.js')
-rw-r--r--extension/popup/main.js374
1 files changed, 319 insertions, 55 deletions
diff --git a/extension/popup/main.js b/extension/popup/main.js
index 06aca18..c628447 100644
--- a/extension/popup/main.js
+++ b/extension/popup/main.js
@@ -1,3 +1,15 @@
+import {getRuleScoreOnPage, getURLParts} from '../lib.js';
+
+function last(arr) {
+    return arr[arr.length - 1];
+}
+
+function clearChildren(el) {
+    while (el.firstChild) {
+        el.removeChild(el.firstChild);
+    }
+}
+
 function changeScreen(id) {
     for (const screen of document.getElementsByClassName('screen')) {
         if (screen.id === id) {
@@ -8,9 +20,19 @@ function changeScreen(id) {
     }
 }
 
+function changeContentScreen(id) {
+    for (const screen of document.getElementsByClassName('content-screen')) {
+        if (screen.id === id) {
+            screen.classList.add('visible');
+        } else {
+            screen.classList.remove('visible');
+        }
+    }
+}
+
 function loadTrackList(opts) {
-    const {tab, hostname, siteSettings} = opts;
-    const site = siteSettings[hostname] || [];
+    const {tab, rule, saveRule, deleteRule} = opts;
+    const {music} = rule;
     return browser.storage.sync.get('tracks').then(({tracks = []}) => {
         const ul = document.getElementById('track-list');
         while (ul.firstChild) {
@@ -32,28 +54,20 @@ function loadTrackList(opts) {
             label.appendChild(checkbox);
 
             checkbox.type = 'checkbox';
-            checkbox.checked = site.includes(track);
-            checkbox.title = `Toggles whether the track "${track}" will play when this site is opened.`
+            checkbox.checked = music.includes(track);
+            checkbox.title = `Toggle whether the track "${track}" will play when this site is opened.`
 
             checkbox.addEventListener('click', () => {
                 if (checkbox.checked) {
-                    if (!site.includes(track)) {
-                        site.push(track);
+                    if (!music.includes(track)) {
+                        music.push(track);
                     }
                 } else {
-                    if (site.includes(track)) {
-                        site.splice(site.indexOf(track), 1);
+                    if (music.includes(track)) {
+                        music.splice(music.indexOf(track), 1);
                     }
                 }
-
-                if (!siteSettings[hostname]) {
-                    siteSettings[hostname] = site;
-                }
-
-                disableButton.style.display = 'inline-block';
-
-                browser.storage.sync.set({siteSettings})
-                    .then(() => browser.runtime.sendMessage({hostname}));
+                saveRule();
             });
 
             label.appendChild(document.createTextNode(' ' + track));
@@ -62,7 +76,7 @@ function loadTrackList(opts) {
             li.appendChild(deleteButton);
 
             deleteButton.appendChild(document.createTextNode('Delete...'));
-            deleteButton.title = `Deletes the track from all sites. You will be confirmed first.`;
+            deleteButton.title = `Delete the track from all site configuration. You will be confirmed first.`;
 
             deleteButton.addEventListener('click', () => {
                 if (confirm(`This will delete "${track}" from ALL sites - this cannot be undone. Are you sure?`)) {
@@ -91,62 +105,312 @@ function loadTrackList(opts) {
         actionLi.appendChild(addButton);
 
         addButton.appendChild(document.createTextNode('Create Track'));
-        addButton.title = `Creates a new track, which will be an option present in all sites.`;
+        addButton.title = `Create a new track, which will be an option present in all sites.`;
 
         addButton.addEventListener('click', () => {
             browser.tabs.sendMessage(tab.id, {createTrack: true});
             window.close();
         });
 
-        const disableButton = document.createElement('button');
-        actionLi.appendChild(disableButton);
+        const deleteRuleButton = document.createElement('button');
+        actionLi.appendChild(deleteRuleButton);
 
-        disableButton.appendChild(document.createTextNode('Disable Site'));
-        disableButton.title = `Removes the entry for this site altogether. It won't change BGM when you open it again.`;
+        deleteRuleButton.appendChild(document.createTextNode('Delete Rule'));
+        deleteRuleButton.title = `Remove this rule altogether. It will be discarded and won't affect the BGM anymore.`;
 
-        disableButton.addEventListener('click', () => {
-            changeScreen('loading-screen');
-            delete siteSettings[hostname];
-            browser.storage.sync.set({siteSettings})
-                .then(() => loadTrackList(opts))
-                .then(() => changeScreen('main-screen'));
+        deleteRuleButton.addEventListener('click', () => {
+            if (confirm(`Are you sure you want to delete this entry? (${getWildcardRepresentation(rule)})`)) {
+                deleteRule();
+            }
         });
+    });
+}
+
+function getWildcardRepresentation(rule, urlString = rule.sourceURL) {
+    const {hostnameMatch, pathnameMatch} = rule;
+    const {hostnameParts, pathnameParts} = getURLParts(urlString);
+
+    let string = 'http(s)://';
 
-        if (!(hostname in siteSettings)) {
-            disableButton.style.display = 'none';
+    if (hostnameMatch.length !== hostnameParts.length) {
+        if (hostnameMatch.length > 0) {
+            string += '*.';
+        } else {
+            string += '*';
         }
-    });
+    }
+
+    string += hostnameMatch.slice().reverse().join('.');
+
+    if (pathnameMatch.length) {
+        string += '/';
+    }
+
+    string += pathnameMatch.join('/');
+
+    if (!pathnameMatch.length || !last(pathnameMatch).includes('.')) {
+        string += '/*';
+    }
+
+    return string;
 }
 
-Promise.all([
-    (async function() {
-        const [[tab], {disableEverywhere: disableEverywhereStatus}] = await Promise.all([
-            browser.tabs.query({active: true, currentWindow: true}),
-            browser.storage.sync.get('disableEverywhere')
-        ]);
+function loadRuleScreen({tab, rule, saveRule, deleteRule}) {
+    if (getRuleScoreOnPage(rule, tab.url) > 0) {
+        rule.sourceURL = tab.url;
+    }
+
+    const heading = document.getElementById('hostname');
+    while (heading.firstChild) {
+        heading.removeChild(heading.firstChild);
+    }
+
+    const hostnameSpans = [];
+    const pathnameSpans = [];
+
+    const isSelected = s => s.dataset.selected === 'yes';
+
+    const setSelected = (s, val) => {
+        s.dataset.selected = val ? 'yes' : 'no';
+    };
+
+    const toggleSelected = s => {
+        setSelected(s, !isSelected(s));
+    };
+
+    const updateWildcardRepresentation = () => {
+        const string = getWildcardRepresentation(rule, rule.sourceURL);
+
+        const el = document.getElementById('wildcard-representation');
+        while (el.firstChild) {
+            el.removeChild(el.firstChild);
+        }
+        el.appendChild(document.createTextNode(string));
+    };
+
+    const addPart = (span, allSpans, save) => {
+        heading.appendChild(span);
+
+        span.style.cursor = 'pointer';
+
+        const getSubordinateSpans = () => {
+            const index = allSpans.indexOf(span);
+            return allSpans.slice(0, index + 1);
+        };
+
+        const getSuperiorSpans = () => {
+            const index = allSpans.indexOf(span);
+            return allSpans.slice(index + 1);
+        };
+
+        const updateStyle = s => {
+            if (s.dataset.selected === 'yes') {
+                s.style.color = '#000000';
+            } else {
+                s.style.color = '#777777';
+            }
+        };
+
+        updateStyle(span);
+
+        span.addEventListener('mouseenter', () => {
+            for (const s of getSubordinateSpans()) {
+                s.style.color = 'orange';
+            }
+        });
+
+        span.addEventListener('mouseleave', () => {
+            for (const s of getSubordinateSpans()) {
+                updateStyle(s);
+            }
+        });
 
-        const {hostname} = new URL(tab.url);
-        document.getElementById('hostname').appendChild(document.createTextNode(hostname));
+        span.addEventListener('click', () => {
+            if (last(allSpans.filter(isSelected)) === span) {
+                toggleSelected(span);
+            } else {
+                setSelected(span, true);
+            }
 
-        const disableEverywhere = document.getElementById('disable-everywhere');
+            for (const s of getSubordinateSpans()) {
+                setSelected(s, isSelected(span));
+                updateStyle(s);
+            }
 
-        disableEverywhere.checked = disableEverywhereStatus;
+            for (const s of getSuperiorSpans()) {
+                setSelected(s, false);
+                updateStyle(s);
+            }
 
-        disableEverywhere.addEventListener('click', () => {
-            browser.storage.sync.set({disableEverywhere: disableEverywhere.checked})
-                .then(() => browser.runtime.sendMessage({hostname}));
+            save(getSubordinateSpans().filter(isSelected).map(s => s.dataset.partValue));
+        });
+    };
+
+    const {hostnameMatch, pathnameMatch} = rule;
+    const {hostnameParts, pathnameParts} = getURLParts(rule.sourceURL);
+
+    for (let i = 0; i < hostnameParts.length; i++) {
+        const part = hostnameParts[i];
+        const span = document.createElement('span');
+        span.appendChild(document.createTextNode(part));
+        if (i < hostnameParts.length - 1) {
+            span.appendChild(document.createTextNode('.'));
+        }
+        span.dataset.partValue = part;
+        setSelected(span, hostnameMatch[hostnameParts.length - i - 1] === part);
+        hostnameSpans.unshift(span);
+        addPart(span, hostnameSpans, val => {
+            rule.hostnameMatch = val;
+            updateWildcardRepresentation();
+            saveRule();
         });
+    }
+
+    for (let i = 0; i < pathnameParts.length; i++) {
+        const part = pathnameParts[i];
+        const span = document.createElement('span');
+        span.appendChild(document.createTextNode('/'));
+        span.appendChild(document.createTextNode(part));
+        span.dataset.partValue = part;
+        setSelected(span, pathnameMatch[i] === part);
+        pathnameSpans.push(span);
+        addPart(span, pathnameSpans, val => {
+            rule.pathnameMatch = val;
+            updateWildcardRepresentation();
+            saveRule();
+        });
+    }
+
+    updateWildcardRepresentation();
+
+    if (hostname) {
+        return loadTrackList({tab, rule, saveRule, deleteRule})
+            .then(() => changeScreen('main-screen'));
+    } else {
+        changeScreen('invalid-host-screen');
+    }
+}
+
+function loadRuleList({tab, siteSettings, selectRule}) {
+    const createRuleItem = rule => {
+        const li = document.createElement('li');
+
+        li.classList.add('rule');
+        li.dataset.wildcardRepresentation = getWildcardRepresentation(rule, rule.sourceURL || tab.url);
+        li.appendChild(document.createTextNode(li.dataset.wildcardRepresentation));
+        li.title = li.dataset.wildcardRepresentation;
+
+        li.addEventListener('click', () => {
+            selectRule(rule);
+        });
+
+        return li;
+    };
+
+    const allRulesList = document.getElementById('all-rules');
+    clearChildren(allRulesList);
+
+    const allRules = siteSettings;
+
+    const allRuleItems = allRules.map(createRuleItem);
+
+    allRuleItems.sort((a, b) => a.dataset.wildcardRepresentation < b.dataset.wildcardRepresentation ? -1 : 1);
+
+    for (const item of allRuleItems) {
+        allRulesList.appendChild(item);
+    }
+
+    const rulesOnThisPageList = document.getElementById('rules-on-this-page');
+    clearChildren(rulesOnThisPageList);
+
+    const createRuleLI = document.createElement('li');
+    rulesOnThisPageList.appendChild(createRuleLI);
+
+    const createRuleButton = document.createElement('button');
+    createRuleLI.appendChild(createRuleButton);
+
+    createRuleButton.appendChild(document.createTextNode('Create Rule'));
+
+    createRuleButton.addEventListener('click', () => {
+        const {hostnameParts} = getURLParts(tab.url);
+        const rule = {
+            sourceURL: tab.url,
+            hostnameMatch: hostnameParts.slice().reverse(),
+            pathnameMatch: [],
+            music: []
+        };
+        selectRule(rule);
+    });
 
-        return {tab, hostname};
-    })(),
-    browser.storage.sync.get('siteSettings')
-        .then(({siteSettings = {}}) => siteSettings)
-])
-    .then(([{tab, hostname}, siteSettings]) => {
-        if (hostname) {
-            return loadTrackList({tab, hostname, siteSettings})
-                .then(() => changeScreen('main-screen'));
+    const rulesOnThisPage = allRules.map(rule => ({
+        rule,
+        score: getRuleScoreOnPage(rule, tab.url)
+    })).filter(obj => obj.score > 0);
+
+    for (const obj of rulesOnThisPage) {
+        obj.item = createRuleItem(obj.rule);
+    }
+
+    rulesOnThisPage.sort((a, b) => {
+        if (a.score === b.score) {
+            return a.item.dataset.wildcardRepresentation < b.item.dataset.wildcardRepresentation ? -1 : 1;
         } else {
-            changeScreen('invalid-host-screen');
+            return b.score - a.score;
         }
     });
+
+    for (const {item} of rulesOnThisPage) {
+        rulesOnThisPageList.appendChild(item);
+    }
+}
+
+Promise.all([
+    browser.tabs.query({active: true, currentWindow: true}),
+    browser.storage.sync.get(['disableEverywhere', 'siteSettings'])
+]).then(([[tab], {disableEverywhere: disableEverywhereStatus, siteSettings}]) => {
+    if (!Array.isArray(siteSettings)) {
+        siteSettings = [];
+    }
+
+    console.log(siteSettings);
+
+    const reloadRuleList = () => {
+        return browser.storage.sync.get('siteSettings').then(({siteSettings}) => loadRuleList({
+            tab, siteSettings,
+            selectRule: rule => {
+                return loadRuleScreen({
+                    rule, tab,
+                    saveRule: () => {
+                        if (!siteSettings.includes(rule)) {
+                            siteSettings.push(rule);
+                        }
+                        return browser.storage.sync.set({siteSettings})
+                            .then(() => browser.runtime.sendMessage({music: rule.music}));
+                    },
+                    deleteRule: () => {
+                        changeScreen('loading-screen');
+                        siteSettings = siteSettings.filter(r => r !== rule);
+                        reloadRuleList();
+                        return browser.storage.sync.set({siteSettings})
+                            .then(() => reloadRuleList())
+                            .then(() => {
+                                changeScreen('main-screen')
+                                changeContentScreen('no-rule-content');
+                            });
+                    }
+                }).then(() => changeContentScreen('rule-content'));
+            }
+        }));
+    };
+
+    const disableEverywhere = document.getElementById('disable-everywhere');
+
+    disableEverywhere.checked = disableEverywhereStatus;
+
+    disableEverywhere.addEventListener('click', () => {
+        browser.storage.sync.set({disableEverywhere: disableEverywhere.checked})
+            .then(() => browser.runtime.sendMessage({urlString: tab.url}));
+    });
+
+    reloadRuleList().then(() => changeScreen('main-screen'));
+});