sitebuilder
This commit is contained in:
parent
0137a30e7b
commit
2caa796523
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -50,6 +50,9 @@
|
|||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
|
||||||
|
<clr-dg-column [clrDgField]="'html'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Html
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'Json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> htmljson
|
<clr-dg-column [clrDgField]="'Json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> htmljson
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
@ -91,7 +94,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<clr-dg-cell (click)="goToReplaceStringhtmljson (user.html)"
|
||||||
|
style="cursor: pointer; align-items: center;"><clr-icon shape="details"></clr-icon>
|
||||||
|
</clr-dg-cell>
|
||||||
|
|
||||||
<clr-dg-cell (click)="goToReplaceStringhtmljson (user.htmljson)"
|
<clr-dg-cell (click)="goToReplaceStringhtmljson (user.htmljson)"
|
||||||
style="cursor: pointer; align-items: center;"><clr-icon shape="details"></clr-icon>
|
style="cursor: pointer; align-items: center;"><clr-icon shape="details"></clr-icon>
|
||||||
@ -128,8 +133,8 @@
|
|||||||
</clr-signpost-content>
|
</clr-signpost-content>
|
||||||
</clr-signpost>
|
</clr-signpost>
|
||||||
<!-- View HTML Button -->
|
<!-- View HTML Button -->
|
||||||
<button class="btn btn-sm btn-outline" (click)="openHtmlPreview(user.id)">
|
<button class="btn btn-sm btn-outline" (click)="openHtmlPreview(user.id)" title="Open Preview in New Page">
|
||||||
<clr-icon shape="eye"></clr-icon>
|
<clr-icon shape="eye"></clr-icon> Preview
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
@ -359,6 +364,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clr-col-sm-12">
|
||||||
|
<label> Html</label>
|
||||||
|
<textarea cols="10" rows="2" [(ngModel)]="rowSelected.html" name="html " placeholder="Textarea"> </textarea>
|
||||||
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label> Json</label>
|
<label> Json</label>
|
||||||
<textarea cols="10" rows="2" [(ngModel)]="rowSelected.htmljson" name="htmljson "
|
<textarea cols="10" rows="2" [(ngModel)]="rowSelected.htmljson" name="htmljson "
|
||||||
@ -482,6 +491,10 @@
|
|||||||
<input type="checkbox" formControlName="active" clrToggle />
|
<input type="checkbox" formControlName="active" clrToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="clr-col-sm-12">
|
||||||
|
<label> Html</label>
|
||||||
|
<textarea cols="15" rows="3" formControlName="html" placeholder="Textarea"> </textarea>
|
||||||
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label> Json</label>
|
<label> Json</label>
|
||||||
<textarea cols="15" rows="3" formControlName="htmljson" placeholder="Textarea"> </textarea>
|
<textarea cols="15" rows="3" formControlName="htmljson" placeholder="Textarea"> </textarea>
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { Design_lbrarycardvariable } from './Design_lbrary_cardvariable';
|
|||||||
import { UserInfoService } from 'src/app/services/user-info.service';
|
import { UserInfoService } from 'src/app/services/user-info.service';
|
||||||
import { SiteTreeservice } from '../SiteBuilderGrid/SiteTree.service';
|
import { SiteTreeservice } from '../SiteBuilderGrid/SiteTree.service';
|
||||||
import { COMMON_CSS } from '../WireframesUi/common-css';
|
import { COMMON_CSS } from '../WireframesUi/common-css';
|
||||||
|
import { Download_Css } from '../WireframesUi/download-css';
|
||||||
|
// import { Download_Css } from '../WireframesUi/download-css';
|
||||||
declare var JsBarcode: any;
|
declare var JsBarcode: any;
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-Design_lbrary',
|
selector: 'app-Design_lbrary',
|
||||||
@ -79,11 +81,13 @@ export class Design_lbraryComponent implements OnInit {
|
|||||||
htmljson: [null],
|
htmljson: [null],
|
||||||
|
|
||||||
css: [null],
|
css: [null],
|
||||||
templatetype:[null],
|
templatetype: [null],
|
||||||
uitype:[null],
|
uitype: [null],
|
||||||
javacode: [null],
|
javacode: [null],
|
||||||
typerender: [null],
|
typerender: [null],
|
||||||
techstack: [null],
|
techstack: [null],
|
||||||
|
html: [null],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -352,12 +356,16 @@ export class Design_lbraryComponent implements OnInit {
|
|||||||
// this.modalHtmlPreview = true;
|
// this.modalHtmlPreview = true;
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
openHtmlPreview(id: number): void {
|
openHtmlPreview(id: number): void {
|
||||||
console.log('preview html start..');
|
console.log('preview html start..');
|
||||||
|
|
||||||
this.siteTreeService.generateHtmlwithcss(id).subscribe((response: any) => {
|
this.siteTreeService.generateHtmlwithcss(id).subscribe((response: any) => {
|
||||||
const bodyContent = response.msg || '<div>Empty</div>'; // fallback
|
const bodyContent = response.msg || '<div>Empty</div>'; // fallback
|
||||||
|
|
||||||
|
// Store raw content for download/copy
|
||||||
|
const rawHtmlContent = bodyContent;
|
||||||
|
|
||||||
|
// Full HTML with styles and controls
|
||||||
const fullHtml = `
|
const fullHtml = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -366,38 +374,204 @@ export class Design_lbraryComponent implements OnInit {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>HTML Preview</title>
|
<title>HTML Preview</title>
|
||||||
<style>
|
<style>
|
||||||
${COMMON_CSS}
|
${Download_Css}
|
||||||
|
|
||||||
|
/* Additional styles for controls */
|
||||||
|
.preview-controls {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 10px !important;
|
||||||
|
right: 10px !important;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
border: 1px solid #ccc !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) !important;
|
||||||
|
z-index: 1000 !important;
|
||||||
|
}
|
||||||
|
.preview-controls button {
|
||||||
|
margin: 5px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
background-color: #0072a3 !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
.preview-controls button:hover {
|
||||||
|
background-color: #005a80 !important;
|
||||||
|
}
|
||||||
|
.toast {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 70px !important;
|
||||||
|
right: 10px !important;
|
||||||
|
padding: 10px 20px !important;
|
||||||
|
background-color: #4CAF50 !important;
|
||||||
|
color: white !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
transition: opacity 0.3s !important;
|
||||||
|
z-index: 1001 !important;
|
||||||
|
}
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
.html-source {
|
||||||
|
display: none !important;
|
||||||
|
margin-top: 20px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 300px !important;
|
||||||
|
font-family: monospace !important;
|
||||||
|
resize: vertical !important;
|
||||||
|
white-space: pre !important;
|
||||||
|
overflow: auto !important;
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
border: 1px solid #ddd !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
.show-source {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
${bodyContent}
|
<div class="preview-controls">
|
||||||
|
<button id="download-btn">Download HTML</button>
|
||||||
|
<button id="copy-btn">Copy to Clipboard</button>
|
||||||
|
<button id="toggle-source-btn">View Source</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toast" class="toast">Copied to clipboard!</div>
|
||||||
|
|
||||||
|
<div id="content-preview">
|
||||||
|
${bodyContent}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre id="html-source" class="html-source"></pre>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Format HTML for display in the source view
|
||||||
|
function formatHtml(html) {
|
||||||
|
return html.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the raw HTML content
|
||||||
|
const rawHtml = \`${rawHtmlContent.replace(/`/g, '\\`')}\`;
|
||||||
|
|
||||||
|
// Download functionality with full HTML and CSS
|
||||||
|
document.getElementById('download-btn').addEventListener('click', function() {
|
||||||
|
const fullHtmlContent = \`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Downloaded HTML</title>
|
||||||
|
<style>
|
||||||
|
${Download_Css}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
\${rawHtml}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\`;
|
||||||
|
|
||||||
|
const blob = new Blob([fullHtmlContent], { type: 'text/html' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'preview-' + new Date().toISOString().slice(0, 10) + '.html';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy to clipboard functionality
|
||||||
|
document.getElementById('copy-btn').addEventListener('click', function() {
|
||||||
|
navigator.clipboard.writeText(rawHtml).then(function() {
|
||||||
|
const toast = document.getElementById('toast');
|
||||||
|
toast.textContent = 'Copied to clipboard!';
|
||||||
|
toast.classList.add('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
}, 2000);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error('Could not copy text: ', err);
|
||||||
|
const toast = document.getElementById('toast');
|
||||||
|
toast.textContent = 'Failed to copy to clipboard';
|
||||||
|
toast.style.backgroundColor = '#f44336';
|
||||||
|
toast.classList.add('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle source view
|
||||||
|
document.getElementById('toggle-source-btn').addEventListener('click', function() {
|
||||||
|
const sourceElem = document.getElementById('html-source');
|
||||||
|
if (!sourceElem.classList.contains('show-source')) {
|
||||||
|
sourceElem.textContent = rawHtml;
|
||||||
|
sourceElem.innerHTML = formatHtml(rawHtml);
|
||||||
|
sourceElem.classList.add('show-source');
|
||||||
|
this.textContent = 'Hide Source';
|
||||||
|
} else {
|
||||||
|
sourceElem.classList.remove('show-source');
|
||||||
|
this.textContent = 'View Source';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.htmlContent = fullHtml;
|
// Open the HTML in a new window/tab
|
||||||
console.log('html code ..', this.htmlContent);
|
const newWindow = window.open('', '_blank');
|
||||||
this.modalHtmlPreview = true;
|
if (newWindow) {
|
||||||
|
newWindow.document.write(fullHtml);
|
||||||
|
newWindow.document.close();
|
||||||
|
} else {
|
||||||
|
// If popup is blocked, inform the user
|
||||||
|
this.toastr.warning('Pop-up blocked. Please allow pop-ups for this site to see the preview.');
|
||||||
|
}
|
||||||
}, error => {
|
}, error => {
|
||||||
this.htmlContent = `
|
// Handle error by opening a new window with error message
|
||||||
|
const errorHtml = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html><head><style>${COMMON_CSS}</style></head>
|
<html>
|
||||||
<body><p style="color:red;">Error loading HTML.</p></body></html>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Error Preview</title>
|
||||||
|
<style>${COMMON_CSS}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="padding: 20px; text-align: center;">
|
||||||
|
<h2 style="color:red;">Error Loading Preview</h2>
|
||||||
|
<p>The preview content could not be loaded. Error details:</p>
|
||||||
|
<pre style="background-color: #f8f8f8; padding: 10px; border-radius: 5px; text-align: left;">${error ? JSON.stringify(error, null, 2) : 'Unknown error'}</pre>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
`;
|
`;
|
||||||
this.modalHtmlPreview = true;
|
|
||||||
|
const newWindow = window.open('', '_blank');
|
||||||
|
if (newWindow) {
|
||||||
|
newWindow.document.write(errorHtml);
|
||||||
|
newWindow.document.close();
|
||||||
|
} else {
|
||||||
|
this.toastr.error('Error loading preview. Pop-up blocked. Please allow pop-ups for this site.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,18 @@ export class SiteTreeservice {
|
|||||||
return this.apiRequest.post(`sureops/deploy?projId=` + projId, data);
|
return this.apiRequest.post(`sureops/deploy?projId=` + projId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createHtmlfile(siteNmae: String, data: any): Observable<any> {
|
||||||
|
|
||||||
|
const _http = this.baseURL + "/createFile?siteBuilderName=" + siteNmae;
|
||||||
|
return this.apiRequest.post(_http, data);
|
||||||
|
}
|
||||||
|
|
||||||
// updateaction
|
readPages(siteNmae: String, pageNmae: String): Observable<any> {
|
||||||
|
|
||||||
|
const _http = this.baseURL + "/read?siteBuilderName=" + siteNmae + '&filename=' + pageNmae;
|
||||||
|
|
||||||
|
return this.apiRequest.get(_http, undefined, 'text');
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateactionƒ
|
||||||
}
|
}
|
||||||
@ -198,7 +198,15 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
.offcanvas-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.3);
|
||||||
|
z-index: 1040;
|
||||||
|
}
|
||||||
.section-content {
|
.section-content {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #555;
|
color: #555;
|
||||||
@ -385,12 +393,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
padding: 8px 15px;
|
padding: 10px 16px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@ -398,10 +409,19 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover:not([disabled]) {
|
||||||
background-color: #0069d9;
|
background-color: #0069d9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-primary[disabled] {
|
||||||
|
background-color: #7fb6ff;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
color: white;
|
color: white;
|
||||||
@ -409,20 +429,70 @@
|
|||||||
|
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background-color: #5a6268;
|
background-color: #5a6268;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { right: -400px; }
|
||||||
|
to { right: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOut {
|
||||||
|
from { right: 0; }
|
||||||
|
to { right: -400px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.offcanvas.show {
|
||||||
|
animation: slideIn 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offcanvas:not(.show) {
|
||||||
|
animation: slideOut 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
display: block;
|
border-radius: 6px;
|
||||||
width: 100%;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #495057;
|
|
||||||
background-color: #fff;
|
|
||||||
background-clip: padding-box;
|
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
border-radius: 0.25rem;
|
padding: 10px 12px;
|
||||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #80bdff;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-control {
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.custom-section-form {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px dashed #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-section-form h4 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #444;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-3 {
|
.mb-3 {
|
||||||
@ -438,7 +508,7 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: -5px 0 15px rgba(0,0,0,0.1);
|
box-shadow: -5px 0 15px rgba(0,0,0,0.1);
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
transition: right 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,27 +520,47 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 15px 20px;
|
padding: 18px 20px;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.offcanvas-title {
|
.offcanvas-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
color: #333;
|
||||||
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.offcanvas-body {
|
.offcanvas-body {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-templates-container {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.section-template {
|
.section-template {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
@ -478,11 +568,30 @@
|
|||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-template:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 0;
|
||||||
|
background-color: #007bff;
|
||||||
|
transition: height 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-template:hover {
|
.section-template:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-template:hover:before {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-template-title {
|
.section-template-title {
|
||||||
|
|||||||
@ -47,31 +47,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-form" *ngIf="editMode">
|
<!-- <div class="editor-form" *ngIf="editMode">
|
||||||
<div *ngIf="editMode === 'section' && selectedSection && selectedPage">
|
<div *ngIf="editMode === 'section' && selectedSection && selectedPage">
|
||||||
<div class="editor-title">Edit Section: {{ selectedSection.type }}</div>
|
<div class="editor-title">Edit Section: {{ selectedSection.type }}</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Content</label>
|
<label class="form-label">Content</label>
|
||||||
<textarea class="form-textarea" [(ngModel)]="selectedSection.content"></textarea>
|
<textarea
|
||||||
|
class="form-textarea"
|
||||||
|
[(ngModel)]="selectedSection.content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn btn-secondary" (click)="cancelEdit()">Cancel</button>
|
<button class="btn btn-secondary" (click)="cancelEdit()">Cancel</button>
|
||||||
<button class="btn btn-primary" (click)="saveEdit()">Save Changes</button>
|
<button class="btn btn-primary" (click)="saveEdit()">Save Changes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="offcanvas" [class.show]="showOffcanvas">
|
<!-- Replace your current offcanvas with these two separate ones -->
|
||||||
|
|
||||||
|
<!-- Add Section Offcanvas -->
|
||||||
|
<div class="offcanvas add-section" [class.show]="showAddSectionOffcanvas">
|
||||||
<div class="offcanvas-header">
|
<div class="offcanvas-header">
|
||||||
<div class="offcanvas-title">Add Section</div>
|
<div class="offcanvas-title">Add Section</div>
|
||||||
<button class="close-btn" (click)="closeOffcanvas()">×</button>
|
<button class="close-btn" (click)="closeAddSectionOffcanvas()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body">
|
<div class="offcanvas-body">
|
||||||
<div class="section-template" *ngFor="let template of sectionTemplates"
|
<div class="section-templates-container">
|
||||||
(click)="addSectionFromTemplate(template)">
|
<div class="section-template" *ngFor="let template of sectionTemplates"
|
||||||
<div class="section-template-title">{{ template.type }}</div>
|
(click)="addSectionFromTemplate(template)">
|
||||||
<div class="section-template-desc">{{ template.description }}</div>
|
<div class="section-template-title">{{ template.type }}</div>
|
||||||
|
<div class="section-template-desc">{{ template.description }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-top: 20px;">
|
<div class="custom-section-form">
|
||||||
<h4>Custom Section</h4>
|
<h4>Custom Section</h4>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Section Type</label>
|
<label class="form-label">Section Type</label>
|
||||||
@ -88,6 +97,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Section Offcanvas -->
|
||||||
|
<div class="offcanvas edit-section" [class.show]="showEditSectionOffcanvas">
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<div class="offcanvas-title">Edit Section: {{ selectedSection?.type }}</div>
|
||||||
|
<button class="close-btn" (click)="closeEditSectionOffcanvas()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Section Type</label>
|
||||||
|
<input type="text" class="form-control" [(ngModel)]="selectedSection.type" *ngIf="selectedSection"
|
||||||
|
placeholder="Section type">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label class="form-label">Content</label>
|
||||||
|
<textarea class="form-control" [(ngModel)]="selectedSection && selectedSection.content" rows="8"
|
||||||
|
placeholder="Enter content"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions mt-4">
|
||||||
|
<button class="btn btn-secondary" (click)="closeEditSectionOffcanvas()">Cancel</button>
|
||||||
|
<button class="btn btn-primary" (click)="saveSection()">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal" [class.show]="showAddChildModal">
|
<div class="modal" [class.show]="showAddChildModal">
|
||||||
<div class="modal-backdrop" *ngIf="showAddChildModal" (click)="closeAddChildModal()"></div>
|
<div class="modal-backdrop" *ngIf="showAddChildModal" (click)="closeAddChildModal()"></div>
|
||||||
<div class="modal-dialog" *ngIf="showAddChildModal">
|
<div class="modal-dialog" *ngIf="showAddChildModal">
|
||||||
@ -123,11 +158,13 @@
|
|||||||
<i class="node-icon" [class]="getNodeIcon(page)"></i>
|
<i class="node-icon" [class]="getNodeIcon(page)"></i>
|
||||||
<span class="node-title">{{ page.name }}</span>
|
<span class="node-title">{{ page.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sections-container" cdkDropList [cdkDropListData]="page.sections">
|
<div class="sections-container" cdkDropList [cdkDropListData]="page.sections"
|
||||||
|
(cdkDropListDropped)="onSectionDrop($event)">
|
||||||
<!-- Update the section card template within the #pageTemplate -->
|
<!-- Update the section card template within the #pageTemplate -->
|
||||||
<!-- Section card with improved hover button placement -->
|
<!-- Section card with improved hover button placement -->
|
||||||
<div class="section-card" *ngFor="let section of page.sections; let i = index" cdkDrag
|
<div class="section-card" *ngFor="let section of page.sections; let i = index" cdkDrag [cdkDragData]="section"
|
||||||
(click)="selectSection(section); $event.stopPropagation()">
|
(click)="selectSection(section); $event.stopPropagation()">
|
||||||
|
|
||||||
<div class="section-title">{{ section.type }}</div>
|
<div class="section-title">{{ section.type }}</div>
|
||||||
<div class="section-content" [innerHTML]="getSectionContentHtml(section.content)"></div>
|
<div class="section-content" [innerHTML]="getSectionContentHtml(section.content)"></div>
|
||||||
<div class="section-actions">
|
<div class="section-actions">
|
||||||
@ -139,7 +176,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Repositioned add button that appears on hover -->
|
<!-- Repositioned add button that appears on hover -->
|
||||||
<div class="add-section-hover-btn" (click)="openSectionOffcanvas(page, i, $event)" title="Add Section">
|
<div class="add-section-hover-btn" (click)="openAddSectionOffcanvas(page, i, $event)" title="Add Section">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -73,6 +73,11 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
|
|
||||||
currentSectionIndex: number = -1;
|
currentSectionIndex: number = -1;
|
||||||
|
|
||||||
|
//off canvas
|
||||||
|
showAddSectionOffcanvas = false;
|
||||||
|
showEditSectionOffcanvas = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section templates with better descriptions
|
// Section templates with better descriptions
|
||||||
sectionTemplates = [
|
sectionTemplates = [
|
||||||
@ -131,8 +136,7 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private siteTreeService: SiteTreeservice,
|
private siteTreeService: SiteTreeservice,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private toastr: ToastrService
|
private toastr: ToastrService,
|
||||||
|
|
||||||
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@ -157,6 +161,9 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
// Remove event listener if component is destroyed while offcanvas is open
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
|
||||||
// Auto-save changes before component destruction
|
// Auto-save changes before component destruction
|
||||||
if (this.hasUnsavedChanges) {
|
if (this.hasUnsavedChanges) {
|
||||||
this.saveTreeData(true);
|
this.saveTreeData(true);
|
||||||
@ -622,6 +629,80 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
this.currentSectionIndex = -1;
|
this.currentSectionIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openAddSectionOffcanvas(page: Page, sectionIndex: number = -1, event?: Event): void {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
this.currentParentPage = page;
|
||||||
|
this.currentSectionIndex = sectionIndex;
|
||||||
|
this.customSectionType = '';
|
||||||
|
this.customSectionContent = '';
|
||||||
|
this.showAddSectionOffcanvas = true;
|
||||||
|
|
||||||
|
// Listen for clicks outside offcanvas
|
||||||
|
setTimeout(() => {
|
||||||
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open edit section offcanvas
|
||||||
|
openEditSectionOffcanvas(section: Section, page: Page, event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.selectedSection = { ...section }; // Clone to avoid direct mutation until save
|
||||||
|
this.currentParentPage = page;
|
||||||
|
this.editMode = 'section';
|
||||||
|
this.showEditSectionOffcanvas = true;
|
||||||
|
|
||||||
|
// Listen for clicks outside offcanvas
|
||||||
|
setTimeout(() => {
|
||||||
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOutsideClick = (event: MouseEvent): void => {
|
||||||
|
const addOffcanvas = document.querySelector('.offcanvas.add-section');
|
||||||
|
const editOffcanvas = document.querySelector('.offcanvas.edit-section');
|
||||||
|
|
||||||
|
if (addOffcanvas && !addOffcanvas.contains(event.target as Node) && this.showAddSectionOffcanvas) {
|
||||||
|
this.closeAddSectionOffcanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editOffcanvas && !editOffcanvas.contains(event.target as Node) && this.showEditSectionOffcanvas) {
|
||||||
|
this.closeEditSectionOffcanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAddSectionOffcanvas(): void {
|
||||||
|
this.showAddSectionOffcanvas = false;
|
||||||
|
this.currentParentPage = null;
|
||||||
|
this.currentSectionIndex = -1;
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close edit section offcanvas
|
||||||
|
closeEditSectionOffcanvas(): void {
|
||||||
|
this.showEditSectionOffcanvas = false;
|
||||||
|
this.selectedSection = null;
|
||||||
|
this.editMode = null;
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save section changes
|
||||||
|
saveSection(): void {
|
||||||
|
if (this.selectedSection && this.currentParentPage) {
|
||||||
|
// Find the section in the parent page
|
||||||
|
const index = this.currentParentPage.sections.findIndex(s =>
|
||||||
|
s.type === this.selectedSection.type && s.content === this.selectedSection.content);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
// Update the section with edited values
|
||||||
|
this.currentParentPage.sections[index] = { ...this.selectedSection };
|
||||||
|
this.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.closeEditSectionOffcanvas();
|
||||||
|
}
|
||||||
|
|
||||||
// Add section from a template
|
// Add section from a template
|
||||||
addSectionFromTemplate(template: any): void {
|
addSectionFromTemplate(template: any): void {
|
||||||
if (this.currentParentPage) {
|
if (this.currentParentPage) {
|
||||||
@ -651,20 +732,23 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
// Similar update for addCustomSection
|
// Similar update for addCustomSection
|
||||||
addCustomSection(): void {
|
addCustomSection(): void {
|
||||||
if (this.currentParentPage && this.customSectionType) {
|
if (this.currentParentPage && this.customSectionType) {
|
||||||
const newSection: Section = {
|
// If in edit mode, update the selected section
|
||||||
type: this.customSectionType,
|
if (this.editMode === 'section' && this.selectedSection) {
|
||||||
content: this.customSectionContent || ''
|
this.selectedSection.type = this.customSectionType;
|
||||||
};
|
this.selectedSection.content = this.customSectionContent || '';
|
||||||
|
|
||||||
// Insert at specific position if provided, otherwise add to end
|
|
||||||
if (this.currentSectionIndex >= 0) {
|
|
||||||
this.currentParentPage.sections.splice(this.currentSectionIndex + 1, 0, newSection);
|
|
||||||
} else {
|
} else {
|
||||||
this.currentParentPage.sections.push(newSection);
|
// Otherwise, create a new section
|
||||||
}
|
const newSection: Section = {
|
||||||
|
type: this.customSectionType,
|
||||||
|
content: this.customSectionContent || ''
|
||||||
|
};
|
||||||
|
|
||||||
if (!this.availableSectionTypes.includes(this.customSectionType)) {
|
// Insert at specific position if provided, otherwise add to end
|
||||||
this.availableSectionTypes.push(this.customSectionType);
|
if (this.currentSectionIndex >= 0) {
|
||||||
|
this.currentParentPage.sections.splice(this.currentSectionIndex + 1, 0, newSection);
|
||||||
|
} else {
|
||||||
|
this.currentParentPage.sections.push(newSection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasUnsavedChanges = true;
|
this.hasUnsavedChanges = true;
|
||||||
@ -673,10 +757,10 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Edit section
|
// Edit section
|
||||||
|
// Edit section - modify this function
|
||||||
editSection(section: Section, event: Event): void {
|
editSection(section: Section, event: Event): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.selectedSection = section;
|
this.openEditSectionOffcanvas(section, this.selectedPage, event);
|
||||||
this.editMode = 'section';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel edit
|
// Cancel edit
|
||||||
@ -762,6 +846,8 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force change detection
|
||||||
|
this.selectedPage = { ...this.selectedPage! };
|
||||||
this.hasUnsavedChanges = true;
|
this.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,7 +881,7 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
|
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
// alert('Site structure saved successfully');
|
// alert('Site structure saved successfully');
|
||||||
this.toastr.success("Site structure saved successfully");
|
this.toastr.success(`Site structure saved successfully`);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -804,9 +890,7 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
console.error('Error saving data:', err);
|
console.error('Error saving data:', err);
|
||||||
|
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
// alert('Failed to save site structure');
|
alert('Failed to save site structure');
|
||||||
this.toastr.error("Failed to save site structure");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
|||||||
|
export const Download_Css = `
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -77,119 +77,287 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<!-- ✅ Render whole page in one block -->
|
<!-- ✅ Render whole page in one block -->
|
||||||
<!-- <div class="canvas-section">
|
<!-- <div class="canvas-section">
|
||||||
<div [innerHTML]="pageSections[pageName]['FullPage']"></div>
|
<div [innerHTML]="pageSections[pageName]['FullPage']"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
<!-- Controls and Actions Row -->
|
||||||
|
|
||||||
|
|
||||||
<!-- part 3 -->
|
<!-- Editable HTML Area -->
|
||||||
<!-- 🔁 Regenerate + Save Buttons -->
|
<!-- <div class="editable-area" *ngIf="editMode">
|
||||||
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
|
<div class="editable-header">
|
||||||
<div>
|
<h3>Editing: {{ editingSection?.page }} > {{ editingSection?.section }}</h3>
|
||||||
<button (click)="regenerateAllModifiedSections()" style="background: orange; color: white; border: none; padding: 8px 14px; border-radius: 4px; cursor: pointer;">
|
<button class="close-btn" (click)="closeEditMode()">×</button>
|
||||||
♻️ Regenerate Modified
|
</div>
|
||||||
|
|
||||||
|
<div class="editable-content">
|
||||||
|
<div
|
||||||
|
[innerHTML]="editingSectionHtml"
|
||||||
|
contenteditable="true"
|
||||||
|
class="editable-html"
|
||||||
|
(blur)="onSectionEdit($event)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editable-footer">
|
||||||
|
<button class="action-btn save-edit-btn" (click)="saveSectionEdit()">
|
||||||
|
Save Changes
|
||||||
</button>
|
</button>
|
||||||
<button (click)="uploadHtmlFiles(37388)" style="background: green; color: white; border: none; padding: 8px 14px; border-radius: 4px; cursor: pointer;">
|
<button class="action-btn cancel-edit-btn" (click)="closeEditMode()">
|
||||||
💾 Save All HTML Files
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<div>
|
|
||||||
<!-- Global Style Controls -->
|
|
||||||
<select [(ngModel)]="liveStyles.fontSize">
|
<div class="action-bar">
|
||||||
|
<div class="left-controls">
|
||||||
|
<button (click)="regenerateAllModifiedSections()" class="action-btn regenerate-btn">
|
||||||
|
♻️ Regenerate Modified
|
||||||
|
</button>
|
||||||
|
<!-- <button (click)="uploadHtmlFiles(11096)" class="action-btn save-btn"> -->
|
||||||
|
<button (click)="createHtmlFiles()" class="action-btn save-btn">
|
||||||
|
|
||||||
|
💾 Save All HTML Files
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button (click)="uploadHtmlFiles(11096)" class="action-btn save-btn">
|
||||||
|
|
||||||
|
💾 build wireframe
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" (click)="recreateWireframe()">🔄 Recreate Wireframe</button>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="navbar-controls">
|
||||||
|
<select [(ngModel)]="selectedNavbarPage" (change)="selectedNavbarPage && selectNavbar(selectedNavbarPage)">
|
||||||
|
<option value="">Select Navbar Page</option>
|
||||||
|
<option *ngFor="let page of objectKeys(pageSections)" [value]="page">{{ page }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Navbar Editor Modal -->
|
||||||
|
<div class="modal" *ngIf="selectedNavbarPage">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Edit Navbar Links - {{ selectedNavbarPage }}</h3>
|
||||||
|
<button class="close-btn" (click)="selectedNavbarPage = ''">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="link-editor" *ngFor="let link of navbarLinks[selectedNavbarPage]; let i = index">
|
||||||
|
<input type="text" [(ngModel)]="link.label" placeholder="Link Label">
|
||||||
|
<select [(ngModel)]="link.href">
|
||||||
|
<option *ngFor="let page of availablePages" [value]="page.href">{{ page.label }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="remove-btn" (click)="removeLink(selectedNavbarPage, i)">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="action-btn add-link-btn" (click)="addLink(selectedNavbarPage)">
|
||||||
|
➕ Add New Link
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="action-btn" (click)="applyNavbarChanges(selectedNavbarPage)">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
<button class="action-btn cancel-btn" (click)="selectedNavbarPage = ''">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="right-controls">
|
||||||
|
<select [(ngModel)]="liveStyles.fontSize" class="style-control">
|
||||||
<option value="">Font Size</option>
|
<option value="">Font Size</option>
|
||||||
<option *ngFor="let size of ['12px', '14px', '16px', '18px', '20px', '24px', '28px']" [value]="size">{{ size }}</option>
|
<option *ngFor="let size of ['12px', '14px', '16px', '18px', '20px', '24px', '28px']" [value]="size">{{ size }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select [(ngModel)]="liveStyles.fontWeight">
|
<select [(ngModel)]="liveStyles.fontWeight" class="style-control">
|
||||||
<option value="">Font Weight</option>
|
<option value="">Font Weight</option>
|
||||||
<option *ngFor="let weight of ['normal', 'bold', 'lighter']" [value]="weight">{{ weight }}</option>
|
<option *ngFor="let weight of ['normal', 'bold', 'lighter']" [value]="weight">{{ weight }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select [(ngModel)]="liveStyles.textAlign">
|
<select [(ngModel)]="liveStyles.textAlign" class="style-control">
|
||||||
<option value="">Align</option>
|
<option value="">Align</option>
|
||||||
<option value="left">Left</option>
|
<option value="left">Left</option>
|
||||||
<option value="center">Center</option>
|
<option value="center">Center</option>
|
||||||
<option value="right">Right</option>
|
<option value="right">Right</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input type="color" [(ngModel)]="liveStyles.color" title="Text Color" />
|
<input type="color" [(ngModel)]="liveStyles.color" title="Text Color" class="color-picker" />
|
||||||
<input type="color" [(ngModel)]="liveStyles.background" title="Background Color" />
|
<input type="color" [(ngModel)]="liveStyles.background" title="Background Color" class="color-picker" />
|
||||||
|
|
||||||
<button (click)="applyStyleToSelection()" style="background: #555; color: white; padding: 6px 10px; border-radius: 4px;">🎨 Apply Style</button>
|
<button (click)="applyStyleToSelection()" class="action-btn style-btn">
|
||||||
|
🎨 Apply Style
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button (click)="verifyCssInSections()" class="action-btn">🔍 Verify CSS</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navbar Link Manager -->
|
<!-- Grid and Wireframe Canvas Area -->
|
||||||
<!-- <div *ngIf="selectedNavbarPage" class="navbar-link-manager">
|
<div class="visual-editor-container">
|
||||||
<h3>🔗 Navbar Link Manager ({{ selectedNavbarPage }})</h3>
|
<!-- Zoom Controls -->
|
||||||
|
<div class="zoom-controls">
|
||||||
|
<button class="zoom-btn" (click)="zoomIn()">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="zoom-btn" (click)="zoomOut()">
|
||||||
|
<i class="fa fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="zoom-btn" (click)="resetZoom()">
|
||||||
|
<i class="fa fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<!-- Canvas Area with Grid -->
|
||||||
<li *ngFor="let link of navbarLinks[selectedNavbarPage]; let i = index">
|
<div class="canvas-wrapper" cdkScrollable>
|
||||||
<input [(ngModel)]="link.label" placeholder="Link Label" />
|
<div class="canvas-boundary" [style.transform]="getTransform()" (mousedown)="startPan($event)"
|
||||||
<select [(ngModel)]="link.href">
|
(mousemove)="doPan($event)" (mouseup)="endPan()" (mouseleave)="endPan()">
|
||||||
<option *ngFor="let page of availablePages" [value]="page.href">{{ page.label }}</option>
|
|
||||||
</select>
|
|
||||||
<button (click)="removeLink(selectedNavbarPage, i)">❌</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<button (click)="addLink(selectedNavbarPage)">➕ Add Link</button>
|
<!-- Grid Background -->
|
||||||
<button (click)="applyNavbarChanges(selectedNavbarPage)">✅ Apply Changes</button>
|
<div class="grid-background"></div>
|
||||||
</div> -->
|
|
||||||
|
<div *ngIf="isLoading" class="loading-overlay">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 🔗 Navbar Link Editor Modal -->
|
<!-- Pages Container - Draggable Pages -->
|
||||||
<div *ngIf="selectedNavbarPage" class="navbar-popup-overlay">
|
<div *ngIf="!isLoading" class="pages-container" cdkDropList cdkDropListOrientation="vertical"
|
||||||
<div class="navbar-popup-content">
|
(cdkDropListDropped)="onPageDrop($event)">
|
||||||
<h3>🔗 Navbar Link Manager ({{ selectedNavbarPage }})</h3>
|
|
||||||
|
|
||||||
<ul>
|
<div *ngFor="let pageName of pageRenderOrder" class="page-card" cdkDrag
|
||||||
<li *ngFor="let link of navbarLinks[selectedNavbarPage]; let i = index">
|
[class.page-hovered]="hoveredPage === pageName" (mouseenter)="showPageTools(pageName)"
|
||||||
<input [(ngModel)]="link.label" placeholder="Link Label" />
|
(mouseleave)="hidePageTools(pageName)">
|
||||||
<select [(ngModel)]="link.href">
|
|
||||||
<option *ngFor="let page of availablePages" [value]="page.href">{{ page.label }}</option>
|
<!-- Page Header with Tools -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h3 class="page-title">{{ pageName }}</h3>
|
||||||
|
<div class="page-tools" [class.visible]="hoveredPage === pageName">
|
||||||
|
<button class="tool-btn" (click)="regenerateWireframe(pageName)" title="Regenerate">♻️</button>
|
||||||
|
<button class="tool-btn" (click)="copyToClipboard(pageName)" title="Copy HTML">📋</button>
|
||||||
|
<button class="tool-btn" (click)="downloadHtml(pageName)" title="Download HTML">⬇️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sections Container - Draggable Sections -->
|
||||||
|
<div class="sections-container" cdkDropList [id]="'section-list-' + pageName"
|
||||||
|
[cdkDropListData]="getSectionsData(pageName)" (cdkDropListDropped)="onSectionDrop($event, pageName)">
|
||||||
|
|
||||||
|
<!-- Individual Draggable Sections -->
|
||||||
|
<div *ngFor="let sectionKey of getSectionKeys(pageName); let i = index" class="section-card"
|
||||||
|
[attr.data-section-id]="sectionKey" cdkDrag [cdkDragData]="sectionKey"
|
||||||
|
(mouseenter)="showSectionTools(pageName, sectionKey)" (mouseleave)="hideSectionTools()">
|
||||||
|
|
||||||
|
<!-- Section Content -->
|
||||||
|
<div class="section-content">
|
||||||
|
<!-- Section Header -->
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-title">{{ sectionKey }}</span>
|
||||||
|
|
||||||
|
<!-- Section Actions -->
|
||||||
|
<div class="section-actions" [class.visible]="hoveredSection === sectionKey">
|
||||||
|
<button class="section-btn" title="Toggle Edit Mode"
|
||||||
|
(click)="toggleSectionEditing(pageName, sectionKey, $event)">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="section-btn" title="Remove Section" (click)="removeSection(pageName, sectionKey)">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section Preview with COMMON_CSS applied -->
|
||||||
|
<!-- Change all [innerHTML] bindings to use $any() to bypass strict checks -->
|
||||||
|
<div class="section-preview" [innerHTML]="$any(getSectionHtml(pageName, sectionKey))"
|
||||||
|
(dblclick)="toggleSectionEditing(pageName, sectionKey, $event)"
|
||||||
|
(blur)="handleDirectContentEdit($event, pageName, sectionKey)"
|
||||||
|
[attr.contenteditable]="isSectionEditable(pageName, sectionKey)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Section Button (appears on hover) -->
|
||||||
|
<div class="add-section-btn" *ngIf="hoveredSection === sectionKey"
|
||||||
|
(click)="addNewSection(pageName, sectionKey)">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Drag Handle -->
|
||||||
|
<div class="drag-handle" cdkDragHandle>
|
||||||
|
<i class="fa fa-grip-lines"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State / Add First Section -->
|
||||||
|
<div *ngIf="getSectionKeys(pageName).length === 0" class="empty-sections">
|
||||||
|
<button class="add-first-section" (click)="addNewSection(pageName)">
|
||||||
|
<i class="fa fa-plus"></i> Add First Section
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal for Adding New Section -->
|
||||||
|
<div class="modal" *ngIf="showNewSectionModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Add New Section to {{ newSectionPage }}</h3>
|
||||||
|
<button class="close-btn" (click)="cancelAddSection()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Section Name:</label>
|
||||||
|
<input type="text" [(ngModel)]="newSectionName" placeholder="Enter section name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Section Type:</label>
|
||||||
|
<select [(ngModel)]="newSectionType">
|
||||||
|
<option value="">Select a type</option>
|
||||||
|
<option value="header">Header</option>
|
||||||
|
<option value="hero">Hero</option>
|
||||||
|
<option value="features">Features</option>
|
||||||
|
<option value="gallery">Gallery</option>
|
||||||
|
<option value="pricing">Pricing</option>
|
||||||
|
<option value="testimonials">Testimonials</option>
|
||||||
|
<option value="contact">Contact</option>
|
||||||
|
<option value="footer">Footer</option>
|
||||||
</select>
|
</select>
|
||||||
<button (click)="removeLink(selectedNavbarPage, i)">❌</button>
|
</div>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div style="margin-top: 10px;">
|
<div class="form-group">
|
||||||
<button (click)="addLink(selectedNavbarPage)">➕ Add Link</button>
|
<label>Section Description:</label>
|
||||||
<button (click)="applyNavbarChanges(selectedNavbarPage)">✅ Apply</button>
|
<textarea [(ngModel)]="newSectionDescription" rows="4"
|
||||||
<button (click)="selectedNavbarPage = null">❌ Close</button>
|
placeholder="Describe the content for this section"></textarea>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ✅ Pages List -->
|
|
||||||
<div class="canvas-wrapper">
|
|
||||||
<div class="canvas-page" *ngFor="let pageName of pageRenderOrder">
|
|
||||||
<div class="canvas-header">
|
|
||||||
<button (click)="regenerateWireframe(pageName)">♻️</button>
|
|
||||||
<h3 (click)="selectNavbar(pageName)" style="cursor: pointer;">
|
|
||||||
{{ pageName }} <span title="Edit Navbar">🧭</span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="canvas-actions">
|
|
||||||
<button (click)="copyToClipboard(pageName)">📋</button>
|
|
||||||
<button (click)="downloadHtml(pageName)">⬇️</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ✅ Render and Edit Section -->
|
<div class="modal-footer">
|
||||||
<div class="canvas-section">
|
<button class="action-btn" [disabled]="!newSectionName || !newSectionType" (click)="confirmAddSection()">
|
||||||
<div [innerHTML]="pageSections[pageName]?.FullPage"
|
Add Section
|
||||||
|
</button>
|
||||||
contenteditable="true"
|
<button class="action-btn cancel-btn" (click)="cancelAddSection()">
|
||||||
class="editable-html"
|
Cancel
|
||||||
(blur)="onEdit(pageName, $event)">
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,92 +1,668 @@
|
|||||||
// .wireframe-container {
|
/* Reset and base styles */
|
||||||
// font-family: Arial, sans-serif;
|
.visual-editor-container {
|
||||||
// background: #f8f8f8;
|
* {
|
||||||
// padding: 1rem;
|
box-sizing: border-box;
|
||||||
// }
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
// .border-dashed {
|
}
|
||||||
// border-style: dashed;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
body {
|
||||||
display: flex;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
flex-wrap: wrap; /* ✅ Allow wrapping on smaller screens */
|
color: #333;
|
||||||
gap: 20px;
|
line-height: 1.5;
|
||||||
padding: 20px;
|
overflow: hidden;
|
||||||
justify-content: center;
|
}
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
}
|
||||||
|
/* Overall layout fixes */
|
||||||
|
.visual-editor-container {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
.canvas-page {
|
/* Action bar styling */
|
||||||
min-width: 300px;
|
.action-bar {
|
||||||
min-height: 500px;
|
display: flex;
|
||||||
resize: both; /* 👈 Allows both vertical and horizontal resizing */
|
justify-content: space-between;
|
||||||
overflow: auto; /* 👈 So content doesn't get cut off */
|
align-items: center;
|
||||||
border: 1px solid #ccc;
|
background: #fff;
|
||||||
border-radius: 8px;
|
padding: 8px 16px;
|
||||||
background: white;
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
padding: 20px;
|
position: sticky;
|
||||||
box-shadow: 0 0 6px rgba(0,0,0,0.1);
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-preview {
|
||||||
|
.preview-mode {
|
||||||
|
min-height: 200px;
|
||||||
|
padding: 20px;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: top left;
|
||||||
|
width: 125%;
|
||||||
|
|
||||||
|
* {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[contenteditable="true"] {
|
||||||
|
.preview-mode {
|
||||||
|
transform: none;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
* {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-controls, .right-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.regenerate-btn {
|
||||||
|
background: #e3f2fd;
|
||||||
|
color: #2196f3;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #bbdefb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.save-btn {
|
||||||
|
background: #e8f5e9;
|
||||||
|
color: #4caf50;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #c8e6c9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-control {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoom controls */
|
||||||
|
.zoom-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
z-index: 50;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvas and grid styling */
|
||||||
|
.canvas-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-boundary {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: move;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
will-change: transform;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-background {
|
||||||
|
position: absolute;
|
||||||
|
width: 5000px;
|
||||||
|
height: 5000px;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px);
|
||||||
|
transform: translate(-2500px, -2500px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pages container */
|
||||||
|
.pages-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 40px;
|
||||||
|
padding: 40px;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
width: 2000px;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page card styling */
|
||||||
|
.page-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 14px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.page-hovered {
|
||||||
|
border: 2px solid #2196f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page header */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar Editor Styles */
|
||||||
|
.navbar-controls {
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-editor {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
background: #ff4444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-link-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tools {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In component's CSS */
|
||||||
|
.pages-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-card {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
.canvas-header {
|
border: 1px solid #ddd;
|
||||||
display: flex;
|
border-radius: 8px;
|
||||||
justify-content: space-between;
|
background: white;
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
.loading-overlay {
|
||||||
.canvas-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-actions button {
|
|
||||||
margin-left: 5px;
|
|
||||||
background: #007bff;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-section {
|
|
||||||
border: 1px dashed #ccc;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.editable-html:focus {
|
|
||||||
outline: 2px solid #007bff;
|
|
||||||
background: #fcfcfc;
|
|
||||||
}
|
|
||||||
.navbar-popup-overlay {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
/* Sections container */
|
||||||
|
.sections-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
// width: 1700px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section card */
|
||||||
|
.section-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.12);
|
||||||
|
border-color: #dadada;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section header */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section preview area */
|
||||||
|
.section-preview {
|
||||||
|
padding: 12px;
|
||||||
|
min-height: 50px;
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-word;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add section button */
|
||||||
|
.add-section-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 5;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1e88e5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag handle */
|
||||||
|
.drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -14px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
width: 18px;
|
||||||
|
height: 36px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
.section-card:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty sections state */
|
||||||
|
.empty-sections {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100px;
|
||||||
|
border: 2px dashed #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-first-section {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CDK drag styles */
|
||||||
|
.cdk-drag-preview {
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3) !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
border: 2px solid #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-placeholder {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-animating {
|
||||||
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections-container.cdk-drop-list-dragging .section-card:not(.cdk-drag-placeholder) {
|
||||||
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.action-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-controls, .right-controls {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visual-editor-container {
|
||||||
|
height: calc(100vh - 110px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global modal styles (outside visual-editor scope) */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
background: rgba(0,0,0,0.5);
|
background: rgba(0,0,0,0.5);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 9999;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-popup-content {
|
.modal-content {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 20px;
|
|
||||||
width: 500px;
|
|
||||||
max-width: 90%;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90%;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section editing styles */
|
||||||
|
.edit-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1000px;
|
||||||
|
height: 80%;
|
||||||
|
max-height: 800px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-header {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-html {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-footer {
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -70,18 +70,32 @@ export class ApiRequestService {
|
|||||||
//return Observable.throw(error.message);
|
//return Observable.throw(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(url: string, urlParams?: HttpParams): Observable<any> {
|
// get(url: string, urlParams?: HttpParams): Observable<any> {
|
||||||
|
// let me = this;
|
||||||
|
// return this.http
|
||||||
|
// .get(this.appConfig.baseApiPath + url, {
|
||||||
|
// headers: this.getHeaders(),
|
||||||
|
// params: urlParams,
|
||||||
|
// }).pipe(
|
||||||
|
// catchError((error) => {
|
||||||
|
// return throwError(error || "Server error");
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
get(url: string, urlParams?: HttpParams, responseType: 'json' | 'text' = 'json'): Observable<any> {
|
||||||
let me = this;
|
let me = this;
|
||||||
return this.http
|
return this.http.get(this.appConfig.baseApiPath + url, {
|
||||||
.get(this.appConfig.baseApiPath + url, {
|
headers: this.getHeaders(),
|
||||||
headers: this.getHeaders(),
|
params: urlParams,
|
||||||
params: urlParams,
|
responseType: responseType as any, // 👈 force-cast because Angular types are strict
|
||||||
}).pipe(
|
}).pipe(
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
return throwError(error || "Server error");
|
return throwError(error || "Server error");
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loginAuthentication(url: string, body: Object): Observable<any> {
|
loginAuthentication(url: string, body: Object): Observable<any> {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user