Snapshot synced v3.6.1 working state as baseline

Machine-sync snapshot: the working tree carried the full 3.0.0-3.6.1 arc
(licensing/FileManager/DatabaseExplorer removal, all new replacement screens
and REST controllers) that had not been committed on this machine. Captured
here as a safety baseline before starting the Adminify-parity revamp.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
NixReaper
2026-07-12 08:41:18 -05:00
co-authored by Claude Opus 4.8
parent 07d77bda57
commit e2db2ad4b5
88 changed files with 8944 additions and 2730 deletions
@@ -1,5 +1,5 @@
<script setup>
import { computed, reactive, ref } from 'vue';
import { computed, ref } from 'vue';
import Toggle from '@/components/Toggle.vue';
import { themePresets, buildPalette } from '@/pages/settings/themePresets.js';
import { googleFonts } from '@/pages/settings/googleFonts.js';
@@ -11,24 +11,15 @@ const props = defineProps({
modelValue: { default: undefined },
// Options for multiselect fields (resolved by the page from optionsSource).
options: { type: Array, default: () => [] },
// License::status()'s cached result, only passed for the license_key field.
licenseStatus: { type: Object, default: null },
// True when this field's `dependsOn` key is falsy — greys out the
// control without hiding it, so the relationship stays visible.
disabled: { type: Boolean, default: false },
// Shared REST fetcher, only used by field types that need their own API
// calls beyond the generic wp/v2/settings save flow (companion-plugin).
api: { type: Function, default: null },
});
const emit = defineEmits(['update:modelValue']);
const licenseStatusText = computed(() => {
const s = props.licenseStatus;
if (!s || s.status === 'none') return null;
if (s.status === 'active') {
const plan = s.plan ? ` (${s.plan} plan)` : '';
return { tone: 'ok', text: `License active${plan}${s.expires_at ? ` — renews/expires ${s.expires_at}` : ''}` };
}
if (s.status === 'unreachable') {
return { tone: 'warn', text: s.message || 'Could not reach the license server — will retry automatically.' };
}
return { tone: 'error', text: s.message || 'License invalid.' };
});
const val = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v),
@@ -54,21 +45,6 @@ const toggleValue = (value) => {
emit('update:modelValue', next);
};
// Remote sites helpers -------------------------------------------------------
const sites = computed(() => (Array.isArray(props.modelValue) ? props.modelValue : []));
const newSite = reactive({ url: '', username: '', app_password: '' });
const addSite = () => {
if (!newSite.url.trim()) return;
emit('update:modelValue', [
...sites.value,
{ url: newSite.url.trim(), username: newSite.username.trim(), app_password: newSite.app_password },
]);
newSite.url = '';
newSite.username = '';
newSite.app_password = '';
};
const removeSite = (i) => emit('update:modelValue', sites.value.filter((_, idx) => idx !== i));
// Media picker (image fields) ---------------------------------------------
const pickImage = () => {
const wp = window.wp;
@@ -103,16 +79,47 @@ const openFontList = () => {
fontQuery.value = props.modelValue || '';
fontOpen.value = true;
};
// Companion plugin (Aurora File Manager) install card -----------------------
const companionStatus = ref(null); // { installed, active } | null while loading
const companionBusy = ref(false);
const companionError = ref('');
const loadCompanionStatus = async () => {
if (props.field.type !== 'companion-plugin' || !props.api) return;
try {
companionStatus.value = await props.api(props.field.statusEndpoint);
} catch {
companionStatus.value = { installed: false, active: false };
}
};
if (props.field.type === 'companion-plugin') {
loadCompanionStatus();
}
const installCompanion = async () => {
if (!props.api) return;
companionBusy.value = true;
companionError.value = '';
try {
const result = await props.api(props.field.installEndpoint, { method: 'POST' });
if (!result.success) {
companionError.value = result.message || 'Installation failed.';
}
await loadCompanionStatus();
} catch (e) {
companionError.value = e.message || 'Installation failed.';
} finally {
companionBusy.value = false;
}
};
</script>
<template>
<div class="field" :id="field.key === 'license_key' ? 'license' : null">
<div class="field">
<div class="field__info">
<div class="field__label">{{ field.label }}</div>
<p v-if="field.description" class="field__desc">{{ field.description }}</p>
<p v-if="licenseStatusText" class="field__license-status" :class="`field__license-status--${licenseStatusText.tone}`">
{{ licenseStatusText.text }}
</p>
</div>
<div class="field__control">
@@ -122,14 +129,39 @@ const openFontList = () => {
type="text"
class="field__input"
:placeholder="field.placeholder || ''"
:readonly="!!field.readonly"
:value="val || ''"
@input="val = $event.target.value"
/>
<!-- Textarea (multi-line plain text — moderation/blocklist fields) -->
<textarea
v-else-if="field.type === 'textarea'"
class="field__input field__textarea"
:placeholder="field.placeholder || ''"
:rows="field.rows || 6"
:value="val || ''"
@input="val = $event.target.value"
></textarea>
<!-- Select (plain dropdown, distinct from the multiselect checkbox list below) -->
<select
v-else-if="field.type === 'select'"
class="field__input"
:value="val ?? ''"
:disabled="disabled"
@change="val = $event.target.value"
>
<option v-for="opt in (field.options || options)" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
<!-- Toggle -->
<Toggle
v-else-if="field.type === 'toggle'"
:model-value="!!val"
:disabled="disabled"
@update:model-value="val = $event"
/>
@@ -186,28 +218,6 @@ const openFontList = () => {
</button>
</div>
<!-- Remote sites -->
<div v-else-if="field.type === 'remote-sites'" class="field__sites">
<div class="field__sites-add">
<input v-model="newSite.url" type="text" class="field__input" placeholder="Site URL (e.g., https://example.com)" />
<div class="field__sites-row">
<input v-model="newSite.username" type="text" class="field__input" placeholder="Username" />
<input v-model="newSite.app_password" type="password" class="field__input" placeholder="Application Password" />
<button type="button" class="field__newitem" @click="addSite">Add Site</button>
</div>
</div>
<div class="field__sites-list">
<div v-if="!sites.length" class="field__sites-empty">No remote sites configured yet</div>
<div v-for="(site, i) in sites" :key="i" class="field__sites-item">
<span class="field__sites-url">{{ site.url }}</span>
<span class="field__sites-user">{{ site.username }}</span>
<button type="button" class="field__repeater-remove" @click="removeSite(i)">×</button>
</div>
</div>
<p class="field__sites-help">
Application passwords can be created in WordPress under Users &gt; Profile &gt; Application Passwords
</p>
</div>
<!-- Theme picker (10 fixed color presets, whole-UI preview) -->
<div v-else-if="field.type === 'theme-picker'" class="field__theme">
@@ -262,6 +272,31 @@ const openFontList = () => {
</div>
</div>
<!-- Companion plugin install card -->
<div v-else-if="field.type === 'companion-plugin'" class="field__companion">
<div v-if="!companionStatus" class="field__companion-status">Checking…</div>
<template v-else>
<a
v-if="companionStatus.active"
class="field__companion-manage"
:href="field.manageUrl"
>
Installed &amp; active — open File Manager →
</a>
<template v-else>
<button
type="button"
class="field__companion-install"
:disabled="companionBusy"
@click="installCompanion"
>
{{ companionBusy ? 'Installing…' : (companionStatus.installed ? 'Activate' : 'Install & Activate') }}
</button>
<p v-if="companionError" class="field__companion-error">{{ companionError }}</p>
</template>
</template>
</div>
<!-- Repeater -->
<div v-else-if="field.type === 'repeater'" class="field__repeater">
<div v-for="(row, i) in rows" :key="i" class="field__repeater-row">
@@ -298,10 +333,6 @@ const openFontList = () => {
}
.field__label { font-size: 0.95rem; font-weight: 600; color: var(--aurora-text); }
.field__desc { margin: 6px 0 0; font-size: 0.8125rem; color: var(--aurora-text-muted); line-height: 1.5; }
.field__license-status { margin: 8px 0 0; font-size: 0.8125rem; font-weight: 600; }
.field__license-status--ok { color: #34d399; }
.field__license-status--warn { color: #fbbf24; }
.field__license-status--error { color: #f87171; }
.field__control { display: flex; justify-content: flex-start; }
.field__input {
@@ -314,6 +345,7 @@ const openFontList = () => {
font-size: 0.875rem;
}
.field__input:focus { outline: none; border-color: var(--aurora-accent); }
.field__textarea { resize: vertical; font-family: inherit; line-height: 1.5; }
.field__color { display: flex; align-items: center; gap: 10px; }
.field__color input[type='color'] {
@@ -379,33 +411,6 @@ const openFontList = () => {
}
.field__seg--active { background: var(--aurora-accent); color: #fff !important; }
/* Remote sites */
.field__sites { width: 100%; }
.field__sites-add .field__input { margin-bottom: 8px; }
.field__sites-row { display: flex; gap: 8px; }
.field__sites-row .field__input { margin-bottom: 0; }
.field__sites-row .field__newitem { flex-shrink: 0; white-space: nowrap; }
.field__sites-list {
margin-top: 12px;
border: 1px solid var(--aurora-border);
border-radius: 10px;
overflow: hidden;
}
.field__sites-empty {
padding: 18px; text-align: center;
font-size: 0.8125rem; color: var(--aurora-text-muted);
}
.field__sites-item {
display: flex; align-items: center; gap: 12px;
padding: 10px 14px;
border-bottom: 1px solid var(--aurora-border);
font-size: 0.8125rem;
}
.field__sites-item:last-child { border-bottom: none; }
.field__sites-url { color: var(--aurora-text); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.field__sites-user { color: var(--aurora-text-muted); }
.field__sites-help { margin: 10px 0 0; font-size: 0.75rem; color: var(--aurora-text-muted); }
/* Theme picker */
.field__theme { display: flex; flex-wrap: wrap; gap: 10px; width: 100%; }
.field__theme-card {
@@ -449,6 +454,20 @@ const openFontList = () => {
.field__font-item:hover { background: var(--aurora-bg-subtle); }
.field__font-item--active { color: var(--aurora-accent); }
/* Companion plugin install card */
.field__companion-status { font-size: 0.8125rem; color: var(--aurora-text-muted); }
.field__companion-install {
border: none; border-radius: 8px; background: var(--aurora-accent); color: #fff;
padding: 8px 16px; font-size: 0.8125rem; font-weight: 600; cursor: pointer;
}
.field__companion-install:disabled { opacity: 0.6; cursor: default; }
.field__companion-manage {
display: inline-block; font-size: 0.8125rem; font-weight: 600;
color: var(--aurora-accent); text-decoration: none;
}
.field__companion-manage:hover { text-decoration: underline; }
.field__companion-error { margin: 8px 0 0; font-size: 0.8125rem; color: #e5484d; }
@media (max-width: 782px) {
.field { grid-template-columns: 1fr; gap: 12px; }
}