Add versioned design profile releases
This commit is contained in:
@@ -56,6 +56,7 @@ import {
|
||||
type ApplicationManifestV01,
|
||||
type ApplicationSummary,
|
||||
type DesignProfileSummary,
|
||||
type DesignProfileStatus,
|
||||
} from "./applicationManifest.js";
|
||||
|
||||
type CatalogSection = "controls" | "media" | "glass" | "modals" | "icons";
|
||||
@@ -315,6 +316,7 @@ export function CatalogApp() {
|
||||
const [designProfileSaveOpen, setDesignProfileSaveOpen] = useState(false);
|
||||
const [designProfileSaveMode, setDesignProfileSaveMode] = useState<"save" | "save-as">("save");
|
||||
const [designProfileName, setDesignProfileName] = useState("");
|
||||
const [designProfilePublishState, setDesignProfilePublishState] = useState<"idle" | "publishing" | "published" | "error">("idle");
|
||||
const [activeDesignProfileId, setActiveDesignProfileId] = useState("default");
|
||||
const workspace = useApplicationWorkspace<StudioView>();
|
||||
const { navigationOpen: guidelineOpen, activeView, contentExpanded: panelExpanded } = workspace;
|
||||
@@ -885,8 +887,18 @@ export function CatalogApp() {
|
||||
body: JSON.stringify({ name: creating ? designProfileName : selected?.name, layout }),
|
||||
});
|
||||
if (!response.ok) return setLayoutSaveState("error");
|
||||
const saved = await response.json() as { id: string; name: string; version: string; layout: StoredLayout; timestamps: { updatedAt: string } };
|
||||
const summary: DesignProfileSummary = { id: saved.id, name: saved.name, version: saved.version, theme: saved.layout.theme === "light" ? "light" : "dark", updatedAt: saved.timestamps.updatedAt };
|
||||
const saved = await response.json() as { id: string; name: string; version: string; status: DesignProfileStatus; layout: StoredLayout; timestamps: { updatedAt: string } };
|
||||
const previous = designProfiles.find((profile) => profile.id === saved.id);
|
||||
const summary: DesignProfileSummary = {
|
||||
id: saved.id,
|
||||
name: saved.name,
|
||||
version: saved.version,
|
||||
status: saved.status,
|
||||
theme: saved.layout.theme === "light" ? "light" : "dark",
|
||||
updatedAt: saved.timestamps.updatedAt,
|
||||
versions: previous?.versions ?? [],
|
||||
latestPublishedVersion: previous?.latestPublishedVersion ?? null,
|
||||
};
|
||||
setDesignProfiles((current) => [...current.filter((profile) => profile.id !== summary.id), summary].sort((left, right) => left.name.localeCompare(right.name)));
|
||||
setActiveDesignProfileId(summary.id);
|
||||
setDesignProfileSaveOpen(false);
|
||||
@@ -894,6 +906,25 @@ export function CatalogApp() {
|
||||
setLayoutSaveState("saved");
|
||||
};
|
||||
|
||||
const publishDesignProfile = async () => {
|
||||
const current = designProfiles.find((profile) => profile.id === activeDesignProfileId);
|
||||
if (!current || current.versions.some((release) => release.version === current.version)) return;
|
||||
setDesignProfilePublishState("publishing");
|
||||
const response = await fetch(`/api/design-profiles/${current.id}/publish`, { method: "POST" });
|
||||
if (!response.ok) {
|
||||
setDesignProfilePublishState("error");
|
||||
return;
|
||||
}
|
||||
const release = await response.json() as { version: string; status: DesignProfileStatus; layout: StoredLayout; timestamps: { publishedAt?: string } };
|
||||
setDesignProfiles((profiles) => profiles.map((profile) => profile.id !== current.id ? profile : {
|
||||
...profile,
|
||||
versions: [{ version: release.version, status: release.status, theme: release.layout.theme === "light" ? "light" : "dark", publishedAt: release.timestamps.publishedAt }, ...profile.versions.filter((item) => item.version !== release.version)],
|
||||
latestPublishedVersion: release.version,
|
||||
}));
|
||||
setDesignProfilePublishState("published");
|
||||
setDesignProfileSaveOpen(false);
|
||||
};
|
||||
|
||||
const addPageTemplateToApplication = (template: PageTemplateDefinition) => {
|
||||
updateApplicationDraft((current) => {
|
||||
const order = current.pages.length;
|
||||
@@ -1312,6 +1343,19 @@ export function CatalogApp() {
|
||||
const renderApplicationDraft = () => {
|
||||
if (!applicationDraft || applicationDraft.id !== activeApplicationId) return null;
|
||||
const selectedProfile = designProfiles.find((profile) => profile.id === applicationDraft.designProfile.id) ?? designProfiles[0];
|
||||
const designProfileOptions = designProfiles.flatMap((profile) => [
|
||||
...profile.versions.map((release) => ({
|
||||
value: `${profile.id}@${release.version}@published`,
|
||||
label: profile.name,
|
||||
description: `${release.version} · Published · ${release.theme}`,
|
||||
})),
|
||||
{
|
||||
value: `${profile.id}@${profile.version}@draft`,
|
||||
label: `${profile.name} — Draft`,
|
||||
description: `${profile.version} · Draft · ${profile.theme}`,
|
||||
},
|
||||
]);
|
||||
const selectedProfileValue = `${applicationDraft.designProfile.id}@${applicationDraft.designProfile.version}@${applicationDraft.designProfile.status}`;
|
||||
const availableTemplates = pageTemplates;
|
||||
|
||||
if (applicationMode === "preview") {
|
||||
@@ -1373,13 +1417,17 @@ export function CatalogApp() {
|
||||
>
|
||||
<Select
|
||||
label="Design Profile"
|
||||
value={selectedProfile?.id ?? "default"}
|
||||
options={designProfiles.map((profile) => ({ value: profile.id, label: profile.name, description: `${profile.version} · ${profile.theme}` }))}
|
||||
onChange={(profileId) => {
|
||||
value={designProfileOptions.some((option) => option.value === selectedProfileValue) ? selectedProfileValue : `${selectedProfile?.id ?? "default"}@${selectedProfile?.version ?? "0.6.0"}@draft`}
|
||||
options={designProfileOptions}
|
||||
onChange={(profileReference) => {
|
||||
const [profileId, version, statusValue] = profileReference.split("@");
|
||||
const profile = designProfiles.find((item) => item.id === profileId);
|
||||
if (!profile) return;
|
||||
setTheme(profile.theme);
|
||||
updateApplicationDraft((current) => ({ ...current, designProfile: { id: profile.id, version: profile.version, theme: profile.theme } }));
|
||||
const status: DesignProfileStatus = statusValue === "published" ? "published" : "draft";
|
||||
const release = status === "published" ? profile.versions.find((item) => item.version === version) : undefined;
|
||||
const profileTheme = release?.theme ?? profile.theme;
|
||||
setTheme(profileTheme);
|
||||
updateApplicationDraft((current) => ({ ...current, designProfile: { id: profile.id, version, status, theme: profileTheme } }));
|
||||
}}
|
||||
/>
|
||||
</SettingsCard>
|
||||
@@ -1623,7 +1671,7 @@ export function CatalogApp() {
|
||||
className="catalog-module-switcher"
|
||||
label="Design Profile"
|
||||
value={activeDesignProfileId}
|
||||
options={designProfiles.map((profile) => ({ value: profile.id, label: profile.name, description: `${profile.version} · ${profile.theme}`, icon: <Icon name="settings" /> }))}
|
||||
options={designProfiles.map((profile) => ({ value: profile.id, label: profile.name, description: `${profile.version} · Draft · ${profile.versions.length} releases`, icon: <Icon name="settings" /> }))}
|
||||
onChange={(id) => { void openDesignProfile(id); }}
|
||||
/>
|
||||
) : undefined}
|
||||
@@ -1798,6 +1846,10 @@ export function CatalogApp() {
|
||||
{designProfileSaveMode === "save" ? (
|
||||
<>
|
||||
<Button onClick={() => { void saveDesignProfile("save"); }}>Сохранить</Button>
|
||||
<Button
|
||||
disabled={designProfilePublishState === "publishing" || Boolean(designProfiles.find((profile) => profile.id === activeDesignProfileId)?.versions.some((release) => release.version === designProfiles.find((profile) => profile.id === activeDesignProfileId)?.version))}
|
||||
onClick={() => { void publishDesignProfile(); }}
|
||||
>{designProfilePublishState === "publishing" ? "Публикуется" : `Опубликовать v${designProfiles.find((profile) => profile.id === activeDesignProfileId)?.version ?? ""}`}</Button>
|
||||
<Button variant="primary" shape="pill" onClick={() => { setDesignProfileName(""); setDesignProfileSaveMode("save-as"); }}>Сохранить как новый</Button>
|
||||
</>
|
||||
) : (
|
||||
@@ -1810,7 +1862,7 @@ export function CatalogApp() {
|
||||
<div className="catalog-form">
|
||||
<SegmentedControl label="Режим сохранения" value={designProfileSaveMode} items={[{ value: "save", label: "Сохранить" }, { value: "save-as", label: "Сохранить как новый" }]} onChange={setDesignProfileSaveMode} />
|
||||
{designProfileSaveMode === "save-as" ? <TextField label="Название нового профиля" hint="обязательно" value={designProfileName} onChange={(event) => setDesignProfileName(event.target.value)} /> : (
|
||||
<div className="catalog-save-profile-current"><Icon name="settings" /><span><strong>{designProfiles.find((profile) => profile.id === activeDesignProfileId)?.name ?? "NODE.DC Default"}</strong><small>Будет создана новая draft-версия текущего профиля.</small></span></div>
|
||||
<div className="catalog-save-profile-current"><Icon name="settings" /><span><strong>{designProfiles.find((profile) => profile.id === activeDesignProfileId)?.name ?? "NODE.DC Default"}</strong><small>Сохранение создаёт новую draft-версию. Publish фиксирует текущую сохранённую версию неизменяемым release.</small></span></div>
|
||||
)}
|
||||
</div>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user