增加自定义尺寸功能

This commit is contained in:
qinhaoyan 2024-10-10 16:53:37 +08:00 committed by lmaomaoz
parent 65a32def9e
commit de359199b8
5 changed files with 280 additions and 65 deletions

View File

@ -286,9 +286,8 @@
border-color: #222;
border-style: solid;
border-radius: 30px;
width: 375px;
height: 96%;
max-height: 812px;
width: 100%;
height: 100%;
flex-grow: unset;
min-height: unset;
min-width: unset;
@ -1971,6 +1970,8 @@ div[class*='Form-group']:empty {
transform: translate(-50%, -50%);
top: 53%;
left: 50%;
width: 395px;
height: 687px;
}
}
}

View File

@ -641,12 +641,6 @@ export default class Editor extends Component<
)}
{isMobile && (
<MobileDevTool
onChange={value => {
this.setState({mobileDimensions: value});
}}
onScaleChange={scale => {
this.setState({mobileScale: scale});
}}
container={this.mainPreviewRef.current}
previewBody={this.mainPreviewBodyRef.current?.currentDom}
/>
@ -664,8 +658,6 @@ export default class Editor extends Component<
autoFocus={autoFocus}
toolbarContainer={this.getToolbarContainer}
readonly={readonly}
mobileDimensions={mobileDimensions}
mobileScale={mobileScale}
ref={this.mainPreviewBodyRef}
></Preview>
</div>

View File

@ -49,11 +49,6 @@ export interface PreviewProps {
toolbarContainer?: () => any;
readonly?: boolean;
mobileDimensions?: {
width: number;
height: number;
};
mobileScale?: number;
ref?: any;
}
@ -166,11 +161,7 @@ export default class Preview extends Component<PreviewProps> {
requestAnimationFrame(() => {
this.layer!.style.cssText += `transform: translate(0, -${
this.scrollLayer!.scrollTop
}px) ${
this.props.isMobile
? `scale(${(this.props.mobileScale || 100) / 100})`
: ''
};`;
}px);`;
});
}
@ -571,15 +562,6 @@ export default class Preview extends Component<PreviewProps> {
isMobile ? 'is-mobile' : 'is-pc hoverShowScrollBar'
)}
ref={this.contentsRef}
style={
isMobile
? {
width: this.props.mobileDimensions?.width,
height: this.props.mobileDimensions?.height,
transform: `scale(${(this.props.mobileScale || 100) / 100})`
}
: undefined
}
>
<div className="ae-Preview-inner">
{!store.ready ? (
@ -628,13 +610,6 @@ export default class Preview extends Component<PreviewProps> {
onDragEnter={this.handleWidgetsDragEnter}
className="ae-Preview-widgets"
id="aePreviewHighlightBox"
style={
isMobile
? {
transform: `scale(${(this.props.mobileScale || 100) / 100})`
}
: undefined
}
>
{store.highlightNodes.map(node => (
<HighlightBox

View File

@ -31,6 +31,22 @@
&-dimension {
span {
margin-right: 5px;
padding: 0 2px;
}
&-input {
width: 32px;
height: 16px;
border: none;
border-radius: 0.125rem;
padding: 0 2px;
font-size: 12px;
text-align: center;
margin-right: 5px;
&:focus,
&:hover {
outline: none;
border: 1px solid #2468f2;
}
}
}
&-right {
@ -75,3 +91,85 @@
auto !important;
}
}
.ae-MobileDevTool-rightHandle {
cursor: ew-resize;
position: absolute;
right: -6px;
top: 40%;
width: 10px;
height: 22px;
z-index: 1000;
border: 1px solid #dee2e6;
background-color: #fff;
border-radius: 0.125rem;
color: #666;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: #000;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08);
}
&::before {
content: '···';
display: block;
transform: rotate(90deg);
}
}
.ae-MobileDevTool-bottomHandle {
cursor: ns-resize;
position: absolute;
bottom: -6px;
left: calc(50% - 11px);
width: 22px;
height: 10px;
z-index: 1000;
border: 1px solid #dee2e6;
background-color: #fff;
border-radius: 0.125rem;
color: #666;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: #000;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08);
}
&::before {
content: '···';
display: block;
}
}
.height-move {
&::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000000;
cursor: ns-resize;
}
* {
user-select: none;
}
}
.width-move {
&::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000000;
cursor: ew-resize;
}
* {
user-select: none;
}
}

View File

@ -2,8 +2,14 @@ import React, {useEffect} from 'react';
import {Icon} from './icons';
import {Select} from 'amis-ui';
import debounce from 'lodash/debounce';
import {Portal} from 'react-overlays';
export const dimensions = [
{
name: 'custom',
width: 375,
height: 667
},
{
name: 'iPhone SE',
width: 375,
@ -94,12 +100,10 @@ export const dimensions = [
const scaleList = [50, 75, 100, 125, 150, 200];
export default function MobileDevTool(props: {
onChange: (dimension: {width: number; height: number}) => void;
onScaleChange: (scale: number) => void;
container: HTMLElement | null;
previewBody: HTMLElement | null;
}) {
const [dimension, setDimension] = React.useState(dimensions[0]);
const [dimension, setDimension] = React.useState(dimensions[1]);
const [scale, setScale] = React.useState(100);
const [autoScale, setAutoScale] = React.useState(100);
// 记录初始时100%的尺寸
@ -108,16 +112,16 @@ export default function MobileDevTool(props: {
height: 0
});
const {onChange, onScaleChange, container, previewBody} = props;
const {container, previewBody} = props;
const resizeObserver = new ResizeObserver(debounce(updateAutoScale, 300));
useEffect(() => {
onChange?.({
updatePreviewSize({
width: dimension.width,
height: dimension.height
});
onScaleChange?.(100);
updatePreviewScale(100);
// 初始化时获取预览区域的尺寸
getPreviewInitialSize();
@ -128,6 +132,11 @@ export default function MobileDevTool(props: {
if (container) {
resizeObserver.unobserve(container);
}
if (previewBody) {
previewBody.style.width = '';
previewBody.style.height = '';
previewBody.style.transform = '';
}
};
}, [container, previewBody]);
@ -161,22 +170,66 @@ export default function MobileDevTool(props: {
}, 500);
}
function rotateScreen() {
function handleRotateScreen() {
setDimension({
name: dimension.name,
width: dimension.height,
height: dimension.width
});
onChange?.({
updatePreviewSize({
width: dimension.height,
height: dimension.width
});
getPreviewInitialSize();
initialSize.current = {
width: initialSize.current.height,
height: initialSize.current.width
};
updateAutoScale();
}
function handleAutoScale() {
setScale(autoScale);
onScaleChange?.(autoScale);
updatePreviewScale(autoScale);
}
function handleDimensionChange(item: any) {
if (item) {
const value = dimensions.find(n => n.name === item.value)!;
setDimension(value);
updatePreviewSize(value);
setScale(100);
updatePreviewScale(100);
getPreviewInitialSize();
}
}
function handleCustomInputDimensionChange(
value: string,
type: 'width' | 'height'
) {
const number = parseInt(value || '0', 10);
const newDimension = {
name: 'custom',
width: type === 'width' ? number : dimension.width,
height: type === 'height' ? number : dimension.height
};
setDimension(newDimension);
updatePreviewSize(newDimension);
}
function updatePreviewSize(dimension: {width: number; height: number}) {
if (previewBody) {
// 预览区域宽高加上20px的padding
previewBody.style.width = dimension.width + 20 + 'px';
previewBody.style.height = dimension.height + 20 + 'px';
}
}
function updatePreviewScale(scale: number) {
if (previewBody) {
previewBody.style.transform =
'translate(-50%, -50%) scale(' + scale / 100 + ')';
}
}
return (
@ -186,30 +239,42 @@ export default function MobileDevTool(props: {
<Select
className="ae-MobileDevTool-select"
value={dimension.name}
onChange={(item: any) => {
if (item) {
const value = dimensions.find(n => n.name === item.value)!;
setDimension(value);
onChange?.({
width: value.width,
height: value.height
});
setScale(100);
onScaleChange?.(100);
getPreviewInitialSize();
}
}}
onChange={handleDimensionChange}
options={dimensions.map(n => ({
label: n.name,
label: n.name === 'custom' ? '自定义' : n.name,
value: n.name
}))}
clearable={false}
/>
</div>
<div className="ae-MobileDevTool-dimension">
<span>{dimension.width}</span>
{dimension.name === 'custom' ? (
<input
className="ae-MobileDevTool-dimension-input"
value={dimension.width}
onChange={event => {
const value = event.currentTarget.value;
handleCustomInputDimensionChange(value, 'width');
}}
/>
) : (
<span>{dimension.width}</span>
)}
<span>×</span>
<span>{dimension.height}</span>
{dimension.name === 'custom' ? (
<input
className="ae-MobileDevTool-dimension-input"
value={dimension.height}
onChange={event => {
const value = event.currentTarget.value;
handleCustomInputDimensionChange(value, 'height');
}}
/>
) : (
<span>{dimension.height}</span>
)}
</div>
<div className="ae-MobileDevTool-right">
<div className="ae-MobileDevTool-right-scale">
@ -225,7 +290,7 @@ export default function MobileDevTool(props: {
]}
onChange={(item: any) => {
setScale(item.value);
onScaleChange?.(item.value);
updatePreviewScale(item.value);
}}
/>
{!scaleList.includes(scale) && (
@ -240,13 +305,97 @@ export default function MobileDevTool(props: {
</div>
</div>
<div onClick={rotateScreen}>
<div onClick={handleRotateScreen}>
<Icon
icon="rotate-screen"
className="ae-MobileDevTool-right-rotate-screen"
/>
</div>
</div>
{dimension.name === 'custom' && (
<CustomSizeHandle
previewBody={previewBody}
onChange={(w, h) => {
setDimension({
name: 'custom',
width: w - 20,
height: h - 20
});
}}
/>
)}
</div>
);
}
function CustomSizeHandle(props: {
previewBody: HTMLElement | null;
onChange?: (w: number, h: number) => void;
}) {
const {previewBody, onChange} = props;
function handleRightDown(e: any) {
e.stopPropagation();
e.preventDefault();
document.body.classList.add('width-move');
document.addEventListener('mousemove', handleRightDragMove);
document.addEventListener('mouseup', handleRightDragEnd);
}
function handleRightDragMove(e: any) {
e.stopPropagation();
if (previewBody) {
let w = previewBody.clientWidth;
w += e.movementX;
w = Math.max(20, w);
previewBody.style.width = w + 'px';
onChange?.(w, previewBody.clientHeight);
}
}
function handleRightDragEnd() {
document.body.classList.remove('width-move');
document.removeEventListener('mousemove', handleRightDragMove);
document.removeEventListener('mouseup', handleRightDragEnd);
}
function handleBottomDown(e: any) {
e.stopPropagation();
e.preventDefault();
document.body.classList.add('height-move');
document.addEventListener('mousemove', handleBottomDragMove);
document.addEventListener('mouseup', handleBottomDragEnd);
}
function handleBottomDragMove(e: any) {
e.stopPropagation();
if (previewBody) {
let h = previewBody.clientHeight;
h += e.movementY;
h = Math.max(20, h);
previewBody.style.height = h + 'px';
onChange?.(previewBody.clientWidth, h);
}
}
function handleBottomDragEnd() {
document.body.classList.remove('height-move');
document.removeEventListener('mousemove', handleBottomDragMove);
document.removeEventListener('mouseup', handleBottomDragEnd);
}
return (
<Portal container={() => previewBody}>
<>
<div
className="ae-MobileDevTool-rightHandle"
onMouseDown={handleRightDown}
></div>
<div
className="ae-MobileDevTool-bottomHandle"
onMouseDown={handleBottomDown}
></div>
</>
</Portal>
);
}