mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
chore: update changelog
This commit is contained in:
@@ -37,8 +37,9 @@
|
|||||||
@layer base, override;
|
@layer base, override;
|
||||||
|
|
||||||
@layer override {
|
@layer override {
|
||||||
* {
|
.legacy-root,
|
||||||
font-family: Rubik, sans-serif !important;
|
.legacy-root * {
|
||||||
|
font-family: var(--betterseqta-font-family, Rubik), sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconFamily,
|
.iconFamily,
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ select option {
|
|||||||
#container {
|
#container {
|
||||||
background: var(--auto-background) !important;
|
background: var(--auto-background) !important;
|
||||||
}
|
}
|
||||||
:root * {
|
.legacy-root,
|
||||||
|
.legacy-root * {
|
||||||
font-family: Rubik, sans-serif !important;
|
font-family: Rubik, sans-serif !important;
|
||||||
--theme-fg-parts: white;
|
--theme-fg-parts: white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { FONT_PRESETS, DEFAULT_FONT_ID, getFontPreset } from "@/seqta/ui/fonts/presets";
|
||||||
|
import {
|
||||||
|
applySelectedFont,
|
||||||
|
buildFontPreviewCss,
|
||||||
|
ensureFontPickerFontsLoaded,
|
||||||
|
} from "@/seqta/ui/fonts/Manager";
|
||||||
|
import { portal } from "@/interface/utils/portal";
|
||||||
|
import { syncPageThemeToElement } from "@/interface/utils/syncPageTheme";
|
||||||
|
import fontPickerStyles from "./fontPickerModal.css?inline";
|
||||||
|
|
||||||
|
let { hidePicker } = $props<{ hidePicker: () => void }>();
|
||||||
|
|
||||||
|
let rootEl = $state<HTMLElement | null>(null);
|
||||||
|
let selectedId = $state(getFontPreset($settingsState.selectedFont).id);
|
||||||
|
let styleEl: HTMLStyleElement | null = null;
|
||||||
|
|
||||||
|
function selectFont(id: string) {
|
||||||
|
selectedId = id;
|
||||||
|
settingsState.selectedFont = id;
|
||||||
|
applySelectedFont(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefault() {
|
||||||
|
selectFont(DEFAULT_FONT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBackdropClick(event: MouseEvent) {
|
||||||
|
if (event.target === event.currentTarget) hidePicker();
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTheme() {
|
||||||
|
if (rootEl) syncPageThemeToElement(rootEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
void ensureFontPickerFontsLoaded();
|
||||||
|
|
||||||
|
styleEl = document.getElementById(
|
||||||
|
"betterseqta-font-picker-styles",
|
||||||
|
) as HTMLStyleElement | null;
|
||||||
|
if (!styleEl) {
|
||||||
|
styleEl = document.createElement("style");
|
||||||
|
styleEl.id = "betterseqta-font-picker-styles";
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
}
|
||||||
|
styleEl.textContent = `${fontPickerStyles}\n${buildFontPreviewCss()}`;
|
||||||
|
|
||||||
|
syncTheme();
|
||||||
|
|
||||||
|
const themeObserver = new MutationObserver(() => syncTheme());
|
||||||
|
themeObserver.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style", "class"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleEscapeKey = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Escape") hidePicker();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handleEscapeKey);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
themeObserver.disconnect();
|
||||||
|
document.removeEventListener("keydown", handleEscapeKey);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
bind:this={rootEl}
|
||||||
|
use:portal={document.body}
|
||||||
|
class="bsplus-font-picker-overlay bsplus-font-picker-root"
|
||||||
|
onclick={handleBackdropClick}
|
||||||
|
onkeydown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") handleBackdropClick(event as unknown as MouseEvent);
|
||||||
|
}}
|
||||||
|
role="presentation"
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bsplus-font-picker-dialog"
|
||||||
|
onclick={(event) => event.stopPropagation()}
|
||||||
|
onkeydown={(event) => event.stopPropagation()}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="font-picker-title"
|
||||||
|
>
|
||||||
|
<header class="bsplus-font-picker-header">
|
||||||
|
<div class="bsplus-font-picker-header-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={resetToDefault}
|
||||||
|
disabled={selectedId === DEFAULT_FONT_ID}
|
||||||
|
class="bsplus-font-picker-reset"
|
||||||
|
aria-label="Reset font to default"
|
||||||
|
>
|
||||||
|
Reset to default
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={hidePicker}
|
||||||
|
class="bsplus-font-picker-done"
|
||||||
|
aria-label="Close font picker"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="bsplus-font-picker-header-text">
|
||||||
|
<h2 id="font-picker-title" class="bsplus-font-picker-title">
|
||||||
|
Choose a font
|
||||||
|
</h2>
|
||||||
|
<p class="bsplus-font-picker-desc">
|
||||||
|
Choose a typeface for SEQTA Learn.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="bsplus-font-picker-list">
|
||||||
|
{#each FONT_PRESETS as preset (preset.id)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => selectFont(preset.id)}
|
||||||
|
class="bsplus-font-picker-option {selectedId === preset.id ? 'is-selected' : ''}"
|
||||||
|
data-font-id={preset.id}
|
||||||
|
>
|
||||||
|
<div class="bsplus-font-picker-option-head">
|
||||||
|
<span class="bsplus-font-picker-option-name">{preset.name}</span>
|
||||||
|
{#if selectedId === preset.id}
|
||||||
|
<span class="bsplus-font-picker-badge">Selected</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
/* Font picker — analytics design tokens & components */
|
||||||
|
|
||||||
|
.bsplus-font-picker-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
background: color-mix(in srgb, #000 52%, transparent);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-root {
|
||||||
|
--bsplus-analytics-radius: 16px;
|
||||||
|
--bsplus-analytics-radius-sm: 12px;
|
||||||
|
--bsplus-analytics-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--bsplus-analytics-surface: var(--background-primary, #ffffff);
|
||||||
|
--bsplus-analytics-surface-2: var(--background-secondary, #f8fafc);
|
||||||
|
--bsplus-analytics-text: var(--text-primary, #1a1a1a);
|
||||||
|
--bsplus-analytics-muted: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-text) 55%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
--bsplus-analytics-border: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--theme-offset-bg, var(--background-secondary, #e2e8f0)) 78%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
--bsplus-analytics-shadow: 0 5px 16px 6px rgba(0, 0, 0, 0.12);
|
||||||
|
--bsplus-analytics-shadow-hover: 0 8px 24px 8px rgba(0, 0, 0, 0.16);
|
||||||
|
--bsplus-analytics-accent: var(--better-main, #007bff);
|
||||||
|
|
||||||
|
font-family: Rubik, system-ui, sans-serif;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--bsplus-analytics-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-root.dark {
|
||||||
|
--bsplus-analytics-shadow: 0 5px 20px 6px rgba(0, 0, 0, 0.45);
|
||||||
|
--bsplus-analytics-shadow-hover: 0 10px 28px 10px rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bsplus-font-picker-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(18px) scale(0.985);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-dialog {
|
||||||
|
width: min(100%, 22rem);
|
||||||
|
max-height: min(88vh, 820px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: auto;
|
||||||
|
border-radius: var(--bsplus-analytics-radius);
|
||||||
|
background: var(--bsplus-analytics-surface);
|
||||||
|
border: 1px solid var(--bsplus-analytics-border);
|
||||||
|
box-shadow: var(--bsplus-analytics-shadow-hover);
|
||||||
|
animation: bsplus-font-picker-in 0.45s var(--bsplus-analytics-ease) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.bsplus-font-picker-dialog {
|
||||||
|
width: min(92vw, 22rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bsplus-font-picker-dialog {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 1.15rem 1.25rem;
|
||||||
|
border-bottom: 1px solid var(--bsplus-analytics-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-header-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-header-text {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--bsplus-analytics-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-desc {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--bsplus-analytics-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-reset {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border-radius: var(--bsplus-analytics-radius-sm);
|
||||||
|
border: 2px solid var(--bsplus-analytics-border);
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--bsplus-analytics-text);
|
||||||
|
transition:
|
||||||
|
transform 0.2s var(--bsplus-analytics-ease),
|
||||||
|
background 0.2s var(--bsplus-analytics-ease),
|
||||||
|
border-color 0.2s var(--bsplus-analytics-ease),
|
||||||
|
opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-reset:hover:not(:disabled) {
|
||||||
|
transform: scale(1.02);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-surface-2) 80%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-reset:active:not(:disabled) {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-reset:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px
|
||||||
|
color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-reset:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-done {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0.65rem 1.25rem;
|
||||||
|
border-radius: var(--bsplus-analytics-radius-sm);
|
||||||
|
border: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--bsplus-analytics-accent);
|
||||||
|
color: var(--text-color, #ffffff);
|
||||||
|
box-shadow: 0 2px 8px
|
||||||
|
color-mix(in srgb, var(--bsplus-analytics-accent) 40%, transparent);
|
||||||
|
transition:
|
||||||
|
transform 0.2s var(--bsplus-analytics-ease),
|
||||||
|
box-shadow 0.2s var(--bsplus-analytics-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-done:hover {
|
||||||
|
transform: scale(1.03);
|
||||||
|
box-shadow: 0 4px 14px
|
||||||
|
color-mix(in srgb, var(--bsplus-analytics-accent) 45%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-done:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-done:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px
|
||||||
|
color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
padding: 1rem 1rem 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.65rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent)
|
||||||
|
transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-list::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-list::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-option {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: var(--bsplus-analytics-radius-sm);
|
||||||
|
border: 1px solid var(--bsplus-analytics-border);
|
||||||
|
background: var(--bsplus-analytics-surface);
|
||||||
|
box-shadow: var(--bsplus-analytics-shadow);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: Rubik, system-ui, sans-serif;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition:
|
||||||
|
transform 0.25s var(--bsplus-analytics-ease),
|
||||||
|
box-shadow 0.25s var(--bsplus-analytics-ease),
|
||||||
|
border-color 0.2s ease,
|
||||||
|
background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-option:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--bsplus-analytics-shadow-hover);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-surface-2) 55%,
|
||||||
|
var(--bsplus-analytics-surface)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-option:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow:
|
||||||
|
var(--bsplus-analytics-shadow-hover),
|
||||||
|
0 0 0 3px color-mix(in srgb, var(--bsplus-analytics-accent) 30%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-option.is-selected {
|
||||||
|
border-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-accent) 45%,
|
||||||
|
var(--bsplus-analytics-border)
|
||||||
|
);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-accent) 10%,
|
||||||
|
var(--bsplus-analytics-surface)
|
||||||
|
);
|
||||||
|
box-shadow: 0 4px 16px
|
||||||
|
color-mix(in srgb, var(--bsplus-analytics-accent) 22%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-option-head {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-root .bsplus-font-picker-option-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--bsplus-analytics-text);
|
||||||
|
text-align: left;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-font-picker-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 0.2rem 0.65rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--bsplus-analytics-accent) 18%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
color: var(--bsplus-analytics-accent);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
//import { OpenMinecraftServerPopup } from "@/seqta/utils/Openers/OpenMinecraftServerPopup";
|
//import { OpenMinecraftServerPopup } from "@/seqta/utils/Openers/OpenMinecraftServerPopup";
|
||||||
|
|
||||||
import ColourPicker from "../components/ColourPicker.svelte";
|
import ColourPicker from "../components/ColourPicker.svelte";
|
||||||
|
import FontPickerModal from "../components/FontPickerModal.svelte";
|
||||||
import CloudPanel from "../components/CloudPanel.svelte";
|
import CloudPanel from "../components/CloudPanel.svelte";
|
||||||
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
||||||
import { settingsPopup } from "../hooks/SettingsPopup";
|
import { settingsPopup } from "../hooks/SettingsPopup";
|
||||||
@@ -47,6 +48,10 @@
|
|||||||
showColourPicker = true;
|
showColourPicker = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openFontPicker = () => {
|
||||||
|
showFontPicker = true;
|
||||||
|
};
|
||||||
|
|
||||||
const openChangelog = () => {
|
const openChangelog = () => {
|
||||||
OpenWhatsNewPopup();
|
OpenWhatsNewPopup();
|
||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
@@ -69,6 +74,7 @@
|
|||||||
|
|
||||||
let { standalone } = $props<{ standalone?: boolean }>();
|
let { standalone } = $props<{ standalone?: boolean }>();
|
||||||
let showColourPicker = $state<boolean>(false);
|
let showColourPicker = $state<boolean>(false);
|
||||||
|
let showFontPicker = $state<boolean>(false);
|
||||||
let showCloudPanel = $state<boolean>(false);
|
let showCloudPanel = $state<boolean>(false);
|
||||||
|
|
||||||
const openCloudPanel = () => {
|
const openCloudPanel = () => {
|
||||||
@@ -85,6 +91,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
settingsPopup.addListener(() => {
|
settingsPopup.addListener(() => {
|
||||||
showColourPicker = false;
|
showColourPicker = false;
|
||||||
|
showFontPicker = false;
|
||||||
showCloudPanel = false;
|
showCloudPanel = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,7 +102,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="w-[384px] no-scrollbar shadow-2xl {$settingsState.DarkMode
|
class="relative w-[384px] no-scrollbar shadow-2xl {$settingsState.DarkMode
|
||||||
? 'dark'
|
? 'dark'
|
||||||
: ''} {standalone ? 'h-[600px]' : 'h-full rounded-xl'} overflow-clip"
|
: ''} {standalone ? 'h-[600px]' : 'h-full rounded-xl'} overflow-clip"
|
||||||
>
|
>
|
||||||
@@ -293,7 +300,7 @@
|
|||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
Content: Settings,
|
Content: Settings,
|
||||||
props: { showColourPicker: openColourPicker, showDisclaimer, showCloudPanel: openCloudPanel },
|
props: { showColourPicker: openColourPicker, showFontPicker: openFontPicker, showDisclaimer, showCloudPanel: openCloudPanel },
|
||||||
},
|
},
|
||||||
{ title: "Shortcuts", Content: Shortcuts },
|
{ title: "Shortcuts", Content: Shortcuts },
|
||||||
{ title: "Themes", Content: Theme },
|
{ title: "Themes", Content: Theme },
|
||||||
@@ -318,6 +325,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if showFontPicker}
|
||||||
|
<FontPickerModal
|
||||||
|
hidePicker={() => {
|
||||||
|
showFontPicker = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if showDisclaimerModal && disclaimerCallbacks}
|
{#if showDisclaimerModal && disclaimerCallbacks}
|
||||||
<DisclaimerModal
|
<DisclaimerModal
|
||||||
title={disclaimerTitle}
|
title={disclaimerTitle}
|
||||||
|
|||||||
@@ -132,8 +132,9 @@
|
|||||||
loadPluginSettings();
|
loadPluginSettings();
|
||||||
})
|
})
|
||||||
|
|
||||||
const { showColourPicker, showDisclaimer, showCloudPanel } = $props<{
|
const { showColourPicker, showFontPicker, showDisclaimer, showCloudPanel } = $props<{
|
||||||
showColourPicker: () => void;
|
showColourPicker: () => void;
|
||||||
|
showFontPicker: () => void;
|
||||||
showDisclaimer: (onConfirm: () => void, onCancel: () => void, title?: string, message?: string) => void;
|
showDisclaimer: (onConfirm: () => void, onCancel: () => void, title?: string, message?: string) => void;
|
||||||
showCloudPanel: () => void;
|
showCloudPanel: () => void;
|
||||||
}>();
|
}>();
|
||||||
@@ -192,6 +193,16 @@
|
|||||||
onClick: showColourPicker
|
onClick: showColourPicker
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Interface Font",
|
||||||
|
description: "Choose the typeface used across SEQTA Learn",
|
||||||
|
id: 16,
|
||||||
|
Component: Button,
|
||||||
|
props: {
|
||||||
|
onClick: showFontPicker,
|
||||||
|
text: "Change"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Icon Only Sidebar",
|
title: "Icon Only Sidebar",
|
||||||
description: "Show only icons in the sidebar for a compact layout",
|
description: "Show only icons in the sidebar for a compact layout",
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ import type { Action } from "svelte/action";
|
|||||||
* Svelte action that moves the element to a different DOM target.
|
* Svelte action that moves the element to a different DOM target.
|
||||||
* Defaults to the nearest ShadowRoot so styles remain intact when the app
|
* Defaults to the nearest ShadowRoot so styles remain intact when the app
|
||||||
* is rendered inside a shadow DOM. Falls back to document.body otherwise.
|
* is rendered inside a shadow DOM. Falls back to document.body otherwise.
|
||||||
* Keeps all Svelte reactivity/events intact while escaping ancestor stacking contexts.
|
* Pass `document.body` to escape transformed/contained settings popups entirely.
|
||||||
*/
|
*/
|
||||||
export const portal: Action<HTMLElement, HTMLElement | ShadowRoot | undefined> = (node, target) => {
|
export const portal: Action<HTMLElement, HTMLElement | ShadowRoot | undefined> = (
|
||||||
|
node,
|
||||||
|
target,
|
||||||
|
) => {
|
||||||
const root = node.getRootNode();
|
const root = node.getRootNode();
|
||||||
const dest = target ?? (root instanceof ShadowRoot ? root : document.body);
|
const dest = target ?? (root instanceof ShadowRoot ? root : document.body);
|
||||||
dest.appendChild(node);
|
dest.appendChild(node);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(newTarget) {
|
update(newTarget) {
|
||||||
(newTarget ?? dest).appendChild(node);
|
const nextDest =
|
||||||
|
newTarget ?? (root instanceof ShadowRoot ? root : document.body);
|
||||||
|
nextDest.appendChild(node);
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
node.remove();
|
node.remove();
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
|
const THEME_CSS_VARS = [
|
||||||
|
"--better-main",
|
||||||
|
"--better-pale",
|
||||||
|
"--better-light",
|
||||||
|
"--text-color",
|
||||||
|
"--background-primary",
|
||||||
|
"--background-secondary",
|
||||||
|
"--text-primary",
|
||||||
|
"--theme-offset-bg",
|
||||||
|
"--better-sub",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const ACCENT_CSS_VARS = [
|
||||||
|
"--better-main",
|
||||||
|
"--accent-color-value",
|
||||||
|
"--accentColor",
|
||||||
|
"--colour-betterseqta-blue",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function extractSolidColor(value: string): string | null {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed || trimmed === "initial") return null;
|
||||||
|
if (
|
||||||
|
trimmed.startsWith("#") ||
|
||||||
|
trimmed.startsWith("rgb") ||
|
||||||
|
trimmed.startsWith("hsl")
|
||||||
|
) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
if (trimmed.includes("gradient")) {
|
||||||
|
const match = trimmed.match(
|
||||||
|
/#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgba?\([^)]+\)/i,
|
||||||
|
);
|
||||||
|
return match?.[0] ?? null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePageAccentColor(): string {
|
||||||
|
const computed = getComputedStyle(document.documentElement);
|
||||||
|
for (const name of ACCENT_CSS_VARS) {
|
||||||
|
const solid = extractSolidColor(computed.getPropertyValue(name));
|
||||||
|
if (solid) return solid;
|
||||||
|
}
|
||||||
|
const fromSettings = settingsState.selectedColor?.trim();
|
||||||
|
if (fromSettings) {
|
||||||
|
const solid = extractSolidColor(fromSettings);
|
||||||
|
if (solid) return solid;
|
||||||
|
}
|
||||||
|
return "#007bff";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copy SEQTA page theme tokens onto a portaled UI root (matches analytics sync). */
|
||||||
|
export function syncPageThemeToElement(target: HTMLElement): void {
|
||||||
|
const computed = getComputedStyle(document.documentElement);
|
||||||
|
|
||||||
|
for (const name of THEME_CSS_VARS) {
|
||||||
|
const value = computed.getPropertyValue(name).trim();
|
||||||
|
if (value) {
|
||||||
|
target.style.setProperty(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accent = resolvePageAccentColor();
|
||||||
|
target.style.setProperty("--bsplus-analytics-accent", accent);
|
||||||
|
target.style.setProperty("--better-main", accent);
|
||||||
|
|
||||||
|
target.classList.toggle(
|
||||||
|
"dark",
|
||||||
|
document.documentElement.classList.contains("dark"),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
|||||||
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners";
|
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners";
|
||||||
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
|
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
|
import { applySelectedFont } from "@/seqta/ui/fonts/Manager";
|
||||||
import loading from "@/seqta/ui/Loading";
|
import loading from "@/seqta/ui/Loading";
|
||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
import { getEngageRoutePage } from "@/seqta/utils/engageRoute";
|
import { getEngageRoutePage } from "@/seqta/utils/engageRoute";
|
||||||
@@ -697,6 +698,7 @@ export function init() {
|
|||||||
new MessageHandler();
|
new MessageHandler();
|
||||||
|
|
||||||
void updateAllColors();
|
void updateAllColors();
|
||||||
|
applySelectedFont();
|
||||||
|
|
||||||
window.addEventListener("hashchange", () => {
|
window.addEventListener("hashchange", () => {
|
||||||
if (settingsState.adaptiveThemeColour) void updateAllColors();
|
if (settingsState.adaptiveThemeColour) void updateAllColors();
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { FONT_PRESETS, getFontPreset, type FontPreset } from "./presets";
|
||||||
|
|
||||||
|
const FONT_STYLE_ID = "betterseqta-font-override";
|
||||||
|
const FONT_PICKER_BATCH_ID = "betterseqta-font-picker-preview";
|
||||||
|
const loadedFontIds = new Set<string>();
|
||||||
|
let pickerFontsPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
/** Elements that show per-font previews must stay outside the global override. */
|
||||||
|
export const FONT_PICKER_ROOT_CLASS = "bsplus-font-picker-root";
|
||||||
|
|
||||||
|
function googleFamilyParam(preset: FontPreset): string | null {
|
||||||
|
if (!preset.googleUrl) return null;
|
||||||
|
const name =
|
||||||
|
preset.stack.split(",")[0]?.trim().replace(/^"|"$/g, "") ?? "";
|
||||||
|
if (!name) return null;
|
||||||
|
return `family=${encodeURIComponent(name)}:wght@400;500;600;700`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectStylesheet(id: string, href: string): Promise<void> {
|
||||||
|
if (document.getElementById(id)) return Promise.resolve();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const link = document.createElement("link");
|
||||||
|
link.id = id;
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
link.href = href;
|
||||||
|
link.setAttribute("data-betterseqta-font-picker-batch", "true");
|
||||||
|
link.onload = () => resolve();
|
||||||
|
link.onerror = () => resolve();
|
||||||
|
document.head.appendChild(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load all Google Fonts for picker previews (batched, awaited). */
|
||||||
|
export function ensureFontPickerFontsLoaded(): Promise<void> {
|
||||||
|
if (!pickerFontsPromise) {
|
||||||
|
pickerFontsPromise = (async () => {
|
||||||
|
const params = FONT_PRESETS.map(googleFamilyParam).filter(
|
||||||
|
(param): param is string => param !== null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const chunkSize = 10;
|
||||||
|
for (let i = 0; i < params.length; i += chunkSize) {
|
||||||
|
const chunk = params.slice(i, i + chunkSize);
|
||||||
|
const url = `https://fonts.googleapis.com/css2?${chunk.join("&")}&display=swap`;
|
||||||
|
await injectStylesheet(`${FONT_PICKER_BATCH_ID}-${i / chunkSize}`, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await document.fonts.ready;
|
||||||
|
} catch {
|
||||||
|
/* FontFaceSet unsupported or blocked */
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickerFontsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureFontLoaded(preset: FontPreset): void {
|
||||||
|
if (!preset.googleUrl || loadedFontIds.has(preset.id)) return;
|
||||||
|
|
||||||
|
if (document.querySelector(`link[data-betterseqta-font="${preset.id}"]`)) {
|
||||||
|
loadedFontIds.add(preset.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = document.createElement("link");
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
link.href = preset.googleUrl;
|
||||||
|
link.setAttribute("data-betterseqta-font", preset.id);
|
||||||
|
document.head.appendChild(link);
|
||||||
|
loadedFontIds.add(preset.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildFontPreviewCss(): string {
|
||||||
|
return FONT_PRESETS.map(
|
||||||
|
(preset) => `
|
||||||
|
.bsplus-font-picker-option[data-font-id="${preset.id}"] .bsplus-font-picker-option-name {
|
||||||
|
font-family: ${preset.stack} !important;
|
||||||
|
}`,
|
||||||
|
).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEQTA_FONT_SCOPE = `
|
||||||
|
.legacy-root,
|
||||||
|
.legacy-root input,
|
||||||
|
.legacy-root textarea,
|
||||||
|
.legacy-root button,
|
||||||
|
.legacy-root select,
|
||||||
|
.legacy-root option,
|
||||||
|
.legacy-root .input,
|
||||||
|
.legacy-root *,
|
||||||
|
#container,
|
||||||
|
#container *
|
||||||
|
`;
|
||||||
|
|
||||||
|
function buildFontOverrideCss(family: string): string {
|
||||||
|
const rule = `font-family: ${family} !important;`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
${SEQTA_FONT_SCOPE} {
|
||||||
|
${rule}
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconFamily,
|
||||||
|
.iconFamily *,
|
||||||
|
button.uiButton.timetable-zoom.iconFamily,
|
||||||
|
[class~="iconFamily"],
|
||||||
|
[class~="iconFamily"] * {
|
||||||
|
font-family: "IconFamily" !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applySelectedFont(fontId?: string | null): void {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
|
||||||
|
const preset = getFontPreset(fontId ?? settingsState.selectedFont);
|
||||||
|
ensureFontLoaded(preset);
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--betterseqta-font-family",
|
||||||
|
preset.stack.split(",")[0]?.trim().replace(/^"|"$/g, "") ?? "Rubik",
|
||||||
|
);
|
||||||
|
|
||||||
|
let style = document.getElementById(FONT_STYLE_ID) as HTMLStyleElement | null;
|
||||||
|
if (!style) {
|
||||||
|
style = document.createElement("style");
|
||||||
|
style.id = FONT_STYLE_ID;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
style.textContent = buildFontOverrideCss(preset.stack);
|
||||||
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
export const DEFAULT_FONT_ID = "rubik";
|
||||||
|
|
||||||
|
export interface FontPreset {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
stack: string;
|
||||||
|
googleUrl?: string;
|
||||||
|
sample: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FONT_PRESETS: FontPreset[] = [
|
||||||
|
{
|
||||||
|
id: "rubik",
|
||||||
|
name: "Rubik",
|
||||||
|
stack: "Rubik, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "inter",
|
||||||
|
name: "Inter",
|
||||||
|
stack: "Inter, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "poppins",
|
||||||
|
name: "Poppins",
|
||||||
|
stack: "Poppins, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "nunito",
|
||||||
|
name: "Nunito",
|
||||||
|
stack: "Nunito, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "montserrat",
|
||||||
|
name: "Montserrat",
|
||||||
|
stack: "Montserrat, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "open-sans",
|
||||||
|
name: "Open Sans",
|
||||||
|
stack: '"Open Sans", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lato",
|
||||||
|
name: "Lato",
|
||||||
|
stack: "Lato, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "source-sans-3",
|
||||||
|
name: "Source Sans 3",
|
||||||
|
stack: '"Source Sans 3", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "raleway",
|
||||||
|
name: "Raleway",
|
||||||
|
stack: "Raleway, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dm-sans",
|
||||||
|
name: "DM Sans",
|
||||||
|
stack: '"DM Sans", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "plus-jakarta-sans",
|
||||||
|
name: "Plus Jakarta Sans",
|
||||||
|
stack: '"Plus Jakarta Sans", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "outfit",
|
||||||
|
name: "Outfit",
|
||||||
|
stack: "Outfit, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "roboto",
|
||||||
|
name: "Roboto",
|
||||||
|
stack: "Roboto, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "work-sans",
|
||||||
|
name: "Work Sans",
|
||||||
|
stack: '"Work Sans", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "manrope",
|
||||||
|
name: "Manrope",
|
||||||
|
stack: "Manrope, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "figtree",
|
||||||
|
name: "Figtree",
|
||||||
|
stack: "Figtree, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lexend",
|
||||||
|
name: "Lexend",
|
||||||
|
stack: "Lexend, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ubuntu",
|
||||||
|
name: "Ubuntu",
|
||||||
|
stack: "Ubuntu, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "karla",
|
||||||
|
name: "Karla",
|
||||||
|
stack: "Karla, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Karla:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "quicksand",
|
||||||
|
name: "Quicksand",
|
||||||
|
stack: "Quicksand, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ibm-plex-sans",
|
||||||
|
name: "IBM Plex Sans",
|
||||||
|
stack: '"IBM Plex Sans", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "space-grotesk",
|
||||||
|
name: "Space Grotesk",
|
||||||
|
stack: '"Space Grotesk", sans-serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mulish",
|
||||||
|
name: "Mulish",
|
||||||
|
stack: "Mulish, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cabin",
|
||||||
|
name: "Cabin",
|
||||||
|
stack: "Cabin, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Cabin:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "oswald",
|
||||||
|
name: "Oswald",
|
||||||
|
stack: "Oswald, sans-serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "merriweather",
|
||||||
|
name: "Merriweather",
|
||||||
|
stack: "Merriweather, serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "playfair-display",
|
||||||
|
name: "Playfair Display",
|
||||||
|
stack: '"Playfair Display", serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lora",
|
||||||
|
name: "Lora",
|
||||||
|
stack: "Lora, serif",
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "crimson-pro",
|
||||||
|
name: "Crimson Pro",
|
||||||
|
stack: '"Crimson Pro", serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "libre-baskerville",
|
||||||
|
name: "Libre Baskerville",
|
||||||
|
stack: '"Libre Baskerville", serif',
|
||||||
|
googleUrl:
|
||||||
|
"https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&display=swap",
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "system",
|
||||||
|
name: "System Default",
|
||||||
|
stack: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
||||||
|
sample: "Assessment due tomorrow at 3:30pm",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getFontPreset(id?: string | null): FontPreset {
|
||||||
|
return (
|
||||||
|
FONT_PRESETS.find((preset) => preset.id === id) ??
|
||||||
|
FONT_PRESETS.find((preset) => preset.id === DEFAULT_FONT_ID)!
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { settingsState } from "./SettingsState";
|
import { settingsState } from "./SettingsState";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
|
import { applySelectedFont } from "@/seqta/ui/fonts/Manager";
|
||||||
|
|
||||||
// Shortcuts rendering
|
// Shortcuts rendering
|
||||||
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||||
@@ -40,6 +41,7 @@ export class StorageChangeHandler {
|
|||||||
"iconOnlySidebar",
|
"iconOnlySidebar",
|
||||||
this.handleIconOnlySidebarChange.bind(this),
|
this.handleIconOnlySidebarChange.bind(this),
|
||||||
);
|
);
|
||||||
|
settingsState.register("selectedFont", () => applySelectedFont());
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleIconOnlySidebarChange(newValue: boolean | undefined) {
|
private handleIconOnlySidebarChange(newValue: boolean | undefined) {
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ export interface SettingsState {
|
|||||||
adaptiveThemeColour?: boolean;
|
adaptiveThemeColour?: boolean;
|
||||||
adaptiveThemeGradient?: boolean;
|
adaptiveThemeGradient?: boolean;
|
||||||
adaptiveThemeColourTransition?: boolean;
|
adaptiveThemeColourTransition?: boolean;
|
||||||
|
/** Google Font preset id for SEQTA interface typography (`rubik` default). */
|
||||||
|
selectedFont?: string;
|
||||||
|
|
||||||
// depreciated keys
|
// depreciated keys
|
||||||
animatedbk: boolean;
|
animatedbk: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user