diff --git a/src/app/components/container/simulator.component.ts b/src/app/components/container/simulator.component.ts
index 0aa06eb..fa06ed4 100644
--- a/src/app/components/container/simulator.component.ts
+++ b/src/app/components/container/simulator.component.ts
@@ -3,6 +3,7 @@ import { PcSimulatorComponent } from '../simulators/pc-simulator/pc-simulator.co
import { Engine, Screen, ScreenType } from '@/app/core/models';
import { MobileSimulatorComponent } from '@/app/components/simulators/mobile-simulator/mobile-simulator.component';
import { NgTemplateOutlet } from '@angular/common';
+import { ResponsiveSimulator } from '@/app/components/simulators/responsive-simulator/responsive-simulator.component';
@Component({
selector: 'app-simulator',
@@ -16,14 +17,14 @@ import { NgTemplateOutlet } from '@angular/common';
} @else {
-
+
-
+
}
`,
styles: [``],
standalone: true,
- imports: [PcSimulatorComponent, MobileSimulatorComponent, NgTemplateOutlet],
+ imports: [PcSimulatorComponent, MobileSimulatorComponent, NgTemplateOutlet, ResponsiveSimulator],
changeDetection: ChangeDetectionStrategy.Default
})
export class SimulatorComponent {
diff --git a/src/app/components/icons/dragmove.ts b/src/app/components/icons/dragmove.ts
new file mode 100644
index 0000000..9e40ed6
--- /dev/null
+++ b/src/app/components/icons/dragmove.ts
@@ -0,0 +1,27 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'app-drag-move-svg',
+ standalone: true,
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class DragMoveSvg {
+ @Input() width: string | number = '1em';
+
+ @Input() height: string | number = '1em';
+}
diff --git a/src/app/components/icons/icon.register.ts b/src/app/components/icons/icon.register.ts
index 60846d2..e60e37a 100644
--- a/src/app/components/icons/icon.register.ts
+++ b/src/app/components/icons/icon.register.ts
@@ -32,6 +32,9 @@ import { CloneSvg } from '@/app/components/icons/clone';
import { ContainerSvg } from '@/app/components/icons/container';
import { RemoveSvg } from '@/app/components/icons/remove';
import { FlipSvg } from '@/app/components/icons/flip';
+import { RecoverSvg } from '@/app/components/icons/recover';
+import { MenuSvg } from '@/app/components/icons/menu';
+import { DragMoveSvg } from '@/app/components/icons/dragmove';
export class IconRegister extends IconFactory {
constructor() {
@@ -69,5 +72,8 @@ export class IconRegister extends IconFactory {
this.register(IconType.Container, ContainerSvg);
this.register(IconType.Remove, RemoveSvg);
this.register(IconType.Flip, FlipSvg);
+ this.register(IconType.Recover, RecoverSvg);
+ this.register(IconType.Menu, MenuSvg);
+ this.register(IconType.DragMove, DragMoveSvg);
}
}
diff --git a/src/app/components/icons/icon.type.ts b/src/app/components/icons/icon.type.ts
index 4d258cd..e8b5e88 100644
--- a/src/app/components/icons/icon.type.ts
+++ b/src/app/components/icons/icon.type.ts
@@ -31,5 +31,8 @@ export enum IconType {
Clone = 'Clone',
Container = 'Container',
Remove = 'Remove',
- Flip = 'Flip'
+ Flip = 'Flip',
+ Recover = 'Recover',
+ Menu = 'Menu',
+ DragMove = 'DragMove'
}
diff --git a/src/app/components/icons/menu.ts b/src/app/components/icons/menu.ts
new file mode 100644
index 0000000..80e9795
--- /dev/null
+++ b/src/app/components/icons/menu.ts
@@ -0,0 +1,26 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'app-menu-svg',
+ standalone: true,
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class MenuSvg {
+ @Input() width: string | number = '1em';
+
+ @Input() height: string | number = '1em';
+}
diff --git a/src/app/components/icons/recover.ts b/src/app/components/icons/recover.ts
new file mode 100644
index 0000000..0a80fe7
--- /dev/null
+++ b/src/app/components/icons/recover.ts
@@ -0,0 +1,26 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'app-recover-svg',
+ standalone: true,
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class RecoverSvg {
+ @Input() width: string | number = '1em';
+
+ @Input() height: string | number = '1em';
+}
diff --git a/src/app/components/simulators/responsive-simulator/resize-handler.component.ts b/src/app/components/simulators/responsive-simulator/resize-handler.component.ts
new file mode 100644
index 0000000..4010d6a
--- /dev/null
+++ b/src/app/components/simulators/responsive-simulator/resize-handler.component.ts
@@ -0,0 +1,46 @@
+import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
+import { usePrefix } from '../../../utils';
+import { Engine } from '../../../core/models';
+import { AttributeDirective } from '../../../directive';
+import { NgClass } from '@angular/common';
+
+export enum ResizeHandleType {
+ Resize = 'RESIZE',
+ ResizeWidth = 'RESIZE_WIDTH',
+ ResizeHeight = 'RESIZE_HEIGHT'
+}
+
+@Component({
+ selector: 'app-resize-handler',
+ template: `
+
+
+
+ `,
+ standalone: true,
+ imports: [AttributeDirective, NgClass],
+ styleUrls: ['./responsive-simulator.component.less'],
+ encapsulation: ViewEncapsulation.None
+})
+export class ResizeHandlerComponent implements OnChanges {
+ prefix = usePrefix('resize-handle');
+
+ @Input() type: ResizeHandleType;
+
+ attributes: { [p: string]: any };
+
+ currentClass: { [p: string]: boolean };
+
+ constructor(private designer: Engine) {}
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.type && changes.type.currentValue) {
+ this.attributes = {
+ [this.designer.props.screenResizeHandlerAttrName]: this.type
+ };
+ this.currentClass = {
+ [`${this.prefix}-${this.type}`]: !!this.type
+ };
+ }
+ }
+}
diff --git a/src/app/components/simulators/responsive-simulator/responsive-simulator.component.less b/src/app/components/simulators/responsive-simulator/responsive-simulator.component.less
new file mode 100644
index 0000000..849851c
--- /dev/null
+++ b/src/app/components/simulators/responsive-simulator/responsive-simulator.component.less
@@ -0,0 +1,56 @@
+@import '../../../../theme/variables.less';
+
+.@{prefix-cls}-responsive-simulator {
+ background-color: var(--dn-responsive-simulator-bg-color);
+}
+
+.@{prefix-cls}-resize-handle {
+ position: absolute;
+ transition: 0.2s all ease-in-out;
+ box-sizing: border-box;
+ user-select: none;
+ bottom: 0;
+ z-index: 10;
+ background: var(--dn-resize-handle-bg-color);
+ color: var(--dn-resize-handle-color);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &-RESIZE_WIDTH {
+ top: 0;
+ bottom: 15px;
+ cursor: ew-resize;
+
+ svg {
+ transform-origin: center;
+ transform: rotate(-90deg);
+ }
+ }
+
+ &-RESIZE_HEIGHT {
+ left: 0;
+ right: 15px;
+ cursor: ns-resize;
+ }
+
+ &-RESIZE {
+ cursor: nwse-resize;
+ }
+
+ &-RESIZE_HEIGHT,
+ &-RESIZE {
+ height: 15px;
+ }
+
+ &-RESIZE_WIDTH,
+ &-RESIZE {
+ right: 0;
+ width: 15px;
+ }
+
+ &:hover {
+ background: var(--dn-resize-handle-hover-bg-color);
+ color: var(--dn-resize-handle-hover-color);
+ }
+}
diff --git a/src/app/components/simulators/responsive-simulator/responsive-simulator.component.ts b/src/app/components/simulators/responsive-simulator/responsive-simulator.component.ts
new file mode 100644
index 0000000..bc7f3cb
--- /dev/null
+++ b/src/app/components/simulators/responsive-simulator/responsive-simulator.component.ts
@@ -0,0 +1,216 @@
+import {
+ AfterViewInit,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Input,
+ OnChanges,
+ signal,
+ SimpleChanges,
+ ViewChild
+} from '@angular/core';
+import { usePrefix } from '@/app/utils';
+import { HookService } from '@/app/services/hook.service';
+import { CursorDragType, Engine, Screen } from '@/app/core/models';
+import {
+ ResizeHandlerComponent,
+ ResizeHandleType
+} from '@/app/components/simulators/responsive-simulator/resize-handler.component';
+import { IconWidget } from '@/app/components/widgets/icon/icon.widget';
+import { DragMoveEvent, DragStartEvent, DragStopEvent } from '@/app/core/events';
+import { calcSpeedFactor, createUniformSpeedAnimation } from '@/app/shared/animation';
+import { fromEvent } from 'rxjs';
+
+@Component({
+ selector: 'app-responsive-simulator',
+ template: `
+
+ `,
+ standalone: true,
+ imports: [ResizeHandlerComponent, IconWidget],
+ styleUrls: ['./responsive-simulator.component.less']
+})
+export class ResponsiveSimulator implements OnChanges, AfterViewInit {
+ @Input() className: string;
+
+ @Input() style: Partial;
+
+ @ViewChild('container') container: ElementRef;
+
+ @ViewChild('content') content: ElementRef;
+
+ prefix = usePrefix('responsive-simulator');
+
+ currentStyle = signal({
+ height: '100%',
+ width: '100%',
+ minHeight: '100px',
+ position: 'relative'
+ });
+
+ screen: Screen;
+
+ engine: Engine;
+
+ contentStyle: { [p: string]: any };
+
+ protected readonly ResizeHandleType = ResizeHandleType;
+
+ constructor(
+ private hookService: HookService,
+ private cdr: ChangeDetectorRef
+ ) {
+ this.screen = this.hookService.useScreen();
+ this.engine = this.hookService.useDesigner();
+
+ this.contentStyle = {
+ width: `${this.screen.width == '100%' ? this.screen.width : this.screen.width + 'px'}`,
+ height: `${this.screen.height == '100%' ? this.screen.height : this.screen.height + 'px'}`,
+ paddingRight: '15px',
+ paddingBottom: '15px',
+ position: 'relative',
+ boxSizing: 'border-box',
+ overflow: 'hidden'
+ };
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.style && changes.style.currentValue) {
+ this.currentStyle.set({
+ height: '100%',
+ width: '100%',
+ minHeight: '100px',
+ position: 'relative',
+ ...this.style
+ });
+ }
+ }
+
+ ngAfterViewInit(): void {
+ useResizeEffect(this.container.nativeElement, this.content.nativeElement, this.engine);
+
+ fromEvent(window, 'mousemove').subscribe(() => {
+ this.contentStyle = {
+ width: `${this.screen.width == '100%' ? this.screen.width : this.screen.width + 'px'}`,
+ height: `${this.screen.height == '100%' ? this.screen.height : this.screen.height + 'px'}`,
+ paddingRight: '15px',
+ paddingBottom: '15px',
+ position: 'relative',
+ boxSizing: 'border-box',
+ overflow: 'hidden'
+ };
+ });
+ }
+}
+
+export const useResizeEffect = (container: HTMLDivElement, content: HTMLDivElement, engine: Engine) => {
+ let status: ResizeHandleType = null;
+ let startX = 0;
+ let startY = 0;
+ let startWidth = 0;
+ let startHeight = 0;
+ let animationX = null;
+ let animationY = null;
+
+ const getStyle = (status: ResizeHandleType) => {
+ if (status === ResizeHandleType.Resize) return 'nwse-resize';
+ if (status === ResizeHandleType.ResizeHeight) return 'ns-resize';
+ if (status === ResizeHandleType.ResizeWidth) return 'ew-resize';
+ return 'nwse-resize';
+ };
+
+ const updateSize = (deltaX: number, deltaY: number) => {
+ const containerRect = container?.getBoundingClientRect();
+ if (status === ResizeHandleType.Resize) {
+ engine.screen.setSize(startWidth + deltaX, startHeight + deltaY);
+ container.scrollBy(containerRect.width + deltaX, containerRect.height + deltaY);
+ } else if (status === ResizeHandleType.ResizeHeight) {
+ engine.screen.setSize(startWidth, startHeight + deltaY);
+ container.scrollBy(container.scrollLeft, containerRect.height + deltaY);
+ } else if (status === ResizeHandleType.ResizeWidth) {
+ engine.screen.setSize(startWidth + deltaX, startHeight);
+ container.scrollBy(containerRect.width + deltaX, container.scrollTop);
+ }
+ };
+
+ engine.subscribeTo(DragStartEvent, e => {
+ if (!engine.workbench.currentWorkspace?.viewport) return;
+ const target = e.data.target as HTMLElement;
+ if (target?.closest(`*[${engine.props.screenResizeHandlerAttrName}]`)) {
+ const rect = content?.getBoundingClientRect();
+ if (!rect) return;
+ status = target.getAttribute(engine.props.screenResizeHandlerAttrName) as ResizeHandleType;
+ engine.cursor.setStyle(getStyle(status));
+ startX = e.data.topClientX;
+ startY = e.data.topClientY;
+ startWidth = rect.width;
+ startHeight = rect.height;
+ engine.cursor.setDragType(CursorDragType.Resize);
+ }
+ });
+ engine.subscribeTo(DragMoveEvent, e => {
+ if (!engine.workbench.currentWorkspace?.viewport) return;
+ if (!status) return;
+ const deltaX = e.data.topClientX - startX;
+ const deltaY = e.data.topClientY - startY;
+ const containerRect = container?.getBoundingClientRect();
+ const distanceX = Math.floor(containerRect.right - e.data.topClientX);
+ const distanceY = Math.floor(containerRect.bottom - e.data.topClientY);
+ const factorX = calcSpeedFactor(distanceX, 10);
+ const factorY = calcSpeedFactor(distanceY, 10);
+ updateSize(deltaX, deltaY);
+ if (distanceX <= 10) {
+ if (!animationX) {
+ animationX = createUniformSpeedAnimation(1000 * factorX, delta => {
+ updateSize(deltaX + delta, deltaY);
+ });
+ }
+ } else {
+ if (animationX) {
+ animationX = animationX();
+ }
+ }
+
+ if (distanceY <= 10) {
+ if (!animationY) {
+ animationY = createUniformSpeedAnimation(300 * factorY, delta => {
+ updateSize(deltaX, deltaY + delta);
+ });
+ }
+ } else {
+ if (animationY) {
+ animationY = animationY();
+ }
+ }
+ });
+ engine.subscribeTo(DragStopEvent, () => {
+ if (!status) return;
+ status = null;
+ engine.cursor.setStyle('');
+ engine.cursor.setDragType(CursorDragType.Move);
+ if (animationX) {
+ animationX = animationX();
+ }
+ if (animationY) {
+ animationY = animationY();
+ }
+ });
+};
diff --git a/src/app/components/widgets/designer-tool/designer-tool.widget.html b/src/app/components/widgets/designer-tool/designer-tool.widget.html
index 6680057..370e01b 100644
--- a/src/app/components/widgets/designer-tool/designer-tool.widget.html
+++ b/src/app/components/widgets/designer-tool/designer-tool.widget.html
@@ -21,9 +21,6 @@
}
- @if (use.includes('SCREEN_TYPE') && screen.type === ScreenType.Responsive){
-
- }
@if (use.includes('SCREEN_TYPE')){
@@ -39,6 +36,18 @@
}
+ @if (use.includes('SCREEN_TYPE') && screen.type === ScreenType.Responsive){
+
+
+
+ @if(screen.width !== '100%' || screen.height !== '100%'){
+
+ }
+ }
+
+
@if (use.includes('SCREEN_TYPE') && screen.type === ScreenType.Mobile){