170 lines
5.6 KiB
JavaScript
170 lines
5.6 KiB
JavaScript
export function createTemplatesMenu({ panel, fetchProjects, openProject, onShow, onHide }) {
|
||
let state = { templates: [], projects: [], loading: false, error: null };
|
||
|
||
const setState = (next) => {
|
||
state = { ...state, ...next };
|
||
render();
|
||
};
|
||
|
||
const renderEmpty = (parent, text) => {
|
||
const empty = document.createElement("div");
|
||
empty.className = "templates-empty";
|
||
empty.textContent = text;
|
||
parent.appendChild(empty);
|
||
};
|
||
|
||
const renderSection = (title, items, emptyText) => {
|
||
const section = document.createElement("div");
|
||
section.className = "templates-section";
|
||
const subtitle = document.createElement("div");
|
||
subtitle.className = "templates-subtitle";
|
||
subtitle.textContent = title;
|
||
section.appendChild(subtitle);
|
||
if (!items || !items.length) {
|
||
renderEmpty(section, emptyText);
|
||
} else {
|
||
items.forEach((item) => {
|
||
const card = document.createElement("button");
|
||
card.className = "example-card template-card";
|
||
card.type = "button";
|
||
card.addEventListener("click", () => openProject?.(item.id, item.type));
|
||
|
||
const titleEl = document.createElement("div");
|
||
titleEl.className = "card-title";
|
||
titleEl.textContent = item.name || item.id;
|
||
card.appendChild(titleEl);
|
||
|
||
const meta = document.createElement("div");
|
||
meta.className = "card-meta";
|
||
const typePill = document.createElement("span");
|
||
typePill.className = "card-pill";
|
||
typePill.dataset.kind = item.type || "project";
|
||
typePill.textContent = item.type === "template" ? "Шаблон" : "Проект";
|
||
meta.appendChild(typePill);
|
||
if (item.updatedAt) {
|
||
const updated = document.createElement("span");
|
||
updated.textContent = new Date(item.updatedAt).toLocaleDateString();
|
||
meta.appendChild(updated);
|
||
}
|
||
card.appendChild(meta);
|
||
|
||
section.appendChild(card);
|
||
});
|
||
}
|
||
return section;
|
||
};
|
||
|
||
const render = () => {
|
||
if (!panel) return;
|
||
panel.innerHTML = "";
|
||
|
||
const header = document.createElement("div");
|
||
header.className = "templates-header";
|
||
header.innerHTML = `<span class="templates-title">Проекты и шаблоны</span>`;
|
||
const actions = document.createElement("div");
|
||
actions.className = "templates-actions";
|
||
const refresh = document.createElement("button");
|
||
refresh.className = "templates-close";
|
||
refresh.type = "button";
|
||
refresh.title = "Обновить список";
|
||
refresh.setAttribute("aria-label", "Обновить список");
|
||
refresh.innerHTML = `
|
||
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
|
||
<path d="M12.2 5.3A4.4 4.4 0 0 0 4.7 3.4L3.6 4.5M3.7 2.1v2.4h2.4M3.8 10.7a4.4 4.4 0 0 0 7.5 1.9l1.1-1.1M12.3 13.9v-2.4H9.9" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"></path>
|
||
</svg>
|
||
`;
|
||
refresh.addEventListener("click", () => load());
|
||
const closeBtn = document.createElement("button");
|
||
closeBtn.className = "templates-close";
|
||
closeBtn.type = "button";
|
||
closeBtn.setAttribute("aria-label", "Закрыть проекты и шаблоны");
|
||
closeBtn.innerHTML = `
|
||
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
|
||
<path d="M4.25 4.25 11.75 11.75M11.75 4.25 4.25 11.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path>
|
||
</svg>
|
||
`;
|
||
closeBtn.addEventListener("click", hide);
|
||
actions.appendChild(refresh);
|
||
actions.appendChild(closeBtn);
|
||
header.appendChild(actions);
|
||
panel.appendChild(header);
|
||
|
||
const body = document.createElement("div");
|
||
body.className = "templates-panel-body";
|
||
panel.appendChild(body);
|
||
|
||
if (state.loading) {
|
||
const loadingEl = document.createElement("div");
|
||
loadingEl.className = "templates-empty";
|
||
loadingEl.textContent = "Загрузка списка проектов...";
|
||
body.appendChild(loadingEl);
|
||
return;
|
||
}
|
||
|
||
if (state.error) {
|
||
const errorEl = document.createElement("div");
|
||
errorEl.className = "templates-empty";
|
||
errorEl.style.color = "var(--error)";
|
||
errorEl.textContent = state.error;
|
||
body.appendChild(errorEl);
|
||
}
|
||
|
||
body.appendChild(renderSection("Шаблоны", state.templates, "Нет шаблонов"));
|
||
body.appendChild(renderSection("Мои проекты", state.projects, "Нет сохранённых проектов"));
|
||
};
|
||
|
||
const load = async () => {
|
||
if (!fetchProjects) return;
|
||
setState({ loading: true, error: null });
|
||
try {
|
||
const data = await fetchProjects();
|
||
setState({
|
||
loading: false,
|
||
templates: data?.templates || [],
|
||
projects: data?.projects || [],
|
||
error: null
|
||
});
|
||
} catch (err) {
|
||
setState({ loading: false, error: err?.message || "Не удалось загрузить проекты" });
|
||
}
|
||
};
|
||
|
||
const hide = () => {
|
||
if (panel) {
|
||
panel.classList.add("hidden");
|
||
panel.style.display = "";
|
||
}
|
||
onHide?.();
|
||
};
|
||
|
||
const show = () => {
|
||
render();
|
||
if (panel) {
|
||
panel.classList.remove("hidden");
|
||
panel.style.display = "flex";
|
||
}
|
||
load();
|
||
onShow?.();
|
||
};
|
||
|
||
const toggle = () => {
|
||
if (!panel) return;
|
||
if (panel.classList.contains("hidden")) {
|
||
show();
|
||
} else {
|
||
hide();
|
||
}
|
||
};
|
||
|
||
const setData = (data = {}) => {
|
||
setState({
|
||
templates: data.templates || [],
|
||
projects: data.projects || [],
|
||
loading: false,
|
||
error: data.error || null
|
||
});
|
||
};
|
||
|
||
return { render, show, hide, toggle, reload: load, setData };
|
||
}
|