wiredrame

This commit is contained in:
string 2025-05-23 20:12:22 +05:30
parent 457a3f35ca
commit 7b855d6553
17 changed files with 4194 additions and 1269 deletions

View File

@ -25,6 +25,7 @@
<div class=" card-block container">
<clr-timeline [clrLayout]="layout.direction">
<clr-timeline-step [clrState]="timelineStyle.step0.state">
<clr-timeline-step-header>Step 0</clr-timeline-step-header>
<clr-timeline-step-title> <span *ngIf="!appToUpdate">Application</span><span *ngIf="appToUpdate"> Update
@ -233,11 +234,6 @@
<option *ngFor="let item of selectnationality" [value]="item.id">{{item.name}}</option>
</select>
</div>
<div class="clr-col-sm-12">
<label> Supplier</label>
<select formControlName="supplier">

View File

@ -61,7 +61,8 @@ export class EditstepperComponent implements OnInit {
private _fb: FormBuilder,
private router: Router,
private route: ActivatedRoute,
private toastr: ToastrService,) { }
private toastr: ToastrService,
) { }
ngOnInit(): void {
// this.college = new College();
@ -150,30 +151,6 @@ export class EditstepperComponent implements OnInit {
block2: "clr-col-lg-9 clr-col-12 ",
}
}
getById(id: number) {
// this.mainService.getBywfId(id).subscribe(
// (data) => {
// this.college = data;
// },
// (err) => {
// console.log(err);
// }
// );
// this.currentservice.getById(id).subscribe(
// (data) => {
// this.student = data;
// var current = JSON.parse(data.current_json);
// this.timelineStyle = current;
// console.log("data", data);
// },
// (err) => {
// console.log(err);
// }
// );
}
onSave() {
@ -190,8 +167,6 @@ export class EditstepperComponent implements OnInit {
}
}
);
return;
}
this.onCreate();
@ -245,8 +220,6 @@ export class EditstepperComponent implements OnInit {
tested: 1,
updated: 1,
published: 1,
created: 1,
};
@ -357,13 +330,6 @@ export class EditstepperComponent implements OnInit {
updategenderEdit(gender: string): void { this.rowSelected.gender = gender }
rsModaldescription = false;
goToReplaceStringdescription(row) {
this.rowSelected = row; this.rsModaldescription = true;

View File

@ -76,16 +76,20 @@ export class SubmenuComponent implements OnInit {
return;
}
this.menuservice.create1(this.entryForm.value).subscribe((data) => {
console.log(data);
console.log('after add ', data);
if (data) {
this.toastr.success('Added successfully');
this.ngOnInit();
}
},
(error) => {
console.log('Error in adding data...', +error);
console.log('Error in adding data...', error);
if (error) {
this.toastr.error('Not added Data Getting Some Error');
}
this.getdata();
});
this.modalAdd = false;
@ -106,16 +110,19 @@ export class SubmenuComponent implements OnInit {
this.modaldelete = false;
console.log("in delete " + id);
this.menuservice.delete1(id).subscribe((data) => {
console.log(data);
console.log('after delete', data);
if (data) {
this.toastr.success('Deleted successfully');
}
this.ngOnInit();
},
(error) => {
console.log('Error in adding data...', +error);
console.log('Error in adding data...', error);
if (error) {
this.toastr.error('Not Deleted Data Getting Some Error');
}
this.ngOnInit();
});
}
@ -126,12 +133,16 @@ export class SubmenuComponent implements OnInit {
if (data) {
this.toastr.success('Updated successfully');
}
this.getdata();
},
(error) => {
console.log('Error in adding data...', +error);
console.log('Error in adding data...', error);
if (error) {
this.toastr.error('Not updated Data Getting Some Error');
}
this.getdata();
});
}
}

View File

@ -61,8 +61,8 @@
display: flex;
flex-direction: column;
align-items: center;
min-width: 5000px; /* Increased from 2000px */
min-height: 3000px; /* Increased from 1000px */
min-width: 8000px; /* Increased from 2000px */
min-height: 5000px; /* Increased from 1000px */
}
.tree-row {
@ -760,3 +760,65 @@ textarea.form-control {
width: 50%;
left: 0;
}
.section-category {
margin-bottom: 2rem;
}
.section-category h4 {
color: #333;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #e0e0e0;
}
.drop-zone-highlight {
background-color: #e3f2fd !important;
border: 2px dashed #2196f3 !important;
}
.node-container[draggable="true"]:hover {
cursor: grab;
}
.node-container[draggable="true"]:active {
cursor: grabbing;
}
/* Drag and Drop Styles */
.node-container.dragging {
opacity: 0.5;
transform: scale(0.95);
transition: all 0.2s ease;
}
.drop-zone {
transition: all 0.2s ease;
}
.drop-zone.drag-over {
background-color: rgba(0, 123, 255, 0.1);
border: 2px dashed #007bff;
border-radius: 8px;
transform: scale(1.02);
}
.drop-zone.drag-over .node-card {
border-color: #007bff;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
}
/* Section template styles */
.no-custom-sections {
margin: 10px 0;
}
.section-templates-container h4 {
margin: 15px 0 10px 0;
color: #333;
font-weight: 600;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.section-templates-container h4:first-child {
margin-top: 0;
}

View File

@ -74,11 +74,27 @@
</div>
<div class="offcanvas-body">
<div class="section-templates-container">
<!-- Default Sections -->
<h4>Default Sections</h4>
<div class="section-template" *ngFor="let template of sectionTemplates"
(click)="addSectionFromTemplate(template)">
<div class="section-template-title">{{ template.type }}</div>
<div class="section-template-desc">{{ template.description }}</div>
</div>
<!-- Custom Sections -->
<h4 style="margin-top: 20px;">Custom Sections</h4>
<div class="section-template" *ngFor="let customSection of customSections"
(click)="addCustomSectionFromList(customSection)">
<div class="section-template-title">{{ customSection.type }}</div>
<div class="section-template-desc">{{ customSection.description }}</div>
</div>
<!-- Message when no custom sections -->
<div *ngIf="customSections.length === 0" class="no-custom-sections"
style="padding: 15px; text-align: center; color: #666; font-style: italic; border: 1px dashed #ccc; border-radius: 4px;">
No custom sections created yet. Create one below!
</div>
</div>
<div class="custom-section-form">
<h4>Custom Section</h4>
@ -99,7 +115,7 @@
</div>
<!-- Edit Section Offcanvas -->
<div class="offcanvas edit-section" [class.show]="showEditSectionOffcanvas">
<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()">
@ -114,7 +130,8 @@
</div>
<div class="form-group mt-3">
<label class="form-label">Content</label>
<textarea class="form-control" [(ngModel)]="selectedSection && selectedSection.content" rows="8"
<!-- Fixed binding here -->
<textarea class="form-control" [(ngModel)]="selectedSection.content" *ngIf="selectedSection" rows="8"
placeholder="Enter content"></textarea>
</div>
<div class="form-actions mt-4">
@ -122,7 +139,8 @@
<button class="btn btn-primary" (click)="saveSection()">Save Changes</button>
</div>
</div>
</div>
</div>
<div class="modal" [class.show]="showAddChildModal">
<div class="modal-backdrop" *ngIf="showAddChildModal" (click)="closeAddChildModal()"></div>
<div class="modal-dialog" *ngIf="showAddChildModal">
@ -149,22 +167,43 @@
<div class="spinner"></div>
<div class="loading-text">Processing...</div>
</div>
<ng-template #pageTemplate let-page>
<div class="node-container" draggable="true" (dragstart)="onPageDragStart(page)" (dragend)="onPageDragEnd()"
(dragover)="$event.preventDefault()" (drop)="onPageDrop(page); $event.preventDefault()">
<div class="connection-line" *ngIf="page.parent && page.parent !== rootPage"></div>
<!-- Updated page template with proper connector lines -->
<ng-template #pageTemplate let-page>
<div class="node-container drop-zone"
draggable="true"
(dragstart)="onPageDragStart(page, $event)"
(dragend)="onPageDragEnd($event)"
(dragover)="onPageDragOver($event)"
(dragleave)="onPageDragLeave($event)"
(drop)="onPageDrop(page, $event)"
style="position: relative; margin: 20px 0;">
<!-- Single consistent connector line from parent -->
<div *ngIf="page.parent && page.parent !== rootPage"
style="position: absolute; top: -20px; left: 50%; width: 2px; height: 20px; background-color: #6c757d !important; z-index: 1; transform: translateX(-50%);"></div>
<div class="node-card" [class.selected]="selectedPage === page" (click)="selectPage(page)">
<div class="node-header">
<i class="node-icon" [class]="getNodeIcon(page)"></i>
<span class="node-title">{{ page.name }}</span>
</div>
<div class="sections-container" cdkDropList [cdkDropListData]="page.sections"
(cdkDropListDropped)="onSectionDrop($event)">
<!-- Update the section card template within the #pageTemplate -->
<!-- Section card with improved hover button placement -->
<!-- Show message and add button for empty pages -->
<div *ngIf="page.sections.length === 0" class="empty-page-container"
style="padding: 20px; text-align: center; border: 2px dashed #ccc; margin: 10px 0; border-radius: 8px;">
<p style="color: #666; margin: 0 0 10px 0; font-style: italic;">No sections added yet</p>
<button class="btn btn-primary btn-sm" (click)="showAddSectionForEmptyPage(page, $event)"
style="background-color: #007bff !important; border: none; padding: 8px 16px; border-radius: 4px; color: white;">
<i class="fas fa-plus"></i> Add First Section
</button>
</div>
<!-- Existing sections -->
<div class="section-card" *ngFor="let section of page.sections; let i = index" cdkDrag [cdkDragData]="section"
(click)="selectSection(section); $event.stopPropagation()">
<div class="section-title">{{ section.type }}</div>
<div class="section-content" [innerHTML]="getSectionContentHtml(section.content)"></div>
<div class="section-actions">
@ -175,15 +214,19 @@
<i class="fas fa-trash"></i>
</button>
</div>
<!-- Repositioned add button that appears on hover -->
<div class="add-section-hover-btn" (click)="openAddSectionOffcanvas(page, i, $event)" title="Add Section">
<i class="fas fa-plus"></i>
</div>
</div>
<!-- <div class="add-section-btn" (click)="openSectionOffcanvas(page); $event.stopPropagation()">
<!-- Add section button for pages with existing sections -->
<div *ngIf="page.sections.length > 0" class="add-section-btn"
(click)="openAddSectionOffcanvas(page, -1, $event)"
style="padding: 10px; text-align: center; border: 1px dashed #007bff; margin-top: 10px; border-radius: 4px; cursor: pointer; color: #007bff;">
<i class="fas fa-plus"></i> Add Section
</div> -->
</div>
</div>
<div class="node-actions">
<button class="node-action-btn" title="Add Child Page"
(click)="showAddChildPageModal(page); $event.stopPropagation()">
@ -197,11 +240,25 @@
<i class="fas fa-plus"></i>
</div>
</div>
<div class="children-container" *ngIf="page.children && page.children.length > 0">
<!-- Connector to children - only show if has children -->
<div *ngIf="page.children && page.children.length > 0">
<!-- Vertical line down from current node -->
<div style="position: absolute; bottom: -20px; left: 50%; width: 2px; height: 20px; background-color: #6c757d !important; z-index: 1; transform: translateX(-50%);"></div>
<!-- Horizontal line connecting all children (only if more than 1 child) -->
<div *ngIf="page.children.length > 1" class="horizontal-connector"
style="position: absolute; bottom: -20px; height: 2px; background-color: #6c757d !important; z-index: 1;"
[style.left.px]="getHorizontalConnectorLeft(page.children.length)"
[style.width.px]="getHorizontalConnectorWidth(page.children.length)"></div>
</div>
<!-- Children container -->
<div class="children-container" *ngIf="page.children && page.children.length > 0"
style="display: flex; justify-content: center; gap: 40px; margin-top: 40px;">
<ng-container *ngFor="let childPage of page.children">
<ng-container *ngTemplateOutlet="pageTemplate; context: { $implicit: childPage }"></ng-container>
</ng-container>
</div>
</div>
</ng-template>
</div>
</ng-template>

View File

@ -138,6 +138,21 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
}
];
customSections = [
{
type: 'Visa Application',
description: 'Visa application form and process',
content: 'Complete visa application with required documents and processing information.'
},
{
type: 'Visa Application 2',
description: 'Alternative visa application layout',
content: 'Alternative visa application design with step-by-step guidance.'
}
];
constructor(
private siteTreeService: SiteTreeservice,
private route: ActivatedRoute,
@ -311,7 +326,7 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
// Position the grid - centered horizontally, but align top of tree with top of viewport
this.currentScale = scale;
this.gridTranslateX = -700; // Set left position to -900px
this.gridTranslateX = -1300; // Set left position to -900px
// Position vertically to place root node near top with some padding
const topPadding = 50; // Pixels from top of viewport
@ -695,18 +710,18 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
// 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);
// Find the original section in the parent page by reference
const originalSection = this.currentParentPage.sections.find(s => s === this.selectedSection);
if (index !== -1) {
// Update the section with edited values
this.currentParentPage.sections[index] = { ...this.selectedSection };
if (originalSection) {
// Update the original section directly
originalSection.type = this.selectedSection.type;
originalSection.content = this.selectedSection.content;
this.hasUnsavedChanges = true;
}
}
this.closeEditSectionOffcanvas();
}
}
// Add section from a template
addSectionFromTemplate(template: any): void {
@ -737,36 +752,78 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
// Similar update for addCustomSection
addCustomSection(): void {
if (this.currentParentPage && this.customSectionType) {
// If in edit mode, update the selected section
if (this.editMode === 'section' && this.selectedSection) {
this.selectedSection.type = this.customSectionType;
this.selectedSection.content = this.customSectionContent || '';
} else {
// Otherwise, create a new section
const newSection: Section = {
type: this.customSectionType,
content: this.customSectionContent || ''
};
// Add to current page
if (this.currentSectionIndex >= 0) {
this.currentParentPage.sections.splice(this.currentSectionIndex + 1, 0, newSection);
} else {
this.currentParentPage.sections.push(newSection);
}
// Add to custom sections list if it doesn't exist
const existingCustomSection = this.customSections.find(cs => cs.type === this.customSectionType);
if (!existingCustomSection) {
this.customSections.push({
type: this.customSectionType,
description: 'Custom section',
content: this.customSectionContent || ''
});
}
// Add to available section types
if (!this.availableSectionTypes.includes(this.customSectionType)) {
this.availableSectionTypes.push(this.customSectionType);
}
this.hasUnsavedChanges = true;
this.closeAddSectionOffcanvas();
}
}
addCustomSectionFromList(customSection: any): void {
if (this.currentParentPage) {
const newSection: Section = {
type: customSection.type,
content: customSection.content
};
// Insert at specific position if provided, otherwise add to end
if (this.currentSectionIndex >= 0) {
this.currentParentPage.sections.splice(this.currentSectionIndex + 1, 0, newSection);
} else {
this.currentParentPage.sections.push(newSection);
}
if (!this.availableSectionTypes.includes(customSection.type)) {
this.availableSectionTypes.push(customSection.type);
}
this.hasUnsavedChanges = true;
this.closeOffcanvas();
}
this.closeAddSectionOffcanvas();
}
}
// Edit section
// Edit section - modify this function
editSection(section: Section, event: Event): void {
event.stopPropagation();
this.openEditSectionOffcanvas(section, this.selectedPage, event);
}
// Store reference to the original section
this.selectedSection = section; // Don't clone, use direct reference
this.currentParentPage = this.selectedPage;
this.editMode = 'section';
this.showEditSectionOffcanvas = true;
// Listen for clicks outside offcanvas
setTimeout(() => {
document.addEventListener('click', this.handleOutsideClick);
}, 10);
}
// Cancel edit
cancelEdit(): void {
@ -802,19 +859,81 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
// DRAG AND DROP METHODS
// Handle page drag start
onPageDragStart(page: Page): void {
onPageDragStart(page: Page, event: DragEvent): void {
this.draggedPage = page;
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/html', page.name);
}
// Add visual feedback
setTimeout(() => {
const draggedElement = event.target as HTMLElement;
if (draggedElement) {
draggedElement.classList.add('dragging');
}
}, 0);
}
onPageDragOver(event: DragEvent): void {
event.preventDefault();
event.stopPropagation();
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move';
}
// Add visual feedback
const dropZone = event.currentTarget as HTMLElement;
if (dropZone) {
dropZone.classList.add('drag-over');
}
}
onPageDragLeave(event: DragEvent): void {
const dropZone = event.currentTarget as HTMLElement;
if (dropZone) {
dropZone.classList.remove('drag-over');
}
}
// Handle page drag end
onPageDragEnd(): void {
this.draggedPage = null;
this.dropTargetPage = null;
onPageDragEnd(event: DragEvent): void {
// Remove visual feedback
const draggedElement = event.target as HTMLElement;
if (draggedElement) {
draggedElement.classList.remove('dragging');
}
// Remove all drop zone highlights
document.querySelectorAll('.drop-zone').forEach(el => {
el.classList.remove('drag-over');
});
this.draggedPage = null;
this.dropTargetPage = null;
}
// Handle page drop
onPageDrop(targetPage: Page): void {
if (!this.draggedPage || this.draggedPage === targetPage) return;
onPageDrop(targetPage: Page, event: DragEvent): void {
event.preventDefault();
event.stopPropagation();
// Remove visual feedback
const dropZone = event.currentTarget as HTMLElement;
if (dropZone) {
dropZone.classList.remove('drag-over');
}
if (!this.draggedPage || this.draggedPage === targetPage) {
return;
}
// Prevent dropping a parent into its own child
if (this.isDescendant(targetPage, this.draggedPage)) {
console.warn('Cannot drop a parent into its own descendant');
return;
}
// Remove from current parent
if (this.draggedPage.parent && this.draggedPage.parent.children) {
@ -834,8 +953,19 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
targetPage.children.push(this.draggedPage);
this.hasUnsavedChanges = true;
console.log(`Moved "${this.draggedPage.name}" to "${targetPage.name}"`);
}
private isDescendant(possibleDescendant: Page, ancestor: Page): boolean {
if (!ancestor.children) return false;
for (const child of ancestor.children) {
if (child === possibleDescendant) return true;
if (this.isDescendant(possibleDescendant, child)) return true;
}
return false;
}
// Handle section drag drop
onSectionDrop(event: CdkDragDrop<Section[]>): void {
if (event.previousContainer === event.container) {
@ -868,6 +998,28 @@ export class TreeNodeComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
showAddSectionForEmptyPage(page: Page, event: Event): void {
event.stopPropagation();
this.openAddSectionOffcanvas(page, -1, event);
}
// Add these helper methods to your component for proper connector positioning
getHorizontalConnectorLeft(childrenCount: number): number {
// Calculate left position for horizontal connector
const nodeWidth = 250; // Approximate node width
const gap = 40; // Gap between nodes
const totalWidth = (childrenCount * nodeWidth) + ((childrenCount - 1) * gap);
return (totalWidth / 2) - (totalWidth / childrenCount / 2);
}
getHorizontalConnectorWidth(childrenCount: number): number {
// Calculate width of horizontal connector
const nodeWidth = 250; // Approximate node width
const gap = 40; // Gap between nodes
return (childrenCount - 1) * (nodeWidth + gap);
}
// Save tree data
saveTreeData(silent: boolean = false): void {
const outputData = this.generateOutputJson();

View File

@ -1,5 +1,7 @@
export const COMMON_CSS = `
body {
font-family: sans-serif;
margin: 0;
padding: 0;
@ -1068,6 +1070,7 @@ body {
background: #f8f8f8;
font-family: sans-serif;
margin: 0;
padding:20px;
}
.footer-1-section {
@ -1204,12 +1207,14 @@ body {
.footer-2-wrapper {
font-family: sans-serif;
background: #f5f5f5;
padding: 20px;
}
.footer-2-section {
background: #fff;
padding: 40px 30px 20px;
border-top: 1px solid #eee;
}
.footer-2-top {
@ -1769,6 +1774,358 @@ body {
background-color: #005fcc;
}
/* Master Reset & Base Styles */
html, body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
min-height: 100% !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
line-height: 1.6 !important;
color: #333 !important;
background-color: #fff !important;
box-sizing: border-box !important;
overflow-x: hidden !important;
text-align: left !important; /* Override center alignment */
}
*, *::before, *::after {
box-sizing: border-box !important;
}
/* Common Container */
.container {
max-width: 1200px !important;
width: 100% !important;
margin: 0 auto !important;
padding: 0 20px !important;
}
/* Section Styling for Consistent Flow */
section {
padding: 80px 0 !important;
position: relative !important;
width: 100% !important;
background-color: #fff !important;
}
section:nth-child(even) {
background-color: #f9f9f9 !important;
}
/* Typography Hierarchy */
h1, h2, h3, h4, h5, h6 {
margin-top: 0 !important;
margin-bottom: 0.5em !important;
line-height: 1.3 !important;
font-weight: 700 !important;
color: #111 !important;
}
h1 {
font-size: 2.5rem !important;
}
h2 {
font-size: 2rem !important;
}
h3 {
font-size: 1.75rem !important;
}
h4 {
font-size: 1.5rem !important;
}
h5 {
font-size: 1.25rem !important;
}
h6 {
font-size: 1rem !important;
}
p {
margin-top: 0 !important;
margin-bottom: 1rem !important;
color: #555 !important;
}
/* Link Styling */
a {
color: #0066cc !important;
text-decoration: none !important;
transition: all 0.3s ease !important;
}
a:hover {
color: #004499 !important;
text-decoration: underline !important;
}
/* Button System */
.btn, button,
.hero-1-btn, .hero-2-btn, .grid-11-btn, .grid-12-btn,
.split-8-btn, .split-10-btn, .text-1-btn, .text-2-btn,
.cta-3-btn, .faq-4-footer-btn, .faq-5-contact-btn,
.faq-6-view-more-btn, .visa-order-btn, .nav-1-btn {
display: inline-block !important;
padding: 12px 24px !important;
font-size: 16px !important;
font-weight: 600 !important;
text-align: center !important;
border-radius: 6px !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
border: 2px solid transparent !important;
letter-spacing: 0.5px !important;
text-decoration: none !important;
}
.btn-primary, .hero-1-solid, .hero-2-solid, .text-1-solid, .text-2-solid, .cta-3-solid, .nav-1-solid {
background-color: #0066cc !important;
color: #fff !important;
border-color: #0066cc !important;
}
.btn-primary:hover, .hero-1-solid:hover, .hero-2-solid:hover, .text-1-solid:hover, .text-2-solid:hover, .cta-3-solid:hover, .nav-1-solid:hover {
background-color: #004499 !important;
border-color: #004499 !important;
}
.btn-outline, .hero-1-outline, .hero-2-outline, .text-1-outline, .text-2-outline, .cta-3-outline, .nav-1-outline {
background-color: transparent !important;
color: #0066cc !important;
border-color: #0066cc !important;
}
.btn-outline:hover, .hero-1-outline:hover, .hero-2-outline:hover, .text-1-outline:hover, .text-2-outline:hover, .cta-3-outline:hover, .nav-1-outline:hover {
background-color: #0066cc !important;
color: #fff !important;
}
/* Image Styling */
img {
max-width: 100% !important;
height: auto !important;
display: block !important;
}
/* Form Elements */
input, textarea, select {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
padding: 12px !important;
border: 1px solid #ccc !important;
border-radius: 6px !important;
width: 100% !important;
transition: border-color 0.3s ease !important;
}
input:focus, textarea:focus, select:focus {
outline: none !important;
border-color: #0066cc !important;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1) !important;
}
/* Navigation Override */
.nav-1-navbar {
background-color: #fff !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05) !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
}
/* Blog Section Fixes */
.tn1-blog-section, .blog-2-section {
background-color: inherit !important;
padding: 80px 20px !important;
}
.tn1-blog-card, .blog-2-card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
overflow: hidden !important;
}
.tn1-blog-card:hover, .blog-2-card:hover {
transform: translateY(-5px) !important;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12) !important;
}
/* FAQ Sections */
.faq-4-section, .faq-5-section, .faq-6-block, .faq-7-container {
background-color: inherit !important;
}
.faq-4-question, .faq-5-question, .faq-6-question, .faq-7-question {
background-color: #f1f1f1 !important;
transition: background-color 0.3s ease !important;
}
.faq-4-question:hover, .faq-5-question:hover, .faq-6-question:hover, .faq-7-question:hover {
background-color: #e7e7e7 !important;
}
/* Footer Override */
.footer-1-section, .footer-2-section {
background-color: #f8f8f8 !important;
border-top: 1px solid #eee !important;
}
/* Contact Section */
.contact-1-section {
background-color: #fff !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
}
.contact-1-submit-btn {
background-color: #0066cc !important;
color: #fff !important;
}
.contact-1-submit-btn:hover {
background-color: #004499 !important;
}
/* Hero Section Enhancements */
.hero-1-two-column, .hero-2-centered-hero, .hero-9-section {
background-color: inherit !important;
}
.hero-1-placeholder, .hero-2-placeholder, .hero-9-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
overflow: hidden !important;
position: relative !important;
}
/* Split Sections */
.split-8-section, .split-10-section {
background-color: inherit !important;
}
.split-8-placeholder, .split-10-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
}
/* Testimonial Styling */
testimonial {
display: block !important;
max-width: 800px !important;
margin: 40px auto !important;
padding: 35px !important;
background-color: #fff !important;
border-radius: 10px !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08) !important;
font-style: italic !important;
color: #444 !important;
position: relative !important;
line-height: 1.8 !important;
font-size: 1.1rem !important;
border-left: 5px solid #0066cc !important;
}
testimonial::before {
content: '"' !important;
font-size: 5rem !important;
color: #0066cc !important;
position: absolute !important;
left: 15px !important;
top: -10px !important;
opacity: 0.2 !important;
font-family: Georgia, serif !important;
}
section:nth-child(even) testimonial {
background-color: #fff !important;
}
/* Custom styles container */
styles {
display: none !important;
}
/* Responsive Adjustments */
@media (max-width: 992px) {
h1 {
font-size: 2rem !important;
}
h2 {
font-size: 1.75rem !important;
}
section {
padding: 60px 0 !important;
}
}
@media (max-width: 768px) {
section {
padding: 50px 0 !important;
}
.hero-1-two-column, .split-8-section, .split-10-section {
flex-direction: column !important;
}
.btn, button {
width: 100% !important;
}
.hero-1-button-group, .hero-2-button-group {
flex-direction: column !important;
width: 100% !important;
}
}
@media (max-width: 576px) {
body {
font-size: 15px !important;
}
h1 {
font-size: 1.75rem !important;
}
h2 {
font-size: 1.5rem !important;
}
section {
padding: 40px 0 !important;
}
testimonial {
padding: 25px !important;
font-size: 1rem !important;
}
}
/* Animations and Transitions */
.fade-in {
animation: fadeIn 1s ease-in-out !important;
}
@keyframes fadeIn {
from {
opacity: 0 !important;
transform: translateY(20px) !important;
}
to {
opacity: 1 !important;
transform: translateY(0) !important;
}
}
/* Ensure smooth scrolling */
html {
scroll-behavior: smooth !important;
}
`;

View File

@ -1,4 +1,94 @@
export const COMMON_CSS2 = `
/* Reset and base styles */
html, body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
min-height: 100% !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
line-height: 1.6 !important;
color: #333 !important;
background-color: #f9f9f9 !important;
box-sizing: border-box !important;
}
/* Content container to ensure consistent padding/margins */
/* Alternate section background for visual separation */
section:nth-child(even) {
background-color: #fff !important;
}
/* Typography enhancements */
h1, h2, h3, h4, h5, h6 {
margin-top: 0 !important;
line-height: 1.2 !important;
font-weight: 600 !important;
}
/* Responsive adjustments */
@media (max-width: 768px) {
section {
padding: 40px 0 !important;
}
}
/* Smooth scrolling for a more unified experience */
html {
scroll-behavior: smooth !important;
}
/* Button consistency */
.button, button, input[type="button"], input[type="submit"] {
display: inline-block !important;
padding: 10px 20px !important;
border-radius: 4px !important;
background-color: #0066cc !important;
color: white !important;
border: none !important;
cursor: pointer !important;
transition: background-color 0.3s ease !important;
}
.button:hover, button:hover, input[type="button"]:hover, input[type="submit"]:hover {
background-color: #004499 !important;
}
/* Testimonial styling */
testimonial {
display: block !important;
max-width: 800px !important;
margin: 40px auto !important;
padding: 30px !important;
background-color: #fff !important;
border-radius: 8px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
font-style: italic !important;
color: #555 !important;
position: relative !important;
line-height: 1.8 !important;
font-size: 1.1rem !important;
}
testimonial::before {
content: '"' !important;
font-size: 4rem !important;
color: #0066cc !important;
position: absolute !important;
left: 10px !important;
top: -10px !important;
opacity: 0.2 !important;
font-family: Georgia, serif !important;
}
/* For dark backgrounds */
section:nth-child(even) testimonial {
background-color: #f9f9f9 !important;
}
.section-preview {
/* ===== RESET & BASE ===== */

View File

@ -1,5 +1,390 @@
export const Download_Css = `
body {
/* Master Reset & Base Styles */
html, body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
min-height: 100% !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
line-height: 1.6 !important;
color: #333 !important;
background-color: #fff !important;
box-sizing: border-box !important;
overflow-x: hidden !important;
text-align: left !important; /* Override center alignment */
}
*, *::before, *::after {
box-sizing: border-box !important;
}
/* Common Container */
.container {
max-width: 1200px !important;
width: 100% !important;
margin: 0 auto !important;
padding: 0 20px !important;
}
/* Section Styling for Consistent Flow */
section {
padding: 80px 0 !important;
position: relative !important;
width: 100% !important;
background-color: #fff !important;
}
section:nth-child(even) {
background-color: #f9f9f9 !important;
}
/* Typography Hierarchy */
h1, h2, h3, h4, h5, h6 {
margin-top: 0 !important;
margin-bottom: 0.5em !important;
line-height: 1.3 !important;
font-weight: 700 !important;
color: #111 !important;
}
h1 {
font-size: 2.5rem !important;
}
h2 {
font-size: 2rem !important;
}
h3 {
font-size: 1.75rem !important;
}
h4 {
font-size: 1.5rem !important;
}
h5 {
font-size: 1.25rem !important;
}
h6 {
font-size: 1rem !important;
}
p {
margin-top: 0 !important;
margin-bottom: 1rem !important;
color: #555 !important;
}
/* Link Styling */
a {
color: #0066cc !important;
text-decoration: none !important;
transition: all 0.3s ease !important;
}
a:hover {
color: #004499 !important;
text-decoration: underline !important;
}
/* Button System */
.btn, button,
.hero-1-btn, .hero-2-btn, .grid-11-btn, .grid-12-btn,
.split-8-btn, .split-10-btn, .text-1-btn, .text-2-btn,
.cta-3-btn, .faq-4-footer-btn, .faq-5-contact-btn,
.faq-6-view-more-btn, .visa-order-btn, .nav-1-btn {
display: inline-block !important;
padding: 12px 24px !important;
font-size: 16px !important;
font-weight: 600 !important;
text-align: center !important;
border-radius: 6px !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
border: 2px solid transparent !important;
letter-spacing: 0.5px !important;
text-decoration: none !important;
}
.btn-primary, .hero-1-solid, .hero-2-solid, .text-1-solid, .text-2-solid, .cta-3-solid, .nav-1-solid {
background-color: #0066cc !important;
color: #fff !important;
border-color: #0066cc !important;
}
.btn-primary:hover, .hero-1-solid:hover, .hero-2-solid:hover, .text-1-solid:hover, .text-2-solid:hover, .cta-3-solid:hover, .nav-1-solid:hover {
background-color: #004499 !important;
border-color: #004499 !important;
}
.btn-outline, .hero-1-outline, .hero-2-outline, .text-1-outline, .text-2-outline, .cta-3-outline, .nav-1-outline {
background-color: transparent !important;
color: #0066cc !important;
border-color: #0066cc !important;
}
.btn-outline:hover, .hero-1-outline:hover, .hero-2-outline:hover, .text-1-outline:hover, .text-2-outline:hover, .cta-3-outline:hover, .nav-1-outline:hover {
background-color: #0066cc !important;
color: #fff !important;
}
/* Image Styling */
img {
max-width: 100% !important;
height: auto !important;
display: block !important;
}
/* Form Elements */
input, textarea, select {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
padding: 12px !important;
border: 1px solid #ccc !important;
border-radius: 6px !important;
width: 100% !important;
transition: border-color 0.3s ease !important;
}
input:focus, textarea:focus, select:focus {
outline: none !important;
border-color: #0066cc !important;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1) !important;
}
/* Navigation Override */
.nav-1-navbar {
background-color: #fff !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05) !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
}
/* Blog Section Fixes */
.tn1-blog-section, .blog-2-section {
background-color: inherit !important;
padding: 80px 20px !important;
}
.tn1-blog-card, .blog-2-card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
overflow: hidden !important;
}
.tn1-blog-card:hover, .blog-2-card:hover {
transform: translateY(-5px) !important;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12) !important;
}
/* FAQ Sections */
.faq-4-section, .faq-5-section, .faq-6-block, .faq-7-container {
background-color: inherit !important;
}
.faq-4-question, .faq-5-question, .faq-6-question, .faq-7-question {
background-color: #f1f1f1 !important;
transition: background-color 0.3s ease !important;
}
.faq-4-question:hover, .faq-5-question:hover, .faq-6-question:hover, .faq-7-question:hover {
background-color: #e7e7e7 !important;
}
/* Footer Override */
.footer-1-section, .footer-2-section {
background-color: #f8f8f8 !important;
border-top: 1px solid #eee !important;
}
/* Contact Section */
.contact-1-section {
background-color: #fff !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
}
.contact-1-submit-btn {
background-color: #0066cc !important;
color: #fff !important;
}
.contact-1-submit-btn:hover {
background-color: #004499 !important;
}
/* Hero Section Enhancements */
.hero-1-two-column, .hero-2-centered-hero, .hero-9-section {
background-color: inherit !important;
}
.hero-1-placeholder, .hero-2-placeholder, .hero-9-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
overflow: hidden !important;
position: relative !important;
}
/* Split Sections */
.split-8-section, .split-10-section {
background-color: inherit !important;
}
.split-8-placeholder, .split-10-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
}
/* Testimonial Styling */
testimonial {
display: block !important;
max-width: 800px !important;
margin: 40px auto !important;
padding: 35px !important;
background-color: #fff !important;
border-radius: 10px !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08) !important;
font-style: italic !important;
color: #444 !important;
position: relative !important;
line-height: 1.8 !important;
font-size: 1.1rem !important;
border-left: 5px solid #0066cc !important;
}
testimonial::before {
content: '"' !important;
font-size: 5rem !important;
color: #0066cc !important;
position: absolute !important;
left: 15px !important;
top: -10px !important;
opacity: 0.2 !important;
font-family: Georgia, serif !important;
}
section:nth-child(even) testimonial {
background-color: #fff !important;
}
/* Custom styles container */
styles {
display: none !important;
}
/* Responsive Adjustments */
@media (max-width: 992px) {
h1 {
font-size: 2rem !important;
}
h2 {
font-size: 1.75rem !important;
}
section {
padding: 60px 0 !important;
}
}
@media (max-width: 768px) {
section {
padding: 50px 0 !important;
}
.hero-1-two-column, .split-8-section, .split-10-section {
flex-direction: column !important;
}
.btn, button {
width: 100% !important;
}
.hero-1-button-group, .hero-2-button-group {
flex-direction: column !important;
width: 100% !important;
}
}
@media (max-width: 576px) {
body {
font-size: 15px !important;
}
h1 {
font-size: 1.75rem !important;
}
h2 {
font-size: 1.5rem !important;
}
section {
padding: 40px 0 !important;
}
testimonial {
padding: 25px !important;
font-size: 1rem !important;
}
}
/* Animations and Transitions */
.fade-in {
animation: fadeIn 1s ease-in-out !important;
}
@keyframes fadeIn {
from {
opacity: 0 !important;
transform: translateY(20px) !important;
}
to {
opacity: 1 !important;
transform: translateY(0) !important;
}
}
/* Ensure smooth scrolling */
html {
scroll-behavior: smooth !important;
}
/* Testimonial styling */
testimonial {
display: block !important;
max-width: 800px !important;
margin: 40px auto !important;
padding: 30px !important;
background-color: #fff !important;
border-radius: 8px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
font-style: italic !important;
color: #555 !important;
position: relative !important;
line-height: 1.8 !important;
font-size: 1.1rem !important;
}
testimonial::before {
content: '"' !important;
font-size: 4rem !important;
color: #0066cc !important;
position: absolute !important;
left: 10px !important;
top: -10px !important;
opacity: 0.2 !important;
font-family: Georgia, serif !important;
}
/* For dark backgrounds */
section:nth-child(even) testimonial {
background-color: #f9f9f9 !important;
}
font-family: sans-serif;
margin: 0;
padding: 0;

View File

@ -13,6 +13,357 @@ body {
color: #222;
line-height: 1.6;
}
/* Master Reset & Base Styles */
html, body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
min-height: 100% !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
line-height: 1.6 !important;
color: #333 !important;
background-color: #fff !important;
box-sizing: border-box !important;
overflow-x: hidden !important;
text-align: left !important; /* Override center alignment */
}
*, *::before, *::after {
box-sizing: border-box !important;
}
/* Common Container */
.container {
max-width: 1200px !important;
width: 100% !important;
margin: 0 auto !important;
padding: 0 20px !important;
}
/* Section Styling for Consistent Flow */
section {
padding: 80px 0 !important;
position: relative !important;
width: 100% !important;
background-color: #fff !important;
}
section:nth-child(even) {
background-color: #f9f9f9 !important;
}
/* Typography Hierarchy */
h1, h2, h3, h4, h5, h6 {
margin-top: 0 !important;
margin-bottom: 0.5em !important;
line-height: 1.3 !important;
font-weight: 700 !important;
color: #111 !important;
}
h1 {
font-size: 2.5rem !important;
}
h2 {
font-size: 2rem !important;
}
h3 {
font-size: 1.75rem !important;
}
h4 {
font-size: 1.5rem !important;
}
h5 {
font-size: 1.25rem !important;
}
h6 {
font-size: 1rem !important;
}
p {
margin-top: 0 !important;
margin-bottom: 1rem !important;
color: #555 !important;
}
/* Link Styling */
a {
color: #0066cc !important;
text-decoration: none !important;
transition: all 0.3s ease !important;
}
a:hover {
color: #004499 !important;
text-decoration: underline !important;
}
/* Button System */
.btn, button,
.hero-1-btn, .hero-2-btn, .grid-11-btn, .grid-12-btn,
.split-8-btn, .split-10-btn, .text-1-btn, .text-2-btn,
.cta-3-btn, .faq-4-footer-btn, .faq-5-contact-btn,
.faq-6-view-more-btn, .visa-order-btn, .nav-1-btn {
display: inline-block !important;
padding: 12px 24px !important;
font-size: 16px !important;
font-weight: 600 !important;
text-align: center !important;
border-radius: 6px !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
border: 2px solid transparent !important;
letter-spacing: 0.5px !important;
text-decoration: none !important;
}
.btn-primary, .hero-1-solid, .hero-2-solid, .text-1-solid, .text-2-solid, .cta-3-solid, .nav-1-solid {
background-color: #0066cc !important;
color: #fff !important;
border-color: #0066cc !important;
}
.btn-primary:hover, .hero-1-solid:hover, .hero-2-solid:hover, .text-1-solid:hover, .text-2-solid:hover, .cta-3-solid:hover, .nav-1-solid:hover {
background-color: #004499 !important;
border-color: #004499 !important;
}
.btn-outline, .hero-1-outline, .hero-2-outline, .text-1-outline, .text-2-outline, .cta-3-outline, .nav-1-outline {
background-color: transparent !important;
color: #0066cc !important;
border-color: #0066cc !important;
}
.btn-outline:hover, .hero-1-outline:hover, .hero-2-outline:hover, .text-1-outline:hover, .text-2-outline:hover, .cta-3-outline:hover, .nav-1-outline:hover {
background-color: #0066cc !important;
color: #fff !important;
}
/* Image Styling */
img {
max-width: 100% !important;
height: auto !important;
display: block !important;
}
/* Form Elements */
input, textarea, select {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
font-size: 16px !important;
padding: 12px !important;
border: 1px solid #ccc !important;
border-radius: 6px !important;
width: 100% !important;
transition: border-color 0.3s ease !important;
}
input:focus, textarea:focus, select:focus {
outline: none !important;
border-color: #0066cc !important;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1) !important;
}
/* Navigation Override */
.nav-1-navbar {
background-color: #fff !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05) !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
}
/* Blog Section Fixes */
.tn1-blog-section, .blog-2-section {
background-color: inherit !important;
padding: 80px 20px !important;
}
.tn1-blog-card, .blog-2-card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
overflow: hidden !important;
}
.tn1-blog-card:hover, .blog-2-card:hover {
transform: translateY(-5px) !important;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12) !important;
}
/* FAQ Sections */
.faq-4-section, .faq-5-section, .faq-6-block, .faq-7-container {
background-color: inherit !important;
}
.faq-4-question, .faq-5-question, .faq-6-question, .faq-7-question {
background-color: #f1f1f1 !important;
transition: background-color 0.3s ease !important;
}
.faq-4-question:hover, .faq-5-question:hover, .faq-6-question:hover, .faq-7-question:hover {
background-color: #e7e7e7 !important;
}
/* Footer Override */
.footer-1-section, .footer-2-section {
background-color: #f8f8f8 !important;
border-top: 1px solid #eee !important;
}
/* Contact Section */
.contact-1-section {
background-color: #fff !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
}
.contact-1-submit-btn {
background-color: #0066cc !important;
color: #fff !important;
}
.contact-1-submit-btn:hover {
background-color: #004499 !important;
}
/* Hero Section Enhancements */
.hero-1-two-column, .hero-2-centered-hero, .hero-9-section {
background-color: inherit !important;
}
.hero-1-placeholder, .hero-2-placeholder, .hero-9-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
overflow: hidden !important;
position: relative !important;
}
/* Split Sections */
.split-8-section, .split-10-section {
background-color: inherit !important;
}
.split-8-placeholder, .split-10-placeholder {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
}
/* Testimonial Styling */
testimonial {
display: block !important;
max-width: 800px !important;
margin: 40px auto !important;
padding: 35px !important;
background-color: #fff !important;
border-radius: 10px !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08) !important;
font-style: italic !important;
color: #444 !important;
position: relative !important;
line-height: 1.8 !important;
font-size: 1.1rem !important;
border-left: 5px solid #0066cc !important;
}
testimonial::before {
content: '"' !important;
font-size: 5rem !important;
color: #0066cc !important;
position: absolute !important;
left: 15px !important;
top: -10px !important;
opacity: 0.2 !important;
font-family: Georgia, serif !important;
}
section:nth-child(even) testimonial {
background-color: #fff !important;
}
/* Custom styles container */
styles {
display: none !important;
}
/* Responsive Adjustments */
@media (max-width: 992px) {
h1 {
font-size: 2rem !important;
}
h2 {
font-size: 1.75rem !important;
}
section {
padding: 60px 0 !important;
}
}
@media (max-width: 768px) {
section {
padding: 50px 0 !important;
}
.hero-1-two-column, .split-8-section, .split-10-section {
flex-direction: column !important;
}
.btn, button {
width: 100% !important;
}
.hero-1-button-group, .hero-2-button-group {
flex-direction: column !important;
width: 100% !important;
}
}
@media (max-width: 576px) {
body {
font-size: 15px !important;
}
h1 {
font-size: 1.75rem !important;
}
h2 {
font-size: 1.5rem !important;
}
section {
padding: 40px 0 !important;
}
testimonial {
padding: 25px !important;
font-size: 1rem !important;
}
}
/* Animations and Transitions */
.fade-in {
animation: fadeIn 1s ease-in-out !important;
}
@keyframes fadeIn {
from {
opacity: 0 !important;
transform: translateY(20px) !important;
}
to {
opacity: 1 !important;
transform: translateY(0) !important;
}
}
/* Ensure smooth scrolling */
html {
scroll-behavior: smooth !important;
}
/* 🧱 Extended for Mega Navbar Dropdown */
.mega-navbar {

View File

@ -113,250 +113,557 @@
</div> -->
<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)="buildWireframe(37688)" class="action-btn save-btn">
💾 build wireframe
</button>
<button class="btn btn-primary" (click)="recreateWireframe()">🔄 Recreate Wireframe</button>
<!-- wireframe-editor.component.html -->
<div class="navbar-controls">
<select [(ngModel)]="selectedNavbarPage" (change)="selectedNavbarPage && selectNavbar(selectedNavbarPage)">
<option value="">Select Navbar Page</option>
<option *ngFor="let page of pageRenderOrder" [value]="page">{{ page }}</option>
</select>
</div>
</div>
<div class="wireframe-editor">
<!-- 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 *ngFor="let size of ['12px', '14px', '16px', '18px', '20px', '24px', '28px']" [value]="size">{{ size }}
</option>
</select>
<select [(ngModel)]="liveStyles.fontWeight" class="style-control">
<option value="">Font Weight</option>
<option *ngFor="let weight of ['normal', 'bold', 'lighter']" [value]="weight">{{ weight }}</option>
</select>
<select [(ngModel)]="liveStyles.textAlign" class="style-control">
<option value="">Align</option>
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
</select>
<input type="color" [(ngModel)]="liveStyles.color" title="Text Color" class="color-picker" />
<input type="color" [(ngModel)]="liveStyles.background" title="Background Color" class="color-picker" />
<button (click)="applyStyleToSelection()" class="action-btn style-btn">
🎨 Apply Style
</button>
<button (click)="verifyCssInSections()" class="action-btn">🔍 Verify CSS</button>
</div>
</div>
<!-- Grid and Wireframe Canvas Area -->
<div class="visual-editor-container">
<!-- Zoom Controls -->
<div class="zoom-controls">
<button class="zoom-btn" (click)="zoomIn()">
<div class="zoom-controls-new">
<button class="zoom-btn" (click)="zoomIn()" title="Zoom In">
<i class="fa fa-plus"></i>
</button>
<button class="zoom-btn" (click)="zoomOut()">
<button class="zoom-btn" (click)="zoomOut()" title="Zoom Out">
<i class="fa fa-minus"></i>
</button>
<button class="zoom-btn" (click)="resetZoom()">
<button class="zoom-btn" (click)="resetZoom()" title="Reset Zoom">
<i class="fa fa-expand"></i>
</button>
</div>
<!-- Canvas Area with Grid -->
<div class="canvas-wrapper" cdkScrollable>
<div class="canvas-boundary" [style.transform]="getTransform()" (mousedown)="startPan($event)"
(mousemove)="doPan($event)" (mouseup)="endPan()" (mouseleave)="endPan()">
<!-- Fixed Right Sidebar -->
<div class="wireframe-controls-sidebar"
[class.collapsed]="sidebarCollapsed"
[class.mobile-open]="sidebarOpen"
[class.visible]="sidebarOpen || !isMobile">
<!-- Grid Background -->
<div class="grid-background"></div>
<!-- Sidebar Header -->
<div class="sidebar-header">
<div (click)="toggleSidebar()">
<i class="fas" [ngClass]="sidebarCollapsed ? 'fa-chevron-left' : 'fa-chevron-right'"></i>
<div *ngIf="isLoading" class="loading-overlay">
<div class="spinner"></div>
</div>
<!-- Pages Container - Draggable Pages -->
<div *ngIf="!isLoading" class="pages-container" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="onPageDrop($event)">
<h2>
<div *ngFor="let pageName of pageRenderOrder" class="page-card" cdkDrag
[class.page-hovered]="hoveredPage === pageName" (mouseenter)="showPageTools(pageName)"
(mouseleave)="hidePageTools(pageName)">
<i class="fas fa-cogs" style="margin-right: 8px; font-size: 18px;"></i>
Wireframe Controls
</h2>
<!-- 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()">
<!-- Main Actions Section -->
<div class="sidebar-content">
<div class="control-section fade-in-up" [@fadeIn]>
<h3>
<i class="fas fa-play-circle" style="margin-right: 6px; color: #28a745;"></i>
Generate Wireframe
</h3>
<!-- 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 class="action-btn primary" (click)="buildWireframe(37688)" [@buttonHover]>
<i class="fas fa-hammer"></i>
Build Wireframe
</button>
<button class="section-btn" title="Remove Section" (click)="removeSection(pageName, sectionKey)">
<i class="fa fa-trash"></i>
<button class="action-btn primary" (click)="regenerateAllModifiedSections()">
<i class="fas fa-sync-alt"></i>
Regenerate Modified
</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 class="action-btn secondary" (click)="createHtmlFiles()">
<i class="fas fa-save"></i>
Save All HTML Files
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<button class="action-btn secondary" (click)="recreateWireframe()">
<i class="fas fa-redo"></i>
Recreate Wireframe
</button>
<button class="action-btn secondary">
<i class="fa-solid fa-link"></i>
{{ deployedUrl ? 'DEPLOYED URL: ' + deployedUrl : 'NO URL YET' }}
</button>
<!-- 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>
<!-- Navbar Controls Section -->
<div class="control-section fade-in-left" [@fadeIn]>
<h3>
<i class="fas fa-bars" style="margin-right: 6px; color: #17a2b8;"></i>
Navbar Editor
</h3>
<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>
<label for="navbarSelect">Select Navbar Page:</label>
<select id="navbarSelect" class="form-control" [(ngModel)]="selectedNavbarPage"
(change)="selectedNavbarPage && selectNavbar(selectedNavbarPage)"
[@focusBorder]>
<option value="">Choose a page...</option>
<option *ngFor="let page of pageRenderOrder" [value]="page">
<i class="fas fa-file-alt"></i> {{ page }}
</option>
</select>
</div>
</div>
<!-- Style Controls Section -->
<div class="control-section bounce-in" [@fadeIn]>
<h3>
<i class="fas fa-palette" style="margin-right: 6px; color: #fd7e14;"></i>
Style Controls
</h3>
<div class="style-grid">
<div class="form-group">
<label>Font Size:</label>
<select class="form-control" [(ngModel)]="liveStyles.fontSize">
<option value="">Default</option>
<option *ngFor="let size of fontSizes" [value]="size">{{ size }}</option>
</select>
</div>
<div class="form-group">
<label>Section Description:</label>
<textarea [(ngModel)]="newSectionDescription" rows="4"
placeholder="Describe the content for this section"></textarea>
<label>Font Weight:</label>
<select class="form-control" [(ngModel)]="liveStyles.fontWeight">
<option value="">Default</option>
<option *ngFor="let weight of fontWeights" [value]="weight">{{ weight }}</option>
</select>
</div>
</div>
<div class="style-grid">
<div class="form-group">
<label>Text Align:</label>
<select class="form-control" [(ngModel)]="liveStyles.textAlign">
<option value="">Default</option>
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
<option value="justify">Justify</option>
</select>
</div>
<div class="form-group">
<button class="action-btn secondary small" (click)="verifyCssInSections()">
<i class="fas fa-search"></i>
Verify CSS
</button>
</div>
</div>
<div class="color-controls">
<div class="color-input-group">
<span class="color-label">
<i class="fas fa-font" style="margin-right: 4px;"></i>
Text Color
</span>
<input type="color" [(ngModel)]="liveStyles.color" class="color-picker" />
</div>
<div class="color-input-group">
<span class="color-label">
<i class="fas fa-fill-drip" style="margin-right: 4px;"></i>
Background
</span>
<input type="color" [(ngModel)]="liveStyles.background" class="color-picker" />
</div>
</div>
<button class="action-btn primary full-width" (click)="applyStyleToSelection()">
<i class="fas fa-paint-brush"></i>
Apply Style to Selection
</button>
</div>
<!-- Page Management Section -->
<div class="control-section fade-in-up" [@fadeIn]>
<h3>
<i class="fas fa-file-alt" style="margin-right: 6px; color: #dc3545;"></i>
Page Management
</h3>
<button class="action-btn secondary" (click)="addNewPage()">
<i class="fas fa-plus"></i>
Add New Page
</button>
<button class="action-btn secondary" (click)="duplicateCurrentPage()">
<i class="fas fa-copy"></i>
Duplicate Current Page
</button>
<button class="action-btn secondary" (click)="deleteCurrentPage()"
[disabled]="pageRenderOrder.length <= 1">
<i class="fas fa-trash"></i>
Delete Current Page
</button>
</div>
<!-- Additional Tools Section -->
<div class="control-section fade-in-up" [@fadeIn]>
<h3>
<i class="fas fa-tools" style="margin-right: 6px; color: #6f42c1;"></i>
Additional Tools
</h3>
<button class="action-btn secondary" (click)="exportProject()">
<i class="fas fa-download"></i>
Export Project
</button>
<button class="action-btn secondary" (click)="importProject()">
<i class="fas fa-upload"></i>
Import Project
</button>
<button class="action-btn secondary" (click)="previewMode()">
<i class="fas fa-eye"></i>
Preview Mode
</button>
<button class="action-btn secondary" (click)="addNewSectionToCurrentPage()">
<i class="fas fa-plus-square"></i>
Add Section
</button>
</div>
</div>
</div>
<!-- Main Canvas Area -->
<div class="canvas-main-area" [class.sidebar-collapsed]="sidebarCollapsed">
<div class="wireframe-controls-sidebar-overlay"
*ngIf="sidebarOpen && !isMobile"
(click)="sidebarOpen = false">
</div>
<!-- Infinite Grid Background -->
<div class="infinite-grid" [style.transform]="getGridTransform()"></div>
<!-- Zoom Controls -->
<!-- Canvas Container -->
<div class="canvas-container"
[class.panning]="isPanning"
(mousedown)="startPan($event)"
(mousemove)="doPan($event)"
(mouseup)="endPan()"
(mouseleave)="endPan()"
(wheel)="onWheel($event)">
<!-- Loading Overlay -->
<div *ngIf="isLoading" class="loading-overlay">
<div class="spinner"></div>
<div class="loading-text">Loading wireframe...</div>
</div>
<!-- Full Page Display -->
<div *ngIf="!isLoading && getCurrentPage()"
class="page-display"
[style.transform]="getPageTransform()">
<!-- Connected Sections Container -->
<div class="connected-sections"
cdkDropList
cdkDropListOrientation="vertical"
[cdkDropListData]="getCurrentPageSections()"
(cdkDropListDropped)="onSectionDrop($event, getCurrentPageName())">
<!-- Individual Connected Sections -->
<!-- Change this in your *ngFor -->
<!-- Update the section wrapper div -->
<div *ngFor="let sectionKey of getCurrentPageSections(); let i = index"
class="section-wrapper"
[class.editing]="isCurrentSectionEditing(sectionKey)"
cdkDrag
[cdkDragData]="sectionKey"
(mouseenter)="hoveredSection = sectionKey"
(mouseleave)="hoveredSection = null"
(dblclick)="toggleSectionEditing(getCurrentPageName(), sectionKey, $event)">
<!-- Section Content (Full Width, No Gaps) -->
<div class="section-content"
[innerHTML]="getSectionHtml(getCurrentPageName(), sectionKey)"
[attr.contenteditable]="isCurrentSectionEditing(sectionKey)"
(blur)="handleDirectContentEdit($event, getCurrentPageName(), sectionKey)">
</div>
<!-- Section Overlay for Hover/Edit State -->
<div class="section-overlay"
[class.visible]="hoveredSection === sectionKey || isCurrentSectionEditing(sectionKey)">
<div class="section-label">
<i class="fas fa-puzzle-piece" style="margin-right: 4px;"></i>
{{ sectionKey }}
<button *ngIf="isCurrentSectionEditing(sectionKey)"
class="btn btn-sm btn-success ms-2"
(click)="saveSectionEdit(getCurrentPageName(), sectionKey)">
<i class="fas fa-check"></i>
</button>
</div>
</div>
<!-- Drag Handle -->
<div class="drag-handle" cdkDragHandle style="display: none;">
<i class="fa fa-grip-lines"></i>
</div>
</div>
<!-- Empty State for Sections -->
<div *ngIf="getCurrentPageSections().length === 0" class="empty-sections"
style="padding: 100px; text-align: center; color: #6c757d;">
<div class="empty-state">
<i class="fas fa-puzzle-piece" style="font-size: 48px; margin-bottom: 20px; opacity: 0.5;"></i>
<h3>No Sections Yet</h3>
<p>Add your first section to start building this page.</p>
<button class="action-btn primary" (click)="addNewSectionToCurrentPage()">
<i class="fas fa-plus"></i>
Add First Section
</button>
</div>
</div>
</div>
</div>
<!-- Empty State for Pages -->
<div *ngIf="!isLoading && pageRenderOrder.length === 0" class="empty-pages"
style="display: flex; align-items: center; justify-content: center; height: 100vh;">
<div class="empty-state" style="text-align: center; color: #6c757d;">
<i class="fas fa-file-plus" style="font-size: 64px; margin-bottom: 30px; opacity: 0.5;"></i>
<h2>No Pages Yet</h2>
<p style="font-size: 16px; margin-bottom: 30px;">Create your first page to get started with wireframing.</p>
<button class="action-btn primary" (click)="createFirstPage()">
<i class="fas fa-plus"></i>
Create First Page
</button>
</div>
</div>
</div>
<div class="page-actions">
<button class="action-btn primary" (click)="copyPageToClipboard()">
<i class="fas fa-copy"></i>
Copy Page
</button>
<button class="action-btn success" (click)="downloadHtml(getCurrentPageName())">
<i class="fas fa-download"></i>
Download Page
</button>
</div>
<!-- Page Navigation Controls -->
<div *ngIf="!isLoading && pageRenderOrder.length > 0" class="page-navigation">
<button class="nav-btn"
(click)="previousPage()"
[disabled]="currentPageIndex <= 0"
title="Previous Page">
<i class="fas fa-chevron-left"></i>
Previous
</button>
<div class="page-indicator">
<span class="page-number">{{ currentPageIndex + 1 }}</span>
<span class="page-separator">/</span>
<span class="total-pages">{{ pageRenderOrder.length }}</span>
<div class="page-title">{{ getCurrentPageName() }}</div>
</div>
<button class="nav-btn"
(click)="nextPage()"
[disabled]="currentPageIndex >= pageRenderOrder.length - 1"
title="Next Page">
Next
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<!-- Mobile Sidebar Toggle (Hidden on Desktop) -->
<button class="mobile-sidebar-toggle"
(click)="toggleMobileSidebar()"
[class.active]="sidebarOpen"
style="display: none; position: fixed; top: 20px; right: 20px; z-index: 1600; background: #212529; color: white; border: none; padding: 12px; border-radius: 50%; font-size: 16px;"
*ngIf="isMobile">
<i class="fas" [ngClass]="sidebarOpen ? 'fa-times' : 'fa-bars'"></i>
</button>
<!-- Navbar Editor Modal -->
<div class="modal-overlay" *ngIf="selectedNavbarPage" [@fadeIn] (click)="closeNavbarModal()">
<div class="modal-content" (click)="$event.stopPropagation()" [@slideIn]>
<div class="modal-header">
<h3>
<i class="fas fa-edit" style="margin-right: 8px; color: #17a2b8;"></i>
Edit Navbar Links - {{ selectedNavbarPage }}
</h3>
<button class="close-btn" (click)="closeNavbarModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<div class="navbar-link-item" *ngFor="let link of navbarLinks[selectedNavbarPage]; let i = index" [@fadeIn]
style="display: flex; gap: 10px; align-items: center; margin-bottom: 15px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<input type="text" [(ngModel)]="link.label" placeholder="Link Label" class="form-control">
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<select [(ngModel)]="link.href" class="form-control">
<option value="">Select page...</option>
<option *ngFor="let page of availablePages" [value]="page.href">{{ page.label }}</option>
</select>
</div>
<button class="remove-btn" (click)="removeLink(selectedNavbarPage, i)" title="Remove Link"
style="background: #dc3545; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer;">
<i class="fas fa-trash"></i>
</button>
</div>
<button class="action-btn secondary full-width add-link-btn" (click)="addLink(selectedNavbarPage)">
<i class="fas fa-plus"></i>
Add New Link
</button>
</div>
<div class="modal-footer">
<button class="action-btn secondary" (click)="closeNavbarModal()">
<i class="fas fa-times" style="margin-right: 5px;"></i>
Cancel
</button>
<button class="action-btn primary" (click)="applyNavbarChanges(selectedNavbarPage)">
<i class="fas fa-check" style="margin-right: 5px;"></i>
Save Changes
</button>
</div>
</div>
</div>
<!-- Modal for Adding New Section -->
<div class="modal-overlay" *ngIf="showNewSectionModal" [@fadeIn] (click)="closeNewSectionModal()">
<div class="modal-content" (click)="$event.stopPropagation()" [@slideIn]>
<div class="modal-header">
<h3>
<i class="fas fa-plus-circle" style="margin-right: 8px; color: #28a745;"></i>
Add New Section to {{ newSectionPage }}
</h3>
<button class="close-btn" (click)="closeNewSectionModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="sectionName">
<i class="fas fa-tag" style="margin-right: 4px;"></i>
Section Name:
</label>
<input id="sectionName"
type="text"
[(ngModel)]="newSectionName"
placeholder="Enter section name (e.g., Hero, About, Contact)"
class="form-control">
</div>
<div class="form-group">
<label for="sectionType">
<i class="fas fa-layer-group" style="margin-right: 4px;"></i>
Section Type:
</label>
<select id="sectionType" [(ngModel)]="newSectionType" class="form-control">
<option value="">Select a type...</option>
<option *ngFor="let type of sectionTypes" [value]="type.value">
{{ type.label }}
</option>
</select>
</div>
<div class="form-group">
<label for="sectionDescription">
<i class="fas fa-align-left" style="margin-right: 4px;"></i>
Section Description:
</label>
<textarea id="sectionDescription"
[(ngModel)]="newSectionDescription"
rows="4"
placeholder="Describe the content and purpose of this section..."
class="form-control"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="action-btn" [disabled]="!newSectionName || !newSectionType" (click)="confirmAddSection()">
Add Section
</button>
<button class="action-btn cancel-btn" (click)="cancelAddSection()">
<button class="action-btn secondary" (click)="closeNewSectionModal()">
<i class="fas fa-times" style="margin-right: 5px;"></i>
Cancel
</button>
<button class="action-btn primary"
[disabled]="!newSectionName || !newSectionType"
(click)="confirmAddSection()">
<i class="fas fa-plus" style="margin-right: 5px;"></i>
Add Section
</button>
</div>
</div>
</div>
<!-- Modal for Adding New Page -->
<div class="modal-overlay" *ngIf="showNewPageModal" [@fadeIn] (click)="closeNewPageModal()">
<div class="modal-content" (click)="$event.stopPropagation()" [@slideIn]>
<div class="modal-header">
<h3>
<i class="fas fa-file-plus" style="margin-right: 8px; color: #28a745;"></i>
Add New Page
</h3>
<button class="close-btn" (click)="closeNewPageModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="pageName">
<i class="fas fa-file-alt" style="margin-right: 4px;"></i>
Page Name:
</label>
<input id="pageName"
type="text"
[(ngModel)]="newPageName"
placeholder="Enter page name (e.g., Home, About, Contact)"
class="form-control">
</div>
<div class="form-group">
<label for="pageDescription">
<i class="fas fa-align-left" style="margin-right: 4px;"></i>
Page Description:
</label>
<textarea id="pageDescription"
[(ngModel)]="newPageDescription"
rows="3"
placeholder="Describe the purpose and content of this page..."
class="form-control"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="action-btn secondary" (click)="closeNewPageModal()">
<i class="fas fa-times" style="margin-right: 5px;"></i>
Cancel
</button>
<button class="action-btn primary"
[disabled]="!newPageName.trim()"
(click)="confirmAddPage()">
<i class="fas fa-plus" style="margin-right: 5px;"></i>
Add Page
</button>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { Component, Input, OnInit, SecurityContext } from '@angular/core';
import { Component, Input, OnInit, SecurityContext, ChangeDetectorRef } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { SiteTreeservice } from '../SiteBuilderGrid/SiteTree.service';
@ -9,11 +9,44 @@ import * as sha256 from 'crypto-js/sha256';
import SHA256 from 'crypto-js/sha256';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Download_Css } from './download-css';
import { trigger, transition, style, animate, state } from '@angular/animations';
@Component({
selector: 'app-wireframe-renderer',
templateUrl: './wireframe-renderer.component.html',
styleUrls: ['./wireframe-renderer.component.scss']
styleUrls: ['./wireframe-renderer.component.scss'],
animations: [
trigger('fadeIn', [
transition(':enter', [
style({ opacity: 0 }),
animate('200ms ease-out', style({ opacity: 1 }))
]),
transition(':leave', [
animate('200ms ease-in', style({ opacity: 0 }))
])
]),
trigger('slideIn', [
transition(':enter', [
style({ transform: 'scale(0.9)', opacity: 0 }),
animate('300ms ease-out', style({ transform: 'scale(1)', opacity: 1 }))
]),
transition(':leave', [
animate('200ms ease-in', style({ transform: 'scale(0.9)', opacity: 0 }))
])
]),
trigger('buttonHover', [
state('void', style({ transform: 'translateY(0)' })),
state('hover', style({ transform: 'translateY(-2px)', boxShadow: '0 4px 8px rgba(0,0,0,0.2)' }))
]),
trigger('focusBorder', [
state('void', style({ borderColor: '#ccc' })),
state('focus', style({ borderColor: '#1976d2', boxShadow: '0 0 0 2px rgba(25,118,210,0.2)' }))
]),
trigger('pulseOnHover', [
state('void', style({ backgroundColor: 'transparent' })),
state('hover', style({ backgroundColor: 'rgba(0,0,0,0.05)' }))
])
]
})
export class WireframeRendererComponent implements OnInit {
@ -30,6 +63,13 @@ export class WireframeRendererComponent implements OnInit {
sectionHtmls: Record<string, Record<string, SafeHtml>> = {};
isLoading: boolean = true;
sitename = '';
sidebarCollapsed: boolean = false;
// Add toggle method
deployedUrl: string | null = null;
// Added properties for grid and drag functionality
hoveredPage: string | null = null;
hoveredSection: string | null = null;
@ -39,7 +79,8 @@ export class WireframeRendererComponent implements OnInit {
isPanning: boolean = false;
lastX: number = 0;
lastY: number = 0;
isDragging = false;
startPanPosition = { x: 0, y: 0 };
// Edit mode properties
editMode: boolean = false;
editingSection: { page: string, section: string } | null = null;
@ -54,14 +95,67 @@ export class WireframeRendererComponent implements OnInit {
newSectionAfter: string | null = null;
sidebarOpen = false;
// Add this property to fix template errors
// Page and section management
pageRenderOrder: string[] = [];
// need help with all the errors
// Style controls
liveStyles = {
fontSize: '',
fontWeight: '',
textAlign: '',
color: '#000000',
background: '#ffffff'
};
// Data arrays
fontSizes = ['12px', '14px', '16px', '18px', '20px', '24px', '28px'];
fontWeights = ['normal', 'bold', 'lighter'];
sectionTypes = [
{ value: 'header', label: 'Header' },
{ value: 'hero', label: 'Hero' },
{ value: 'features', label: 'Features' },
{ value: 'gallery', label: 'Gallery' },
{ value: 'pricing', label: 'Pricing' },
{ value: 'testimonials', label: 'Testimonials' },
{ value: 'contact', label: 'Contact' },
{ value: 'footer', label: 'Footer' }
];
// Mock data - replace with your actual data structures
navbarLinks: { [key: string]: any[] } = {};
availablePages: { href: string, label: string }[] = [];
zoomLevel = 1;
currentPageIndex = 0;
sectionOrderMap: { [key: string]: string[] } = {};
// Add these new properties
draggedSection: string | null = null;
lastDeletedSection: { page: string; section: string; content: SafeHtml } | null = null;
gridColumns = 12;
gridGap = 16;
constructor(
private siteTreeService: SiteTreeservice,
private route: ActivatedRoute,
private sanitizer: DomSanitizer,
private toastr: ToastrService,
private cdr: ChangeDetectorRef
) { }
ngOnInit(): void {
// sectionOrderMap: Record<string, string[]> = {};
// Initialize in ngOnInit()
ngOnInit(): void {
this.sectionOrderMap = {};
this.id = this.route.snapshot.params['id'];
console.log('id is: ', this.id);
this.fetchTreeById(this.id);
@ -85,32 +179,16 @@ export class WireframeRendererComponent implements OnInit {
toggleSectionEditing(pageName: string, sectionKey: string, event: Event): void {
event.stopPropagation();
const previewElement = event.target as HTMLElement;
const sectionPreview = previewElement.closest('.section-preview') as HTMLElement;
if (sectionPreview) {
const isEditable = sectionPreview.getAttribute('contenteditable') === 'true';
// Toggle edit mode
sectionPreview.setAttribute('contenteditable', !isEditable + '');
if (!isEditable) {
// Add specific class for styling when editing
sectionPreview.classList.add('editing-active');
// Ensure CSS is available for editing
this.ensureCommonCssForAllSections();
sectionPreview.focus();
if (this.isCurrentSectionEditing(sectionKey)) {
this.editingSection = null;
} else {
// Remove editing class
sectionPreview.classList.remove('editing-active');
this.editingSection = { page: pageName, section: sectionKey };
}
// Save changes
this.saveSectionChanges(pageName, sectionKey, sectionPreview.innerHTML);
}
}
}
// Force change detection
this.cdr.detectChanges();
}
verifyCssInSections(): void {
// First ensure common CSS is in the head
@ -167,14 +245,16 @@ export class WireframeRendererComponent implements OnInit {
}
saveSectionChanges(pageName: string, sectionKey: string, html: string): void {
// Remove temporary style tags
const cleanedHtml = html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
const cleanedHtml = html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
this.sectionHtmls[pageName][sectionKey] =
this.sanitizer.bypassSecurityTrustHtml(cleanedHtml);
// this.sectionHtmls[pageName][sectionKey] =
// this.sanitizer.bypassSecurityTrustHtml(cleanedHtml);
this.updateFullPageHtml(pageName);
this.toastr.success(`Updated section: ${sectionKey}`);
}
this.sectionHtmls[pageName][sectionKey] = cleanedHtml;
this.updateFullPageHtml(pageName);
this.toastr.success(`Updated section: ${sectionKey}`);
}
// Step 1: Get data from tree by ID
fetchTreeById(id: number) {
@ -189,6 +269,8 @@ this.toastr.success(`Updated section: ${sectionKey}`);
const maybeParsed = typeof res.model === 'string' ? JSON.parse(res.model) : res.model;
this.jsonInput = JSON.stringify(maybeParsed, null, 2);
this.sitename = res.name;
this.deployedUrl = res.deployedUrl;
console.log("Deployed Url "+res.deployedUrl);
this.generatePromptsForAllSections(this.jsonInput);
this.isLoading = false;
} catch (err) {
@ -215,11 +297,15 @@ this.toastr.success(`Updated section: ${sectionKey}`);
console.error('❌ Invalid JSON format', e);
}
}
isCurrentSectionEditing(sectionKey: string): boolean {
const currentPage = this.getCurrentPageName();
return this.editingSection?.page === currentPage &&
this.editingSection?.section === sectionKey;
}
// Step 3
pageRenderOrder: string[] = []; // 🚀 New array to hold correct page order
// 🔄 Maintain strict page and section render order
sectionOrderMap: Record<string, string[]> = {}; // 🆕 Added to track section order
missingHtmlPages: string[] = [];
async processPages(treeJson: any,forceRegenerate = false) {
@ -227,7 +313,7 @@ this.toastr.success(`Updated section: ${sectionKey}`);
for (const [pageName, sections] of Object.entries(treeJson)) {
this.pageRenderOrder.push(pageName); // ✅ Track the order of pages
this.sectionOrderMap[pageName] = []; // ✅ Init section order array
this.sectionOrderMap[pageName] = this.sectionOrderMap[pageName] || [];
console.log('process page is : ',pageName)
@ -448,6 +534,20 @@ HTML Only. No CSS.
});
}
}
nextPage(): void {
if (this.currentPageIndex < this.pageRenderOrder.length - 1) {
this.currentPageIndex++;
this.resetZoom();
}
}
previousPage(): void {
if (this.currentPageIndex > 0) {
this.currentPageIndex--;
this.resetZoom();
}
}
// Process page sections
async processPageSections(pageName: string, sectionMap: Record<string, string>) {
@ -464,7 +564,9 @@ HTML Only. No CSS.
sectionHtmls.push(html);
// Store individual section HTML
this.sectionHtmls[pageName][sectionName] = this.sanitizer.bypassSecurityTrustHtml(html);
// this.sectionHtmls[pageName][sectionName] = this.sanitizer.bypassSecurityTrustHtml(html);
this.sectionHtmls[pageName][sectionName] = html
console.log("Processing Html in process page "+sectionHtmls)
});
await Promise.all(htmlTasks);
@ -478,7 +580,7 @@ HTML Only. No CSS.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${pageName}</title>
<style>
${Download_Css}
${COMMON_CSS}
html, body {
margin: 0 !important;
@ -500,6 +602,28 @@ HTML Only. No CSS.
const safeHtml = this.sanitizer.bypassSecurityTrustHtml(finalHtml);
this.pageSections[pageName] = { FullPage: safeHtml };
}
// async processPageSections(pageName: string, sectionMap: Record<string, string>) {
// // Initialize section order map
// this.sectionOrderMap[pageName] = this.sectionOrderMap[pageName] || [];
// // Clear existing sections
// this.sectionHtmls[pageName] = {};
// const htmlTasks = Object.entries(sectionMap).map(async ([sectionName, jsonPrompt]) => {
// const html = await this.generateJson({ sectionType: sectionName, jsonStructure: jsonPrompt });
// // Store with proper ordering
// this.sectionOrderMap[pageName].push(sectionName);
// this.sectionHtmls[pageName][sectionName] = this.sanitizer.bypassSecurityTrustHtml(html);
// });
// await Promise.all(htmlTasks);
// // Update full page HTML with all sections
// this.updateFullPageHtml(pageName);
// }
// Generate JSON to HTML
async generateJson(data: { jsonStructure: any, sectionType: string }): Promise<string> {
return new Promise((resolve) => {
@ -520,6 +644,8 @@ HTML Only. No CSS.
next: (res) => {
console.log('✅ Response from HTML API:', res);
if (res && res.msg) {
console.log("CHECKING DEPLOYED URL ",res.deployedUrl)
this.deployedUrl = res.deployedUrl;
resolve(res.msg);
} else {
resolve('⚠️ No response received from HTML API.');
@ -545,9 +671,23 @@ HTML Only. No CSS.
});
}
// Download HTML
downloadHtml(pageName: string) {
const fullHtml = Object.values(this.pageSections[pageName]).join('\n');
downloadHtml(pageName: string) {
// Make sure we're using the sections in the correct order
const orderedSectionKeys = this.sectionOrderMap[pageName] || this.getSectionKeys(pageName);
// Update the sectionHtmls order to match orderedSectionKeys
const reorderedSectionHtmls: Record<string, SafeHtml> = {};
for (const sectionKey of orderedSectionKeys) {
if (this.sectionHtmls[pageName][sectionKey]) {
reorderedSectionHtmls[sectionKey] = this.sectionHtmls[pageName][sectionKey];
}
}
this.sectionHtmls[pageName] = reorderedSectionHtmls;
// Now update the full page HTML with correct order
this.updateFullPageHtml(pageName);
const fullHtml = this.pageSections[pageName].FullPage.toString();
const blob = new Blob([fullHtml], { type: 'text/html' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
@ -555,7 +695,58 @@ HTML Only. No CSS.
link.download = `${pageName}.html`;
link.click();
window.URL.revokeObjectURL(url);
}
/*
///// commited code //// download
// Download HTML
downloadHtml(pageName: string) {
const sectionOrder = this.sectionOrderMap[pageName] || Object.keys(this.sectionHtmls[pageName] || {});
let fullHtml = '';
for (const sectionName of sectionOrder) {
const html = this.sectionHtmls[pageName]?.[sectionName];
if (html) {
fullHtml += html + '\n\n';
}
}
// const fullHtml = Object.values(this.pageSections[pageName]).join('\n');
const finalHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${pageName}</title>
<style>
${COMMON_CSS}
html, body {
margin: 0 !important;
padding: 0 !important;
min-height: 100% !important;
width: 100% !important;
font-family: 'Segoe UI', sans-serif !important;
}
</style>
</head>
<body>
${fullHtml}
</body>
</html>`;
// const fullHtml = Object.values(this.pageSections[pageName]).join('\n');
const blob = new Blob([finalHtml], { type: 'text/html' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${pageName}.html`;
link.click();
window.URL.revokeObjectURL(url);
}
*/
// Upload HTML files
buildWireframe(projId: number) {
const pageHtmlMap: Record<string, string> = {};
@ -588,6 +779,8 @@ HTML Only. No CSS.
});
}
createHtmlFiles() {
const pageHtmlMap: Record<string, string> = {};
for (const [pageName, section] of Object.entries(this.pageSections)) {
@ -793,15 +986,15 @@ return new Promise((resolve) => {
// Style properties
activeEditTarget: string | null = null;
liveStyles = {
background: '',
color: '',
fontSize: '',
padding: '',
margin: '',
fontWeight: '',
textAlign: '',
};
// liveStyles = {
// background: '',
// color: '',
// fontSize: '',
// padding: '',
// margin: '',
// fontWeight: '',
// textAlign: '',
// };
// Select editable element
selectEditable(pageName: string) {
@ -1039,51 +1232,29 @@ return new Promise((resolve) => {
// Handle section drop
onSectionDrop(event: CdkDragDrop<string[]>, pageName: string) {
// Get current section keys
const sectionKeys = this.getSectionKeys(pageName);
// Move item in the array
moveItemInArray(sectionKeys, event.previousIndex, event.currentIndex);
// Create new section map in correct order
const newSectionMap: Record<string, string> = {};
sectionKeys.forEach(key => {
newSectionMap[key] = this.allPagePrompts[pageName][key];
});
// Update section map
this.allPagePrompts[pageName] = newSectionMap;
// Update section HTMLs order
const newSectionHtmls: Record<string, SafeHtml> = {};
sectionKeys.forEach(key => {
newSectionHtmls[key] = this.sectionHtmls[pageName][key];
});
// Update section HTMLs
this.sectionHtmls[pageName] = newSectionHtmls;
// Update the full page HTML
this.updateFullPageHtml(pageName);
this.toastr.success(`Reordered sections in ${pageName}`);
const currentOrder = this.sectionOrderMap[pageName] || [];
moveItemInArray(currentOrder, event.previousIndex, event.currentIndex);
this.sectionOrderMap[pageName] = [...currentOrder];
this.cdr.detectChanges();
}
// Zoom control methods
zoomIn() {
this.scale += 0.1;
this.scale = Math.min(this.scale, 2); // Max zoom 200%
this.zoomLevel = Math.min(2, this.zoomLevel + 0.1);
}
zoomOut() {
this.scale -= 0.1;
this.scale = Math.max(this.scale, 0.5); // Min zoom 50%
this.zoomLevel = Math.max(0.5, this.zoomLevel - 0.1);
}
selectedNavbarPage: string = '';
navbarLinks: Record<string, { label: string, href: string }[]> = {};
availablePages: { label: string, href: string }[] = [];
// navbarLinks: Record<string, { label: string, href: string }[]> = {};
// availablePages: { label: string, href: string }[] = [];
// Select Navbar Section
selectNavbar(pageName: string) {
@ -1132,6 +1303,81 @@ return new Promise((resolve) => {
this.navbarLinks[pageName].splice(index, 1);
}
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
this.updateGridLayout();
}
private updateGridLayout() {
setTimeout(() => {
const canvas = document.querySelector('.canvas-main-area') as HTMLElement;
if (canvas) {
canvas.style.transition = 'margin 0.3s ease';
canvas.style.marginLeft = this.sidebarCollapsed ? '0' : '300px';
}
}, 50);
}
// Export project as JSON file
exportProject(): void {
const exportData = {
sitename: this.sitename,
jsonInput: this.jsonInput,
allPagePrompts: this.allPagePrompts,
sectionHtmls: this.sectionHtmls,
pageSections: this.pageSections,
sectionOrderMap: this.sectionOrderMap,
pageRenderOrder: this.pageRenderOrder,
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${this.sitename || 'project'}.json`;
link.click();
window.URL.revokeObjectURL(url);
this.toastr.success('Project exported as JSON');
}
// Import project from JSON file
importProject(): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.onchange = async (event: any) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (e: any) => {
try {
const data = JSON.parse(e.target.result);
this.sitename = data.sitename || '';
this.jsonInput = data.jsonInput || '';
this.allPagePrompts = data.allPagePrompts || {};
this.sectionHtmls = data.sectionHtmls || {};
this.pageSections = data.pageSections || {};
this.sectionOrderMap = data.sectionOrderMap || {};
this.pageRenderOrder = data.pageRenderOrder || [];
this.toastr.success('Project imported successfully');
// Optionally, re-render or trigger change detection
await this.processAllPagesLiveUpdate();
} catch (err) {
this.toastr.error('Failed to import project: Invalid JSON');
}
};
reader.readAsText(file);
};
input.click();
}
// Toggle preview mode (simple implementation)
previewMode(): void {
this.sidebarOpen = false;
this.toastr.info('Preview mode toggled');
}
// Apply Changes to navbar
applyNavbarChanges(pageName: string): void {
const links = this.navbarLinks[pageName] || [];
@ -1160,11 +1406,8 @@ return new Promise((resolve) => {
this.pageSections[pageName].FullPage = this.sanitizer.bypassSecurityTrustHtml(updatedHtml);
this.toastr.success('Navbar updated successfully');
}
resetZoom() {
this.scale = 1;
this.panX = 0;
this.panY = 0;
this.zoomLevel = 1;
}
// Get transform CSS for pan and zoom
@ -1174,51 +1417,27 @@ return new Promise((resolve) => {
// Pan methods
startPan(event: MouseEvent): void {
// Start panning with any mouse button or when space key is pressed
if (event.button === 0 || event.button === 1 || event.ctrlKey || event.metaKey) {
// Don't start panning if clicking on an editable element
const target = event.target as HTMLElement;
if (target.hasAttribute('contenteditable') && target.getAttribute('contenteditable') === 'true') {
return;
}
this.isPanning = true;
this.lastX = event.clientX;
this.lastY = event.clientY;
// Change cursor to indicate panning
const canvasBoundary = document.querySelector('.canvas-boundary') as HTMLElement;
if (canvasBoundary) {
canvasBoundary.style.cursor = 'grabbing';
}
this.isDragging = true;
this.startPanPosition = { x: event.clientX, y: event.clientY };
event.preventDefault();
}
}
}
doPan(event: MouseEvent) {
if (!this.isPanning) return;
const deltaX = (event.clientX - this.lastX) / this.scale;
const deltaY = (event.clientY - this.lastY) / this.scale;
doPan(event: MouseEvent): void {
if (!this.isDragging) return;
const deltaX = event.clientX - this.startPanPosition.x;
const deltaY = event.clientY - this.startPanPosition.y;
this.panX += deltaX;
this.panY += deltaY;
this.lastX = event.clientX;
this.lastY = event.clientY;
}
endPan(): void {
if (this.isPanning) {
this.isPanning = false;
// Restore cursor
const canvasBoundary = document.querySelector('.canvas-boundary') as HTMLElement;
if (canvasBoundary) {
canvasBoundary.style.cursor = 'move';
}
this.startPanPosition = { x: event.clientX, y: event.clientY };
this.cdr.detectChanges();
}
endPan(): void {
this.isDragging = false;
}
// Edit section
@ -1256,16 +1475,25 @@ if (this.isPanning) {
onSectionEdit(event: any) {
// This is handled in saveSectionEdit
}
copyPageToClipboard(): void {
const currentPage = this.getCurrentPageName();
const html = this.pageSections[currentPage]?.FullPage?.toString() || '';
navigator.clipboard.writeText(html).then(() => {
this.toastr.success(`Copied ${currentPage} to clipboard`);
});
}
handleDirectContentEdit(event: FocusEvent, pageName: string, sectionKey: string): void {
const element = event.target as HTMLElement;
if (element.hasAttribute('contenteditable') && element.getAttribute('contenteditable') === 'true') {
// Save the content when the element loses focus
this.saveSectionChanges(pageName, sectionKey, element.innerHTML);
downloadCurrentPage(): void {
const currentPage = this.getCurrentPageName();
this.downloadHtml(currentPage);
}
// Turn off contenteditable
element.setAttribute('contenteditable', 'false');
}
// Update existing download method
handleDirectContentEdit(event: Event, page: string, section: string) {
const target = event.target as HTMLElement;
const content = target.innerHTML;
this.sectionHtmls[page][section] = this.sanitizer.bypassSecurityTrustHtml(content);
this.updateFullPageHtml(page);
}
// Close edit mode
@ -1337,6 +1565,13 @@ if (this.isPanning) {
};
}
// Add this to initialize the current page
getPageTransform(): string {
return `scale(${this.scale})`;
}
refreshAllSectionCSS(): void {
// Refresh CSS in all section previews
for (const pageName of this.objectKeys(this.pageSections)) {
@ -1356,7 +1591,7 @@ if (this.isPanning) {
// Ensure CSS is applied to the DOM
this.ensureCommonCssForAllSections();
this.toastr.success('Refreshed CSS for all sections');
// this.toastr.success('Refreshed CSS for all sections');
}
// Remove section
@ -1369,10 +1604,172 @@ if (this.isPanning) {
// Update the full page HTML
this.updateFullPageHtml(pageName);
this.toastr.success(`Removed section: ${sectionKey}`);
// this.toastr.success(`Removed section: ${sectionKey}`);
}
}
// Modal state for new page
showNewPageModal: boolean = false;
// Track current page index for navigation and actions
// currentPageIndex: number = 0;
// Used for new page creation
newPageName: string = '';
// Add a new page (shows modal)
addNewPage(): void {
this.showNewPageModal = true;
this.newPageName = '';
}
// Close the new page modal
closeNewPageModal(): void {
this.showNewPageModal = !this.showNewPageModal;
this.newPageName = '';
}
// Confirm adding a new page
confirmAddNewPage(): void {
if (!this.newPageName.trim()) {
this.toastr.error('Please enter a page name');
return;
}
if (this.pageRenderOrder.includes(this.newPageName)) {
this.toastr.error('Page name already exists');
return;
}
this.pageRenderOrder.push(this.newPageName);
this.allPagePrompts[this.newPageName] = {};
this.sectionHtmls[this.newPageName] = {};
this.pageSections[this.newPageName] = { FullPage:''};
this.sectionOrderMap[this.newPageName] = [];
this.closeNewPageModal();
this.toastr.success(`Added new page: ${this.newPageName}`);
}
// Duplicate the current page
duplicateCurrentPage(): void {
const currentPage = this.getCurrentPage();
if (!currentPage) {
this.toastr.error('No page selected to duplicate');
return;
}
let newPageName = currentPage + '_Copy';
let count = 1;
while (this.pageRenderOrder.includes(newPageName)) {
newPageName = `${currentPage}_Copy${count++}`;
}
// Deep clone all structures
this.pageRenderOrder.push(newPageName);
this.allPagePrompts[newPageName] = JSON.parse(JSON.stringify(this.allPagePrompts[currentPage]));
this.sectionHtmls[newPageName] = JSON.parse(JSON.stringify(this.sectionHtmls[currentPage]));
this.pageSections[newPageName] = { FullPage: this.pageSections[currentPage].FullPage };
this.sectionOrderMap[newPageName] = [...(this.sectionOrderMap[currentPage] || [])];
this.toastr.success(`Duplicated page: ${currentPage}`);
}
// Delete the current page
deleteCurrentPage(): void {
const currentPage = this.getCurrentPage();
if (!currentPage) {
this.toastr.error('No page selected to delete');
return;
}
if (this.pageRenderOrder.length === 1) {
this.toastr.error('At least one page must remain');
return;
}
if (confirm(`Are you sure you want to delete the page "${currentPage}"?`)) {
// Remove from all structures
this.pageRenderOrder = this.pageRenderOrder.filter(p => p !== currentPage);
delete this.allPagePrompts[currentPage];
delete this.sectionHtmls[currentPage];
delete this.pageSections[currentPage];
delete this.sectionOrderMap[currentPage];
// Adjust currentPageIndex if needed
if (this.currentPageIndex >= this.pageRenderOrder.length) {
this.currentPageIndex = this.pageRenderOrder.length - 1;
}
this.toastr.success(`Deleted page: ${currentPage}`);
}
}
getGridSize(): string {
// Example: returns a 20px grid, adjust as needed
return `translate(${this.panX}px, ${this.panY}px) scale(${this.scale})`;
}
// Add a new section to the current page
addNewSectionToCurrentPage() {
const page = this.getCurrentPageName();
if (!page) return;
const newSection = `section-${Date.now()}`;
this.sectionOrderMap[page] = [...(this.sectionOrderMap[page] || []), newSection];
this.sectionHtmls[page][newSection] = this.sanitizer.bypassSecurityTrustHtml('<div>New Section</div>');
this.cdr.detectChanges();
}
// Get the transform for the grid (alias for getTransform)
getGridTransform(): string {
return `translate(${this.panX}px, ${this.panY}px) scale(${this.scale})`;
}
deleteSection(page: string, section: string) {
this.lastDeletedSection = {
page,
section,
content: this.sectionHtmls[page][section]
};
this.sectionOrderMap[page] = this.sectionOrderMap[page].filter(s => s !== section);
delete this.sectionHtmls[page][section];
this.cdr.detectChanges();
}
// Add this method to fix template errors
// Add this method to your component
getCurrentPageSections(): string[] {
const currentPage = this.getCurrentPageName();
return currentPage ?
this.sectionOrderMap[currentPage] || Object.keys(this.sectionHtmls[currentPage] || {})
: [];
}
// Also add this helper method if missing
getCurrentPageName(): string {
return this.pageRenderOrder[this.currentPageIndex] || '';
}
// Handle mouse wheel for zooming
onWheel(event: WheelEvent): void {
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
if (event.deltaY < 0) {
this.zoomIn();
} else {
this.zoomOut();
}
}
}
// Get the current page name
getCurrentPage(): string | null {
if (this.pageRenderOrder.length === 0) return null;
this.currentPageIndex = Math.min(this.currentPageIndex, this.pageRenderOrder.length - 1);
return this.pageRenderOrder[this.currentPageIndex];
}
// Update the canvas transform methods
// Detect if the device is mobile (simple check)
get isMobile(): boolean {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// Open new section modal
addNewSection(pageName: string, afterSection: string = null) {
this.showNewSectionModal = true;
@ -1391,6 +1788,16 @@ if (this.isPanning) {
this.newSectionDescription = '';
}
undoDelete() {
if (this.lastDeletedSection) {
const { page, section, content } = this.lastDeletedSection;
this.sectionOrderMap[page].push(section);
this.sectionHtmls[page][section] = content;
this.lastDeletedSection = null;
this.cdr.detectChanges();
}
}
// Confirm adding new section
async confirmAddSection() {
if (!this.newSectionName || !this.newSectionType) {
@ -1471,9 +1878,15 @@ if (this.isPanning) {
this.newSectionType = '';
this.newSectionDescription = '';
}
}
// // wireframe-renderer.component.ts
// import { Component, Input, OnInit } from '@angular/core';
// import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

View File

@ -58,8 +58,7 @@
<!-- Floating Square Button -->
<!-- Tab Section Container -->
<div style="margin-top: 10px; padding: 20px;">
<div style="margin-top: -30px; padding: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
<!-- Left: Floating Square Button -->
@ -77,11 +76,6 @@
</div>
</div>
<!-- Right: Generate Wireframe Button -->
<!-- <button (click)="activeTab = 'wireframe'" style="background: #28a745; color: white; border: none; padding: 8px 14px; border-radius: 4px; cursor: pointer;">
Generate Wireframe
</button> -->
<!-- Right: Generate Wireframe Button -->
<button (click)="generateWireframe()"
style="background: #28a745; color: white; border: none; padding: 8px 14px; border-radius: 4px; cursor: pointer;">
@ -101,67 +95,101 @@
<!-- Wireframe Content -->
<div *ngIf="activeTab === 'wireframe'" style="margin-top: 20px; max-width: 100%; overflow-x: auto;">
<div style="min-width: 800px;">
<!-- <app-wireframe-renderer *ngIf="treeData"></app-wireframe-renderer> -->
<app-wireframe-renderer *ngIf="isWireframeLoaded && treeData"></app-wireframe-renderer>
</div>
</div>
</div>
<!-- Popup Dialog -->
<!-- <div *ngIf="showPopup"
style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1000;">
<div style="background: white; padding: 20px; width: 500px; border-radius: 8px; position: relative;">
<button (click)="showPopup = false"
style="position: absolute; top: 10px; right: 10px; background: #ff4d4f; border: none; color: white; border-radius: 50%; width: 30px; height: 30px; font-size: 18px; cursor: pointer;">
×
</button>
<h2 style="margin-bottom: 10px;">Generate JSON</h2>
<textarea [(ngModel)]="rawInputText" rows="6"
style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"></textarea>
<button (click)="generateJson()"
style="margin-top: 10px; padding: 8px 16px; background: green; color: white; border: none; border-radius: 4px;">
Generate
</button>
</div>
</div> -->
<!-- Popup Dialog with improved z-index and animations -->
<div *ngIf="showPopup" [@fadeIn] class="modal-overlay"
style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.75); display: flex; align-items: center; justify-content: center; z-index: 1000;">
<div class="chart-box" [@slideIn]
style="width: 600px; max-height: none; padding: 24px; box-shadow: 0 8px 24px rgba(0,0,0,0.25); border-radius: 8px; border: none; transform-origin: center; background-color: snow">
<!-- Enhanced Popup Dialog with Loading States -->
<div *ngIf="showPopup" class="modal-overlay">
<!-- Main Content State -->
<div *ngIf="!isLoading && !showError" class="popup-content">
<!-- Close button -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3 style="margin: 0; color: #333; font-weight: 500;">Generate Site Structure</h3>
<button (click)="showPopup = false" [@pulseOnHover]
style="background: transparent; border: none; color: #666; font-size: 24px; cursor: pointer; padding: 4px; height: 36px; width: 36px; border-radius: 50%; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; color: #f5f5f5;">
×
</button>
<button (click)="closePopup()" class="close-btn">×</button>
</div>
<p style="margin-bottom: 16px; color: #666;">Describe your website structure in plain language:</p>
<textarea [(ngModel)]="rawInputText" rows="8" [@focusBorder]
class="tag-input"
style="width: 100%; margin-bottom: 20px; font-family: inherit; resize: vertical; transition: border 0.3s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.05);"></textarea>
<textarea [(ngModel)]="rawInputText" rows="8" class="input-textarea"></textarea>
<!-- Dropdown for number of pages -->
<div style="margin-bottom: 20px;">
<label for="pageCount" style="display: block; margin-bottom: 6px; color: #333; font-weight: 500;">Select Number of Pages:</label>
<select id="pageCount" [(ngModel)]="selectedPageRange" class="page-select">
<option value="5-10">5 - 10</option>
<option value="10-15">10 - 15</option>
<option value="15-20">15 - 20</option>
</select>
</div>
<!-- Buttons -->
<div style="display: flex; justify-content: flex-end; gap: 12px;">
<button (click)="showPopup = false" [@buttonHover]
style="padding: 8px 16px; background: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; transition: all 0.2s ease;">
Cancel
</button>
<button (click)="generateJson()" [@buttonHover]
style="padding: 8px 18px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
Generate Structure
</button>
<button (click)="closePopup()" class="cancel-btn">Cancel</button>
<button (click)="generateJsonWithLoading()" class="generate-btn">Generate Structure</button>
</div>
</div>
<!-- Loading State -->
<div *ngIf="isLoading" class="loading-container">
<!-- Animated Background Pattern -->
<div class="bg-pattern"></div>
<!-- Main Loader -->
<div class="loader-content">
<!-- Spinning Brain Icon -->
<div class="thinking-icon">
<div class="brain-emoji">🧠</div>
</div>
<!-- Thinking Text with Typewriter Effect -->
<h2 class="thinking-text">
Thinking<span class="dots">...</span>
</h2>
<!-- Subtitle -->
<p class="thinking-subtitle">Crafting your perfect site structure</p>
<!-- Progress Dots -->
<div class="progress-dots">
<div class="dot dot-1"></div>
<div class="dot dot-2"></div>
<div class="dot dot-3"></div>
<div class="dot dot-4"></div>
<div class="dot dot-5"></div>
</div>
<!-- Floating Particles -->
<div class="particles">
<div class="particle particle-1"></div>
<div class="particle particle-2"></div>
<div class="particle particle-3"></div>
<div class="particle particle-4"></div>
</div>
</div>
</div>
<!-- Error State -->
<div *ngIf="showError" class="error-container">
<!-- Error Icon -->
<div class="error-icon">
<div style="font-size: 36px;">⚠️</div>
</div>
<!-- Error Message -->
<h2 class="error-title">Something went wrong</h2>
<p class="error-message">
We couldn't generate your site structure. Please check your input and try again.
</p>
<!-- Action Buttons -->
<div style="display: flex; gap: 12px; justify-content: center;">
<button (click)="retryGeneration()" class="retry-btn">Try Again</button>
<button (click)="closePopup()" class="error-close-btn">Close</button>
</div>
</div>
</div>

View File

@ -140,7 +140,411 @@
// }
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.75);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(8px);
animation: fadeIn 0.3s ease-out;
}
/* Main Popup Content */
.popup-content {
width: 600px;
max-height: none;
padding: 24px;
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
border-radius: 8px;
background-color: snow;
animation: slideIn 0.4s ease-out;
}
.close-btn {
background: transparent;
border: none;
color: #666;
font-size: 24px;
cursor: pointer;
padding: 4px;
height: 36px;
width: 36px;
border-radius: 50%;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
.input-textarea {
width: 100%;
margin-bottom: 20px;
font-family: inherit;
resize: vertical;
transition: border 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.input-textarea:focus {
border-color: #1976d2;
outline: none;
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
}
.page-select {
width: 100%;
padding: 8px 12px;
font-size: 15px;
border-radius: 4px;
border: 1px solid #ccc;
background-color: white;
color: #333;
}
.cancel-btn {
padding: 8px 16px;
background: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.cancel-btn:hover {
background: #e0e0e0;
transform: translateY(-1px);
}
.generate-btn {
padding: 8px 18px;
background: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.generate-btn:hover {
background: #1565c0;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
/* Loading Container */
.loading-container {
width: 400px;
padding: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
text-align: center;
position: relative;
overflow: hidden;
animation: slideIn 0.4s ease-out;
}
.bg-pattern {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0.1;
background-image: repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
rgba(255,255,255,0.1) 10px,
rgba(255,255,255,0.1) 20px
);
}
.loader-content {
position: relative;
z-index: 2;
}
.thinking-icon {
width: 80px;
height: 80px;
margin: 0 auto 24px;
background: rgba(255,255,255,0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
animation: spinPulse 2s ease-in-out infinite;
}
.brain-emoji {
font-size: 36px;
animation: brainThink 1.5s ease-in-out infinite;
}
.thinking-text {
color: white;
font-size: 28px;
font-weight: 600;
margin-bottom: 16px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.dots {
animation: typewriter 1.5s infinite;
}
.thinking-subtitle {
color: rgba(255,255,255,0.9);
font-size: 16px;
margin-bottom: 32px;
}
.progress-dots {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255,255,255,0.4);
animation: dotPulse 1.4s infinite ease-in-out;
}
.dot-1 { animation-delay: 0s; }
.dot-2 { animation-delay: 0.2s; }
.dot-3 { animation-delay: 0.4s; }
.dot-4 { animation-delay: 0.6s; }
.dot-5 { animation-delay: 0.8s; }
.particles {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.particle {
position: absolute;
border-radius: 50%;
background: rgba(255,255,255,0.6);
animation: float 3s infinite ease-in-out;
}
.particle-1 {
width: 4px;
height: 4px;
top: 20%;
left: 15%;
animation-duration: 3s;
}
.particle-2 {
width: 6px;
height: 6px;
background: rgba(255,255,255,0.4);
top: 60%;
right: 20%;
animation-duration: 4s;
animation-direction: reverse;
}
.particle-3 {
width: 3px;
height: 3px;
background: rgba(255,255,255,0.7);
bottom: 30%;
left: 25%;
animation-duration: 2.5s;
}
.particle-4 {
width: 5px;
height: 5px;
background: rgba(255,255,255,0.5);
top: 40%;
right: 15%;
animation-duration: 3.5s;
animation-direction: reverse;
}
/* Error Container */
.error-container {
width: 450px;
padding: 32px;
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
text-align: center;
position: relative;
animation: slideIn 0.4s ease-out;
}
.error-icon {
width: 80px;
height: 80px;
margin: 0 auto 24px;
background: rgba(255,255,255,0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
animation: errorShake 0.5s ease-in-out;
}
.error-title {
color: white;
font-size: 24px;
font-weight: 600;
margin-bottom: 12px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.error-message {
color: rgba(255,255,255,0.9);
font-size: 16px;
margin-bottom: 32px;
line-height: 1.5;
}
.retry-btn {
padding: 12px 20px;
background: rgba(255,255,255,0.2);
color: white;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.retry-btn:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-1px);
}
.error-close-btn {
padding: 12px 20px;
background: white;
color: #ee5a52;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.error-close-btn:hover {
background: #f8f8f8;
transform: translateY(-1px);
}
/* Keyframe Animations */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes spinPulse {
0%, 100% {
transform: rotate(0deg) scale(1);
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
}
50% {
transform: rotate(180deg) scale(1.1);
box-shadow: 0 12px 48px rgba(0,0,0,0.3);
}
}
@keyframes brainThink {
0%, 100% { transform: scale(1) rotate(0deg); }
25% { transform: scale(1.1) rotate(-5deg); }
75% { transform: scale(0.95) rotate(5deg); }
}
@keyframes typewriter {
0%, 20% { opacity: 1; }
50% { opacity: 0; }
80%, 100% { opacity: 1; }
}
@keyframes dotPulse {
0%, 20%, 80%, 100% {
transform: scale(1);
background: rgba(255,255,255,0.4);
}
40% {
transform: scale(1.5);
background: rgba(255,255,255,0.9);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
opacity: 0.6;
}
50% {
transform: translateY(-20px) rotate(180deg);
opacity: 1;
}
}
@keyframes errorShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
/* Responsive Design */
@media (max-width: 768px) {
.popup-content, .loading-container, .error-container {
width: 90%;
margin: 0 20px;
}
}
/* Your existing styles */
.controls {
margin-bottom: 20px;
}
@ -171,51 +575,6 @@
background-color: #1565c0;
}
.toolbar {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 1rem;
background-color: #f7f7f7;
}
textarea {
width: 300px;
height: 120px;
font-family: monospace;
padding: 10px;
border-radius: 8px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
background-color: #3f51b5;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #303f9f;
}
mat-tab-group {
margin: 1rem;
}
.tab-content {
padding: 1rem;
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 10px;
min-height: 400px;
}
.active-tab {
border-bottom: 2px solid #007bff;
color: #007bff;
@ -228,30 +587,16 @@ mat-tab-group {
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
color: black; /* 👈 Text color black */
color: black;
font-weight: bold;
}
.tab-button.active {
border-bottom: 2px solid #000;
background: #e0e0e0;
color: black; /* 👈 Ensure active tab also stays black */
color: black;
}
.editor-container {
position: relative; /* 🔑 Important to contain absolute button */
max-width: 1200px;
margin: 0 auto;
font-family: 'Segoe UI', sans-serif;
padding: 20px;
background-color: #ffffff;
}
.modal-overlay *{
z-index: 1000;
}
:host {
--zoom-level: 1;
--primary-color: #2196f3;
@ -263,4 +608,3 @@ mat-tab-group {
--primary-color: #2196f3;
--border-color: #e2e8f0;
}

View File

@ -122,9 +122,11 @@ export class TreeVisualizerComponent {
}`;
treeData: any = null;
rawInputText: string = `make a website , where we can login , sign up.
inside that there is a menu of icard where we can add a icard detail iacardname, description
after click this icard inside we upload excel of student data, icard template, after click convert it fetch all data of excel and replace the key value of icard template..`;
inside that there is a menu of icard where we can add a icard detail iacardname, description
after click this icard inside we upload excel of student data, icard template, after click convert it fetch all data of excel and replace the key value of icard template..`;
id: number;
selectedPageRange: string = '5-10'; // 👈 Default value
constructor(private treeService: treeVisualizerService,
private siteTreeService: SiteTreeservice,
@ -143,7 +145,41 @@ after click this icard inside we upload excel of student data, icard template, a
}
// Add these properties to your component class
isLoading = false;
showError = false;
// Method to handle the enhanced generate flow
generateJsonWithLoading() {
this.isLoading = true;
this.showError = false;
// Your existing generateJson logic here
// Replace with your actual API call
setTimeout(() => {
// Success case
this.isLoading = false;
this.showPopup = false;
this.activeTab = 'sitemap';
// For error case, use this instead:
// this.isLoading = false;
// this.showError = true;
}, 3000);
}
// Enhanced close method
closePopup() {
this.showPopup = false;
this.isLoading = false;
this.showError = false;
}
// Retry method for error state
retryGeneration() {
this.showError = false;
this.generateJsonWithLoading();
}
convertJson() {
try {
const parsed = JSON.parse(this.inputJson);
@ -223,7 +259,7 @@ after click this icard inside we upload excel of student data, icard template, a
generateJson() {
console.log('genearte json start ')
const suffix = ` You are a JSON structure generator for website page hierarchies.
const suffix = `You are a JSON structure generator for website page hierarchies.
You are a JSON structure generator for website UI and sitemap hierarchy.