Очерки жизни
Шаблоны
100%
Страницы 0
Анимированное изображение
Создать книгу
Запустить тур заново

Выберите стиль книги

Классический
Скетч

Название книги

// ========== ЯДРО КОНСТРУКТОРА ========== // Глобальное пространство имен — чтобы не засорять window window.BookBuilder = window.BookBuilder || {}; // Конфигурация системы BookBuilder.config = { debug: false, // включи для отладки autoSaveDelay: 1000, maxHistory: 30, defaultZoom: 1, pageWidth: 450, pageHeight: 450 }; // Глобальное состояние BookBuilder.state = { pages: [], currentPage: 0, currentView: 'single', currentZoom: 1, history: [], historyPos: -1, originalImages: new Map() }; // Регистр шаблонов — сюда будут добавляться новые шаблоны BookBuilder.templates = {}; // Регистр категорий шаблонов BookBuilder.categories = { special: { title: 'Специальные', keys: [] }, photoText: { title: 'Фото + текст', keys: [] }, photoOnly: { title: 'Только фото', keys: [] }, textOnly: { title: 'Только текст', keys: [] } }; // Система плагинов — для расширения функционала BookBuilder.plugins = { beforeRender: [], afterRender: [], beforeSave: [], afterSave: [] }; // Утилиты BookBuilder.utils = { debounce(fn, delay) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, args), delay); }; }, generateId() { return Date.now() + '_' + Math.random().toString(36).substr(2, 9); }, saveToLocalStorage(key, data) { try { localStorage.setItem(`bookbuilder_${key}`, JSON.stringify(data)); return true; } catch(e) { return false; } }, loadFromLocalStorage(key) { try { return JSON.parse(localStorage.getItem(`bookbuilder_${key}`)); } catch(e) { return null; } }, getTextPreview(htmlText, maxLength = 20) { let text = htmlText ? htmlText.replace(/<[^>]*>/g, '').replace(/ /g, ' ') : ''; text = text.trim(); if (text.length > maxLength) text = text.substring(0, maxLength) + '…'; return text || 'Пустая страница'; } }; // Функция регистрации нового шаблона BookBuilder.registerTemplate = function(key, template) { if (!key || !template) { console.error('BookBuilder: неверные параметры шаблона', key, template); return false; } // Проверяем обязательные поля if (!template.type || !template.name || !template.render) { console.error(`BookBuilder: шаблон "${key}" должен иметь type, name и render функцию`); return false; } // Добавляем метаданные template.key = key; template.registeredAt = Date.now(); // Сохраняем шаблон BookBuilder.templates[key] = template; // Добавляем в категорию if (template.category && BookBuilder.categories[template.category]) { BookBuilder.categories[template.category].keys.push(key); } if (BookBuilder.config.debug) { console.log(`✅ Шаблон "${key}" зарегистрирован`, template); } return true; }; // Функция создания страницы из шаблона BookBuilder.createPageFromTemplate = function(templateKey) { const template = BookBuilder.templates[templateKey]; if (!template) { console.error(`Шаблон "${templateKey}" не найден`); return null; } // Если у шаблона есть фабричная функция — используем её if (template.factory) { return template.factory(); } // Иначе создаём базовую структуру return { id: BookBuilder.utils.generateId(), type: template.type, templateKey: templateKey, customNumber: null, transform: { scale: 1, translateX: 0, translateY: 0, rotate: 0, scaleX: 1, scaleY: 1 }, ...template.defaultData }; }; // Загрузка избранных шаблонов BookBuilder.favorites = (function() { let favorites = {}; return { load() { const saved = BookBuilder.utils.loadFromLocalStorage('favorites'); if (saved) favorites = saved; return favorites; }, save() { BookBuilder.utils.saveToLocalStorage('favorites', favorites); }, isFavorite(key) { return !!favorites[key]; }, toggle(key) { if (favorites[key]) { delete favorites[key]; } else { favorites[key] = true; } this.save(); return this.isFavorite(key); }, getAll() { return { ...favorites }; } }; })(); // Система событий (для расширений) BookBuilder.events = { listeners: {}, on(event, callback) { if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(callback); }, off(event, callback) { if (!this.listeners[event]) return; this.listeners[event] = this.listeners[event].filter(cb => cb !== callback); }, emit(event, data) { if (!this.listeners[event]) return; this.listeners[event].forEach(callback => { try { callback(data); } catch(e) { console.error(`Ошибка в обработчике события "${event}":`, e); } }); } }; console.log('✅ Ядро конструктора загружено');
// ========== БАЗОВЫЕ ШАБЛОНЫ ========== // Здесь регистрируются стандартные шаблоны: фото+текст, только фото и т.д. // ----- Вспомогательные функции для базовых шаблонов ----- function getDefaultTextForTemplate(type, variant) { if (type === 'photo-text') { if (variant === 'normal') { return `Новая страница. Напишите здесь свой текст.

Кликните на область слева, чтобы добавить фото.

ВАША ИСТОРИЯ
`; } else { return `Новая страница. Напишите здесь свой текст.

Кликните на область справа, чтобы добавить фото.

ВАША ИСТОРИЯ
`; } } return 'Ваш текст здесь...'; } // ----- Шаблон №5: Фото слева, текст справа ----- BookBuilder.registerTemplate('photo-text-normal', { type: 'photo-text', name: 'шаблон №5', desc: 'Фото слева, текст справа', category: 'photoText', defaultData: { text: getDefaultTextForTemplate('photo-text', 'normal'), photo: null, template: 'normal' }, // Функция рендеринга render(page, pageNumber, container) { const hasPhoto = page.photo && page.photo !== ''; const transform = page.transform; container.innerHTML = `
${hasPhoto ? `` : `
Добавить фото
` }
${page.text}
${pageNumber}
`; return { hasPhoto: true, textEditable: true }; } }); // ----- Шаблон №6: Фото справа, текст слева ----- BookBuilder.registerTemplate('photo-text-mirror', { type: 'photo-text', name: 'шаблон №6', desc: 'Фото справа, текст слева', category: 'photoText', defaultData: { text: getDefaultTextForTemplate('photo-text', 'mirror'), photo: null, template: 'mirror' }, render(page, pageNumber, container) { const hasPhoto = page.photo && page.photo !== ''; const transform = page.transform; container.innerHTML = `
${hasPhoto ? `` : `
Добавить фото
` }
${page.text}
${pageNumber}
`; return { hasPhoto: true, textEditable: true }; } }); // ----- Шаблоны "Только фото" (№7-11) ----- const photoOnlyVariants = [ { key: 'photo-only-1', name: 'шаблон №7', desc: 'Фото во всю ширину, текст снизу', photoClass: 'photo-only-1', captionClass: 'caption-only-1', captionPosition: 'bottom', radius: '0 0 32px 32px' }, { key: 'photo-only-2', name: 'шаблон №8', desc: 'Фото до низа, текст справа вверху', photoClass: 'photo-only-2', captionClass: 'caption-only-2', captionPosition: 'top-right', radius: '32px 32px 0 0' }, { key: 'photo-only-3', name: 'шаблон №9', desc: 'Фото до низа, текст слева вверху', photoClass: 'photo-only-3', captionClass: 'caption-only-3', captionPosition: 'top-left', radius: '32px 32px 0 0' }, { key: 'photo-only-4', name: 'шаблон №10', desc: 'Фото в край справа и снизу, текст слева вверху', photoClass: 'photo-only-4', captionClass: 'caption-only-4', captionPosition: 'top-left', radius: '32px 0 0 0' }, { key: 'photo-only-4-mirror', name: 'шаблон №11', desc: 'Фото в край слева и снизу, текст справа вверху', photoClass: 'photo-only-4-mirror', captionClass: 'caption-only-4-mirror', captionPosition: 'top-right', radius: '0 32px 0 0' } ]; photoOnlyVariants.forEach(variant => { BookBuilder.registerTemplate(variant.key, { type: 'photo-only', name: variant.name, desc: variant.desc, category: 'photoOnly', defaultData: { text: 'Ваш текст здесь...', photo: null, photoVariant: variant.key }, render(page, pageNumber, container) { const hasPhoto = page.photo && page.photo !== ''; const transform = page.transform; container.innerHTML = `
${hasPhoto ? `` : `
Добавить фото
` }
${page.text}
${pageNumber}
`; return { hasPhoto: true, textEditable: true }; } }); }); // ----- Пустой шаблон ----- BookBuilder.registerTemplate('special-empty', { type: 'special-empty', name: 'шаблон №1', desc: 'Пустая страница', category: 'special', defaultData: {}, render(page, pageNumber, container) { container.innerHTML = `
${pageNumber}
`; return { hasPhoto: false, textEditable: false }; } }); console.log('✅ Базовые шаблоны зарегистрированы');
// ========== СПЕЦИАЛЬНЫЕ ШАБЛОНЫ ========== // История, Герой, Крит, Благодарность // ----- Шаблон №2: История семьи ----- BookBuilder.registerTemplate('special-history', { type: 'special-history', name: 'шаблон №2', desc: 'Страница с историей семьи', category: 'special', defaultData: { topText: 'Ваш текст здесь', subtitleText: 'история', nameText: 'Ивановичи', bottomText: 'Ваш текст здесь...' }, render(page, pageNumber, container) { container.innerHTML = `
${page.topText || 'Ваш текст здесь'}
${page.subtitleText || 'история'}
${(page.nameText || 'Ивановичи').charAt(0)} ${(page.nameText || 'Ивановичи').slice(1)}
${page.bottomText || 'Ваш текст здесь...'}
${pageNumber}
`; // Настраиваем обработчики для имени const nameDiv = container.querySelector('.name-wrapper'); if (nameDiv) { nameDiv.addEventListener('blur', function() { let t = this.innerText.trim(); if (t.length) { const parts = t.split(' '); if (parts.length >= 2) { const first = parts[0]; const second = parts.slice(1).join(' '); this.innerHTML = `${first.charAt(0)}${first.slice(1)} ${second.charAt(0)}${second.slice(1)}`; } else { this.innerHTML = `${t.charAt(0)}${t.slice(1)}`; } } }); } return { hasPhoto: false, textEditable: true }; } }); // ----- Шаблон №3: Герой ----- BookBuilder.registerTemplate('special-hero', { type: 'special-hero', name: 'шаблон №3', desc: 'Страница с портретом героя', category: 'special', defaultData: { photo: null, nameText: 'Иван Иванович', subtitleText: 'русский', bottomText: 'Ваш текст здесь...', transform: { scale: 1, translateX: 0, translateY: 0, rotate: 0, scaleX: 1, scaleY: 1 } }, render(page, pageNumber, container) { const hasPhoto = page.photo && page.photo !== ''; const transform = page.transform; const nameText = page.nameText || 'Иван Иванович'; const subtitleText = page.subtitleText || 'русский'; const bottomText = page.bottomText || 'Ваш текст здесь...'; let nameHtml = ''; const nameParts = nameText.split(' '); for (let i = 0; i < nameParts.length; i++) { const part = nameParts[i]; if (part.length > 0) { nameHtml += `${part.charAt(0)}${part.slice(1)}`; if (i < nameParts.length - 1) nameHtml += ' '; } } container.innerHTML = `
${hasPhoto ? `` : `
Добавить фото
` }
${nameHtml}
${subtitleText}
${bottomText}
${pageNumber}
`; return { hasPhoto: true, textEditable: true }; } }); // ----- Шаблон №4: Крит (город/страна) ----- const KRIT_DEFAULT_TEXT = `
*Справка:
Остров Крит*, крупнейший из греческих островов...`; BookBuilder.registerTemplate('special-krit', { type: 'special-krit', name: 'шаблон №4', desc: 'Крит — город страна', category: 'special', defaultData: { photo: null, storytellerPhoto: null, kritWord: 'КРИТ', greeceText: 'ГРЕЦИЯ', text: KRIT_DEFAULT_TEXT, kritColor: 'gold', transform: { scale: 1, translateX: 0, translateY: 0, rotate: 0, scaleX: 1, scaleY: 1 } }, render(page, pageNumber, container) { const hasPhoto = page.photo && page.photo !== ''; const transform = page.transform; const kritColor = page.kritColor || 'gold'; container.innerHTML = `
${hasPhoto ? `` : `
Добавить фото
` }
${page.kritWord || 'КРИТ'}
${page.greeceText || 'ГРЕЦИЯ'}
${page.text || KRIT_DEFAULT_TEXT}
${pageNumber}
`; return { hasPhoto: true, textEditable: true }; } }); // ----- Шаблон №12: Благодарность ----- BookBuilder.registerTemplate('special-thanks', { type: 'special-thanks', name: 'шаблон №12', desc: 'Страница благодарности', category: 'textOnly', defaultData: { capitalLetter: 'Б', subtitleText: 'лагодарность' }, render(page, pageNumber, container) { container.innerHTML = `
${page.capitalLetter || 'Б'}
${page.subtitleText || 'лагодарность'}
${pageNumber}
`; return { hasPhoto: false, textEditable: true }; } }); console.log('✅ Специальные шаблоны зарегистрированы');
// ========== СИСТЕМА РЕНДЕРИНГА ========== // Отвечает за отрисовку страниц и управление DOM BookBuilder.renderer = { // Кэш DOM-элементов elements: { leftSlot: null, rightSlot: null, editorArea: null, editorWrapper: null }, // Инициализация ссылок на DOM-элементы init() { this.elements.leftSlot = document.getElementById('leftSlot'); this.elements.rightSlot = document.getElementById('rightSlot'); this.elements.editorArea = document.getElementById('editorArea'); this.elements.editorWrapper = document.getElementById('editorWrapper'); }, // Основная функция рендеринга render() { const state = BookBuilder.state; const elements = this.elements; if (!state.pages.length) { if (elements.leftSlot) { elements.leftSlot.innerHTML = '
Добавить страницу
'; const emptyAdd = document.getElementById('emptyLeftAdd'); if (emptyAdd) emptyAdd.onclick = () => BookBuilder.actions.addEmptyPage(); } if (elements.rightSlot) elements.rightSlot.innerHTML = ''; return; } // Фиксим индекс текущей страницы if (state.currentPage >= state.pages.length) state.currentPage = state.pages.length - 1; if (state.currentPage < 0) state.currentPage = 0; // Рендерим левую страницу const leftPage = state.pages[state.currentPage]; const leftNumber = leftPage.customNumber || (state.currentPage + 1); this.renderSinglePage(leftPage, leftNumber, 'leftSlot'); // Рендерим правую страницу (если разворот) if (state.currentView === 'spread') { if (state.pages[state.currentPage + 1]) { const rightPage = state.pages[state.currentPage + 1]; const rightNumber = rightPage.customNumber || (state.currentPage + 2); this.renderSinglePage(rightPage, rightNumber, 'rightSlot'); } else { if (elements.rightSlot) { elements.rightSlot.innerHTML = '
Добавить страницу
'; const emptyAdd = document.getElementById('emptyRightAdd'); if (emptyAdd) emptyAdd.onclick = () => BookBuilder.actions.addEmptyPage(); } } } // Применяем зум this.applyZoom(); // Вызываем плагины после рендера BookBuilder.plugins.afterRender.forEach(plugin => plugin()); }, // Рендер одной страницы renderSinglePage(page, pageNumber, slotId) { const container = document.getElementById(slotId); if (!container) return; // Получаем шаблон для этой страницы const templateKey = page.templateKey || this.detectTemplateKey(page); const template = BookBuilder.templates[templateKey]; if (!template || !template.render) { console.error(`Не удалось найти шаблон для страницы:`, page); container.innerHTML = `
Ошибка: шаблон не найден
`; return; } // Вызываем рендер шаблона const renderResult = template.render(page, pageNumber, container); // Сохраняем результат рендера для последующей настройки container._renderResult = renderResult; // Настраиваем обработчики для текстовых полей this.setupTextHandlers(page, container); // Настраиваем обработчики для фото this.setupPhotoHandlers(page, container); }, // Определение шаблона по данным страницы (для обратной совместимости) detectTemplateKey(page) { if (page.type === 'photo-text') return page.template === 'mirror' ? 'photo-text-mirror' : 'photo-text-normal'; if (page.type === 'photo-only') return page.photoVariant || 'photo-only-1'; if (page.type === 'special-empty') return 'special-empty'; if (page.type === 'special-history') return 'special-history'; if (page.type === 'special-hero') return 'special-hero'; if (page.type === 'special-krit') return 'special-krit'; if (page.type === 'special-thanks') return 'special-thanks'; return 'special-empty'; }, // Настройка обработчиков для текстовых полей setupTextHandlers(page, container) { const textEditable = container.querySelector('.editable-text'); if (textEditable) { textEditable.oninput = (e) => { page.text = e.target.innerHTML; BookBuilder.history.push(); BookBuilder.thumbnails.render(); }; } const captions = container.querySelectorAll('[class^="caption-only"]'); captions.forEach(caption => { caption.oninput = (e) => { page.text = e.target.innerHTML; BookBuilder.history.push(); BookBuilder.thumbnails.render(); }; }); }, // Настройка обработчиков для фото setupPhotoHandlers(page, container) { const photoArea = container.querySelector('.photo-area, .photo-only-1, .photo-only-2, .photo-only-3, .photo-only-4, .photo-only-4-mirror, .left-bg-container, .image-container'); if (!photoArea) return; const imgElement = photoArea.querySelector('img'); const placeholder = photoArea.querySelector('.photo-placeholder'); if (imgElement && imgElement.id && imgElement.id.startsWith('img-')) { this.setupImageControls(page, photoArea, imgElement); } else if (placeholder) { placeholder.onclick = (e) => { e.stopPropagation(); this.openFilePicker(page, photoArea); }; } }, // Настройка контролов для изображения setupImageControls(page, photoArea, imgElement) { if (!imgElement) return; let transform = page.transform; const updateTransform = () => { imgElement.style.transform = `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.scale*transform.scaleX}, ${transform.scale*transform.scaleY}) rotate(${transform.rotate}deg)`; page.transform = { ...transform }; BookBuilder.history.push(); }; const controlsDiv = document.createElement('div'); controlsDiv.className = 'image-controls'; controlsDiv.innerHTML = ` `; photoArea.appendChild(controlsDiv); controlsDiv.querySelector('.replace-btn').onclick = (e) => { e.stopPropagation(); this.openFilePicker(page, photoArea); }; controlsDiv.querySelector('.zoom-in').onclick = (e) => { e.stopPropagation(); transform.scale = Math.min(transform.scale + 0.1, 3); updateTransform(); }; controlsDiv.querySelector('.zoom-out').onclick = (e) => { e.stopPropagation(); transform.scale = Math.max(transform.scale - 0.1, 0.3); updateTransform(); }; controlsDiv.querySelector('.reset').onclick = (e) => { e.stopPropagation(); transform.scale = 1; transform.translateX = 0; transform.translateY = 0; transform.rotate = 0; transform.scaleX = 1; transform.scaleY = 1; updateTransform(); }; controlsDiv.querySelector('.rotate').onclick = (e) => { e.stopPropagation(); transform.rotate = (transform.rotate + 90) % 360; updateTransform(); }; controlsDiv.querySelector('.mirror-hor').onclick = (e) => { e.stopPropagation(); transform.scaleX = transform.scaleX === 1 ? -1 : 1; updateTransform(); }; controlsDiv.querySelector('.mirror-ver').onclick = (e) => { e.stopPropagation(); transform.scaleY = transform.scaleY === 1 ? -1 : 1; updateTransform(); }; controlsDiv.querySelector('.move-up').onclick = (e) => { e.stopPropagation(); transform.translateY -= 10; updateTransform(); }; controlsDiv.querySelector('.move-down').onclick = (e) => { e.stopPropagation(); transform.translateY += 10; updateTransform(); }; controlsDiv.querySelector('.move-left').onclick = (e) => { e.stopPropagation(); transform.translateX -= 10; updateTransform(); }; controlsDiv.querySelector('.move-right').onclick = (e) => { e.stopPropagation(); transform.translateX += 10; updateTransform(); }; }, // Открытие файлового пикера openFilePicker(page, photoArea) { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (ev) => { page.photo = ev.target.result; BookBuilder.renderer.render(); BookBuilder.history.push(); BookBuilder.thumbnails.render(); }; reader.readAsDataURL(file); } }; input.click(); }, // Применение зума applyZoom() { const state = BookBuilder.state; const elements = this.elements; if (elements.editorArea) { elements.editorArea.style.transform = `scale(${state.currentZoom})`; elements.editorArea.style.transformOrigin = 'center center'; } if (elements.editorWrapper) { elements.editorWrapper.style.display = 'flex'; elements.editorWrapper.style.justifyContent = 'center'; elements.editorWrapper.style.alignItems = 'center'; } const zoomValue = document.getElementById('zoomValueFixed'); if (zoomValue) { zoomValue.innerText = Math.round(state.currentZoom * 100) + '%'; } } }; // Система миниатюр BookBuilder.thumbnails = { render() { const state = BookBuilder.state; const container = document.getElementById('thumbnailsContainer'); const pagesCountElem = document.getElementById('pagesCount'); if (pagesCountElem) pagesCountElem.innerText = state.pages.length; if (!container) return; if (state.pages.length === 0) { container.innerHTML = '
Нет страниц
Нажмите +
'; return; } if (state.currentView === 'single') { container.innerHTML = state.pages.map((p, idx) => { let content = ''; if (p.photo && p.photo !== '') { content = `
`; } else { let previewText = ''; if (p.type === 'empty') previewText = 'Пустая'; else if (p.type === 'special-empty') previewText = 'Пустой'; else if (p.type === 'special-thanks') previewText = 'Благодарность'; else if (p.type === 'special-history') previewText = 'История'; else if (p.type === 'special-hero') previewText = 'Герой'; else if (p.type === 'special-krit') previewText = 'Крит'; else if (p.type === 'photo-only') previewText = 'Фото'; else previewText = BookBuilder.utils.getTextPreview(p.text); content = `
${previewText}
`; } return `
${content}
${p.customNumber || (idx + 1)}
`; }).join(''); } else { const spreads = []; for (let i = 0; i < state.pages.length; i += 2) { spreads.push({ left: state.pages[i], right: state.pages[i+1], startIdx: i }); } container.innerHTML = spreads.map((spread, spreadIdx) => { const isActive = state.currentPage === spread.startIdx || state.currentPage === spread.startIdx + 1; const getPreview = (p) => { if (!p) return `
Пусто
`; if (p.photo && p.photo !== '') return `
`; let text = ''; if (p.type === 'empty') text = 'Пустая'; else if (p.type === 'special-empty') text = 'Пустой'; else if (p.type === 'special-thanks') text = 'Благодарность'; else if (p.type === 'special-history') text = 'История'; else if (p.type === 'special-hero') text = 'Герой'; else if (p.type === 'special-krit') text = 'Крит'; else if (p.type === 'photo-only') text = 'Фото'; else text = BookBuilder.utils.getTextPreview(p.text); return `
${text}
`; }; return `
${getPreview(spread.left)}
${spread.left?.customNumber || (spread.startIdx + 1)}
${getPreview(spread.right)}
${spread.right?.customNumber || (spread.startIdx + 2)}
`; }).join(''); } this.setupDragAndDrop(); }, setupDragAndDrop() { // Настройка перетаскивания (аналогично оригиналу) // Здесь можно оставить код из оригинального файла } }; console.log('✅ Система рендеринга загружена');
// ========== ОБРАБОТЧИКИ СОБЫТИЙ И ЭКСПОРТ ========== BookBuilder.actions = { // Добавление пустой страницы addEmptyPage() { const newPage = { id: BookBuilder.utils.generateId(), text: '', photo: null, customNumber: null, transform: { scale: 1, translateX: 0, translateY: 0, rotate: 0, scaleX: 1, scaleY: 1 }, template: 'normal', type: 'empty', templateKey: 'special-empty' }; BookBuilder.state.pages.push(newPage); BookBuilder.state.currentPage = BookBuilder.state.pages.length - 1; BookBuilder.history.push(); BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); }, // Добавление страницы из шаблона addPageFromTemplate(templateKey) { const newPage = BookBuilder.createPageFromTemplate(templateKey); if (!newPage) return false; BookBuilder.state.pages.push(newPage); BookBuilder.state.currentPage = BookBuilder.state.pages.length - 1; BookBuilder.history.push(); BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); return true; }, // Замена текущей страницы шаблоном replaceCurrentPageWithTemplate(templateKey) { if (!BookBuilder.state.pages.length) return this.addPageFromTemplate(templateKey); const newPage = BookBuilder.createPageFromTemplate(templateKey); if (!newPage) return false; BookBuilder.state.pages[BookBuilder.state.currentPage] = newPage; BookBuilder.history.push(); BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); return true; }, // Удаление страницы deletePage(idx) { if (idx < 0 || idx >= BookBuilder.state.pages.length) return; BookBuilder.state.pages.splice(idx, 1); if (BookBuilder.state.pages.length === 0) { BookBuilder.state.currentPage = 0; } else { BookBuilder.state.currentPage = Math.min(idx, BookBuilder.state.pages.length - 1); } BookBuilder.history.push(); BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); }, // Дублирование страницы duplicatePage() { if (!BookBuilder.state.pages.length) return; const original = BookBuilder.state.pages[BookBuilder.state.currentPage]; const newPage = { ...original, id: BookBuilder.utils.generateId(), customNumber: null, transform: { ...original.transform } }; BookBuilder.state.pages.splice(BookBuilder.state.currentPage + 1, 0, newPage); BookBuilder.state.currentPage++; BookBuilder.history.push(); BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); }, // Переключение вида (одиночный/разворот) switchView(view) { BookBuilder.state.currentView = view; const editorArea = document.getElementById('editorArea'); if (editorArea) { editorArea.className = `editor-area ${view === 'single' ? 'single-mode' : 'spread-mode'}`; } BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); document.querySelectorAll('.view-btn-thumb').forEach(b => b.classList.remove('active')); const activeBtn = document.querySelector(`.view-btn-thumb[data-view="${view}"]`); if (activeBtn) activeBtn.classList.add('active'); }, // Изменение зума setZoom(z) { BookBuilder.state.currentZoom = Math.min(Math.max(0.5, z), 2.8); BookBuilder.renderer.applyZoom(); }, // Сохранение проекта saveProject() { BookBuilder.utils.saveToLocalStorage('project', { pages: BookBuilder.state.pages, name: document.getElementById('logoText')?.innerText || 'Моя книга' }); alert('Проект сохранён!'); }, // Экспорт в PDF (упрощённая версия) async exportToPDF(printQuality, isSpread = false) { if (!BookBuilder.state.pages.length) { alert('Нет страниц'); return; } if (typeof window.jspdf === 'undefined') { alert('Библиотека PDF загружается. Подождите 2 секунды и попробуйте снова.'); return; } // Здесь код экспорта из оригинального файла alert('Функция экспорта требует настройки'); } }; // История изменений BookBuilder.history = { push() { const state = BookBuilder.state; state.history = state.history.slice(0, state.historyPos + 1); state.history.push(JSON.parse(JSON.stringify(state.pages))); state.historyPos++; if (state.history.length > BookBuilder.config.maxHistory) state.history.shift(); }, undo() { const state = BookBuilder.state; if (state.historyPos > 0) { state.historyPos--; state.pages = JSON.parse(JSON.stringify(state.history[state.historyPos])); if (state.currentPage >= state.pages.length) state.currentPage = state.pages.length - 1; if (state.currentPage < 0 && state.pages.length > 0) state.currentPage = 0; BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); } }, redo() { const state = BookBuilder.state; if (state.historyPos < state.history.length - 1) { state.historyPos++; state.pages = JSON.parse(JSON.stringify(state.history[state.historyPos])); if (state.currentPage >= state.pages.length) state.currentPage = state.pages.length - 1; if (state.currentPage < 0 && state.pages.length > 0) state.currentPage = 0; BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); } } }; console.log('✅ Обработчики загружены');
// ========== ИНИЦИАЛИЗАЦИЯ ========== // Запуск конструктора после загрузки страницы document.addEventListener('DOMContentLoaded', function() { // Инициализируем рендерер BookBuilder.renderer.init(); // Загружаем сохранённый проект (если есть) const savedProject = BookBuilder.utils.loadFromLocalStorage('project'); if (savedProject && savedProject.pages && savedProject.pages.length) { BookBuilder.state.pages = savedProject.pages; BookBuilder.state.currentPage = 0; BookBuilder.history.push(); } // Рендерим начальное состояние BookBuilder.renderer.render(); BookBuilder.thumbnails.render(); // Настраиваем обработчики событий для кнопок document.getElementById('zoomInFixed')?.addEventListener('click', () => BookBuilder.actions.setZoom(BookBuilder.state.currentZoom + 0.1)); document.getElementById('zoomOutFixed')?.addEventListener('click', () => BookBuilder.actions.setZoom(BookBuilder.state.currentZoom - 0.1)); document.getElementById('zoomResetFixed')?.addEventListener('click', () => BookBuilder.actions.setZoom(1)); document.getElementById('undoBtn')?.addEventListener('click', () => BookBuilder.history.undo()); document.getElementById('redoBtn')?.addEventListener('click', () => BookBuilder.history.redo()); document.getElementById('saveBtn')?.addEventListener('click', () => BookBuilder.actions.saveProject()); document.getElementById('addPageBtn')?.addEventListener('click', () => BookBuilder.actions.addEmptyPage()); document.getElementById('duplicatePageBtn')?.addEventListener('click', () => BookBuilder.actions.duplicatePage()); document.getElementById('trashBtn')?.addEventListener('click', () => BookBuilder.actions.deletePage(BookBuilder.state.currentPage)); // Настройка переключения вида document.querySelectorAll('.view-btn-thumb').forEach(btn => { btn.addEventListener('click', () => BookBuilder.actions.switchView(btn.dataset.view)); }); // Инициализация панели шаблонов initTemplatesPanel(); console.log('✅ Конструктор запущен'); }); // Функция инициализации панели шаблонов function initTemplatesPanel() { const container = document.getElementById('templatesContainer'); if (!container) return; // Очищаем контейнер container.innerHTML = ''; // Для каждой категории создаём секцию for (const [catKey, catData] of Object.entries(BookBuilder.categories)) { if (catData.keys.length === 0) continue; const categoryDiv = document.createElement('div'); categoryDiv.className = 'template-category'; categoryDiv.innerHTML = `
${catData.title}
`; const grid = document.createElement('div'); grid.className = 'template-grid'; for (const templateKey of catData.keys) { const template = BookBuilder.templates[templateKey]; if (!template) continue; const isFavorite = BookBuilder.favorites.isFavorite(templateKey); const item = document.createElement('div'); item.className = 'template-item'; item.setAttribute('data-template-key', templateKey); item.setAttribute('draggable', 'true'); item.innerHTML = `
${template.name}
`; // Звёздочка избранного const starWrapper = document.createElement('div'); starWrapper.className = 'favorite-star'; starWrapper.innerHTML = isFavorite ? `` : ``; starWrapper.onclick = (e) => { e.stopPropagation(); const newState = BookBuilder.favorites.toggle(templateKey); updateStarStyle(starWrapper, newState); }; item.appendChild(starWrapper); // Drag & drop item.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/plain', templateKey); e.dataTransfer.effectAllowed = 'copy'; item.style.opacity = '0.5'; }); item.addEventListener('dragend', () => { item.style.opacity = ''; }); // Клик для замены текущей страницы item.addEventListener('click', () => { BookBuilder.actions.replaceCurrentPageWithTemplate(templateKey); }); grid.appendChild(item); } categoryDiv.appendChild(grid); container.appendChild(categoryDiv); container.appendChild(document.createElement('div')).className = 'template-divider'; } } function updateStarStyle(starElement, isFavorite) { if (isFavorite) { starElement.innerHTML = ``; } else { starElement.innerHTML = ``; } } console.log('✅ Инициализация завершена');
Made on
Tilda