feat: ui布局

This commit is contained in:
unanmed 2024-02-03 12:54:37 +08:00
parent dac253cd7b
commit 67857841ca
3 changed files with 360 additions and 1 deletions

View File

@ -43,7 +43,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue'; import { onMounted, onUnmounted, onUpdated, ref, useSlots, watch } from 'vue';
import { DragOutlined } from '@ant-design/icons-vue'; import { DragOutlined } from '@ant-design/icons-vue';
import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use'; import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use';
import { has } from '../plugin/utils'; import { has } from '../plugin/utils';

View File

@ -28,6 +28,7 @@ import { AudioPlayer } from './audio/audio';
import { CustomToolbar } from './main/custom/toolbar'; import { CustomToolbar } from './main/custom/toolbar';
import { Hotkey } from './main/custom/hotkey'; import { Hotkey } from './main/custom/hotkey';
import { Keyboard } from './main/custom/keyboard'; import { Keyboard } from './main/custom/keyboard';
import './main/layout';
function ready() { function ready() {
readyAllResource(); readyAllResource();

358
src/core/main/layout.ts Normal file
View File

@ -0,0 +1,358 @@
import {
Component,
RenderFunction,
SetupContext,
VNode,
VNodeChild,
defineComponent,
h,
onMounted
} from 'vue';
interface VForRenderer {
type: '@v-for';
items: any[] | (() => any[]);
map: (value: any, index: number) => VNode;
}
interface MotaComponent extends MotaComponentConfig {
type: string;
children: MComponent[] | MComponent;
}
interface MotaComponentConfig {
innerText?: string | (() => string);
props?: Record<string, () => any>;
component?: Component | MComponent;
dComponent?: () => Component;
/** 传递插槽 */
slots?: Record<string, (props: Record<string, any>) => VNode | VNode[]>;
vif?: () => boolean;
velse?: boolean;
}
type OnSetupFunction = (props: Record<string, any>) => void;
type SetupFunction = (
props: Record<string, any>,
ctx: SetupContext
) => RenderFunction | Promise<RenderFunction>;
type RetFunction = (
props: Record<string, any>,
ctx: SetupContext
) => VNodeChild | VNodeChild[];
type OnMountedFunction = (
props: Record<string, any>,
canvas: HTMLCanvasElement[]
) => void;
type NonComponentConfig = Omit<
MotaComponentConfig,
'innerText' | 'component' | 'slots' | 'dComponent'
>;
export class MComponent {
static mountNum: number = 0;
content: (MotaComponent | VForRenderer)[] = [];
/** 渲染插槽 */
slots: Record<string, Record<string, any>> = {};
private onSetupFn?: OnSetupFunction;
private setupFn?: SetupFunction;
private onMountedFn?: OnMountedFunction;
private retFn?: RetFunction;
/**
* props可选props传入被渲染的组件
* @param name
* @param props props
*/
slot(name: string, props?: Record<string, any>): this {
this.slots[name] = props ?? {};
return this;
}
/**
* div渲染内容
* @param children
* @param config {@link MComponent.h}
*/
div(children?: MComponent[] | MComponent, config?: NonComponentConfig) {
return this.h('div', children, config);
}
/**
* span渲染内容
* @param children
* @param config {@link MComponent.h}
*/
span(children?: MComponent[] | MComponent, config?: NonComponentConfig) {
return this.h('span', children, config);
}
/**
* canvas渲染内容
* @param config {@link MComponent.h}
*/
canvas(config?: NonComponentConfig) {
return this.h('canvas', [], config);
}
/**
*
* @param text
*/
text(text: string | (() => string), config: NonComponentConfig = {}) {
return this.h('text', [], { ...config, innerText: text });
}
/**
*
* @param component
* @param config {@link MComponent.h}
*/
com(
component: Component | MComponent,
config: Omit<MotaComponentConfig, 'innerText' | 'component'>
) {
return this.h(component, [], config);
}
vfor<T>(items: T[] | (() => T[]), map: (value: T, index: number) => VNode) {
this.content.push({
type: '@v-for',
items,
map
});
return this;
}
/**
* `MComponent``MComponent``export`
* `Vue``h``h``export`
*
* `slots``vfor.map`
* 使使`setup`
* setup函数
* @param type
* - dom元素`div` `span`
* - `MComponent`
* - `text``children`
* `config``innerText`
* - `component``config``component`
* `children`
* - `@v-for`
* @param children
* - `MComponent`
* - `MComponent`
* @param config
* - `innerText`:
* - `props`: `props``props`
* `prop``class` `id`html属性`attribute`
* `Vue``attribute`on开头
* v-on
* - `component`: `dComponent`
* - `dComponent`: `component`
* `component`使
* - `slots`:
* VNode或数组`MComponent.vNode`VNode数组
*
* - `vif`: `velse``true`
* `else-if`
* - `velse`:
*/
h(
type: string | Component | MComponent,
children?: MComponent[] | MComponent,
config: MotaComponentConfig = {}
): this {
if (typeof type === 'string') {
this.content.push({
type,
children: children ?? [],
props: config.props,
innerText: config.innerText,
slots: config.slots,
vif: config.vif,
velse: config.velse,
component: config.component
});
} else {
this.content.push({
type: 'component',
children: children ?? [],
props: config.props,
innerText: config.innerText,
slots: config.slots,
vif: config.vif,
velse: config.velse,
component: type
});
}
return this;
}
/**
* setup被执行时props
*/
onSetup(fn: OnSetupFunction) {
this.onSetupFn = fn;
return this;
}
onMounted(fn: OnMountedFunction) {
this.onMountedFn = fn;
return this;
}
/**
* setup执行函数props, slotsVNode
*/
setup(fn: SetupFunction) {
this.setupFn = fn;
return this;
}
/**
* setup返回的函数
* @param fn setup返回的函数
*/
ret(fn: RetFunction) {
this.retFn = fn;
return this;
}
/**
* MComponent实例导出成为一个组件
*/
export() {
if (!this.setupFn) {
return defineComponent((props, ctx) => {
const mountNum = MComponent.mountNum++;
this.onSetupFn?.(props);
onMounted(() => {
this.onMountedFn?.(
props,
Array.from(
document.getElementsByClassName(
`--mota-component-canvas-${mountNum}`
) as HTMLCollectionOf<HTMLCanvasElement>
)
);
});
if (this.retFn) return () => this.retFn!(props, ctx);
else {
return () => {
const vNodes = MComponent.vNode(this.content, mountNum);
return vNodes;
};
}
});
} else {
return defineComponent((props, ctx) => this.setupFn!(props, ctx));
}
}
static vNode(children: (MotaComponent | VForRenderer)[], mount?: number) {
const mountNum = mount ?? this.mountNum++;
const res: VNode[] = [];
const vifRes: Map<number, boolean> = new Map();
children.forEach((v, i) => {
if (v.type === '@v-for') {
const node = v as VForRenderer;
const items =
typeof node.items === 'function'
? node.items()
: node.items;
items.forEach((v, i) => {
res.push(node.map(v, i));
});
} else {
const node = v as MotaComponent;
if (node.velse && vifRes.get(i - 1)) {
vifRes.set(i, true);
return;
}
let vif = true;
if (node.vif) {
vifRes.set(i, (vif = node.vif()));
}
if (!vif) return;
const props = this.unwrapProps(node.props);
if (v.type === 'component') {
if (!v.component && !v.dComponent) {
throw new Error(
`Using dynamic component must provide component property.`
);
}
if (v.dComponent) {
res.push(h(v.dComponent(), props, v.slots));
} else {
if (v.component instanceof MComponent) {
res.push(
...MComponent.vNode(
v.component.content,
mountNum
)
);
} else {
res.push(h(v.component!, props, v.slots));
}
}
} else if (v.type === 'text') {
res.push(
h(
'span',
typeof v.innerText === 'function'
? v.innerText()
: v.innerText
)
);
} else if (v.type === 'canvas') {
const cls = `--mota-component-canvas-${mountNum}`;
const mix = !!props.class ? cls + ' ' + props.class : cls;
props.class = mix;
res.push(h('canvas', props, node.slots));
} else {
// 这个时候不可能会有插槽,只会有子内容,因此直接渲染子内容
const content = [node.children].flat(2);
const vn = this.vNode(
content.map(v => v.content).flat(),
mountNum
);
res.push(h(v.type, props, vn));
}
}
});
return res;
}
static unwrapProps(props?: Record<string, () => any>): Record<string, any> {
if (!props) return {};
const res: Record<string, any> = {};
for (const [key, value] of Object.entries(props)) {
res[key] = value();
}
return res;
}
/**
* propsprops
* @param component props的组件
* @param props props
*/
static prop(component: Component, props: Record<string, any>) {
return h(component, props);
}
}
/**
* MComponent实例ui时会频繁使用使m这个简单的名字作为函数名
* @returns MComponent实例
*/
export function m() {
return new MComponent();
}