feat(themeCreator): add svelte theme creator

This commit is contained in:
sethburkart123
2024-09-20 10:37:26 +10:00
parent 548dcbf34e
commit 6267a77a71
12 changed files with 377 additions and 144 deletions
+2
View File
@@ -67,6 +67,7 @@
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/webextension-polyfill": "^0.10.7", "@types/webextension-polyfill": "^0.10.7",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"color": "^4.2.3", "color": "^4.2.3",
@@ -75,6 +76,7 @@
"kolorist": "^1.8.0", "kolorist": "^1.8.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"million": "^3.1.11",
"motion": "^10.18.0", "motion": "^10.18.0",
"postcss": "^8.4.45", "postcss": "^8.4.45",
"publish-browser-extension": "^2.2.1", "publish-browser-extension": "^2.2.1",
-1
View File
@@ -37,7 +37,6 @@ import injectedCSS from '@/css/injected.scss?inline'
import documentLoadCSS from '@/css/documentload.scss?inline' import documentLoadCSS from '@/css/documentload.scss?inline'
import renderSvelte from '@/svelte-interface/main' import renderSvelte from '@/svelte-interface/main'
import Settings from '@/svelte-interface/pages/settings.svelte' import Settings from '@/svelte-interface/pages/settings.svelte'
import { renderStore } from './seqta/ui/renderStore'
import { settingsPopup } from './svelte-interface/hooks/SettingsPopup' import { settingsPopup } from './svelte-interface/hooks/SettingsPopup'
let SettingsClicked = false let SettingsClicked = false
+73 -30
View File
@@ -1,9 +1,9 @@
@charset "UTF-8"; @charset "UTF-8";
@import url('https://fonts.googleapis.com/css?family=Rubik:300,400,500,600'); @import url("https://fonts.googleapis.com/css?family=Rubik:300,400,500,600");
@import './injected/sidebar-animation.scss'; @import "./injected/sidebar-animation.scss";
@import './injected/theme.scss'; @import "./injected/theme.scss";
@import './injected/transparency.scss'; @import "./injected/transparency.scss";
:root { :root {
background: var(--better-main) !important; background: var(--better-main) !important;
@@ -16,7 +16,12 @@
} }
body, body,
.legacy-root input, .legacy-root textarea, .legacy-root button, .legacy-root select, .legacy-root option, .legacy-root .input, .legacy-root input,
.legacy-root textarea,
.legacy-root button,
.legacy-root select,
.legacy-root option,
.legacy-root .input,
html { html {
font-family: Rubik, sans-serif !important; font-family: Rubik, sans-serif !important;
} }
@@ -35,7 +40,8 @@ html {
height: 100%; height: 100%;
visibility: visible !important; visibility: visible !important;
} }
#themeCreatorIframe {
#themeCreator {
position: fixed; position: fixed;
right: 0; right: 0;
height: 100%; height: 100%;
@@ -195,7 +201,8 @@ html {
background: unset; background: unset;
} }
.legacy-root button:active, .legacy-root a:active:not(.cke_combo_button) { .legacy-root button:active,
.legacy-root a:active:not(.cke_combo_button) {
background-image: unset !important; background-image: unset !important;
} }
@@ -205,7 +212,8 @@ html {
} }
.dark .dashboard section { .dark .dashboard section {
input, select { input,
select {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
} }
} }
@@ -227,7 +235,7 @@ html {
input, input,
select { select {
border: transparent; border: transparent;
background: rgba(0, 0, 0, .1); background: rgba(0, 0, 0, 0.1);
color: var(--text-primary); color: var(--text-primary);
} }
@@ -269,7 +277,7 @@ html {
color: var(--text-primary); color: var(--text-primary);
} }
ul.magicDelete > li:hover { ul.magicDelete > li:hover {
background: rgba(0, 0, 0, .1); background: rgba(0, 0, 0, 0.1);
} }
.dashlet-notes > .editor { .dashlet-notes > .editor {
background: unset; background: unset;
@@ -292,7 +300,8 @@ ul.magicDelete > li.deleting {
height: 100%; height: 100%;
} }
#media-container video, #media-container img { #media-container video,
#media-container img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
@@ -372,14 +381,16 @@ ul.magicDelete > li.deleting {
position: relative; position: relative;
&::before { &::before {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 3px; width: 3px;
background: var(--item-colour, transparent); background: var(--item-colour, transparent);
transition: width 100ms, transform 0.3s ease; transition:
width 100ms,
transform 0.3s ease;
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
} }
@@ -481,7 +492,7 @@ ol:has(.MessageList__avatar___2wxyb svg) {
.quickbar .actions [title="Choose a colour"] > svg { .quickbar .actions [title="Choose a colour"] > svg {
scale: 0.9; scale: 0.9;
} }
.quickbar[data-yiq='light'] .actions { .quickbar[data-yiq="light"] .actions {
color: white !important; color: white !important;
} }
.singleSelect > li { .singleSelect > li {
@@ -508,7 +519,7 @@ ol:has(.MessageList__avatar___2wxyb svg) {
} }
#main .timetablepage .quickbar { #main .timetablepage .quickbar {
border: none; border: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.5); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
border-radius: 16px; border-radius: 16px;
} }
.quickbar .actions { .quickbar .actions {
@@ -539,7 +550,7 @@ ol:has(.MessageList__avatar___2wxyb svg) {
margin: 0 0 0 -12px; margin: 0 0 0 -12px;
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
clip-path: polygon(50% 40%, 0 0, 100% 0); clip-path: polygon(50% 40%, 0 0, 100% 0);
border: 12px solid rgba(255,255,255,0); border: 12px solid rgba(255, 255, 255, 0);
border-top-color: transparent; border-top-color: transparent;
} }
#main > .timetablepage > .quickbar.above::before { #main > .timetablepage > .quickbar.above::before {
@@ -1328,7 +1339,9 @@ div > ol:has(.uiFileHandlerWrapper) {
height: 25px; height: 25px;
width: 24px; width: 24px;
} }
.notifications__notifications___3mmLY > button > .notifications__bubble___1EkSQ { .notifications__notifications___3mmLY
> button
> .notifications__bubble___1EkSQ {
background: var(--better-alert-highlight); background: var(--better-alert-highlight);
width: 25px; width: 25px;
height: 25px; height: 25px;
@@ -1534,7 +1547,9 @@ iframe.userHTML {
.Collapsible__Collapsible___3O8P3 > .Collapsible__header___-Afvq { .Collapsible__Collapsible___3O8P3 > .Collapsible__header___-Afvq {
background: none; background: none;
} }
.AssessmentList__AssessmentList___1GdCl > .AssessmentList__searchFilter___3N70o + .AssessmentList__items___3LcmQ { .AssessmentList__AssessmentList___1GdCl
> .AssessmentList__searchFilter___3N70o
+ .AssessmentList__items___3LcmQ {
color: var(--text-primary); color: var(--text-primary);
} }
.Thermoscore__Thermoscore___2tWMi { .Thermoscore__Thermoscore___2tWMi {
@@ -1630,10 +1645,13 @@ ul {
> .SelectedAssessment__clearBtn___21D85 { > .SelectedAssessment__clearBtn___21D85 {
background: var(--better-main); background: var(--better-main);
} }
.SelectedAssessment__SelectedAssessment___3Bu5D > .SelectedAssessment__meta___1gq_y { .SelectedAssessment__SelectedAssessment___3Bu5D
> .SelectedAssessment__meta___1gq_y {
border-bottom: 1px solid var(--better-main); border-bottom: 1px solid var(--better-main);
} }
.TabSet__TabSet___Vo-SZ > ol.TabSet__tabs___1RRZk > li.TabSet__selected___1psfF { .TabSet__TabSet___Vo-SZ
> ol.TabSet__tabs___1RRZk
> li.TabSet__selected___1psfF {
border-bottom-color: var(--better-main); border-bottom-color: var(--better-main);
} }
.TabSet__TabSet___Vo-SZ > ol.TabSet__tabs___1RRZk { .TabSet__TabSet___Vo-SZ > ol.TabSet__tabs___1RRZk {
@@ -1758,7 +1776,15 @@ div.entry.class[style*="left: 46.5%"] {
div.entry.class[style*="width: 46.5%"] { div.entry.class[style*="width: 46.5%"] {
width: 50% !important; width: 50% !important;
} }
.timetablepage .dailycal > .content > .wrapper > .days > tbody > tr > td > .entriesWrapper { .timetablepage
.dailycal
> .content
> .wrapper
> .days
> tbody
> tr
> td
> .entriesWrapper {
min-width: 0; min-width: 0;
width: auto !important; width: auto !important;
} }
@@ -1952,7 +1978,6 @@ div.bar.flat {
.cke_toolbox > .cke_toolbar .cke_button_on { .cke_toolbox > .cke_toolbar .cke_button_on {
background-color: #3d3d3e !important; background-color: #3d3d3e !important;
} }
} }
.legacy-root input.singleSelect:focus { .legacy-root input.singleSelect:focus {
background: var(--auto-background); background: var(--auto-background);
@@ -1996,7 +2021,15 @@ body {
.forumView .assessment { .forumView .assessment {
background: var(--better-main); background: var(--better-main);
} }
.dailycal > .content > .wrapper > .days > tbody > tr > td > .entriesWrapper > .entry { .dailycal
> .content
> .wrapper
> .days
> tbody
> tr
> td
> .entriesWrapper
> .entry {
padding: 3px; padding: 3px;
} }
.Viewer__Viewer___32BH- { .Viewer__Viewer___32BH- {
@@ -2035,7 +2068,9 @@ li.MessageList__unread___3imtO {
border-radius: 1600px; border-radius: 1600px;
} }
.MessageList__MessageList___3DxoC > ol > li.MessageList__selected___1SJNz.MessageList__unread___3imtO { .MessageList__MessageList___3DxoC
> ol
> li.MessageList__selected___1SJNz.MessageList__unread___3imtO {
box-shadow: none; box-shadow: none;
} }
@@ -2055,7 +2090,9 @@ li.MessageList__unread___3imtO {
transition: width 0.1s; transition: width 0.1s;
} }
.MessageList__MessageList___3DxoC > ol > li.MessageList__unread___3imtO::before { .MessageList__MessageList___3DxoC
> ol
> li.MessageList__unread___3imtO::before {
width: 3px; width: 3px;
} }
.connectedNotificationsWrapper > div > button { .connectedNotificationsWrapper > div > button {
@@ -2129,7 +2166,10 @@ li.MessageList__unread___3imtO {
cursor: pointer; cursor: pointer;
} }
.dark .MessageList__MessageList___3DxoC > ol > li.MessageList__selected___1SJNz { .dark
.MessageList__MessageList___3DxoC
> ol
> li.MessageList__selected___1SJNz {
background: var(--background-secondary); background: var(--background-secondary);
} }
@@ -2620,7 +2660,9 @@ li.MessageList__unread___3imtO {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: inset 0px 6px 0 var(--item-colour, transparent), inset 0px 40px 50px -40px rgba(179, 179, 179, 0.9); box-shadow:
inset 0px 6px 0 var(--item-colour, transparent),
inset 0px 40px 50px -40px rgba(179, 179, 179, 0.9);
transition: 200ms; transition: 200ms;
position: relative; position: relative;
height: 15em; height: 15em;
@@ -2629,7 +2671,9 @@ li.MessageList__unread___3imtO {
font-family: Rubik, sans-serif; font-family: Rubik, sans-serif;
} }
.dark .day { .dark .day {
box-shadow: inset 0px 6px 0 var(--item-colour, transparent), inset 0px 40px 50px -40px rgba(0,0,0,0.9); box-shadow:
inset 0px 6px 0 var(--item-colour, transparent),
inset 0px 40px 50px -40px rgba(0, 0, 0, 0.9);
} }
.clickable { .clickable {
cursor: pointer; cursor: pointer;
@@ -2666,7 +2710,7 @@ li.MessageList__unread___3imtO {
border-radius: 16px; border-radius: 16px;
} }
.dark .upcoming-items { .dark .upcoming-items {
box-shadow: inset 0px 40px 80px -40px rgba(0,0,0,0.6); box-shadow: inset 0px 40px 80px -40px rgba(0, 0, 0, 0.6);
} }
.upcoming-assessment-title { .upcoming-assessment-title {
color: var(--text-primary); color: var(--text-primary);
@@ -2946,7 +2990,6 @@ li.MessageList__unread___3imtO {
width: 90%; width: 90%;
border-radius: 16px; border-radius: 16px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
} }
.whatsnewTextContainer { .whatsnewTextContainer {
display: flex; display: flex;
+66 -57
View File
@@ -1,73 +1,79 @@
import browser from "webextension-polyfill"; import renderSvelte from "@/svelte-interface/main"
import themeCreator from "@/svelte-interface/pages/themeCreator.svelte"
import { unmount } from "svelte"
let themeCreatorSvelteApp: any = null
/** /**
* Open the Theme Creator sidebar, it is an embedded page loaded similar to the extension popup * Open the Theme Creator sidebar, it is an embedded page loaded similar to the extension popup
* @param themeID - The ID of the theme to load in the Theme Creator * @param themeID - The ID of the theme to load in the Theme Creator
* @returns void * @returns void
*/ */
export function OpenThemeCreator( themeID: string = '' ) { export function OpenThemeCreator(themeID: string = "") {
CloseThemeCreator(); CloseThemeCreator()
const width = '310px'; const width = "310px"
const themeCreatorIframe: HTMLIFrameElement = document.createElement('iframe'); const themeCreatorDiv: HTMLDivElement = document.createElement("div")
themeCreatorIframe.src = `${browser.runtime.getURL('interface/index.html')}${ themeID != '' ? `?themeID=${themeID}` : '' }#themeCreator`; themeCreatorDiv.id = "themeCreator"
themeCreatorIframe.id = 'themeCreatorIframe'; themeCreatorDiv.style.width = width
themeCreatorIframe.setAttribute('allowTransparency', 'true');
themeCreatorIframe.setAttribute('excludeDarkCheck', 'true');
themeCreatorIframe.style.border = 'none';
themeCreatorIframe.style.width = width;
const mainContent = document.querySelector('#container') as HTMLDivElement; const shadow = themeCreatorDiv.attachShadow({ mode: "open" })
if (mainContent) mainContent.style.width = `calc(100% - ${width})`; themeCreatorSvelteApp = renderSvelte(themeCreator, shadow, {
themeID: themeID,
})
const mainContent = document.querySelector("#container") as HTMLDivElement
if (mainContent) mainContent.style.width = `calc(100% - ${width})`
// close button // close button
const closeButton = document.createElement('button'); const closeButton = document.createElement("button")
closeButton.classList.add('themeCloseButton'); closeButton.classList.add("themeCloseButton")
closeButton.textContent = '×'; closeButton.textContent = "×"
closeButton.addEventListener('click', CloseThemeCreator); closeButton.addEventListener("click", CloseThemeCreator)
document.body.appendChild(closeButton); document.body.appendChild(closeButton)
const resizeBar = document.createElement('div'); const resizeBar = document.createElement("div")
resizeBar.classList.add('resizeBar'); resizeBar.classList.add("resizeBar")
resizeBar.style.right = '307.5px'; resizeBar.style.right = "307.5px"
let isDragging = false; let isDragging = false
let currentX: number; let currentX: number
const mouseDownHandler = (e: MouseEvent) => { const mouseDownHandler = (e: MouseEvent) => {
isDragging = true; isDragging = true
currentX = e.clientX; currentX = e.clientX
document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener("mousemove", mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler); document.addEventListener("mouseup", mouseUpHandler)
document.body.style.userSelect = 'none'; document.body.style.userSelect = "none"
themeCreatorIframe.style.pointerEvents = 'none'; // Disable pointer events on iframe during resize themeCreatorDiv.style.pointerEvents = "none"
}; }
const mouseMoveHandler = (e: MouseEvent) => { const mouseMoveHandler = (e: MouseEvent) => {
if (!isDragging) return; if (!isDragging) return
const dx = e.clientX - currentX; const windowWidth = window.innerWidth
currentX = e.clientX; const newWidth = Math.min(Math.max(310, windowWidth - e.clientX), 600)
const newWidth = Math.min(Math.max(310, themeCreatorIframe.offsetWidth - dx), 600); themeCreatorDiv.style.width = `${newWidth}px`
themeCreatorIframe.style.width = `${newWidth}px`; mainContent.style.width = `calc(100% - ${newWidth}px)`
mainContent.style.width = `calc(100% - ${newWidth}px)`; resizeBar.style.right = `${newWidth - 2.5}px`
resizeBar.style.right = `${newWidth - 2.5}px`;
}; currentX = e.clientX
}
const mouseUpHandler = () => { const mouseUpHandler = () => {
isDragging = false; isDragging = false
document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener("mousemove", mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler); document.removeEventListener("mouseup", mouseUpHandler)
document.body.style.userSelect = ''; document.body.style.userSelect = ""
themeCreatorIframe.style.pointerEvents = 'auto'; themeCreatorDiv.style.pointerEvents = "auto"
}; }
resizeBar.addEventListener('mousedown', mouseDownHandler); resizeBar.addEventListener("mousedown", mouseDownHandler)
resizeBar.addEventListener('mouseover', () => resizeBar.style.opacity = '1'); resizeBar.addEventListener("mouseover", () => (resizeBar.style.opacity = "1"))
resizeBar.addEventListener('mouseout', () => resizeBar.style.opacity = '0'); resizeBar.addEventListener("mouseout", () => (resizeBar.style.opacity = "0"))
document.body.appendChild(themeCreatorIframe); document.body.appendChild(themeCreatorDiv)
document.body.appendChild(resizeBar); document.body.appendChild(resizeBar)
} }
/** /**
@@ -75,14 +81,17 @@ export function OpenThemeCreator( themeID: string = '' ) {
* @returns void * @returns void
*/ */
export function CloseThemeCreator() { export function CloseThemeCreator() {
const themeCreatorIframe = document.getElementById('themeCreatorIframe'); const themeCreator = document.getElementById("themeCreator")
const closeButton = document.querySelector('.themeCloseButton') as HTMLButtonElement; const closeButton = document.querySelector(
const resizeBar = document.querySelector('.resizeBar') as HTMLDivElement; ".themeCloseButton",
) as HTMLButtonElement
const resizeBar = document.querySelector(".resizeBar") as HTMLDivElement
if (themeCreatorIframe) themeCreatorIframe.remove(); if (themeCreatorSvelteApp) unmount(themeCreatorSvelteApp)
if (closeButton) closeButton.remove(); if (themeCreator) themeCreator.remove()
if (resizeBar) resizeBar.remove(); if (closeButton) closeButton.remove()
if (resizeBar) resizeBar.remove()
const mainContent = document.querySelector('#container') as HTMLDivElement; const mainContent = document.querySelector("#container") as HTMLDivElement
if (mainContent) mainContent.style.width = '100%'; if (mainContent) mainContent.style.width = "100%"
} }
@@ -0,0 +1,4 @@
<script lang="ts">
</script>
<div>Code Editor Here</div>
@@ -1,8 +1,8 @@
.dark { div:has(> #rbgcp-wrapper) {
div:has(> #rbgcp-wrapper) {
background: transparent !important; background: transparent !important;
} }
.dark {
#rbgcp-wrapper { #rbgcp-wrapper {
div[style="padding-top: 11px; position: relative;"] div { div[style="padding-top: 11px; position: relative;"] div {
color: white !important; color: white !important;
@@ -31,11 +31,14 @@
#rbgcp-radial-btn, #rbgcp-radial-btn,
#rbgcp-linear-btn { #rbgcp-linear-btn {
&[style*="background: white;"] { &[style*="background: white;"] {
background-color: #28282B !important; background-color: #28282b !important;
} }
svg { svg {
path, g, polyline, circle { path,
g,
polyline,
circle {
stroke: white !important; stroke: white !important;
fill: transparent !important; fill: transparent !important;
} }
@@ -5,14 +5,17 @@
import { animate, spring } from 'motion'; import { animate, spring } from 'motion';
import { delay } from '@/seqta/utils/delay.ts' import { delay } from '@/seqta/utils/delay.ts'
const { hidePicker } = $props<{ const { hidePicker, standalone = false } = $props<{
hidePicker: () => void hidePicker?: () => void,
standalone?: boolean
}>(); }>();
let background: HTMLDivElement; let background: HTMLDivElement;
let content: HTMLDivElement; let content: HTMLDivElement;
const closePicker = async () => { const closePicker = async () => {
if (standalone) return;
animate( animate(
content, content,
{ scale: [1, 0.4], opacity: [1, 0] }, { scale: [1, 0.4], opacity: [1, 0] },
@@ -25,12 +28,13 @@
{ easing: [0.4, 0, 0.2, 1] } { easing: [0.4, 0, 0.2, 1] }
); );
await delay(400); await delay(400);
hidePicker(); hidePicker();
} }
onMount(() => { onMount(() => {
if (standalone) return;
animate( animate(
background, background,
{ opacity: [0, 1] }, { opacity: [0, 1] },
@@ -43,11 +47,17 @@
{ easing: spring({ stiffness: 400, damping: 30 }) } { easing: spring({ stiffness: 400, damping: 30 }) }
); );
document.addEventListener('keydown', (e) => { const handleEscapeKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
closePicker(); closePicker();
} }
}); };
document.addEventListener('keydown', handleEscapeKey);
return () => {
document.removeEventListener('keydown', handleEscapeKey);
};
}); });
function handleBackgroundClick(event: MouseEvent) { function handleBackgroundClick(event: MouseEvent) {
@@ -57,17 +67,23 @@
} }
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> {#if standalone}
<div <div class="h-auto rounded-xl overflow-clip">
<ReactAdapter el={ColourPicker} />
</div>
{:else}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={background} bind:this={background}
class="absolute top-0 left-0 z-50 flex items-center justify-center w-full h-full cursor-pointer bg-black/20" class="absolute top-0 left-0 z-50 flex items-center justify-center w-full h-full cursor-pointer bg-black/20"
onclick={handleBackgroundClick} onclick={handleBackgroundClick}
onkeydown={(e) => { e.key === 'Enter' && handleBackgroundClick }} onkeydown={(e) => { e.key === 'Enter' && handleBackgroundClick }}
> >
<div <div
bind:this={content} bind:this={content}
class="h-auto p-4 bg-white border shadow-lg cursor-auto rounded-xl dark:bg-zinc-800 border-zinc-100 dark:border-zinc-700" class="h-auto p-4 bg-white border shadow-lg cursor-auto rounded-xl dark:bg-zinc-800 border-zinc-100 dark:border-zinc-700"
> >
<ReactAdapter el={ColourPicker} /> <ReactAdapter el={ColourPicker} />
</div> </div>
</div> </div>
{/if}
@@ -0,0 +1,3 @@
<script lang="ts"></script>
<div class='w-full h-0.5 my-4 bg-zinc-200 dark:bg-zinc-700'></div>
+13 -12
View File
@@ -1,23 +1,24 @@
import styles from './index.css?inline'; import styles from "./index.css?inline"
import { mount } from 'svelte'; import { mount } from "svelte"
import type { ComponentType } from 'svelte'; import type { ComponentType } from "svelte"
export default function renderSvelte( export default function renderSvelte(
Component: ComponentType | any, Component: ComponentType | any,
mountPoint: ShadowRoot | HTMLElement, mountPoint: ShadowRoot | HTMLElement,
props: Record<string, any> = {} props: Record<string, any> = {},
) { ) {
const app = mount(Component, { const app = mount(Component, {
target: mountPoint, target: mountPoint,
props: { props: {
standalone: false, standalone: false,
...props ...props,
} },
}); })
const style = document.createElement("style"); const style = document.createElement("style")
style.setAttribute("type", "text/css"); style.setAttribute("type", "text/css")
style.innerHTML = styles; style.innerHTML = styles
mountPoint.appendChild(style); mountPoint.appendChild(style)
return app;
return app
} }
@@ -0,0 +1,160 @@
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { onMount } from 'svelte';
import type { CustomTheme } from '@/types/CustomThemes'
import { settingsState } from '@/seqta/utils/listeners/SettingsState'
import { getTheme } from '@/seqta/ui/themes/getTheme'
import Divider from '@/svelte-interface/components/themeCreator/divider.svelte'
import Switch from '@/svelte-interface/components/Switch.svelte'
import Button from '@/svelte-interface/components/Button.svelte'
import Slider from '@/svelte-interface/components/Slider.svelte'
import ColourPicker from '../components/ColourPicker.svelte'
import CodeEditor from '../components/CodeEditor.svelte'
const { themeID } = $props<{ themeID: string }>()
let theme = $state<CustomTheme>({
id: uuidv4(),
name: '',
description: '',
defaultColour: 'blue',
CanChangeColour: true,
allowBackgrounds: true,
CustomCSS: '',
CustomImages: [],
coverImage: null,
isEditable: true,
hideThemeName: false,
forceDark: false
})
onMount(async () => {
console.log(themeID)
if (themeID) {
const tempTheme = await getTheme(themeID)
if (tempTheme) theme = tempTheme
}
});
type SettingType = 'switch' | 'button' | 'slider' | 'colourPicker' | 'select' | 'codeEditor';
type SwitchProps = { state: boolean; onChange: (value: boolean) => void };
type ButtonProps = { onClick: () => void; text: string };
type SliderProps = { state: number; onChange: (value: number) => void; min?: number; max?: number };
type ColourPickerProps = { color: string; onChange: (color: string) => void };
type SelectProps = { options: Array<{ value: string; label: string }>; value: string; onChange: (value: string) => void };
type CodeEditorProps = { value: string; onChange: (value: string) => void };
type ComponentProps = SwitchProps | ButtonProps | SliderProps | ColourPickerProps | SelectProps | CodeEditorProps;
type SettingItem = {
type: SettingType;
title: string;
description: string;
direction?: 'horizontal' | 'vertical';
props: ComponentProps;
};
</script>
{#snippet settingItem(item: SettingItem)}
<div class="flex justify-between {item.direction === 'vertical' ? 'flex-col items-start gap-2' : 'items-center'} py-3">
<div class="pr-4">
<h2 class="text-sm font-bold">{item.title}</h2>
<p class="text-xs">{item.description}</p>
</div>
<div>
{#if item.type === 'switch'}
<Switch {...(item.props as SwitchProps)} />
{:else if item.type === 'button'}
<Button {...(item.props as ButtonProps)} />
{:else if item.type === 'slider'}
<Slider {...(item.props as SliderProps)} />
{:else if item.type === 'colourPicker'}
<ColourPicker standalone={true} {...(item.props)} />
{:else if item.type === 'codeEditor'}
<CodeEditor {...(item.props as CodeEditorProps)} />
{/if}
</div>
</div>
{/snippet}
<div class='h-screen overflow-y-scroll {$settingsState.DarkMode && "dark"} '>
<div class='w-full min-h-screen bg-zinc-100 dark:bg-zinc-800 flex flex-col p-2 dark:text-white'>
<h1 class='text-xl font-semibold'>Theme Creator</h1>
<a href='https://betterseqta.gitbook.io/betterseqta-docs' target='_blank' class='text-sm font-light text-zinc-500 dark:text-zinc-400'>
<span class='no-underline font-IconFamily pr-0.5'>{'\ueb44'}</span>
<span class='underline'>
Need help? Check out the docs!
</span>
</a>
<Divider />
<div>
<div class='pb-2 text-sm'>Theme Name</div>
<input
id='themeName'
type='text'
placeholder='What is your theme called?'
bind:value={theme.name}
class='w-full p-2 mb-4 transition-all duration-300 rounded-lg focus:outline-none ring-0 focus:ring-1 ring-zinc-100 dark:ring-zinc-700 dark:bg-zinc-900 dark:text-white' />
</div>
<div>
<div class='pb-2 text-sm'>Description <span class='italic font-light opacity-80'>(optional)</span></div>
<textarea
id='themeDescription'
placeholder="Don't worry, this one's optional!"
bind:value={theme.description}
class='w-full p-2 rounded-lg focus:outline-none ring-0 focus:ring-1 ring-zinc-100 dark:ring-zinc-700 dark:bg-zinc-900 dark:text-white'></textarea>
</div>
<Divider />
{#each [
{
type: 'switch',
title: 'Hide Theme Name',
description: 'Useful when your cover image contains text',
props: {
state: theme.hideThemeName,
onChange: (value) => theme.hideThemeName = value
}
},
{
type: 'switch',
title: 'Force Theme',
description: 'Force users to use either dark or light mode',
props: {
state: theme.forceDark !== undefined,
onChange: (value) => theme.forceDark = value ? false : undefined
}
},
{
type: 'colourPicker',
title: 'Default Theme Colour',
description: 'Set the default color for your theme',
direction: 'vertical',
props: {
color: theme.defaultColour,
onChange: (color) => theme.defaultColour = color
}
},
{
type: 'codeEditor',
title: 'Custom CSS',
description: 'Add custom CSS to your theme',
props: {
value: theme.CustomCSS,
onChange: (value) => theme.CustomCSS = value
}
}
] as SettingItem[] as setting}
{@render settingItem(setting)}
{/each}
</div>
</div>
@@ -1,4 +1,3 @@
import type { SettingsState } from './AppProps';
export interface SettingsList { export interface SettingsList {
title: string; title: string;
id: number; id: number;
@@ -6,9 +5,3 @@ export interface SettingsList {
Component: any; /* TODO: Give this a type */ Component: any; /* TODO: Give this a type */
props?: any; props?: any;
} }
export interface SettingsProps {
settingsState: SettingsState;
setSettingsState: React.Dispatch<React.SetStateAction<SettingsState>>;
}
+3 -3
View File
@@ -4,8 +4,8 @@ import { join, resolve } from 'path';
import { base64Loader } from './lib/base64loader'; import { base64Loader } from './lib/base64loader';
import type { BuildTarget } from './lib/types'; import type { BuildTarget } from './lib/types';
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react';
//import million from "million/compiler"; import million from "million/compiler";
//import MillionLint from '@million/lint'; //import MillionLint from '@million/lint';
import { svelte } from '@sveltejs/vite-plugin-svelte' import { svelte } from '@sveltejs/vite-plugin-svelte'
@@ -31,7 +31,7 @@ export default defineConfig({
svelte({ svelte({
emitCss: false emitCss: false
}), }),
//million.vite({ auto: true }), million.vite({ auto: true }),
//MillionLint.vite(), /* enable for testing and debugging performance */ //MillionLint.vite(), /* enable for testing and debugging performance */
crx({ crx({
manifest: targets.find(t => t.browser === mode.toLowerCase())?.manifest ?? chrome.manifest, manifest: targets.find(t => t.browser === mode.toLowerCase())?.manifest ?? chrome.manifest,