mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
feat(editor): 新增Layout
This commit is contained in:
parent
22c57f444f
commit
835189adc9
@ -277,8 +277,6 @@ export default defineComponent({
|
||||
},
|
||||
);
|
||||
|
||||
uiService.initColumnWidth();
|
||||
|
||||
onUnmounted(() => {
|
||||
editorService.destroy();
|
||||
historyService.destroy();
|
||||
|
@ -10,77 +10,96 @@
|
||||
@save="saveCode"
|
||||
></magic-code-editor>
|
||||
|
||||
<div class="m-editor-content" v-else>
|
||||
<div class="m-editor-framework-left" :style="`width: ${columnWidth?.left}px`">
|
||||
<layout
|
||||
v-else
|
||||
class="m-editor-content"
|
||||
left-class="m-editor-framework-left"
|
||||
center-class="m-editor-framework-center"
|
||||
right-class="m-editor-framework-right"
|
||||
v-model:left="columnWidth.left"
|
||||
v-model:right="columnWidth.right"
|
||||
:min-left="45"
|
||||
:min-right="1"
|
||||
@change="columnWidthChange"
|
||||
>
|
||||
<template #left>
|
||||
<slot name="sidebar"></slot>
|
||||
</div>
|
||||
|
||||
<resizer type="left"></resizer>
|
||||
|
||||
<template v-if="pageLength > 0">
|
||||
<div class="m-editor-framework-center" :style="`width: ${columnWidth?.center}px`">
|
||||
<slot name="workspace"></slot>
|
||||
</div>
|
||||
|
||||
<resizer type="right"></resizer>
|
||||
|
||||
<div class="m-editor-framework-right" :style="`width: ${columnWidth?.right}px`">
|
||||
<el-scrollbar>
|
||||
<slot name="props-panel"></slot>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot v-else name="empty">
|
||||
<add-page-box></add-page-box>
|
||||
</slot>
|
||||
</div>
|
||||
<template #center>
|
||||
<slot v-if="pageLength > 0" name="workspace"></slot>
|
||||
<slot v-else name="empty">
|
||||
<add-page-box></add-page-box>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template v-if="pageLength > 0" #right>
|
||||
<el-scrollbar>
|
||||
<slot name="props-panel"></slot>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from 'vue';
|
||||
|
||||
import type { MApp } from '@tmagic/schema';
|
||||
|
||||
import { GetColumnWidth, Services } from '../type';
|
||||
|
||||
import AddPageBox from './AddPageBox.vue';
|
||||
import Resizer from './Resizer.vue';
|
||||
import Layout from './Layout.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AddPageBox,
|
||||
Resizer,
|
||||
const DEFAULT_LEFT_COLUMN_WIDTH = 310;
|
||||
const DEFAULT_RIGHT_COLUMN_WIDTH = 480;
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
codeOptions?: Record<string, any>;
|
||||
}>(),
|
||||
{
|
||||
codeOptions: () => ({}),
|
||||
},
|
||||
);
|
||||
|
||||
props: {
|
||||
codeOptions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
const { editorService, uiService } = inject<Services>('services') || {};
|
||||
|
||||
setup() {
|
||||
const { editorService, uiService } = inject<Services>('services') || {};
|
||||
const root = computed(() => editorService?.get<MApp>('root'));
|
||||
|
||||
const root = computed(() => editorService?.get<MApp>('root'));
|
||||
|
||||
return {
|
||||
root,
|
||||
pageLength: computed(() => editorService?.get<number>('pageLength') || 0),
|
||||
showSrc: computed(() => uiService?.get<boolean>('showSrc')),
|
||||
columnWidth: computed(() => uiService?.get<GetColumnWidth>('columnWidth')),
|
||||
|
||||
saveCode(value: string) {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
editorService?.set('root', eval(value));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
const pageLength = computed(() => editorService?.get<number>('pageLength') || 0);
|
||||
const showSrc = computed(() => uiService?.get<boolean>('showSrc'));
|
||||
const columnWidth = ref({
|
||||
left: DEFAULT_LEFT_COLUMN_WIDTH,
|
||||
center: globalThis.document.body.clientWidth - DEFAULT_LEFT_COLUMN_WIDTH - DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||
right: DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||
});
|
||||
uiService?.set('columnWidth', columnWidth.value);
|
||||
|
||||
const saveCode = (value: string) => {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
editorService?.set('root', eval(value));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorColumnWidthData';
|
||||
|
||||
const columnWidthCacheData = globalThis.localStorage.getItem(COLUMN_WIDTH_STORAGE_KEY);
|
||||
if (columnWidthCacheData) {
|
||||
try {
|
||||
const columnWidthCache = JSON.parse(columnWidthCacheData);
|
||||
columnWidth.value = columnWidthCache;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const columnWidthChange = (columnWidth: GetColumnWidth) => {
|
||||
uiService?.set('columnWidth', columnWidth);
|
||||
globalThis.localStorage.setItem(COLUMN_WIDTH_STORAGE_KEY, JSON.stringify(columnWidth));
|
||||
};
|
||||
</script>
|
||||
|
100
packages/editor/src/layouts/Layout.vue
Normal file
100
packages/editor/src/layouts/Layout.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div ref="el">
|
||||
<template v-if="typeof props.left !== 'undefined'">
|
||||
<div class="m-editor-layout-left" :class="leftClass" :style="`width: ${left}px`">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<Resizer @change="changeLeft"></Resizer>
|
||||
</template>
|
||||
|
||||
<div class="m-editor-layout-center" :class="centerClass" :style="`width: ${center}px`">
|
||||
<slot name="center"></slot>
|
||||
</div>
|
||||
|
||||
<template v-if="typeof props.right !== 'undefined'">
|
||||
<Resizer @change="changeRight"></Resizer>
|
||||
<div class="m-editor-layout-right" :class="rightClass" :style="`width: ${right}px`">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import Resizer from './Resizer.vue';
|
||||
|
||||
const emit = defineEmits(['update:left', 'change', 'update:right']);
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
left?: number;
|
||||
right?: number;
|
||||
minLeft?: number;
|
||||
minRight?: number;
|
||||
leftClass?: string;
|
||||
rightClass?: string;
|
||||
centerClass?: string;
|
||||
}>(),
|
||||
{
|
||||
minLeft: 1,
|
||||
minRight: 1,
|
||||
},
|
||||
);
|
||||
|
||||
const el = ref<HTMLElement>();
|
||||
|
||||
let clientWidth = 0;
|
||||
const resizerObserver = new ResizeObserver((entries) => {
|
||||
for (const { contentRect } of entries) {
|
||||
clientWidth = contentRect.width;
|
||||
|
||||
center.value = clientWidth - (props.left || 0) - (props.right || 0);
|
||||
|
||||
emit('change', {
|
||||
left: props.left,
|
||||
center: center.value,
|
||||
right: props.right,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (el.value) {
|
||||
resizerObserver.observe(el.value);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resizerObserver.disconnect();
|
||||
});
|
||||
|
||||
const center = ref(0);
|
||||
|
||||
const changeLeft = (deltaX: number) => {
|
||||
if (typeof props.left === 'undefined') return;
|
||||
const left = Math.max(props.left + deltaX, props.minLeft) || 0;
|
||||
emit('update:left', left);
|
||||
center.value = clientWidth - left - (props.right || 0);
|
||||
|
||||
emit('change', {
|
||||
left,
|
||||
center: center.value,
|
||||
right: props.right,
|
||||
});
|
||||
};
|
||||
|
||||
const changeRight = (deltaX: number) => {
|
||||
if (typeof props.right === 'undefined') return;
|
||||
const right = Math.max(props.right - deltaX, props.minRight) || 0;
|
||||
emit('update:right', right);
|
||||
center.value = clientWidth - (props.left || 0) - right;
|
||||
|
||||
emit('change', {
|
||||
left: props.left,
|
||||
center: center.value,
|
||||
right,
|
||||
});
|
||||
};
|
||||
</script>
|
@ -4,58 +4,29 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, onMounted, onUnmounted, ref, toRaw } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import Gesto from 'gesto';
|
||||
|
||||
import { Services } from '../type';
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
export default defineComponent({
|
||||
name: 'm-editor-resize',
|
||||
const target = ref<HTMLSpanElement>();
|
||||
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
let getso: Gesto;
|
||||
|
||||
setup(props) {
|
||||
const services = inject<Services>('services');
|
||||
onMounted(() => {
|
||||
if (!target.value) return;
|
||||
getso = new Gesto(target.value, {
|
||||
container: window,
|
||||
pinchOutside: true,
|
||||
}).on('drag', (e) => {
|
||||
if (!target.value) return;
|
||||
|
||||
const target = ref<HTMLSpanElement>();
|
||||
emit('change', e.deltaX);
|
||||
});
|
||||
});
|
||||
|
||||
let getso: Gesto;
|
||||
|
||||
onMounted(() => {
|
||||
if (!target.value) return;
|
||||
getso = new Gesto(target.value, {
|
||||
container: window,
|
||||
pinchOutside: true,
|
||||
}).on('drag', (e) => {
|
||||
if (!target.value || !services) return;
|
||||
|
||||
let { left, right } = {
|
||||
...toRaw(services.uiService.get('columnWidth')),
|
||||
};
|
||||
if (props.type === 'left') {
|
||||
left += e.deltaX;
|
||||
} else if (props.type === 'right') {
|
||||
right -= e.deltaX;
|
||||
}
|
||||
services.uiService.set('columnWidth', {
|
||||
left,
|
||||
right,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
getso?.unset();
|
||||
});
|
||||
|
||||
return {
|
||||
target,
|
||||
};
|
||||
},
|
||||
onUnmounted(() => {
|
||||
getso?.unset();
|
||||
});
|
||||
</script>
|
||||
|
@ -16,28 +16,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import type StageCore from '@tmagic/stage';
|
||||
|
||||
import editorService from '../services/editor';
|
||||
import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '../type';
|
||||
import type { StageRect, UiState } from '../type';
|
||||
|
||||
import BaseService from './BaseService';
|
||||
|
||||
const DEFAULT_LEFT_COLUMN_WIDTH = 310;
|
||||
const MIN_LEFT_COLUMN_WIDTH = 45;
|
||||
const DEFAULT_RIGHT_COLUMN_WIDTH = 480;
|
||||
const MIN_RIGHT_COLUMN_WIDTH = 1;
|
||||
|
||||
const COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorColumnWidthData';
|
||||
|
||||
const defaultColumnWidth = {
|
||||
left: DEFAULT_LEFT_COLUMN_WIDTH,
|
||||
center: globalThis.document.body.clientWidth - DEFAULT_LEFT_COLUMN_WIDTH - DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||
right: DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||
};
|
||||
|
||||
const state = reactive<UiState>({
|
||||
uiSelectMode: false,
|
||||
showSrc: false,
|
||||
@ -50,7 +37,7 @@ const state = reactive<UiState>({
|
||||
width: 375,
|
||||
height: 817,
|
||||
},
|
||||
columnWidth: defaultColumnWidth,
|
||||
columnWidth: {},
|
||||
showGuides: true,
|
||||
showRule: true,
|
||||
propsPanelSize: 'small',
|
||||
@ -59,7 +46,7 @@ const state = reactive<UiState>({
|
||||
|
||||
class Ui extends BaseService {
|
||||
constructor() {
|
||||
super(['initColumnWidth', 'zoom', 'calcZoom']);
|
||||
super(['zoom', 'calcZoom']);
|
||||
globalThis.addEventListener('resize', () => {
|
||||
this.setColumnWidth({
|
||||
center: 'auto',
|
||||
@ -70,11 +57,6 @@ class Ui extends BaseService {
|
||||
public set<T = any>(name: keyof UiState, value: T) {
|
||||
const mask = editorService.get<StageCore>('stage')?.mask;
|
||||
|
||||
if (name === 'columnWidth') {
|
||||
this.setColumnWidth(value as unknown as SetColumnWidth);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'stageRect') {
|
||||
this.setStageRect(value as unknown as StageRect);
|
||||
return;
|
||||
@ -95,18 +77,6 @@ class Ui extends BaseService {
|
||||
return (state as any)[name];
|
||||
}
|
||||
|
||||
public async initColumnWidth() {
|
||||
const columnWidthCacheData = globalThis.localStorage.getItem(COLUMN_WIDTH_STORAGE_KEY);
|
||||
if (columnWidthCacheData) {
|
||||
try {
|
||||
const columnWidthCache = JSON.parse(columnWidthCacheData);
|
||||
this.setColumnWidth(columnWidthCache);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async zoom(zoom: number) {
|
||||
this.set('zoom', (this.get<number>('zoom') * 100 + zoom * 100) / 100);
|
||||
if (this.get<number>('zoom') < 0.1) this.set('zoom', 0.1);
|
||||
@ -132,36 +102,6 @@ class Ui extends BaseService {
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
private setColumnWidth({ left, center, right }: SetColumnWidth) {
|
||||
const columnWidth = {
|
||||
...toRaw(this.get<GetColumnWidth>('columnWidth')),
|
||||
};
|
||||
|
||||
if (left) {
|
||||
columnWidth.left = Math.max(left, MIN_LEFT_COLUMN_WIDTH);
|
||||
}
|
||||
|
||||
if (right) {
|
||||
columnWidth.right = Math.max(right, MIN_RIGHT_COLUMN_WIDTH);
|
||||
}
|
||||
|
||||
if (!center || center === 'auto') {
|
||||
const bodyWidth = globalThis.document.body.clientWidth;
|
||||
columnWidth.center = bodyWidth - (columnWidth?.left || 0) - (columnWidth?.right || 0);
|
||||
if (columnWidth.center <= 0) {
|
||||
columnWidth.left = defaultColumnWidth.left;
|
||||
columnWidth.center = defaultColumnWidth.center;
|
||||
columnWidth.right = defaultColumnWidth.right;
|
||||
}
|
||||
} else {
|
||||
columnWidth.center = center;
|
||||
}
|
||||
|
||||
globalThis.localStorage.setItem(COLUMN_WIDTH_STORAGE_KEY, JSON.stringify(columnWidth));
|
||||
|
||||
state.columnWidth = columnWidth;
|
||||
}
|
||||
|
||||
private async setStageRect(value: StageRect) {
|
||||
state.stageRect = {
|
||||
...state.stageRect,
|
||||
|
Loading…
x
Reference in New Issue
Block a user