Import and Export Themes
-
I had a little fun and wrote an import/export function for Vivaldi themes – a feature still missing from the browser. The new version adds some more useful functionality…
Features
- Import user theme
- Export user theme
- Backup all user themes
- Import a backup and add it to themes
- Sort user themes alphabetically
- Move user theme left/right with buttons
Paste following code into a
custom.js
file to implement the mod. Explanation for the uninitiated here. Caution: Works only when you check “Open Settings in a Tab” invivaldi://settings/appearance/
.// Theme Import and Export // version 2021.9.0 // https://forum.vivaldi.net/topic/33154/import-and-export-themes // Adds functionality to import, export, backup, sort and move themes to // Vivaldi's settings page. (function () { function _checkImport() { // written by tam710562 if ( typeof _test.colors !== "object" || typeof _test.colors.accentBg !== "string" || !/^#(?:[0-9a-f]{3}){1,2}$/i.test(_test.colors.accentBg) || typeof _test.colors.baseBg !== "string" || !/^#(?:[0-9a-f]{3}){1,2}$/i.test(_test.colors.baseBg) || typeof _test.colors.baseFg !== "string" || !/^#(?:[0-9a-f]{3}){1,2}$/i.test(_test.colors.baseFg) || typeof _test.colors.highlightBg !== "string" || !/^#(?:[0-9a-f]{3}){1,2}$/i.test(_test.colors.highlightBg) || typeof _test.name !== "string" || typeof _test.settings !== "object" || typeof _test.settings.accentFromPage !== "boolean" || typeof _test.settings.accentOnWindow !== "boolean" || (typeof _test.settings.borderRadius !== "number" && typeof _test.settings.borderRadius !== "string") || typeof _test.settings.tabsTransparent !== "boolean" || typeof _test.version !== "number" ) { return false; } else { return true; } } function _message(pnt) { clearTimeout(_timeout); if (pnt === "export") { _msg.innerText = "Theme code copied to clipboard."; } else if (pnt === "backup") { _msg.innerText = "Backup copied to clipboard."; } else if (pnt === "import") { _msg.innerText = "Theme imported."; } else if (pnt === "restore") { _msg.innerText = "Backup imported."; } else if (pnt === "notice") { _msg.innerText = "Nothing to import. Check console.log"; } else if (pnt === "sort") { _msg.innerText = "User themes sorted alphabetically."; } else { _msg.innerText = "Theme code error."; } _timeout = setTimeout(function () { _msg.innerText = ""; }, 5000); } function _importBackup() { chrome.storage.local.get({ THEMES_USER: "" }, function (res) { var userThemes = res.THEMES_USER; console.log("Importing themes..."); for (i = 0; i < _set.length; i++) { _test = _set[i]; var test = _checkImport; if (test()) { var compare = userThemes.findIndex((x) => x.name == _set[i].name); if (compare === -1) { var ok = true; userThemes.push(_set[i]); console.log(_set[i].name + " imported"); } else { console.log(_set[i].name + " is a duplicate"); } } else { console.log(_set[i].name + " failed"); } } if (ok === true) { chrome.storage.local.set({ THEMES_USER: userThemes }, function () { _message("restore"); }); } else { _message("notice"); } }); } function _importTheme() { event.stopPropagation(); event.preventDefault(); if (_eventType === "paste") { var clipboardData = event.clipboardData || window.clipboardData; var themeCode = clipboardData.getData("text"); } else { var themeCode = event.dataTransfer.getData("text"); } try { _set = JSON.parse(themeCode); } catch (err) { _message("error"); return; } if (Object.keys(_set)[0] === "colors") { _test = _set; var test = _checkImport; if (test()) { const nameField = document.querySelector(".theme-name"); nameField.select(); document.execCommand("insertText", false, _set.name); chrome.storage.local.get({ THEMES_USER: "" }, function (imp) { var userThemes = imp.THEMES_USER; for (i = 0; i < userThemes.length; i++) { if (userThemes[i].name === nameField.value) { _set.name = nameField.value; userThemes[i] = _set; chrome.storage.local.set({ THEMES_USER: userThemes, BROWSER_COLOR_ACCENT_BG: _set.colors.accentBg, BROWSER_COLOR_BG: _set.colors.baseBg, BROWSER_COLOR_FG: _set.colors.baseFg, BROWSER_COLOR_HIGHLIGHT_BG: _set.colors.highlightBg, TABCOLOR_BEHIND_TABS: _set.settings.accentOnWindow, USE_TABCOLOR: _set.settings.accentFromPage, BORDER_RADIUS: _set.settings.borderRadius, USE_TAB_TRANSPARENT_TABS: _set.settings.tabsTransparent, THEME_CURRENT: _set.name, }); _message("import"); break; } } }); } else { _message("error"); } } else if (Object.keys(_set)[0] === "0") { _importBackup(); } else { _message("error"); } } function _exportTheme(event) { if (event.altKey) { var backup = true; } if (event.shiftKey) { var order = true; } chrome.storage.local.get( { THEME_CURRENT: "", THEMES_USER: "", }, function (exp) { const themeName = exp.THEME_CURRENT; const userThemes = exp.THEMES_USER; if (backup === true) { const themeCode = JSON.stringify(userThemes); navigator.clipboard.writeText(themeCode); _message("backup"); } else if (order === true) { userThemes.sort(function (a, b) { return a.name.localeCompare(b.name); }); chrome.storage.local.set({ THEMES_USER: userThemes }, function () { _message("sort"); }); } else { for (i = 0; i < userThemes.length; i++) { if (userThemes[i].name === themeName) { const themeCode = JSON.stringify(userThemes[i]); navigator.clipboard.writeText(themeCode); _message("export"); break; } } } } ); } function _moveTheme() { chrome.storage.local.get( { THEME_CURRENT: "", THEMES_USER: "", }, function (mv) { const themeName = mv.THEME_CURRENT; const userThemes = mv.THEMES_USER; var index = userThemes.findIndex((x) => x.name == themeName); if (index !== -1) { if (_toMove === "left") { if (index !== 0) { var fromI = userThemes[index]; var toI = userThemes[index - 1]; userThemes[index - 1] = fromI; userThemes[index] = toI; } else { return; } } else { var last = userThemes.length - 1; if (index < last) { var fromI = userThemes[index]; var toI = userThemes[index + 1]; userThemes[index + 1] = fromI; userThemes[index] = toI; } else { return; } } chrome.storage.local.set({ THEMES_USER: userThemes }); } } ); } function createPort() { if ( document.querySelector(_themeBtn).classList.contains("button-pressed") ) { const cont = document.querySelector(".theme-metadata"); const importBtn = document.createElement("input"); importBtn.setAttribute("type", "text"); importBtn.setAttribute("placeholder", "Import"); importBtn.id = "importTheme"; cont.appendChild(importBtn); const exportBtn = document.createElement("input"); exportBtn.setAttribute("type", "submit"); exportBtn.classList.add("primary"); exportBtn.setAttribute("value", "Export"); exportBtn.setAttribute( "title", "Click to export theme\nAlt-click to backup all themes\nShift-click to sort themes" ); exportBtn.id = "exportTheme"; cont.appendChild(exportBtn); _msg = document.createElement("span"); _msg.id = "modInfo"; cont.appendChild(_msg); document .getElementById("exportTheme") .addEventListener("click", _exportTheme); const importInput = document.getElementById("importTheme"); importInput.addEventListener("paste", function () { _eventType = "paste"; _importTheme(event); }); importInput.addEventListener("drop", function () { _eventType = "drop"; _importTheme(event); }); _timeout = {}; } } function portThemes() { const styleCheck = document.getElementById("portThemes"); if (!styleCheck) { const style = document.createElement("style"); style.type = "text/css"; style.id = "portThemes"; style.innerHTML = ".move-left button:focus, .move-right button:focus {border-color: var(--colorBorder) !important;box-shadow: none !important;}#importTheme, #exportTheme {width: 80px;margin-left: 6px;}#importTheme::-webkit-input-placeholder {opacity: 1;color: var(--colorHighlightBg);text-align: center;}#modInfo {margin-top: 6px;margin-left: 12px;}"; document.getElementsByTagName("head")[0].appendChild(style); } const modCheck = document.querySelector(".move-left"); if (!modCheck) { const group = document.createElement("div"); group.classList.add("toolbar", "toolbar-group"); group.innerHTML = '<div class="button-toolbar move-left"><button draggable="false" tabindex="auto" title="Move Theme Left" class=""><svg width="16" height="16" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1216 448v896q0 26-19 45t-45 19-45-19l-448-448q-19-19-19-45t19-45l448-448q19-19 45-19t45 19 19 45z"/></svg></button></div><hr><div class="button-toolbar move-right"><button draggable="false" tabindex="auto" title="Move Theme Right" class=""><svg width="16" height="16" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1152 896q0 26-19 45l-448 448q-19 19-45 19t-45-19-19-45v-896q0-26 19-45t45-19 45 19l448 448q19 19 19 45z"/></svg></button></div>'; document .querySelector(_themeBtn) .parentNode.parentNode.appendChild(group); document .querySelector(".move-left") .addEventListener("click", function () { _toMove = "left"; _moveTheme(); }); document .querySelector(".move-right") .addEventListener("click", function () { _toMove = "right"; _moveTheme(); }); document.querySelector(_themeBtn).addEventListener("click", function () { setTimeout(createPort, 50); }); } } const settingsUrl = "chrome-extension://mpognobbkildjkofajifpdfhcoklimli/components/settings/settings.html?path="; const _themeBtn = ".setting-group.unlimited > .toolbar.toolbar-default > .button-toolbar > button"; chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (changeInfo.url === `${settingsUrl}themes`) { setTimeout(portThemes, 100); } }); })();
How the mod works
The mod adds buttons to move themes to the right of the
Edit Theme
button invivaldi://settings/themes
. When you click theEdit Theme
button, an import input field and an export button are available- Move selected user theme left or right by clicking the arrow buttons beside the "edit theme" button.
- Export a theme by clicking Export. The theme code will be copied to the clipboard. It's easiest to save the theme code as note in Vivaldi's notes panel (native Sync!!).
- Alt-click on Export button to make a backup of all user themes – save copy to notes.
- Import: Paste or Drag&Drop a copy of an individual user theme or a whole backup into the import field.
- Shift-click on Export button to sort all user themes alphabetically.
Anyway, have fun with this – some might even find it useful
-
@luetage Well done, apart the needed space trick to force the refresh of the values, which is no biggie, all ok: I could save my 2 custom themes, delete them and reimport them.
If you could save/import also the theme preferences like Accent color from active page, rounding, etc it would be 100% perfect. -
@iAN-CooG I haven't tried it yet. We can likely export and import the additional values without problem, but Vivaldi won't commit them to storage. I have tried to trick Vivaldi with simulated keyboard events, but so far I haven't found a satisfying solution.
-
The code and description in the first post have been updated. I got rid of a nasty bug and added import and export of theme preferences (accent color from active page, apply accent color to window, transparent tabs and corner rounding). All is working now, except that you still have to manually trigger every input to commit to Vivaldi storage proper after theme import.
-
@luetage said in Import and Export Themes:
You can only import themes that have been exported by this mod previously.
I think, you can create such a file by hand, if you know the structure and values ...
-
@burbuja Yeah, you can of course create and modify the json file yourself. You can also import other user's themes, if they share the file.
I automated setting the checkboxes now, which was simple (OP updated). But the really annoying thing is that I can't get keyboard events to fire, which would be required to automate everything. I remember I had this problem before… on another mod about a year ago. It seems like chromium is just bugged and simulating keypresses is very complicated. If anyone has a working solution, I'd be interested.
-
flags are set on import, well done!
-
wait a minute, I just said nonsense
-
I had a breakthrough. Everything but corner rounding is automated now. Original post code and description have been updated, I also included explanations how to share themes and an example theme to download and import. Feel free to share your own themes
-
OMG perfect, @luetage #1
-
@luetage It works like a charm. Amazing job. Is this your Christmas gift to V users? Anyway, thanks a lot (I missed the ability to import/export themes, just in case I should refresh my profile).
-
@hlehyaric No, the christmas gift to the community is in progress and will be released over the next days. This is just something I wanted for myself for the longest time and I thought why not give it a try? Doesn't seem like Vivaldi will be doing something about it any time soon.
I could make a version that lets you save and share the themes as a string instead of a file. This way the themes could be saved to Vivaldi's notes feature and shared by just pasting the string in the forum. It's also something I'm considering for the forum mod, because I noticed no one is sharing themes. Uploading a file seems to bother users.
Question to everyone: Does uploading a file bother you? Which do you prefer, string or file?
-
@luetage I don't see the difference, a json IS a string
-
@iAN-CooG True, but currently you can only export the string to a file and import it from a file, therefore sharing a theme requires an upload in order for others to access and try it out comfortably.
-
@luetage said:
Question to everyone: Does uploading a file bother you? Which do you prefer, string or file?
I like strings because it's much easier and I can e. g. save multiple themes into one file.
Another thing that bothers me is exporting/importing all themes with files. I have to click the button, then go through the save dialog, sometimes rename the file and confirm overwrite (when overwriting).I'll be also very happy with a button 'Export all' (if as string or file doesn't matter).
-
@potmeklecbohdan Exporting all themes is likely very hard to do from an outside script. I'm sorry, but I don't see this happening.
-
@luetage I didn't expect you'll do that, it was only idea what to do if you (or somebody in V Team) are really bored and need something to do.
-
-
@potmeklecbohdan It's not about boredom, it's just far easier to do this from the inside. Vivaldi has access to all the theme variables, while I have to read them out individually. This would probably mean that I have to simulate click events to load every theme and then take each value and collect it – it's just not feasible. So yeah, I'm not gonna do it – it would be really ugly lol.
I have no doubt that Vivaldi will come out with a solution one day. Themes in sync is bound to happen, it's a given. The question is if they will introduce a way to share themes.
-
I implement the copy/pasting of theme code in the forum extension now. Should I do the same thing for importing/exporting themes?