Spaces:
Sleeping
Sleeping
Added customer block and began quest block, updated width of block-container from 350px to 350px
f6ba86d
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <link href="./dependencies/all.css" rel="stylesheet" /> | |
| <link href="./dependencies/css.css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> | |
| <link href='./dependencies/bundle.css' rel='stylesheet' /> | |
| <link href="./dependencies/style.css" rel='stylesheet' /> | |
| <link href="./dependencies/5ePHBstyle.css" rel='stylesheet' /> | |
| <title>DnD Stat Block</title> | |
| <link rel="stylesheet" href="styles.css"> | |
| <script src="https://unpkg.com/[email protected]/dist/htmx.min.js"></script> | |
| </head> | |
| <style> | |
| :root { | |
| --HB_Color_Background: #EEE5CE; | |
| --HB_Color_Accent: #E0E5C1; | |
| --HB_Color_HeaderUnderline: #C0AD6A; | |
| --HB_Color_HorizontalRule: #9C2B1B; | |
| --HB_Color_HeaderText: #58180D; | |
| --HB_Color_MonsterStatBackground: #F2E5B5; | |
| --HB_Color_CaptionText: #766649; | |
| --HB_Color_WatercolorStain: #BBAD82; | |
| --HB_Color_Footnotes: #C9AD6A; | |
| } | |
| input[type="text"], textarea { | |
| width: auto; | |
| padding: 8px; | |
| margin: 5px 0; | |
| border: 1px solid #ccc; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| background-color: #f9f9f9; | |
| transition: background-color 0.3s ease, border-color 0.3s ease; | |
| } | |
| .grid-container { | |
| display: grid; | |
| grid-template-columns: 1fr 3fr; /* Two columns with the second column being three times as wide */ | |
| grid-gap: 20px; | |
| padding: 20px; | |
| height: 100vh; | |
| } | |
| .block-container { | |
| position: fixed; /* Lock the block-container in place */ | |
| top: 20px; /* Distance from the top of the viewport */ | |
| left: 20px; /* Distance from the left of the viewport */ | |
| width: 450px; /* Set the width of the block-container */ | |
| height: calc(100vh - 40px); /* Full viewport height minus top and bottom padding */ | |
| overflow-y: auto; /* Enable vertical scrolling if needed */ | |
| border-right: 1px solid #ccc; /* Right border for visual separation */ | |
| padding-right: 20px; /* Padding inside the block-container */ | |
| box-sizing: border-box; /* Include padding and border in the element's total width and height */ | |
| background-color: #f9f9f9; /* Background color */ | |
| z-index: 1000; /* Ensure it is on top of other elements */ | |
| } | |
| .block-container .page { | |
| column-count: 1; | |
| padding: 0; | |
| width: 425px; | |
| height: auto; /* Allow the page to expand to fit content */ | |
| overflow: visible; /* Allow content to overflow if necessary */ | |
| page-break-before: auto; | |
| page-break-after: auto; | |
| } | |
| .page-container { | |
| margin-left: 450px; /* Offset the page content by the width of block-container plus margin */ | |
| width: 900px; | |
| padding: 20px; | |
| overflow: auto; /* Enable scrolling if needed */ | |
| height: 100vh; /* Full viewport height */ | |
| box-sizing: border-box; /* Include padding and border in the element's total width and height */ | |
| } | |
| .page { | |
| column-count: 2; | |
| column-gap: .9cm; | |
| column-width: 8cm; | |
| -webkit-column-count: 2; | |
| -moz-column-count: 2; | |
| -webkit-column-width: 8cm; | |
| -moz-column-width: 8cm; | |
| -webkit-column-gap: .9cm; | |
| -moz-column-gap: .9cm; | |
| position: relative; | |
| z-index: 15; | |
| box-sizing: border-box; | |
| width: 215.9mm; | |
| height: 279.4mm; /* Original height for print layout */ | |
| padding: 1.4cm 1.9cm 1.7cm; | |
| overflow: hidden; | |
| font-family: "BookInsanityRemake"; | |
| font-size: .34cm; | |
| counter-increment: phb-page-numbers; | |
| background-color: var(--HB_Color_Background); | |
| background-image: url('./themes/assets/parchmentBackground.jpg'); | |
| text-rendering: optimizeLegibility; | |
| page-break-before: always; | |
| page-break-after: always; | |
| contain: size; | |
| } | |
| .page .monster hr:last-of-type + * { | |
| margin-top: .1cm; | |
| } | |
| .page * + h4 { | |
| margin-top: .1cm; | |
| } | |
| .page h4 + * { | |
| margin-top: .1cm; | |
| } | |
| .page dl + * { | |
| margin-top: .1cm; | |
| } | |
| .page p + * { | |
| margin-top: .1cm; | |
| } | |
| .page img { | |
| width: 100%; | |
| height: auto; | |
| cursor: pointer; | |
| } | |
| .page .classTable.frame{ | |
| margin-right:0.1cm; | |
| margin-left: 0.1cm; | |
| } | |
| /* Ensure the h1 tag is constrained within its column */ | |
| .block-content h1 { | |
| column-span: none; | |
| box-sizing: border-box; /* Include padding and border in the element's total width and height */ | |
| margin: 0 auto; /* Center the h1 within the column */ | |
| overflow: hidden; /* Handle overflow content */ | |
| word-wrap: break-word; /* Break long words to prevent overflow */ | |
| } | |
| .columnWrapper { | |
| column-gap: inherit; | |
| max-height: 100%; | |
| column-span: all; | |
| columns: inherit; | |
| height: 100%; /* Ensure it takes full height of the parent */ | |
| box-sizing: border-box; /* Ensures padding and border are included in the element's total width and height */ | |
| } | |
| /* block-item styling */ | |
| .block-item { | |
| border: 1px solid #ccc; | |
| border-radius: 8px; | |
| background-color: transparent; | |
| padding: 15px; | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| transition: transform 0.3s; | |
| } | |
| .block-item:hover { | |
| /* transform: translateY(-5px); | |
| background-color: rgba(255, 255, 255, 0.5); /* Slightly visible background on hover */ | |
| } | |
| .block-item img { | |
| width: 100%; | |
| height: auto; | |
| cursor: pointer; | |
| } | |
| /* Modal styling */ | |
| .modal { | |
| display: none; /* Hidden by default */ | |
| position: fixed; /* Stay in place */ | |
| z-index: 1001; /* Sit on top */ | |
| left: 0; | |
| top: 0; | |
| width: 100%; /* Full width */ | |
| height: 100%; /* Full height */ | |
| overflow: auto; /* Enable scroll if needed */ | |
| background-color: rgb(0,0,0); /* Fallback color */ | |
| background-color: rgba(0,0,0,0.9); /* Black w/ opacity */ | |
| } | |
| .modal-content { | |
| margin: auto; | |
| display: block; | |
| width: 80%; | |
| max-width: 700px; | |
| } | |
| .modal-content, #caption { | |
| animation-name: zoom; | |
| animation-duration: 0.6s; | |
| } | |
| @keyframes zoom { | |
| from {transform: scale(0)} | |
| to {transform: scale(1)} | |
| } | |
| .close { | |
| position: absolute; | |
| top: 15px; | |
| right: 35px; | |
| color: #f1f1f1; | |
| font-size: 40px; | |
| font-weight: bold; | |
| transition: 0.3s; | |
| } | |
| .close:hover, | |
| .close:focus { | |
| color: #bbb; | |
| text-decoration: none; | |
| cursor: pointer; | |
| } | |
| input[type="text"]:focus, textarea:focus { | |
| background-color: #e9e9e9; | |
| border-color: #aaa; | |
| outline: none; | |
| } | |
| /* Specific styles for different textboxes */ | |
| .user-description-textarea { | |
| width: 400px; | |
| height: 40px; /* Adjust as needed for 3 lines */ | |
| resize: vertical; | |
| background: none; | |
| font-family: "ScalySansRemake"; | |
| font-weight: 800; | |
| } | |
| /* Focus styles for description textbox */ | |
| .user-description-textarea:focus { | |
| background-color: #e9e9e9; | |
| border-color: #aaa; | |
| outline: none; | |
| } | |
| .heading-textarea { | |
| width: 100%; | |
| font-size: .458cm; /* Matches the font size of an h4 heading */ | |
| line-height: .7em; | |
| font-weight: 800; | |
| border: none; | |
| background: none; | |
| margin: 0; | |
| padding: 0; | |
| resize: none; /* Prevents the textarea from being resizable */ | |
| overflow: hidden; /* Prevents scrollbars */ | |
| outline: none; /* Removes the focus outline */ | |
| font-family: "MrEavesRemake"; /* Ensures font family is inherited */ | |
| color: var(--HB_Color_HeaderText) | |
| } | |
| .title-textarea{ | |
| height:30px; | |
| margin-bottom: .19cm; | |
| column-span: all; | |
| font-size: .89cm; | |
| line-height: 1em; | |
| font-family: "MrEavesRemake"; | |
| font-weight: 800; | |
| color: var(--HB_Color_HeaderText); | |
| border: 0; | |
| font: inherit; | |
| background: none; | |
| padding: 0; | |
| resize: none; /* Prevents the textarea from being resizable */ | |
| overflow: hidden; /* Prevents scrollbars */ | |
| outline: none; /* Removes the focus outline */ | |
| } | |
| div[contenteditable="true"]:focus { | |
| background-color: #e9e9e9; | |
| border-color: #aaa; | |
| outline: none; | |
| } | |
| div[contenteditable="true"] p::first-letter { | |
| float: left; | |
| padding-bottom: 2px; | |
| padding-left: 40px; | |
| margin-top: 0cm; | |
| margin-bottom: -20px; | |
| margin-left: -40px; | |
| font-family: "SolberaImitationRemake"; | |
| font-size: 3.5cm; | |
| line-height: 1em; | |
| color: rgba(0, 0, 0, 0); | |
| background-image: linear-gradient(-45deg, #322814, #998250, #322814); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| border: 0; | |
| } | |
| .properties-textarea { | |
| width: 100%; | |
| font-size: 12px; | |
| font-weight: 400; | |
| line-height: .7em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being resizable */ | |
| } | |
| .description-textarea { | |
| width: 100%; | |
| height: auto; | |
| font-size: .318cm; | |
| font-weight: 400; | |
| line-height: .9em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansSmallCapsRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| } | |
| .red-integer-stat-textarea { | |
| width: 20px; | |
| height:13px; | |
| font-size: .318cm; | |
| font-weight: 400; | |
| line-height: 1.2em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansSmallCapsRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| color: var(--HB_Color_HeaderText); | |
| white-space: pre-line; | |
| } | |
| .integer-stat-textarea { | |
| width: 20px; | |
| height:13px; | |
| font-size: .318cm; | |
| font-weight: 400; | |
| line-height: 1.2em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| white-space: pre-line; | |
| } | |
| .string-stat-textarea { | |
| width: 200px; | |
| height:13px; | |
| font-size: .318cm; | |
| font-weight: 400; | |
| line-height: 1.2em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| white-space: pre-wrap; | |
| } | |
| .string-action-name-textarea { | |
| width: 100%; | |
| height:13px; | |
| font-size: .318cm; | |
| font-style: italic; | |
| font-weight: bold; | |
| line-height: 1.2em; | |
| margin-bottom: 0; | |
| font-style: italic; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| } | |
| .string-action-description-textarea { | |
| width: 100%; | |
| height:16px; | |
| font-size: 14px; | |
| font-weight: 400; | |
| line-height: 16px; | |
| margin-bottom: 0; | |
| box-sizing: border-box; | |
| border: 0; | |
| font-family: "ScalySansRemake"; | |
| vertical-align: baseline; | |
| margin: 0; | |
| padding: 0; | |
| overflow-wrap: break-word; | |
| text-rendering: optimizeLegibility; | |
| background: none; | |
| resize: none; /* Prevents the textarea from being manually resizable */ | |
| overflow: hidden; /* Hide scrollbars */ | |
| } | |
| .block.monster.frame.wide { | |
| column-count: inherit; | |
| min-height: 100px; /* Set an appropriate minimum height */ | |
| height: 859px; /* Allow height to expand automatically */ | |
| column-fill: auto; | |
| overflow: hidden; /* Ensure content overflow is visible */ | |
| width: 100%; /* Ensure it takes the full width of the container */ | |
| } | |
| .highlight-page { | |
| outline: 2px dashed #2196F3; /* Blue dashed border */ | |
| background-color: rgba(33, 150, 243, 0.1); /* Light blue background */ | |
| } | |
| .highlight-block { | |
| border-bottom: 2px solid #2196F3; /* Blue solid border */ | |
| background-color: rgba(33, 150, 243, 0.1); /* Light blue background */ | |
| } | |
| .highlight-block-top { | |
| border-top: 2px solid #2196F3; /* Blue solid border at the top */ | |
| background-color: rgba(33, 150, 243, 0.1); /* Light blue background */ | |
| } | |
| .name-textbox { | |
| width: 50px; | |
| font-size: 1.5em; | |
| padding: 10px; | |
| } | |
| .stat-textbox { | |
| width: 50px; | |
| text-align: center; | |
| font-size: 1em; | |
| padding: 5px; | |
| } | |
| .trash-area { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| width: 100px; | |
| height: 100px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| background-image: url('./closed-mimic-trashcan.png'); /* Adjust the path to your image file */ | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .trash-area:hover { | |
| background-image: url('./mimic_trashcan.png'); | |
| } | |
| .trash-area.over { | |
| color: white; | |
| background-image: url('./mimic_trashcan.png'); /* Example image change */ | |
| } | |
| </style> | |
| <body> | |
| <div class="grid-container"> | |
| <div class="block-container" id="blockContainer" > | |
| <div class="page" id = "block-page" data-page-id="block-container"> | |
| <!-- Blocks will be wrapped in a page div and loaded here --> | |
| </div> | |
| </div> | |
| <div class="page-container" id="pageContainer"> | |
| <div class="brewRenderer"> | |
| <h1>Describe your creature</h1> | |
| <textarea id="user-description" class="user-description-textarea" | |
| hx-post="/update-stats" hx-trigger="change" | |
| hx-target="#user-description" hx-swap="outerHTML" | |
| title="As much or as little description as you want to provide. You can provide specific employees, inventory etc">A very standard gear store run by a fae potted plant named Gorgeous</textarea> | |
| <button id="submitDescription">Submit</button> | |
| <button id="parseHTML">Parse HTML</button> | |
| <button id="resetButton">Reset</button> | |
| <div class="pages"> | |
| <div id="page-1" class="page" data-page-id="page-0"> | |
| <div class="columnWrapper"> | |
| <div class="block monster frame wide"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="trash-area" id="trashArea"></div> | |
| <!-- The Modal --> | |
| <div id="imageModal" class="modal"> | |
| <span class="close">×</span> | |
| <img class="modal-content" id="modalImage"> | |
| <div id="caption"></div> | |
| </div> | |
| <script> | |
| // Waits for DOM content to be fully loaded and assigns critical elements to variables. | |
| let initialPositions = []; | |
| document.addEventListener("DOMContentLoaded", function() { | |
| const blockContainer = document.getElementById('blockContainer'); | |
| let blockContainerPage = document.getElementById('block-page'); | |
| const pageContainer = document.getElementById('pageContainer'); | |
| const trashArea = document.getElementById('trashArea'); | |
| const resetButton = document.getElementById('resetButton'); | |
| let currentPage = pageContainer.querySelector('.block.monster.frame.wide'); | |
| const modal = document.getElementById('imageModal'); | |
| const modalImg = document.getElementById('modalImage'); | |
| const captionText = document.getElementById('caption'); | |
| const closeModal = document.getElementsByClassName('close')[0]; | |
| const MAX_COLUMN_HEIGHT = 847; | |
| if (!blockContainer || !pageContainer || !trashArea || !currentPage) { | |
| console.error('Required elements are null'); | |
| return; | |
| } | |
| if (!modal) { | |
| console.error('modal element not found'); | |
| return; | |
| } | |
| if (!modalImg) { | |
| console.error('modalImg element not found'); | |
| return; | |
| } | |
| if (!captionText) { | |
| console.error('captionText element not found'); | |
| return; | |
| } | |
| if (!closeModal) { | |
| console.error('closeModal element not found'); | |
| return; | |
| } | |
| // Event delegation for image clicks | |
| blockContainer.addEventListener('click', function(event) { | |
| console.log('Click detected in blockContainer:', event.target); | |
| if (event.target.tagName === 'IMG' && event.target.id.startsWith('generated-image-')) { | |
| console.log('Image clicked for modal display. Image ID:', event.target.id); | |
| modal.style.display = 'block'; | |
| modalImg.src = event.target.src; | |
| captionText.innerHTML = event.target.alt; | |
| } else { | |
| console.log('Clicked element is not an image or does not match ID pattern.'); | |
| } | |
| }); | |
| // Function to close the modal | |
| closeModal.onclick = function() { | |
| modal.style.display = "none"; | |
| } | |
| // Function to close the modal when clicking outside of the modal content | |
| window.onclick = function(event) { | |
| if (event.target == modal) { | |
| modal.style.display = "none"; | |
| } | |
| } | |
| document.getElementById('submitDescription').addEventListener('click', function() { | |
| const userInput = document.getElementById('user-description').value; | |
| // Clear the block container before inserting new blocks | |
| blockContainerPage.innerHTML = ''; | |
| fetch('http://127.0.0.1:5000/process-description', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ user_input: userInput }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log('Success:', data); | |
| initialPositions.length = 0; // Clear the initialPositions array | |
| insertHtmlBlocks(data.html_blocks); | |
| const blocks = blockContainerPage.querySelectorAll('.block-item'); | |
| blocks.forEach(block => { | |
| block.setAttribute('data-page-id', 'block-container'); | |
| block.setAttribute('draggable', true); | |
| block.addEventListener('dragstart', handleDragStart); | |
| block.addEventListener('dragend', handleDragEnd); | |
| }); | |
| storeInitialPositions(); | |
| }) | |
| .catch((error) => { | |
| console.error('Error:', error); | |
| }); | |
| }); | |
| // Store initial positions of the blocks | |
| function storeInitialPositions() { | |
| initialPositions = []; // Clear initialPositions before updating | |
| const blocks = blockContainer.querySelectorAll('.block-item'); | |
| blocks.forEach((block, index) => { | |
| const blockId = block.getAttribute('data-block-id'); | |
| if (!blockId) { | |
| console.error(`Block at index ${index} is missing data-block-id`); | |
| } | |
| initialPositions.push({ | |
| id: blockId, | |
| index: index | |
| }); | |
| }); | |
| console.log('Initial positions:', initialPositions); | |
| } | |
| function insertHtmlBlocks(blocks) { | |
| console.log('blockContainerPage = ', blockContainerPage) | |
| console.log('List of blocks:', blocks); | |
| const parser = new DOMParser(); | |
| blocks.forEach(blockHtml => { | |
| console.log('Original blockHtml:', blockHtml); | |
| // Parse the HTML string | |
| const doc = parser.parseFromString(blockHtml, 'text/html'); | |
| console.log('Parsed document:', doc); | |
| const block = doc.body.firstChild; | |
| console.log('Parsed block:', block); | |
| if (block) { | |
| blockContainerPage.appendChild(block); // Append the parsed block to the container | |
| console.log('Appended block:', block); | |
| } | |
| }); | |
| // console.log('Final state of blockContainer:', blockContainer.innerHTML); | |
| initializeTextareaResizing(); | |
| } | |
| storeInitialPositions(); | |
| function adjustTextareaHeight(el) { | |
| if (el.scrollHeight > 16){ | |
| el.style.height = 'auto'; | |
| el.style.height = (el.scrollHeight) + 'px'; | |
| } | |
| console.log('Original height:', el.style.height); | |
| } | |
| function initializeTextareaResizing() { | |
| const classes = [ | |
| 'description-textarea', | |
| 'user-description-textarea', | |
| 'heading-textarea', | |
| 'properties-textarea', | |
| 'string-stat-textarea', | |
| 'string-action-description-textarea', | |
| ]; | |
| classes.forEach(className => { | |
| const textareas = document.querySelectorAll(`.${className}`); | |
| console.log(`Textareas found for ${className}:`, textareas.length); // Debugging line | |
| textareas.forEach(textarea => { | |
| console.log('scrollHeight:', textarea.scrollHeight); | |
| console.log('clientHeight:', textarea.clientHeight); | |
| console.log('offsetHeight:', textarea.offsetHeight); | |
| console.log('Computed line-height:', window.getComputedStyle(textarea).lineHeight); | |
| // Adjust height on page load | |
| adjustTextareaHeight(textarea); | |
| // Adjust height on input | |
| textarea.addEventListener('input', function() { | |
| adjustTextareaHeight(textarea); | |
| console.log('Input event triggered for:', textarea.id); // Debugging line | |
| }); | |
| }); | |
| }); | |
| } | |
| // Initial run on page load | |
| initializeTextareaResizing(); | |
| async function extractBlocks() { | |
| try { | |
| if (blockContainerPage.children.length > 0) { | |
| console.log('Blocks already loaded, skipping fetch'); | |
| return; | |
| } | |
| const response = await fetch('The_Mirage_Emporium.html'); | |
| if (!response.ok) { | |
| throw new Error('Network response was not ok ' + response.statusText); | |
| } | |
| const text = await response.text(); | |
| const parser = new DOMParser(); | |
| const doc = parser.parseFromString(text, 'text/html'); | |
| const blocks = doc.querySelectorAll('[class^="Block_"]'); | |
| blocks.forEach((block, index) => { | |
| const blockContent = block.innerHTML; | |
| const blockItem = document.createElement('div'); | |
| blockItem.classList.add('block-item'); | |
| blockItem.innerHTML = blockContent; | |
| const blockId = `block-${index}`; | |
| blockItem.setAttribute('data-block-id', blockId); | |
| const pageId = 'block-container'; | |
| blockItem.setAttribute('data-page-id', pageId); | |
| blockItem.setAttribute('draggable', true); | |
| blockItem.addEventListener('dragstart', handleDragStart); | |
| blockItem.addEventListener('dragend', handleDragEnd); | |
| console.log(`Loaded block with ID: ${blockId}`); | |
| blockContainerPage.appendChild(blockItem); | |
| }); | |
| storeInitialPositions(); | |
| } catch (error) { | |
| console.error('Error fetching and parsing template.html:', error); | |
| } | |
| } | |
| blockContainer.addEventListener('click', function(event) { | |
| if (event.target && event.target.classList.contains('generate-image-button')) { | |
| const blockId = event.target.getAttribute('data-block-id'); | |
| generateImage(blockId); | |
| } | |
| }); | |
| // Function to generate image | |
| function generateImage(blockId) { | |
| const sdPromptElement = document.getElementById(`user-storefront-prompt-${blockId}`); | |
| const imageElement = document.getElementById(`generated-image-${blockId}`); | |
| if (!sdPromptElement) { | |
| console.error('Element with ID user-storefront-prompt not found'); | |
| return; | |
| } | |
| if (!imageElement) { | |
| console.error('Element with ID generated-image not found'); | |
| return; | |
| } | |
| const sdPrompt = sdPromptElement.value; | |
| fetch('http://127.0.0.1:5000/generate-image', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ sd_prompt: sdPrompt }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log('Received data:', data); | |
| imageElement.src = data.image_url; | |
| imageElement.style.display = 'block'; | |
| // Log the image element's HTML structure | |
| console.log('Updated imageElement HTML:', imageElement.outerHTML); | |
| }) | |
| .catch((error) => { | |
| console.error('Error:', error); | |
| }); | |
| } | |
| function handleDragStart(e) { | |
| const target = e.target.closest('.block-item, .block-content'); | |
| if (!target) { | |
| console.error('Drag started for an element without a valid target'); | |
| return; | |
| } | |
| const blockId = target.getAttribute('data-block-id'); | |
| const pageId = target.getAttribute('data-page-id'); | |
| if (!blockId) { | |
| console.error('Drag started for an element without a data-block-id'); | |
| return; | |
| } | |
| if (!pageId) { | |
| console.error('Drag started for an element without a data-page-id'); | |
| return; | |
| } | |
| const innerHTML = target.innerHTML; | |
| e.dataTransfer.setData('block-id', blockId); | |
| e.dataTransfer.setData('text/plain', innerHTML); // Store inner HTML | |
| e.dataTransfer.setData('data-page-id', pageId); // Store original page ID | |
| e.dataTransfer.effectAllowed = 'move'; | |
| target.style.opacity = '0.4'; | |
| // Create an invisible drag image | |
| const dragImage = document.createElement('div'); | |
| dragImage.style.width = '1px'; | |
| dragImage.style.height = '1px'; | |
| dragImage.style.opacity = '0'; | |
| document.body.appendChild(dragImage); | |
| e.dataTransfer.setDragImage(dragImage, 0, 0); | |
| console.log(`Drag started for block ID: ${blockId} page ID: ${pageId}`); | |
| } | |
| function handleDragEnd(e) { | |
| const target = e.target.closest('.block-item, .block-content'); | |
| if (target) { | |
| target.style.opacity = '1'; // Reset the opacity | |
| const blockId = target.getAttribute('data-block-id'); | |
| console.log(`Drag ended for block ID: ${blockId}`); | |
| } | |
| // Remove highlight classes from pages and blocks | |
| document.querySelectorAll('.highlight-page').forEach(el => el.classList.remove('highlight-page')); | |
| document.querySelectorAll('.highlight-block').forEach(el => el.classList.remove('highlight-block')); | |
| document.querySelectorAll('.highlight-block-top').forEach(el => el.classList.remove('highlight-block-top')); | |
| } | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| e.dataTransfer.dropEffect = 'move'; | |
| console.log('Drag over event'); | |
| const targetPage = e.target.closest('.page'); | |
| if (targetPage) { | |
| targetPage.classList.add('highlight-page'); // Add highlight class for pages | |
| } | |
| const targetBlock = e.target.closest('.block-item, .block-content'); | |
| if (targetBlock) { | |
| const bounding = targetBlock.getBoundingClientRect(); | |
| const offset = e.clientY - bounding.top; | |
| if (offset > bounding.height / 2) { | |
| targetBlock.classList.add('highlight-block'); | |
| targetBlock.classList.remove('highlight-block-top'); | |
| } else { | |
| targetBlock.classList.add('highlight-block-top'); | |
| targetBlock.classList.remove('highlight-block'); | |
| } | |
| } | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| const blockId = e.dataTransfer.getData('block-id'); | |
| const originalPageId = e.dataTransfer.getData('data-page-id'); | |
| const innerHTML = e.dataTransfer.getData('text/plain'); | |
| console.log(`Drop event for block ID: ${blockId} from page ID: ${originalPageId}`); | |
| // Ensure we are not dropping into a textarea or another block | |
| if (event.target.classList.contains('block-item', 'block-content') || event.target.tagName === 'TEXTAREA') { | |
| console.log('Cannot drop block inside another block or textarea'); | |
| return; | |
| } | |
| if (blockId && originalPageId) { | |
| const originalBlock = document.querySelector(`[data-block-id="${blockId}"]`); | |
| const newPage = e.target.closest('.page'); | |
| console.log(`Over page ${newPage} from page ID: ${originalPageId}`); | |
| const newPageId = newPage.getAttribute('data-page-id'); | |
| // Ensure the original block exists before proceeding | |
| if (!originalBlock || !newPage) { | |
| console.error(`Block with ID ${blockId} on page ${originalPageId} not found`); | |
| return; | |
| } | |
| const newBlockContent = document.createElement('div'); | |
| newBlockContent.classList.add('block-content'); | |
| newBlockContent.innerHTML = originalBlock.innerHTML; // Transfer inner content only | |
| // Add necessary attributes and event listeners | |
| newBlockContent.setAttribute('data-block-id', blockId); | |
| newBlockContent.setAttribute('data-page-id', newPageId); | |
| console.log('newPageID:', newPageId); | |
| newBlockContent.setAttribute('draggable', true); | |
| newBlockContent.addEventListener('dragstart', handleDragStart); | |
| newBlockContent.addEventListener('dragend', handleDragEnd); | |
| const target = e.target.closest('.block-item, .block-content'); | |
| let targetColumn = 1; | |
| if (target) { | |
| const bounding = target.getBoundingClientRect(); | |
| const offset = e.clientY - bounding.top; | |
| console.log('Drop target found:', target); | |
| console.log('Bounding rectangle:', bounding); | |
| console.log('Offset from top:', offset); | |
| console.log('Target height:', bounding.height); | |
| console.log('Insert before or after decision point (height / 2):', bounding.height / 2); | |
| targetColumn = getColumnFromOffset(target, offset); | |
| if (offset > bounding.height / 2) { | |
| console.log('Inserting after the target'); | |
| target.parentNode.insertBefore(newBlockContent, target.nextSibling); | |
| } else { | |
| console.log('Inserting before the target'); | |
| target.parentNode.insertBefore(newBlockContent, target); | |
| } | |
| // Remove highlight borders | |
| target.style['border-bottom'] = ''; | |
| target.style['border-top'] = ''; | |
| } else { | |
| console.log('No valid drop target found, appending to the end'); | |
| newPage.querySelector('.block.monster.frame.wide').appendChild(newBlockContent); | |
| } | |
| // Remove the original block from the original container | |
| originalBlock.parentNode.removeChild(originalBlock); | |
| // Reset opacity of dragged element | |
| newBlockContent.style.opacity = '1'; | |
| console.log(`Moved existing block with ID: ${blockId} to page ID: ${newPageId}`); | |
| initializeTextareaResizing(); | |
| // Adjust layouts | |
| if (originalPageId !== 'block-container') { | |
| adjustPageLayout(originalPageId); | |
| } | |
| adjustPageLayout(newPageId, targetColumn); | |
| } else { | |
| console.log('No data transferred'); | |
| } | |
| } | |
| function getColumnFromOffset(block, offset) { | |
| const page = block.closest('.page'); | |
| if (!page) return 1; | |
| const columnWrapper = page.querySelector('.columnWrapper'); | |
| const columnWrapperRect = columnWrapper.getBoundingClientRect(); | |
| const relativeOffset = offset - columnWrapperRect.left; // Calculate the offset relative to the column wrapper | |
| const columnWidth = columnWrapper.clientWidth / 2; // Assuming two columns | |
| // Log details for debugging | |
| console.log('Block offset:', offset); | |
| console.log('Relative offset:', relativeOffset); | |
| const columnNumber = Math.ceil(relativeOffset / columnWidth); | |
| // Ensure the column number is within valid bounds (1 or 2) | |
| const validColumnNumber = Math.min(Math.max(columnNumber, 1), 2); | |
| return validColumnNumber; | |
| } | |
| // Function to get the height of a column by index | |
| function getColumnHeights(pageElement) { | |
| const columns = [0, 0]; // Assuming two columns for simplicity | |
| const blocks = pageElement.querySelectorAll('.block-content'); | |
| blocks.forEach(block => { | |
| const column = getColumnFromOffset(block, block.getBoundingClientRect().left); | |
| columns[column - 1] += block.offsetHeight; | |
| }); | |
| return columns; | |
| } | |
| function adjustPageLayout(pageId) { | |
| const page = document.querySelector(`[data-page-id="${pageId}"]`); | |
| if (!page) { | |
| console.error(`Page with ID ${pageId} not found`); | |
| return; | |
| } | |
| const columnHeights = getColumnHeights(page); | |
| console.log(`Total height of columns in ${pageId}: ${columnHeights}`); | |
| for (let i = 0; i < columnHeights.length; i++) { | |
| if (columnHeights[i] > MAX_COLUMN_HEIGHT) { | |
| console.log(`Column ${i + 1} in ${pageId} exceeds max height, total height: ${columnHeights[i]}px`); | |
| handleColumnOverflow(page, i + 1); | |
| } | |
| } | |
| } | |
| let pageCounter = 1; | |
| // Function to create new page | |
| function createNewPage() { | |
| const newPage = document.createElement('div'); | |
| newPage.classList.add('page'); | |
| newPage.setAttribute('data-page-id', `page-${pageCounter}`); | |
| newPage.id = `page-${pageCounter}`; | |
| const columnWrapper = document.createElement('div'); | |
| columnWrapper.classList.add('columnWrapper'); | |
| const newMonsterFrame = document.createElement('div'); | |
| newMonsterFrame.classList.add('block', 'monster', 'frame', 'wide'); | |
| columnWrapper.appendChild(newMonsterFrame); | |
| newPage.appendChild(columnWrapper); | |
| pageContainer.appendChild(newPage); | |
| currentPage = newMonsterFrame; | |
| console.log(`Created new page with ID: ${newPage.id}`); | |
| // Add event listeners to the new currentPage | |
| currentPage.addEventListener('dragover', handleDragOver); | |
| currentPage.addEventListener('drop', handleDrop); | |
| pageCounter++; | |
| return newPage; | |
| } | |
| function handleColumnOverflow(page, targetColumn) { | |
| console.log(`Handling overflow for page ID: ${page.getAttribute('data-page-id')} in column ${targetColumn}`); | |
| const blocks = Array.from(page.querySelectorAll('.block-content')); | |
| let columnHeights = [0, 0]; | |
| let overflowStartIndex = -1; | |
| // Find the start index where overflow begins in the target column | |
| blocks.forEach((block, index) => { | |
| const column = getColumnFromOffset(block, block.getBoundingClientRect().left); | |
| columnHeights[column - 1] += block.offsetHeight; | |
| if (columnHeights[targetColumn - 1] > MAX_COLUMN_HEIGHT && overflowStartIndex === -1) { | |
| overflowStartIndex = index; | |
| } | |
| }); | |
| // If no overflow, return early | |
| if (overflowStartIndex === -1) { | |
| return; | |
| } | |
| const overflowBlocks = blocks.slice(overflowStartIndex); | |
| const overflowHeight = overflowBlocks.reduce((acc, block) => acc + block.offsetHeight, 0); | |
| // If the target column is the first column, check if the second column has enough space | |
| if (targetColumn === 1) { | |
| const secondColumnAvailableHeight = MAX_COLUMN_HEIGHT - columnHeights[1]; | |
| if (overflowHeight <= secondColumnAvailableHeight) { | |
| // Move the overflowing blocks to the second column within the same page | |
| overflowBlocks.forEach(block => { | |
| const blockWrapper = block.closest('.block.monster.frame.wide'); | |
| if (blockWrapper) { | |
| blockWrapper.appendChild(block); | |
| block.setAttribute('data-page-id', page.getAttribute('data-page-id')); | |
| } | |
| }); | |
| return; | |
| } | |
| } | |
| // Get the next page if it exists | |
| const nextPage = getNextPage(page); | |
| if (nextPage) { | |
| const nextPageBlocks = nextPage.querySelectorAll('.block-content, .block-item'); | |
| let nextPageColumnHeights = [0, 0]; | |
| nextPageBlocks.forEach(block => { | |
| const column = getColumnFromOffset(block, block.getBoundingClientRect().left); | |
| nextPageColumnHeights[column - 1] += block.offsetHeight; | |
| }); | |
| // Check if there's enough space in the target column of the next page | |
| if (nextPageColumnHeights[targetColumn - 1] + overflowHeight <= MAX_COLUMN_HEIGHT) { | |
| const nextPageContainer = nextPage.querySelector('.block.monster.frame.wide'); | |
| overflowBlocks.forEach(block => { | |
| nextPageContainer.appendChild(block); | |
| block.setAttribute('data-page-id', nextPage.getAttribute('data-page-id')); | |
| }); | |
| return; | |
| } | |
| // If the next page's second column has enough space for overflow from the first column | |
| if (targetColumn === 1 && nextPageColumnHeights[1] + overflowHeight <= MAX_COLUMN_HEIGHT) { | |
| const nextPageContainer = nextPage.querySelector('.block.monster.frame.wide'); | |
| overflowBlocks.forEach(block => { | |
| nextPageContainer.appendChild(block); | |
| block.setAttribute('data-page-id', nextPage.getAttribute('data-page-id')); | |
| }); | |
| return; | |
| } | |
| } | |
| // Otherwise, create a new page and move the overflowing blocks there | |
| const newPage = createNewPage(); | |
| if (!newPage) { | |
| console.error('Failed to create a new page'); | |
| return; | |
| } | |
| const newMonsterFrame = newPage.querySelector('.block.monster.frame.wide'); | |
| if (!newMonsterFrame) { | |
| console.error('New monster frame not found in the new page'); | |
| return; | |
| } | |
| overflowBlocks.forEach(block => { | |
| newMonsterFrame.appendChild(block); | |
| }); | |
| console.log(`Moved overflowing blocks to new page with ID: ${newPage.getAttribute('data-page-id')}`); | |
| } | |
| // Utility function to get the next page element | |
| function getNextPage(currentPage) { | |
| const nextPageId = parseInt(currentPage.getAttribute('data-page-id').split('-')[1]) + 1; | |
| return document.querySelector(`[data-page-id="page-${nextPageId}"]`); | |
| } | |
| function moveBlockToPage(block, newPageId) { | |
| block.setAttribute('data-page-id', newPageId); | |
| const newPage = document.querySelector(`[data-page-id="${newPageId}"] .block-container`); | |
| newPage.appendChild(block); | |
| } | |
| // Handle the drop event on the trash area | |
| function handleTrashDrop(e) { | |
| e.preventDefault(); | |
| const innerHTML = e.dataTransfer.getData('text/plain'); | |
| const blockId = e.dataTransfer.getData('block-id'); | |
| console.log('Trash Drop event:', e); | |
| console.log('Dragged block ID to trash:', blockId); | |
| if (innerHTML && blockId) { | |
| // Find the dragged element and remove it from the DOM | |
| let draggedElement = document.querySelector(`[data-block-id="${blockId}"].block-content`); | |
| if (!draggedElement) { | |
| draggedElement = document.querySelector(`[data-block-id="${blockId}"].block-item`); | |
| } | |
| if (draggedElement && draggedElement.parentElement) { | |
| draggedElement.parentElement.removeChild(draggedElement); | |
| console.log(`Removed block with ID: ${blockId} from the page`); | |
| } | |
| // Check if the block already exists in the block-container and remove it if it does | |
| let existingBlock = blockContainer.querySelector(`[data-block-id="${blockId}"].block-content`); | |
| if (!existingBlock) { | |
| existingBlock = blockContainer.querySelector(`[data-block-id="${blockId}"].block-item`); | |
| } | |
| if (existingBlock && existingBlock.parentElement) { | |
| existingBlock.parentElement.removeChild(existingBlock); | |
| console.log(`Removed duplicate block with ID: ${blockId} from block-container`); | |
| } | |
| // Create a new block-item to be placed back in the block-container | |
| const newBlock = document.createElement('div'); | |
| newBlock.classList.add('block-item'); | |
| newBlock.setAttribute('data-block-id', blockId); | |
| newBlock.setAttribute('data-page-id', 'block-container'); | |
| newBlock.innerHTML = innerHTML; | |
| newBlock.setAttribute('draggable', true); | |
| newBlock.addEventListener('dragstart', handleDragStart); | |
| newBlock.addEventListener('dragend', handleDragEnd); | |
| // Ensure the block is appended to the page wrapper inside blockContainer | |
| let pageWrapper = blockContainer.querySelector('.page'); | |
| if (!pageWrapper) { | |
| pageWrapper = document.createElement('div'); | |
| pageWrapper.classList.add('page'); | |
| pageWrapper.setAttribute('data-page-id', 'block-container'); | |
| blockContainer.appendChild(pageWrapper); | |
| } | |
| // Debugging output | |
| console.log('Page wrapper:', pageWrapper); | |
| console.log('New block:', newBlock); | |
| // Find the original position to insert the new block | |
| const originalPosition = initialPositions.find(pos => pos.id === blockId); | |
| console.log('Original position:', originalPosition); | |
| if (originalPosition) { | |
| const blocks = pageWrapper.querySelectorAll('.block-item'); | |
| console.log('Blocks in pageWrapper:', blocks); | |
| console.log('Inserting at position:', originalPosition.index); | |
| if (originalPosition.index < blocks.length) { | |
| const referenceNode = blocks[originalPosition.index]; | |
| if (referenceNode && referenceNode.parentNode === pageWrapper) { | |
| console.log('Inserting before block at index:', originalPosition.index); | |
| pageWrapper.insertBefore(newBlock, referenceNode); | |
| console.log(`Moved block back to original position ${originalPosition.index} in block-container`); | |
| } else { | |
| console.warn('Reference node does not belong to pageWrapper, appending to the end'); | |
| pageWrapper.appendChild(newBlock); | |
| console.log('Appended block to the end of block-container'); | |
| } | |
| } else { | |
| console.log('Appending block to the end of pageWrapper'); | |
| pageWrapper.appendChild(newBlock); | |
| console.log('Appended block to the end of block-container'); | |
| } | |
| } else { | |
| console.log('Original position not found, appending block to the end of pageWrapper'); | |
| pageWrapper.appendChild(newBlock); | |
| console.log('Appended block to the end of block-container'); | |
| } | |
| console.log(`Restored block with ID: ${blockId}`); | |
| } else { | |
| console.log('No data transferred'); | |
| } | |
| // Remove the "over" class and reset the background image | |
| trashArea.classList.remove('over'); | |
| trashArea.style.backgroundImage = "url('./closed-mimic-trashcan.png')"; | |
| initializeTextareaResizing(); | |
| } | |
| function handleTrashOver(e) { | |
| e.preventDefault(); | |
| e.dataTransfer.dropEffect = 'move'; | |
| trashArea.classList.add('over'); | |
| trashArea.style.backgroundImage = "url('./mimic_trashcan.png')"; | |
| console.log('Trash over event'); | |
| } | |
| function handleTrashLeave(e) { | |
| trashArea.classList.remove('over'); | |
| trashArea.style.backgroundImage = "url('./closed-mimic-trashcan.png')"; | |
| console.log('Trash leave event'); | |
| } | |
| currentPage.addEventListener('dragover', handleDragOver); | |
| currentPage.addEventListener('drop', handleDrop); | |
| trashArea.addEventListener('dragover', handleTrashOver); | |
| trashArea.addEventListener('dragleave', handleTrashLeave); | |
| trashArea.addEventListener('drop', handleTrashDrop); | |
| function handleReset() { | |
| console.log('Reset button clicked'); | |
| // Collect all blocks from all pages | |
| const allBlocks = []; | |
| const pages = document.querySelectorAll('.page'); | |
| pages.forEach(page => { | |
| const blocksOnPage = page.querySelectorAll('[data-block-id]'); | |
| blocksOnPage.forEach(block => { | |
| const blockId = block.getAttribute('data-block-id'); | |
| allBlocks.push({ | |
| id: blockId, | |
| innerHTML: block.innerHTML | |
| }); | |
| block.remove(); | |
| console.log(`Removed block with ID: ${blockId} from page ID: ${page.getAttribute('data-page-id')}`); | |
| }); | |
| }); | |
| // Clear all pages | |
| pages.forEach(page => page.remove()); | |
| // Clear blockContainer before reinserting blocks | |
| blockContainer.innerHTML = ''; | |
| // Reinsert blocks back into the blockContainer in their original order | |
| let pageWrapper = blockContainer.querySelector('.page'); | |
| if (!pageWrapper) { | |
| pageWrapper = document.createElement('div'); | |
| pageWrapper.classList.add('page'); | |
| pageWrapper.setAttribute('id', 'block-page'); | |
| blockContainer.appendChild(pageWrapper); | |
| } | |
| // Reassign blockContainerPage to the newly created block-page element | |
| blockContainerPage = document.getElementById('block-page'); | |
| initialPositions.forEach(pos => { | |
| const blockData = allBlocks.find(block => block.id === pos.id); | |
| if (blockData) { | |
| const newBlock = document.createElement('div'); | |
| newBlock.classList.add('block-item'); | |
| newBlock.setAttribute('data-block-id', blockData.id); | |
| newBlock.setAttribute('data-page-id', 'block-container'); | |
| newBlock.innerHTML = blockData.innerHTML; | |
| newBlock.setAttribute('draggable', true); | |
| newBlock.addEventListener('dragstart', handleDragStart); | |
| newBlock.addEventListener('dragend', handleDragEnd); | |
| const blocks = pageWrapper.querySelectorAll('.block-item'); | |
| if (pos.index < blocks.length) { | |
| pageWrapper.insertBefore(newBlock, blocks[pos.index]); | |
| console.log(`Moved block back to original position ${pos.index} in block-container`); | |
| } else { | |
| pageWrapper.appendChild(newBlock); | |
| console.log('Appended block to the end of block-container'); | |
| } | |
| } | |
| }); | |
| createNewPage(); | |
| console.log('Reset complete, all blocks moved back to block-container'); | |
| initializeTextareaResizing(); | |
| } | |
| pageContainer.addEventListener('dragover', handleDragOver); | |
| pageContainer.addEventListener('drop', handleDrop); | |
| trashArea.addEventListener('dragover', handleTrashOver); | |
| trashArea.addEventListener('dragleave', handleTrashLeave); | |
| trashArea.addEventListener('drop', handleTrashDrop); | |
| resetButton.addEventListener('click', handleReset); | |
| extractBlocks(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |