v1.0.0.0
StocketBase
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/includes/config.php';
- require_once __DIR__ . '/includes/database.php';
- require_once __DIR__ . '/includes/auth.php';
- require_once __DIR__ . '/includes/csrf.php';
- require_once __DIR__ . '/includes/font-library.php';
- require_once __DIR__ . '/includes/ai.php';
-
- Auth::boot();
- Auth::requireLogin();
-
- $currentUser = Auth::user();
- $currentRole = Auth::role();
- $interfaceAvailableFonts = FontLibrary::available();
- $interfaceAiAvailable = Ai::availableFor($currentRole);
-
- $activeTab = $_GET['tab'] ?? 'appearance';
- $allowedTabs = ['appearance', 'shortcuts'];
- if (!in_array($activeTab, $allowedTabs, true)) {
- $activeTab = 'appearance';
- }
-
- $dashActivePage = 'interface';
- $dashPageTitle = 'Interface';
-
- require __DIR__ . '/includes/dash-header.php';
-
- ?>
-
- <h1 class="dash-title"><?= Icons::icon("sliders", "icon icon-lg") ?>Interface</h1>
-
- <div class="settings-tabs">
- <a href="interface.php?tab=appearance" class="<?= $activeTab === 'appearance' ? 'active' : '' ?>"><?= Icons::icon("palette", "icon icon-sm") ?>Appearance</a>
- <a href="interface.php?tab=shortcuts" class="<?= $activeTab === 'shortcuts' ? 'active' : '' ?>"><?= Icons::icon("keyboard", "icon icon-sm") ?>Shortcuts</a>
- </div>
-
- <?php if ($activeTab === 'appearance'): ?>
- <div class="settings-section">
- <p style="font-size:12.5px;color:var(--muted);margin:-4px 0 20px;">Personal preferences for how the dashboard and product editor look and behave on this device — text size, fonts, editor helpers. Everything here is stored in this browser only and applies just to you; it's never saved to the server or shared with other team members.</p>
-
- <label>Sidebar text size</label>
- <select id="appearance-sidebar-scale" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="80">80%</option>
- <option value="90">90%</option>
- <option value="100">100% (default)</option>
- <option value="110">110%</option>
- <option value="120">120%</option>
- <option value="130">130%</option>
- <option value="140">140%</option>
- <option value="150">150%</option>
- </select>
-
- <label>Main content text size</label>
- <select id="appearance-main-scale" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="80">80%</option>
- <option value="90">90%</option>
- <option value="100">100% (default)</option>
- <option value="110">110%</option>
- <option value="120">120%</option>
- <option value="130">130%</option>
- <option value="140">140%</option>
- <option value="150">150%</option>
- </select>
-
- <label>Editor interface font</label>
- <select id="appearance-ui-font" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="">Default</option>
- <?php foreach ($interfaceAvailableFonts as $interfaceFont): ?>
- <option value="<?= htmlspecialchars($interfaceFont['display_name'], ENT_QUOTES) ?>"><?= htmlspecialchars($interfaceFont['display_name']) ?></option>
- <?php endforeach; ?>
- </select>
-
- <label>Content font (product editor text)</label>
- <select id="appearance-content-font" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="">Default</option>
- <?php foreach ($interfaceAvailableFonts as $interfaceFont): ?>
- <option value="<?= htmlspecialchars($interfaceFont['display_name'], ENT_QUOTES) ?>"><?= htmlspecialchars($interfaceFont['display_name']) ?></option>
- <?php endforeach; ?>
- </select>
-
- <label style="margin-top:18px;"><input type="checkbox" id="appearance-glass"> Glass effect (translucent buttons and blurred backgrounds). Turn off for solid colors.</label>
- <label><input type="checkbox" id="appearance-minimal"> Minimalism (removes all transitions, animations and gradients; also turns the glass effect off). Recommended for older computers and devices.</label>
- <label><input type="checkbox" id="appearance-block-icons"> Show block type icons in the product editor</label>
- <label><input type="checkbox" id="appearance-spellcheck"> Show browser spell check underlines in the product editor</label>
- <label><input type="checkbox" id="appearance-paste-cleanup"> Always strip formatting when pasting into the product editor (Ctrl+Shift+V always does this regardless)</label>
-
- <?php if ($interfaceAiAvailable): ?>
- <label><input type="checkbox" id="appearance-ai-helper"> Show the AI helper button in the product and page editors</label>
- <?php endif; ?>
-
- <div class="publish-actions">
- <button type="button" id="appearance-reset" class="dash-btn dash-btn-small"><?= Icons::icon('trash', 'icon icon-sm') ?>Reset to defaults</button>
- </div>
- </div>
-
- <script>
- (function() {
- var SIDEBAR_KEY = 'stocketbase_dashboard_sidebar_scale';
- var MAIN_KEY = 'stocketbase_dashboard_main_scale';
- var UI_FONT_KEY = 'stocketbase_dashboard_ui_font';
- var CONTENT_FONT_KEY = 'stocketbase_dashboard_content_font';
- var BLOCK_ICONS_KEY = 'stocketbase_dashboard_block_icons';
- var SPELLCHECK_KEY = 'stocketbase_dashboard_spellcheck';
- var PASTE_CLEANUP_KEY = 'stocketbase_dashboard_paste_cleanup';
-
- var sidebarSelect = document.getElementById('appearance-sidebar-scale');
- var mainSelect = document.getElementById('appearance-main-scale');
- var blockIconsCheckbox = document.getElementById('appearance-block-icons');
- var spellcheckCheckbox = document.getElementById('appearance-spellcheck');
- var pasteCleanupCheckbox = document.getElementById('appearance-paste-cleanup');
-
- var GLASS_KEY = 'stocketbase_dashboard_glass';
- var glassCheckbox = document.getElementById('appearance-glass');
-
- var MINIMAL_KEY = 'stocketbase_dashboard_minimal';
- var minimalCheckbox = document.getElementById('appearance-minimal');
-
- function syncGlassCheckbox() {
- var minimal = localStorage.getItem(MINIMAL_KEY) === '1';
- glassCheckbox.disabled = minimal;
- glassCheckbox.checked = !minimal && localStorage.getItem(GLASS_KEY) !== '0';
- }
-
- minimalCheckbox.checked = localStorage.getItem(MINIMAL_KEY) === '1';
- minimalCheckbox.addEventListener('change', function() {
- if (this.checked) {
- localStorage.setItem(MINIMAL_KEY, '1');
- } else {
- localStorage.removeItem(MINIMAL_KEY);
- }
- syncGlassCheckbox();
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- syncGlassCheckbox();
- glassCheckbox.addEventListener('change', function() {
- if (this.checked) {
- localStorage.removeItem(GLASS_KEY);
- } else {
- localStorage.setItem(GLASS_KEY, '0');
- }
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- blockIconsCheckbox.checked = localStorage.getItem(BLOCK_ICONS_KEY) !== '0';
- blockIconsCheckbox.addEventListener('change', function() {
- localStorage.setItem(BLOCK_ICONS_KEY, this.checked ? '1' : '0');
- });
-
- spellcheckCheckbox.checked = localStorage.getItem(SPELLCHECK_KEY) !== '0';
- spellcheckCheckbox.addEventListener('change', function() {
- localStorage.setItem(SPELLCHECK_KEY, this.checked ? '1' : '0');
- });
-
- pasteCleanupCheckbox.checked = localStorage.getItem(PASTE_CLEANUP_KEY) === '1';
- pasteCleanupCheckbox.addEventListener('change', function() {
- localStorage.setItem(PASTE_CLEANUP_KEY, this.checked ? '1' : '0');
- });
- var AI_HELPER_KEY = 'stocketbase_dashboard_ai_helper';
- var aiHelperCheckbox = document.getElementById('appearance-ai-helper');
-
- if (aiHelperCheckbox) {
- aiHelperCheckbox.checked = localStorage.getItem(AI_HELPER_KEY) !== '0';
- aiHelperCheckbox.addEventListener('change', function() {
- localStorage.setItem(AI_HELPER_KEY, this.checked ? '1' : '0');
- });
- }
- var uiFontSelect = document.getElementById('appearance-ui-font');
- var contentFontSelect = document.getElementById('appearance-content-font');
-
- function loadScale(key) {
- var stored = parseInt(localStorage.getItem(key), 10);
- var valid = [80, 90, 100, 110, 120, 130, 140, 150];
- return valid.indexOf(stored) !== -1 ? stored : 100;
- }
-
- sidebarSelect.value = String(loadScale(SIDEBAR_KEY));
- mainSelect.value = String(loadScale(MAIN_KEY));
- uiFontSelect.value = localStorage.getItem(UI_FONT_KEY) || '';
- contentFontSelect.value = localStorage.getItem(CONTENT_FONT_KEY) || '';
-
- sidebarSelect.addEventListener('change', function() {
- localStorage.setItem(SIDEBAR_KEY, this.value);
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- mainSelect.addEventListener('change', function() {
- localStorage.setItem(MAIN_KEY, this.value);
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- uiFontSelect.addEventListener('change', function() {
- if (this.value) {
- localStorage.setItem(UI_FONT_KEY, this.value);
- } else {
- localStorage.removeItem(UI_FONT_KEY);
- }
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- contentFontSelect.addEventListener('change', function() {
- if (this.value) {
- localStorage.setItem(CONTENT_FONT_KEY, this.value);
- } else {
- localStorage.removeItem(CONTENT_FONT_KEY);
- }
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
-
- document.getElementById('appearance-reset').addEventListener('click', function() {
- localStorage.removeItem(SIDEBAR_KEY);
- localStorage.removeItem(MAIN_KEY);
- localStorage.removeItem(UI_FONT_KEY);
- localStorage.removeItem(CONTENT_FONT_KEY);
- localStorage.removeItem(BLOCK_ICONS_KEY);
- localStorage.removeItem(SPELLCHECK_KEY);
- localStorage.removeItem(PASTE_CLEANUP_KEY);
- localStorage.removeItem(AI_HELPER_KEY);
- localStorage.removeItem(GLASS_KEY);
- localStorage.removeItem(MINIMAL_KEY);
- minimalCheckbox.checked = false;
- syncGlassCheckbox();
- if (aiHelperCheckbox) {
- aiHelperCheckbox.checked = true;
- }
- sidebarSelect.value = '100';
- mainSelect.value = '100';
- uiFontSelect.value = '';
- contentFontSelect.value = '';
- blockIconsCheckbox.checked = true;
- spellcheckCheckbox.checked = true;
- pasteCleanupCheckbox.checked = false;
- if (window.StocketBaseAppearance) {
- window.StocketBaseAppearance.apply();
- }
- });
- })();
- </script>
- <?php endif; ?>
-
- <?php if ($activeTab === 'shortcuts'): ?>
- <style>
- .shortcut-row {
- display: flex;
- align-items: center;
- gap: 14px;
- padding: 10px 0;
- border-bottom: 1px solid var(--border);
- }
-
- .shortcut-row:last-child {
- border-bottom: none;
- }
-
- .shortcut-row-label {
- flex: 1;
- font-size: 13.5px;
- }
-
- .shortcut-combo-btn {
- font-family: inherit;
- font-size: 12.5px;
- font-weight: 600;
- padding: 6px 14px;
- border-radius: 8px;
- border: 1px solid var(--border);
- background: var(--panel-raised);
- color: var(--text);
- cursor: pointer;
- min-width: 150px;
- text-align: center;
- }
-
- .shortcut-combo-btn:hover {
- border-color: var(--border-strong);
- }
-
- .shortcut-combo-btn.capturing {
- border-color: var(--accent);
- color: var(--accent);
- }
- </style>
-
- <div class="settings-section">
- <p style="font-size:12.5px;color:var(--muted);margin:-4px 0 20px;">Keyboard shortcuts for the product editor. Stored in this browser only.</p>
-
- <div id="shortcuts-list"></div>
-
- <div class="publish-actions" style="margin-top:16px;">
- <button type="button" id="shortcuts-reset-all" class="dash-btn dash-btn-small"><?= Icons::icon('trash', 'icon icon-sm') ?>Reset all to defaults</button>
- </div>
- </div>
-
- <?= Asset::js('assets/shortcuts.js') ?>
- <script>
- (function() {
- var listRoot = document.getElementById('shortcuts-list');
- var captureRow = null;
-
- function comboLabel(combo) {
- return combo.split('+').join(' + ');
- }
-
- function render() {
- var shortcuts = window.StocketBaseShortcuts.getShortcuts();
- listRoot.innerHTML = '';
-
- Object.keys(shortcuts).forEach(function(action) {
- var entry = shortcuts[action];
-
- var row = document.createElement('div');
- row.className = 'shortcut-row';
- row.dataset.action = action;
-
- var labelEl = document.createElement('span');
- labelEl.className = 'shortcut-row-label';
- labelEl.textContent = entry.label;
-
- var comboBtn = document.createElement('button');
- comboBtn.type = 'button';
- comboBtn.className = 'shortcut-combo-btn';
- comboBtn.textContent = comboLabel(entry.combo);
- comboBtn.title = 'Click, then press a new key combination';
-
- var resetBtn = document.createElement('button');
- resetBtn.type = 'button';
- resetBtn.className = 'dash-btn-small';
- resetBtn.textContent = 'Reset';
- resetBtn.disabled = entry.isDefault;
- resetBtn.addEventListener('click', function() {
- window.StocketBaseShortcuts.resetShortcut(action);
- render();
- });
-
- comboBtn.addEventListener('click', function() {
- startCapture(action, comboBtn);
- });
-
- row.appendChild(labelEl);
- row.appendChild(comboBtn);
- row.appendChild(resetBtn);
- listRoot.appendChild(row);
- });
- }
-
- function startCapture(action, btn) {
- if (captureRow) {
- captureRow.classList.remove('capturing');
- }
- captureRow = btn;
- btn.classList.add('capturing');
- btn.textContent = 'Press a key…';
-
- function onKeydown(event) {
- event.preventDefault();
- event.stopPropagation();
-
- var combo = window.StocketBaseShortcuts.comboFromEvent(event);
- if (!combo) {
- return;
- }
-
- window.StocketBaseShortcuts.setShortcut(action, combo);
- cleanup();
- render();
- }
-
- function onBlurAway(event) {
- if (event.target !== btn) {
- cleanup();
- render();
- }
- }
-
- function cleanup() {
- document.removeEventListener('keydown', onKeydown, true);
- document.removeEventListener('mousedown', onBlurAway, true);
- btn.classList.remove('capturing');
- captureRow = null;
- }
-
- document.addEventListener('keydown', onKeydown, true);
- setTimeout(function() {
- document.addEventListener('mousedown', onBlurAway, true);
- }, 0);
- }
-
- document.getElementById('shortcuts-reset-all').addEventListener('click', function() {
- window.StocketBaseShortcuts.resetAllShortcuts();
- render();
- });
-
- render();
- })();
- </script>
- <?php endif; ?>
-
- <?php require __DIR__ . '/includes/dash-footer.php'; ?>