change pkg

This commit is contained in:
yehuozhili
2021-07-09 01:41:03 +08:00
commit 968a072537
115 changed files with 19556 additions and 0 deletions

20
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: build
on: [push]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
with:
persist-credentials: false
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
run: |
yarn install
npm run deploy
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@releases/v3
with:
GITHUB_TOKEN: ${{secrets.ACCESS_TOKEN}}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: packages/dooringx-doc/build # The folder the action should deploy.

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
node_modules
.svelte-kit
/package
yarn-error.log
.vercel_build_output
build
dist

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
.svelte-kit/**
static/**
build/**
node_modules/**

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"useTabs": true,
"singleQuote": true,
"printWidth": 100,
"semi": true,
"trailingComma": "es5"
}

4
lerna.json Normal file
View File

@@ -0,0 +1,4 @@
{
"packages": ["packages/*"],
"version": "0.0.0"
}

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "root",
"workspaces": [
"packages/*"
],
"scripts": {
"start": "lerna exec npm run start --scope=dooringx-lib",
"build": "lerna exec npm run build --scope=dooringx-lib",
"deploy": "lerna exec npm run deploy --scope=dooringx-doc",
"pub": "node ./script/publish.js"
},
"private": true,
"devDependencies": {
"lerna": "^3.22.1",
"husky": "4.3.0",
"lint-staged": "^11.0.0",
"rimraf": "^3.0.2",
"fs-extra": "^10.0.0",
"prettier": "^2.2.0",
"typescript": "^4.1.2",
"tslib": "^2.1.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,tsx,ts,less,md,json}": [
"npx prettier --write ./packages/dooringx-doc/src ./packages/dooringx-lib/src",
"git add ."
]
}
}

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

View File

@@ -0,0 +1,39 @@
{
"name": "dooringx-doc",
"version": "0.0.1",
"scripts": {
"start": "svelte-kit dev",
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"deploy": "cross-env DEPLOY=TRUE svelte-kit build",
"preview": "svelte-kit preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/kit": "next",
"@types/cookie": "^0.4.0",
"@types/marked": "^2.0.3",
"prettier": "~2.2.1",
"prettier-plugin-svelte": "^2.2.0",
"svelte": "^3.34.0",
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"@sveltejs/adapter-node": "^1.0.0-next.29",
"@sveltejs/adapter-static": "^1.0.0-next.13"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.2.2",
"@lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1",
"cross-env": "^7.0.3",
"marked": "^2.1.3",
"prism-svelte": "^0.4.7",
"prismjs": "^1.24.0",
"sass": "^1.35.1",
"slug": "^5.1.0"
}
}

View File

@@ -0,0 +1,15 @@
---
title: xcxzc
sTitle: vc
order: 4
---
## dsas
saff
sa
d
ad
sa
d
fsad

View File

@@ -0,0 +1,351 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type='checkbox'],
[type='radio'] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@@ -0,0 +1,20 @@
<!--
* @Author: yehuozhili
* @Date: 2021-06-29 11:14:15
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-01 22:13:09
* @FilePath: \my-app\src\app.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
---
title: dooringx-lib 是什么?
sTitle: 介绍
order: 1
---
dooringx-lib 是 dooringx 的基座,是移除了 dooringx 插件的无代码低代码框架。
dooringx-lib 提供自己的一套数据流事件机制以及弹窗等解决方案,可以让你更快地自己定制开发无代码或低代码平台。

View File

@@ -0,0 +1,13 @@
---
title: dooringx-lib 如何工作?
sTitle: 介绍
order: 2
---
dooringx-lib 在载入后会进行实例化,如果有插件需要传递给 config。
开发者通过调用 api 来获取想要的数据,来开发出自己想要的功能。
对于概念部分请参考 dooringx-lib 基础,对于 api 部分请参考 api。
建议先学习 dooringx-lib 基础和 dooringx-lib 插件开发注意事项再去看 api

View File

@@ -0,0 +1,15 @@
---
title: 快速上手
sTitle: 介绍
order: 3
---
### 安装
使用 npm 或者 yarn 安装
```bash
npm i dooringx-lib
```
有关 api 部分请参考 api

View File

@@ -0,0 +1,17 @@
---
title: store
sTitle: dooringx-lib基础
order: 3
---
store 类似于 redux 的概念,它内部实现了 redo、undo、发布订阅、置换数据、强制刷新等功能。
store 可以在 config 中获取。
在最开始时,需要通过 useStoreState 与 react 结合,此时可以在任意位置使用 store.forceUpdate 强刷,也可以使用 state 获取 store 中的数据。
store 的主要数据是保存着每次修改 jsonSchema 队列。
如果你需要更新数据,在深拷贝后使用 setData 方法进行更新。
如果你需要更新时不记录在 redo 或 undo 上留下记录,那么请操作队列删除其中保存内容即可。

View File

@@ -0,0 +1,5 @@
---
title: functionCenter
sTitle: dooringx-lib基础
order: 3
---

1
packages/dooringx-doc/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="@sveltejs/kit" />

View File

@@ -0,0 +1,43 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-29 11:14:15
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-01 10:13:37
* @FilePath: \my-app\src\hooks.ts
*/
import cookie from 'cookie';
import { v4 as uuid } from '@lukeed/uuid';
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ request, resolve }) => {
const cookies = cookie.parse(request.headers.cookie || '');
request.locals.userid = cookies.userid || uuid();
// TODO https://github.com/sveltejs/kit/issues/1046
if (request.query.has('_method')) {
request.method = request.query.get('_method').toUpperCase();
}
const response = await resolve(request);
if (!cookies.userid) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
response.headers['set-cookie'] = `userid=${request.locals.userid}; Path=/; HttpOnly`;
}
return response;
};
/** @type {import('@sveltejs/kit').ServerFetch} */
export async function serverFetch(request) {
/*
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
*/
return fetch(request);
}

View File

@@ -0,0 +1,37 @@
<script lang="ts">
export let href = '';
export let color = '#4d5164';
let backgroundColor = 'white';
export let style = '';
export let onClick = () => {};
</script>
<button
class="yh-btn"
style={`color: ${color} ; background-color:${backgroundColor};${style} `}
on:click={() => {
if (href !== '') {
location.href = href;
}
onClick();
}}
>
<slot />
</button>
<style lang="scss">
.yh-btn {
border: none;
transition: all 0.3s linear;
padding: 5px 20px;
&:active,
&:hover,
&:focus {
border: none;
outline-width: 0;
}
&:hover {
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,177 @@
<script lang="ts">
import type { MarkDownItemProps } from 'src/routes/docs/_api';
import './prism.css';
export let sections: Map<string, MarkDownItemProps[]>;
let arr = [];
$: arr = Array.from(sections.keys());
export let active = '';
</script>
<div style="display: flex;">
<div class="sidebar">
{#each arr as stitle}
{#if stitle !== 'default'}
<div
class={`stitle ahref fbold ${active === stitle ? 'active' : ''}`}
style="cursor: pointer;"
title={stitle}
on:click={() => {
location.href = `#${stitle}`;
active = stitle;
}}
>
{@html stitle}
</div>
{#each sections.get(stitle) as section}
<div class="title-item" style="cursor: pointer;">
<span
class={`ahref ${active === section.metadata.title ? 'active' : ''}`}
on:click={() => {
location.href = `#${section.slug}`;
active = section.metadata.title;
}}
title={section.metadata.title}
>
{@html section.metadata.title}
</span>
</div>
{/each}
{/if}
{#if stitle === 'default'}
{#each sections.get(stitle) as section}
<!-- 没有主标题则二级变一级 -->
<div class="stitle fbold" style="cursor: pointer;">
<span
class={`ahref ${active === section.metadata.title ? 'active' : ''}`}
on:click={() => {
location.href = `#${section.slug}`;
active = section.metadata.title;
}}
title={section.metadata.title}
>
{@html section.metadata.title}
</span>
</div>
{/each}
{/if}
{/each}
</div>
<div class="markdown-wrapper">
{#each arr as stitle}
{#if stitle !== 'default'}
<h1 class="stitle-content" id={stitle}>
{@html stitle}
</h1>
<div class="yh-interval-s" />
{/if}
{#each sections.get(stitle) as section}
<section data-id={section.slug}>
<h2>
<span class="offset-anchor" id={section.slug} />
{@html section.metadata.title}
</h2>
{@html section.html}
</section>
<div class="yh-interval" />
{/each}
{/each}
</div>
</div>
<style lang="scss">
.yh-interval-s {
width: 100%;
padding: 2px;
}
.yh-interval {
width: 100%;
padding: 10px;
}
.markdown-wrapper {
height: calc(100vh - 40px);
overflow: auto;
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.fbold {
font-weight: bold;
}
.sidebar {
padding: 20px;
overflow: auto;
height: calc(100vh - 40px);
border-right: 1px solid #eee;
width: 300px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji,
Segoe UI Emoji, Segoe UI Symbol;
.ahref {
text-decoration: none;
color: #717484;
&:hover {
color: #4569d4;
}
&:visited,
&:link,
&:active {
color: #717484;
}
}
.stitle {
margin: 20px 0;
}
.title-item {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin: 20px 0 20px 20px;
& ::selection {
cursor: pointer;
}
}
.active {
color: #4569d4;
}
}
section :global(blockquote) {
color: hsl(204, 100%, 50%);
border: 2px solid var(--flash);
}
section :global(blockquote) :global(code) {
background: hsl(204, 100%, 95%) !important;
color: hsl(204, 100%, 50%);
}
::-webkit-scrollbar {
width: 5px; /*对垂直流动条有效*/
height: 5px; /*对水平流动条有效*/
}
/*定义滚动条的轨道颜色、内阴影及圆角*/
::-webkit-scrollbar-track {
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
background-color: #eee;
border-radius: 3px;
}
/*定义滑块颜色、内阴影及圆角*/
::-webkit-scrollbar-thumb {
border-radius: 7px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #444444;
}
/*定义两端按钮的样式*/
::-webkit-scrollbar-button {
background-color: #b9c6d2;
}
/*定义右下角汇合处的样式*/
::-webkit-scrollbar-corner {
background: #b9c6d2;
}
</style>

View File

@@ -0,0 +1,120 @@
/*
-----------------------------------------------
syntax-highlighting [prism]
-----------------------------------------------
*/
/* colors --------------------------------- */
pre[class*='language-'] {
--background: var(--back-light);
--base: #545454;
--comment: #696969;
--keyword: #007f8a;
--function: #bb5525;
--string: #856e3d;
--number: #008000;
--tags: var(--function);
--important: var(--string);
}
/* type-base ------------------------------ */
code[class*='language-'],
pre[class*='language-'] {
background: none;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
font: 300 var(--code-fs) / 1.7 var(--font-mono);
color: var(--base);
tab-size: 2;
-moz-tab-size: 2;
-webkit-hyphens: none;
hyphens: none;
}
/* code-blocks ---------------------------- */
pre[class*='language-'] {
overflow: auto;
padding: 1.5rem 2rem;
margin: 0.8rem 0 2.4rem;
/* max-width: var(--code-w); */
border-radius: var(--border-r);
box-shadow: 1px 1px 1px rgba(68, 68, 68, 0.12) inset;
}
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: var(--background);
}
/* tokens --------------------------------- */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: var(--comment);
}
.token.punctuation {
color: var(--base);
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: var(--tags);
}
.token.boolean,
.token.number {
color: var(--number);
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: var(--string);
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: var(--base);
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: var(--function);
}
.token.keyword {
color: var(--keyword);
}
.token.regex,
.token.important {
color: var(--important);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,85 @@
<script lang="ts">
import { page } from '$app/stores';
import { base } from '$app/paths';
import Button from '../Button/index.svelte';
import Switch from '../Switch/index.svelte';
import logo from './svelte-logo.svg';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
const lang = getContext<Writable<string>>('lang');
let checked = true;
lang.subscribe((value) => {
value === 'cn' ? (checked = true) : (checked = false);
});
const home = base + '/';
const docs = base + '/docs';
const api = base + '/api';
</script>
<header>
<div class="corner">
<img src={logo} alt="SvelteKit" />
</div>
<nav style="width: 100%;">
<div class="nav-item-wrapper">
<div class:active={$page.path === '/'}>
<Button href={home} color={$page.path === '/' ? '#4569d4' : '#4d5164'}>首页</Button>
</div>
<div class:active={$page.path === '/docs'}>
<Button href={docs} color={$page.path === '/docs' ? '#4569d4' : '#4d5164'}>文档</Button>
</div>
<div class:active={$page.path === '/api'}>
<Button href={api} color={$page.path === '/api' ? '#4569d4' : '#4d5164'}>API</Button>
</div>
<div class:active={$page.path === '/about'}>
<Button>Github</Button>
</div>
<Switch
{checked}
onChange={() => {
lang.update((pre) => {
return pre === 'cn' ? 'en' : 'cn';
});
}}
/>
</div>
</nav>
</header>
<style lang="scss">
$height: 40px;
header {
display: flex;
justify-content: space-between;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji,
Segoe UI Emoji, Segoe UI Symbol;
border-bottom: 1px solid #e2e2e2;
}
.active {
color: #4569d4;
}
.corner {
height: $height;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20px;
img {
width: $height - 5px;
height: $height - 5px;
}
}
.nav-item-wrapper {
height: $height;
display: flex;
justify-content: flex-end;
align-items: center;
:nth-last-child(1) {
margin-right: 20px;
}
}
</style>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,32 @@
<!--
-----------------------------------------------
svg icon
- https://github.com/jacobmischka/svelte-feather-icon
- https://feathericons.com/
-----------------------------------------------
-->
<script>
export let name;
export let size = 20;
</script>
<svg class="icon" width={size} height={size}>
<use xlink:href="#{name}" />
</svg>
<style>
.icon {
position: relative;
overflow: hidden;
vertical-align: middle;
-o-object-fit: contain;
object-fit: contain;
-webkit-transform-origin: center center;
transform-origin: center center;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
</style>

View File

@@ -0,0 +1,123 @@
<div style="display: none">
<!-- wrapper div allows use of innerHTML -->
<svg>
<symbol id="arrow-left" class="icon" viewBox="0 0 24 24">
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</symbol>
<symbol id="arrow-right" class="icon" viewBox="0 0 24 24">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</symbol>
<symbol id="arrow-up" class="icon" viewBox="0 0 24 24">
<line x1="12" y1="19" x2="12" y2="5" />
<polyline points="5 12 12 5 19 12" />
</symbol>
<symbol id="arrow-down" class="icon" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19" />
<polyline points="19 12 12 19 5 12" />
</symbol>
<symbol id="check" class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</symbol>
<symbol id="close" class="icon" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</symbol>
<symbol id="download" class="icon" viewBox="0 0 24 24">
<path d="M21 15V19A2 2 0 0 1 19 21H5A2 2 0 0 1 3 19V15" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</symbol>
<symbol id="edit" class="icon" viewBox="0 0 24 24">
<path d="M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34" />
<polygon points="18 2 22 6 12 16 8 16 8 12 18 2" />
</symbol>
<symbol id="github" class="icon" viewBox="0 0 24 24">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
/>
</symbol>
<symbol id="git-branch" class="icon" viewBox="0 0 24 24">
<line x1="6" y1="3" x2="6" y2="15" />
<circle cx="18" cy="6" r="3" />
<circle cx="6" cy="18" r="3" />
<path d="M18 9a9 9 0 0 1-9 9" />
</symbol>
<symbol id="log-in" class="icon" viewBox="0 0 24 24">
<path d="M15 3H19A2 2 0 0 1 21 5V19A2 2 0 0 1 19 21H15" />
<polyline points="10 17 15 12 10 7" />
<line x1="15" y1="12" x2="3" y2="12" />
</symbol>
<symbol id="maximize" class="icon" viewBox="0 0 24 24">
<path
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"
/>
</symbol>
<symbol id="maximize-2" class="icon" viewBox="0 0 24 24">
<polyline points="15 3 21 3 21 9" />
<polyline points="9 21 3 21 3 15" />
<line x1="21" y1="3" x2="14" y2="10" />
<line x1="3" y1="21" x2="10" y2="14" />
</symbol>
<symbol id="menu" class="icon" viewBox="0 0 24 24">
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="18" x2="21" y2="18" />
</symbol>
<symbol id="message-square" class="icon" viewBox="0 0 24 24">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</symbol>
<symbol id="minus" class="icon" viewBox="0 0 24 24">
<line x1="5" y1="12" x2="19" y2="12" />
</symbol>
<symbol id="plus" class="icon" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</symbol>
<symbol id="save" class="icon" viewBox="0 0 24 24">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</symbol>
<symbol id="link" class="icon" viewBox="0 0 24 24">
<path d="M9,7L6,7A2 2 0 0 0 6,17L9,17" />
<path d="M15,7L18,7A2 2 0 0 1 18,17L15,17" />
<path d="M7,12L17,12" />
</symbol>
<symbol id="chevron" class="icon" viewBox="0 0 24 24">
<path d="M2,7 L12,17 L20,7" />
</symbol>
<symbol id="404" class="icon" viewBox="0 0 128 128">
<path
d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 01-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 012.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 00-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 00-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 01-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 01-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 01-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 00.665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"
/>
</symbol>
<symbol id="user" class="icon" viewBox="0 0 130 130">
<path
d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z"
stroke="#979797"
/>
</symbol>
</svg>
</div>

View File

@@ -0,0 +1,89 @@
<script lang="ts">
export let checked = true;
export let disabled = false;
export let onChange = () => {
checked = !checked;
};
export let unCheckText = 'EN';
export let checkText = '中文';
</script>
<label class="yh-switch-label">
<input
class="yh-swtich-input"
type="checkbox"
{checked}
{disabled}
on:change={() => {
onChange();
}}
/>
<span class={`yh-switch-sp1`}>
{#if !checked}
<span class="yh-switch-sp1-uncheck">
{unCheckText}
</span>
{:else}
<span class="yh-switch-sp1-check">
{checkText}
</span>
{/if}
</span>
<span class={`yh-switch-sp2 ${checked ? 'right' : ''}`} />
</label>
<style lang="scss">
.yh-switch-label {
height: 22.5px;
width: 60px;
position: relative;
cursor: pointer;
display: inline-block;
margin: 0 10px;
}
.yh-swtich-input {
opacity: 0;
}
.yh-switch-sp1 {
background: #fff;
box-shadow: inset 2px 2px 4px #d9d9d9, inset -2px -2px 4px #fff, 2px 2px 4px #d9d9d9;
color: #595959;
height: 100%;
left: 0;
padding: 1px;
position: absolute;
top: 0;
width: 100%;
border-radius: 60px;
&-uncheck {
font-size: 10px;
top: 7px;
right: 10px;
position: absolute;
}
&-check {
font-size: 10px;
left: 10px;
top: 7px;
position: absolute;
}
}
.yh-switch-sp2 {
height: 22.5px;
width: 22.5px;
background: #fff;
border: none;
box-shadow: 2px 2px 4px #d9d9d9;
text-shadow: 1px 1px 4px #d9d9d9, -1px -1px 4px #fff;
border-radius: 50%;
display: inline-block;
left: 0;
position: absolute;
transition: all 0.36s cubic-bezier(0.78, 0.14, 0.15, 0.86);
&.right {
left: calc(100% - 22.5px);
}
}
</style>

View File

@@ -0,0 +1,60 @@
// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
export function enhance(
form: HTMLFormElement,
{
pending,
error,
result,
}: {
pending?: (data: FormData, form: HTMLFormElement) => void;
error?: (res: Response, error: Error, form: HTMLFormElement) => void;
result: (res: Response, form: HTMLFormElement) => void;
}
) {
let current_token: {};
async function handle_submit(e: Event) {
const token = (current_token = {});
e.preventDefault();
const body = new FormData(form);
if (pending) pending(body, form);
try {
const res = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json',
},
body,
});
if (token !== current_token) return;
if (res.ok) {
result(res, form);
} else if (error) {
error(res, null, form);
} else {
console.error(await res.text());
}
} catch (e) {
if (error) {
error(null, e, form);
} else {
throw e;
}
}
}
form.addEventListener('submit', handle_submit);
return {
destroy() {
form.removeEventListener('submit', handle_submit);
},
};
}

View File

@@ -0,0 +1,7 @@
/**
* Can be made globally available by placing this
* inside `global.d.ts` and removing `export` keyword
*/
export interface Locals {
userid: string;
}

View File

@@ -0,0 +1,39 @@
<script context="module">
import { browser, dev } from '$app/env';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;
</script>
<script lang="ts">
import Header from '$lib/Header/index.svelte';
import '../app.css';
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
const lang = writable('cn');
setContext('lang', lang);
</script>
<Header />
<main>
<slot />
</main>
<style lang="scss">
main {
position: relative;
height: calc(100vh - 41px);
display: flex;
flex-direction: column;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,45 @@
<script lang="ts">
import Icon from '$lib/Icon/index.svelte';
import Icons from '$lib/Icons/index.svelte';
</script>
<svelte:head>
<title>About</title>
</svelte:head>
<Icons />
<div class="content">
<h1>About this app</h1>
<Icon name="arrow-left" />
<Icon name="arrow-right" />
<Icon name="arrow-up" />
<Icon name="arrow-down" />
<Icon name="check" />
<Icon name="close" />
<Icon name="download" />
<Icon name="edit" />
<Icon name="github" />
<Icon name="git-branch" />
<Icon name="log-in" />
<Icon name="maximize" />
<Icon name="maximize-2" />
<Icon name="menu" />
<Icon name="message-square" />
<Icon name="minus" />
<Icon name="plus" />
<Icon name="save" />
<Icon name="link" />
<Icon name="chevron" />
<Icon name="404" />
<Icon name="user" />
</div>
<style>
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
</style>

View File

@@ -0,0 +1,17 @@
/*
* @Author: yehuozhili
* @Date: 2021-07-06 20:19:21
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 20:20:07
* @FilePath: \my-app\src\routes\api\index.json.ts
*/
// 必须建立json否则不生成json文件
import type { RequestHandler } from '@sveltejs/kit';
import { api } from '../docs/_api';
export const get: RequestHandler = async (request) => {
const param = request.query;
const name = param.get('name');
const response = await api(name);
return response;
};

View File

@@ -0,0 +1,64 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import type { MarkDownItemProps } from '../docs/_api';
import { base } from '$app/paths';
export const load: Load = async ({ fetch }) => {
const path = `${base}/api.json?name=api`;
const res = await fetch(path);
if (res.ok) {
const files: MarkDownItemProps[] = await res.json();
return {
props: { files: files }
};
}
const { message } = await res.json();
return {
error: new Error(message)
};
};
</script>
<script lang="ts">
import DocRender from '$lib/DocRender/index.svelte';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
export let files: MarkDownItemProps[];
const lang = getContext<Writable<string>>('lang');
let sections = new Map<string, MarkDownItemProps[]>();
lang.subscribe((la) => {
sections = new Map<string, MarkDownItemProps[]>();
let tmp = files.filter((v) => {
const name = v.file;
const sp = name.split('.');
if (Array.isArray(sp) && sp.length > 1 && sp[sp.length - 2] === 'EN') {
return la === 'en';
} else {
return la === 'cn';
}
});
tmp.forEach((v) => {
const stitle = v.sTitle || 'default';
const value = sections.get(stitle);
if (value) {
value.push(v);
} else {
sections.set(stitle, [v]);
}
});
});
let active = '';
</script>
<svelte:head>
<title>API Docs • Svelte</title>
<meta name="twitter:title" content="Svelte API docs" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Cybernetically enhanced web apps" />
</svelte:head>
<DocRender {active} {sections} />
<style lang="scss">
</style>

View File

@@ -0,0 +1,126 @@
import fs from 'fs';
import path from 'path';
import marked from 'marked';
import { extract_frontmatter } from '../../utils/markdown';
import { highlight } from '../../utils/highlight';
import slugf from 'slug';
export interface MarkDownItemProps {
html: string;
metadata: Record<string, string>;
subsections: { slug: string; title: string; level: string }[];
slug: string;
file: string;
order: number;
sTitle: string;
}
export async function api(name: string) {
const res = await getMarkDown(name);
return {
status: 200,
body: res,
};
}
const blockTypes = [
'blockquote',
'html',
'heading',
'hr',
'list',
'listitem',
'paragraph',
'table',
'tablerow',
'tablecell',
];
export const getMarkDown = (name: string) => {
const root = process.cwd();
const docPath = path.resolve(root, 'src', name);
return fs
.readdirSync(docPath)
.filter((file) => file[0] !== '.' && path.extname(file) === '.md')
.map((file) => {
const currentFilePath = path.resolve(docPath, file);
const markdown = fs.readFileSync(currentFilePath, 'utf-8');
const { content, metadata } = extract_frontmatter(markdown);
const order = parseFloat(metadata.order);
const sTitle = metadata.sTitle;
const subsections = [];
const section_slug = slugf(metadata.title, '_');
const renderer = new marked.Renderer();
let block_open = false;
renderer.hr = () => {
block_open = true;
return '<div class="side-by-side"><div class="copy">';
};
renderer.code = (source, lang) => {
source = source.replace(/^ +/gm, (match) => match.split(' ').join('\t'));
let prefix = '';
let className = 'code-block';
const html = `<div class='${className}'>${prefix}${highlight(source, lang)}</div>`;
if (block_open) {
block_open = false;
return `</div><div class="code">${html}</div></div>`;
}
return html;
};
// 这个heading是md的标题
renderer.heading = (text, level, rawtext) => {
let slug;
const match = /<a href="([^"]+)"[^>]*>(.+)<\/a>/.exec(text); // 提取a标签链接为slug
if (match) {
slug = match[1];
text = match[2];
} else {
slug = slugf(rawtext, '_');
}
if (level === 1 || level === 2 || level === 3 || level === 4) {
const title = text
.replace(/<\/?code>/g, '')
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
if ($3) return `.${$1}(...)`;
if ($2) return `.${$1}()`;
return `.${$1}`;
});
subsections.push({ slug, title, level });
}
return `
<h${level + 1}>
<span id="${slug}" ></span>
<a href="docs#${slug}" class="anchor" aria-hidden="true"></a>
${text}
</h${level + 1}>`;
};
blockTypes.forEach((type) => {
const fn = renderer[type];
renderer[type] = function () {
return fn.apply(this, arguments);
};
});
const html = marked(content, { renderer });
const hashes = {};
return {
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m),
metadata,
subsections,
slug: section_slug,
order,
file,
sTitle,
};
})
.sort((a, b) => a.order - b.order);
};

View File

@@ -0,0 +1,16 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-30 16:57:15
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 14:53:18
* @FilePath: \my-app\src\routes\docs\index.json.ts
*/
import type { RequestHandler } from '@sveltejs/kit';
import { api } from './_api';
export const get: RequestHandler = async (request) => {
const param = request.query;
const name = param.get('name');
const response = await api(name);
return response;
};

View File

@@ -0,0 +1,64 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import type { MarkDownItemProps } from './_api';
import { base } from '$app/paths';
export const load: Load = async ({ fetch }) => {
const path = `${base}/docs.json?name=docs`;
const res = await fetch(path);
if (res.ok) {
const files: MarkDownItemProps[] = await res.json();
return {
props: { files: files }
};
}
const { message } = await res.json();
return {
error: new Error(message)
};
};
</script>
<script lang="ts">
import DocRender from '$lib/DocRender/index.svelte';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
export let files: MarkDownItemProps[];
const lang = getContext<Writable<string>>('lang');
let sections = new Map<string, MarkDownItemProps[]>();
lang.subscribe((la) => {
sections = new Map<string, MarkDownItemProps[]>();
let tmp = files.filter((v) => {
const name = v.file;
const sp = name.split('.');
if (Array.isArray(sp) && sp.length > 1 && sp[sp.length - 2] === 'EN') {
return la === 'en';
} else {
return la === 'cn';
}
});
tmp.forEach((v) => {
const stitle = v.sTitle || 'default';
const value = sections.get(stitle);
if (value) {
value.push(v);
} else {
sections.set(stitle, [v]);
}
});
});
let active = '';
</script>
<svelte:head>
<title>API Docs • Svelte</title>
<meta name="twitter:title" content="Svelte API docs" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Cybernetically enhanced web apps" />
</svelte:head>
<DocRender {active} {sections} />
<style lang="scss">
</style>

View File

@@ -0,0 +1,50 @@
<script context="module" lang="ts">
export const prerender = true;
</script>
<script lang="ts">
</script>
<svelte:head>
<title>Home</title>
</svelte:head>
<section>
<h1>
<div class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</div>
</h1>
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
h1 {
width: 100%;
}
.welcome {
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

View File

@@ -0,0 +1,21 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-30 19:20:22
* @LastEditors: yehuozhili
* @LastEditTime: 2021-06-30 21:22:04
* @FilePath: \my-app\src\utils\highlight.ts
*/
import { langs } from './markdown';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash.js';
import 'prismjs/components/prism-diff.js';
import 'prism-svelte';
export function highlight(source, lang) {
const plang = langs[lang] || '';
const highlighted = plang
? PrismJS.highlight(source, PrismJS.languages[plang], lang)
: source.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
return `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
}

View File

@@ -0,0 +1,56 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-30 19:09:39
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 14:19:17
* @FilePath: \my-app\src\utils\markdown.ts
*/
export function extract_frontmatter(markdown) {
const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown);
let content = '';
let metadata: Record<string, string> = {};
if (match) {
const frontMatter = match[1];
content = markdown.slice(match[0].length);
metadata = {};
frontMatter.split('\n').forEach((pair) => {
// split on the colon
const colonIndex = pair.indexOf(':');
let value = pair.slice(colonIndex + 1).trim();
// if surrounded by double quotes then remove those quotes
if (value && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
value = value.substring(1, value.length - 1);
}
metadata[pair.slice(0, colonIndex).trim()] = value;
});
}
return { metadata, content };
}
// map lang to prism-language-attr
export const langs = {
bash: 'bash',
html: 'markup',
sv: 'svelte',
js: 'javascript',
css: 'css',
diff: 'diff',
};
// links renderer
export function link_renderer(href, title, text) {
let target_attr = '';
let title_attr = '';
if (href.startsWith('http')) {
target_attr = ' target="_blank"';
}
if (title !== null) {
title_attr = ` title="${title}"`;
}
return `<a href="${href}"${target_attr}${title_attr} rel="noopener noreferrer">${text}</a>`;
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -0,0 +1,32 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-29 11:14:15
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 11:29:23
* @FilePath: \my-app\svelte.config.js
*/
import preprocess from 'svelte-preprocess';
import staticAdapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
adapter: staticAdapter({
pages: 'build',
assets: 'build',
}),
paths: process.env.DEPLOY
? {
base: '/svelte-kit-markdown',
}
: {},
trailingSlash: 'ignore',
},
};
export default config;

View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020"],
"target": "es2019",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}

View File

@@ -0,0 +1,18 @@
{
"plugins": [
[
"import-v2",
{
"libraryName": "@ant-design",
"libraryDirectory": "icons",
"camel2DashComponentName": false,
"style": false
}
],
[
"import",
{ "libraryName": "antd", "libraryDirectory": "lib", "style": true },
"antd"
]
]
}

View File

@@ -0,0 +1,10 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:00:20
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-09 01:34:14
* @FilePath: \dooringx\packages\dooringx-lib\.eslintrc.js
*/
module.exports = {
extends: ['react-app'],
};

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 yehuozhili
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,16 @@
<!--
* @Author: yehuozhili
* @Date: 2021-01-31 20:44:16
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:44:20
* @FilePath: \DooringV2\packages\dooringx-lib\README.md
-->
## DooringX 核心包
## changelog
- 0.1.7 修改预览特殊条件显示,删除 console
- 0.1.6 调整初始缩放,画布初始比例,增加回正画布功能。
- 0.1.5 删除未作按钮,增加 fixed 配置
- 0.1.4 基础功能

View File

@@ -0,0 +1,52 @@
{
"version": "0.1.7",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/dooringx-lib.esm.js",
"browser": "dist/dooringx-lib.esm.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=10"
},
"scripts": {
"start": "tsdx watch --noclean ",
"build": "tsdx build ",
"test": "tsdx test --passWithNoTests",
"lint": "tsdx lint",
"prepare": "tsdx build"
},
"peerDependencies": {
"react": ">=16.8",
"antd": ">=4"
},
"name": "dooringx-lib",
"author": "yehuozhili",
"devDependencies": {
"@ant-design/icons": "^4.6.2",
"antd": "^4.15.2",
"postcss-modules": "^4.0.0",
"babel-plugin-import": "^1.13.3",
"babel-plugin-import-v2": "^1.0.0",
"@types/react-color": "^3.0.4",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"size-limit": "^4.10.1",
"tsdx": "^0.14.1",
"react": "17.x",
"react-dom": "17.x",
"postcss": "^8.2.8",
"less": "^4.1.1",
"rollup-plugin-postcss": "^4.0.0",
"@types/uuid": "^8.3.0"
},
"dependencies": {
"deepcopy": "^2.1.0",
"react-color": "^2.19.3",
"react-sortable-hoc": "^2.0.0",
"uuid": "^8.3.2",
"animate.css": "^4.1.1"
}
}

View File

@@ -0,0 +1,201 @@
import { IBlockType } from '../core/store/storetype';
import { CSSProperties, PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react';
import { innerDrag } from '../core/innerDrag';
import { BlockResizer } from '../core/resizeHandler';
import { contextMenuEvent } from '../core/contextMenu';
import React from 'react';
import { transfer } from '../core/transfer';
import { UserConfig } from '../config';
import styles from '../index.less';
interface BlockProps {
data: IBlockType;
context: 'edit' | 'preview';
config: UserConfig;
}
/**
*
* 用来从component里拿到渲染进行渲染,由于异步拉代码,所以需要等待代码拉取完毕
* @param {*} props
* @returns
*/
function Blocks(props: PropsWithChildren<BlockProps>) {
const [state, setState] = useState<JSX.Element | null>(null);
const [previewState, setPreviewState] = useState({
top: props.data.top,
left: props.data.left,
height: props.data.height,
width: props.data.width,
});
useEffect(() => {
const fn = () => props.config.getComponentRegister().getComp(props.data.name);
const data = fn();
let unregist = () => {};
let newdata = { ...props.data };
if (props.context === 'preview') {
newdata = {
...props.data,
top: previewState.top,
left: previewState.left,
height: previewState.height,
width: previewState.width,
};
}
if (data) {
setState(data.render(newdata, props.context, props.config.getStore(), props.config));
} else {
const callback = () => {
const tmp = fn();
setState(tmp.render(newdata, props.context, props.config.getStore(), props.config));
unregist();
};
unregist = props.config.getComponentRegister().on(props.data.name, callback);
}
return () => {
unregist();
};
}, [props.data, props.context, props.config, previewState]);
const ref = useRef<HTMLDivElement>(null);
const innerDragData = useMemo(() => {
return { ...innerDrag(props.data, ref) };
}, [props.data]);
useEffect(() => {
const fn = () => {
const { top, left, width, height } = transfer(
props.data.top,
props.data.left,
props.data.height,
props.data.width,
props.data.fixed
);
setPreviewState({ top, left, width, height });
};
fn();
window.addEventListener('resize', fn);
return () => {
window.removeEventListener('resize', fn);
};
}, [
previewState.height,
previewState.left,
previewState.top,
previewState.width,
props.data.height,
props.data.left,
props.data.top,
props.data.width,
props.data.fixed,
]);
const animatecss = useMemo(() => {
const animate = props.data.animate;
if (Object.keys(animate).length > 0) {
return `animate__animated ${animate.animate ?? ''} ${animate.delay ?? ''} ${
animate.speed ?? ''
}`;
}
return '';
}, [
props.data.animate.animate,
props.data.animate.delay,
// props.data.animate.duration,
props.data.animate.speed,
]);
const animateCount = useMemo(() => {
const animate = props.data.animate;
if (Object.keys(animate).length > 0) {
return { animationIterationCount: animate.animationIterationCount };
}
return { animationIterationCount: 1 };
}, [props.data.animate.animationIterationCount]);
const render = useMemo(() => {
// 如果是编辑模式下,则需要包裹不能选中层,位移层,缩放控制层,平面移动层。
if (state && props.context === 'edit') {
const style: CSSProperties = props.data.canDrag ? { pointerEvents: 'none' } : {};
return (
<div
ref={ref}
className={
props.data.focus && props.data.position !== 'static' ? styles.yh_block_focus : ''
}
style={{
position: props.data.position,
top: props.data.top,
left: props.data.left,
width: props.data.width,
height: props.data.height,
zIndex: props.data.zIndex,
display: props.data.display,
}}
{...innerDragData}
onContextMenu={(e) => {
if (props.data.name !== 'modalMask') {
contextMenuEvent(e, ref);
}
}}
>
{props.data.position !== 'static' && (
<div className={animatecss} style={{ ...style, ...animateCount }}>
{state}
</div>
)}
{/* 这里暂不考虑布局影响 */}
{props.data.position === 'static' && props.data.display !== 'inline' && (
<div
className={animatecss}
style={{
pointerEvents: 'none',
width: '100%',
height: '100%',
...animateCount,
}}
>
{state}
</div>
)}
{props.data.position === 'static' && props.data.display === 'inline' && (
<span style={{ pointerEvents: 'none' }}>{state}</span>
)}
<BlockResizer data={props.data} rect={ref}></BlockResizer>
</div>
);
} else {
return (
<div
className={animatecss}
style={{
position: props.data.fixed ? 'fixed' : props.data.position,
top: previewState.top,
left: previewState.left,
width: previewState.width,
height: previewState.height,
zIndex: props.data.zIndex,
display: props.data.display,
...animateCount,
}}
>
{state}
</div>
);
}
}, [
state,
props.context,
props.data,
innerDragData,
previewState.top,
previewState.left,
previewState.width,
previewState.height,
]);
return render;
}
export default Blocks;

View File

@@ -0,0 +1,131 @@
import { containerDragResolve } from '../core/crossDrag';
import { containerFocusRemove } from '../core/focusHandler';
import { innerContainerDrag } from '../core/innerDrag';
import { NormalMarkLineRender } from '../core/markline';
import { scaleState } from '../core/scale/state';
import { IStoreData } from '../core/store/storetype';
import { wrapperMoveState } from './wrapperMove/event';
import { CSSProperties, PropsWithChildren, useMemo } from 'react';
import Blocks from './blocks';
import { containerResizer } from '../core/resizeHandler/containerResizer';
import React from 'react';
import UserConfig from '../config';
import styles from '../index.less';
import { getRealHeight } from '../core/transfer';
import { IconFont } from '../core/utils/icon';
interface ContainerProps {
state: IStoreData;
context: 'edit' | 'preview';
config: UserConfig;
editContainerStyle?: CSSProperties;
previewContainerStyle?: CSSProperties;
}
function Container(props: PropsWithChildren<ContainerProps>) {
const { editContainerStyle, previewContainerStyle } = props;
const transform = useMemo(() => {
if (props.context === 'edit') {
return `scale(${scaleState.value}) translate(${wrapperMoveState.needX}px, ${wrapperMoveState.needY}px)`;
} else {
return undefined;
}
}, [props.context]);
const bgColor = () => {
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
return 'rgba(255,255,255,1)';
} else {
return props.state.globalState.containerColor;
}
};
return (
<>
{props.context === 'edit' && (
<>
<div
style={{
position: 'absolute',
height: `${props.state.container.height + 60}px`,
width: `${props.state.container.width}px`,
transform: `scale(${scaleState.value}) translate(${wrapperMoveState.needX}px, ${wrapperMoveState.needY}px)`,
}}
>
<div style={{ display: 'flex' }}>
<div
id="yh-container"
className={styles.yh_container}
style={{
height: `${props.state.container.height}px`,
width: `${props.state.container.width}px`,
backgroundColor: bgColor(),
position: 'relative',
overflow: 'hidden',
...editContainerStyle,
}}
{...(props.context === 'edit' ? containerDragResolve : null)}
{...(props.context === 'edit' ? innerContainerDrag() : null)}
{...(props.context === 'edit' ? containerFocusRemove() : null)}
>
{props.context === 'edit' && <NormalMarkLineRender></NormalMarkLineRender>}
{props.state.block.map((v) => {
return (
<Blocks
config={props.config}
key={v.id}
data={v}
context={props.context}
></Blocks>
);
})}
</div>
</div>
<div
style={{
height: '50px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: `${props.state.container.width}px`,
}}
>
<IconFont
type="icon-suofang"
onMouseDown={containerResizer.onMousedown}
style={{ fontSize: '20px', cursor: 's-resize' }}
></IconFont>
{/* <BoxPlotFilled
onMouseDown={containerResizer.onMousedown}
style={{ fontSize: '20px', cursor: 's-resize' }}
rotate={90}
/> */}
</div>
</div>
</>
)}
{props.context === 'preview' && (
<div
id="yh-container-preview"
className={styles.yh_container_preview}
style={{
height: `${getRealHeight(props.state.container.height)}px`,
width: `100%`,
position: 'relative' as 'absolute' | 'relative',
overflow: 'hidden',
backgroundColor: bgColor(),
transform: transform,
...previewContainerStyle,
}}
>
{props.state.block.map((v) => {
return (
<Blocks key={v.id} config={props.config} data={v} context={props.context}></Blocks>
);
})}
</div>
)}
</>
);
}
export default Container;

View File

@@ -0,0 +1,281 @@
import {
CompressOutlined,
DeleteOutlined,
FullscreenExitOutlined,
FullscreenOutlined,
GatewayOutlined,
MenuOutlined,
SyncOutlined,
UnorderedListOutlined,
} from '@ant-design/icons';
import { Button, Divider, Form, Input, List, Modal, Popconfirm, Popover } from 'antd';
import React, { CSSProperties, PropsWithChildren, useState } from 'react';
import { UserConfig } from '..';
import { IBlockType, IStoreData } from '../core/store/storetype';
import { deepCopy, arrayMove, changeItem, changeLayer, focusEle } from '../core/utils';
import { SortEnd, SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { wrapperMoveState } from './wrapperMove/event';
export interface ControlProps {
config: UserConfig;
style?: CSSProperties;
}
const DragHandle = SortableHandle(() => <MenuOutlined />);
const SortableItem = SortableElement(
({ value }: { value: { value: IBlockType; config: UserConfig } }) => (
<div
style={{
userSelect: 'none',
display: 'flex',
alignItems: 'center',
width: 430,
}}
>
<div style={{ width: 30, textAlign: 'center', cursor: 'move' }}>
<DragHandle></DragHandle>
</div>
<Divider type="vertical"></Divider>
<div style={{ width: 100, textAlign: 'center' }}>
{value.config.getComponentRegister().getMap()[value.value.name].display}
</div>
<Divider type="vertical"></Divider>
<div style={{ width: 50, textAlign: 'center' }}>{value.value.id.slice(-6)}</div>
<Divider type="vertical"></Divider>
<div style={{ width: 50, textAlign: 'center' }}>{value.value.position}</div>
<Divider type="vertical"></Divider>
<div style={{ width: 200 }}>
<Popconfirm
title="确认变更为绝对定位吗?"
onConfirm={() => {
changeItem(value.config.getStore(), value.value.id, 'position', 'absolute');
}}
>
<Button type="link" title="切换绝对定位" icon={<FullscreenOutlined />}></Button>
</Popconfirm>
<Popconfirm
title="确认变更为静态定位吗?"
onConfirm={() => {
changeItem(value.config.getStore(), value.value.id, 'position', 'static');
}}
>
<Button type="link" title="切换静态定位" icon={<FullscreenExitOutlined />}></Button>
</Popconfirm>
<Button
type="link"
title="选中聚焦"
icon={<CompressOutlined />}
onClick={() => {
focusEle(value.config.getStore(), value.value.id);
}}
></Button>
<Popconfirm
title="确认删除操作吗?"
onConfirm={() => {
changeLayer(value.config.getStore(), value.value.id, 'delete');
}}
>
<Button icon={<DeleteOutlined />} title="删除" type="link"></Button>
</Popconfirm>
</div>
</div>
)
);
const SortableList = SortableContainer(
({ items }: { items: { data: IBlockType[]; config: UserConfig } }) => {
return (
<div>
{items.data.map((value, index: number) => (
<SortableItem key={value.id} index={index} value={{ value, config: items.config }} />
))}
</div>
);
}
);
export function Control(props: PropsWithChildren<ControlProps>) {
const { style } = props;
const [visible, setVisible] = useState(false);
const [configVisible, setConfigVisible] = useState(false);
const [form] = Form.useForm();
const data = props.config.getStore().getData().block;
const onSortEnd = (sort: SortEnd) => {
const { oldIndex, newIndex } = sort;
const newblocks: IBlockType[] = arrayMove(data, oldIndex, newIndex);
// 这里要判断是否edit ,如果edit时只要看第一个是不是container不是则不移动
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const firstType = newblocks[0].name;
if (firstType !== 'modalMask') {
return;
}
}
const store = props.config.getStore();
const cloneData: IStoreData = deepCopy(store.getData());
cloneData.block = newblocks;
store.setData(cloneData);
};
const content =
data.length === 0 ? (
<div></div>
) : (
<div style={{ maxHeight: 300, overflow: 'auto' }}>
<SortableList
distance={2}
useDragHandle
items={{
data,
config: props.config,
}}
onSortEnd={onSortEnd}
axis="y"
></SortableList>
</div>
);
return (
<>
<div
className="ant-menu"
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
...style,
}}
>
<Popover style={{ minWidth: '208px' }} content={content} trigger="click">
<Button icon={<UnorderedListOutlined />}></Button>
</Popover>
{/* <Button icon={<FolderOpenOutlined />}></Button> */}
<Popover
placement="left"
content={
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<Button
onClick={() => {
setVisible(true);
}}
>
</Button>
<Button
onClick={() => {
setConfigVisible(true);
}}
>
</Button>
</div>
}
>
<Button icon={<GatewayOutlined />}></Button>
</Popover>
<Button
icon={<SyncOutlined />}
onClick={() => {
wrapperMoveState.needX = 0;
wrapperMoveState.needY = 0;
props.config.getStore().forceUpdate();
}}
></Button>
</div>
<Modal
title="弹窗配置"
visible={configVisible}
onOk={() => setConfigVisible(false)}
onCancel={() => setConfigVisible(false)}
footer={null}
>
<List>
{props.config.getStoreChanger().getState().modalEditName !== '' && (
<div>退</div>
)}
{props.config.getStoreChanger().getState().modalEditName === '' &&
Object.keys(props.config.getStore().getData().modalMap).map((v) => {
return (
<List.Item
key={v}
actions={[
<Popconfirm
title="是否切换至该弹窗并进行编辑?"
onConfirm={() => {
props.config.getStoreChanger().updateModal(props.config.getStore(), v);
setConfigVisible(false);
}}
okText={'是'}
cancelText={'否'}
>
<Button type="link"></Button>
</Popconfirm>,
<Popconfirm
title="您确定要删除这个弹窗吗?"
onConfirm={() => {
props.config.getStoreChanger().removeModal(props.config.getStore(), v);
setConfigVisible(false);
}}
okText={'是'}
cancelText={'否'}
>
<Button type="link"></Button>
</Popconfirm>,
]}
>
{v}
</List.Item>
);
})}
{props.config.getStoreChanger().getState().modalEditName === '' &&
Object.keys(props.config.getStore().getData().modalMap).length === 0 && (
<div style={{ textAlign: 'center' }}></div>
)}
</List>
</Modal>
<Modal
onOk={() => {
form
.validateFields()
.then((values) => {
form.resetFields();
const modalName = values.modalName;
props.config.getStoreChanger().newModalMap(props.config.getStore(), modalName);
setVisible(false);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
title="新增弹窗"
onCancel={() => setVisible(false)}
visible={visible}
>
<Form layout="vertical" name="basic" form={form}>
<Form.Item
label="弹窗名称"
name="modalName"
rules={[{ required: true, message: '请输入弹窗名称!' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
</>
);
}
export default Control;

View File

@@ -0,0 +1,185 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-04 10:32:45
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:55:37
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\components\leftConfig.tsx
*/
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
import { Input, Menu } from 'antd';
import { dragEventResolve, LeftRegistComponentMapItem } from '../core/crossDrag';
import UserConfig from '../config';
import { DoubleLeftOutlined, DoubleRightOutlined, SearchOutlined } from '@ant-design/icons';
import styles from '../index.less';
interface LeftConfigProps {
config: UserConfig;
}
/**
*
* 注册加载左侧组件方法,由于异步拉取,所以要异步加载
* 不同tab页可以使用不同type区分
* @param {*} props
* @returns
*/
function LeftConfig(props: LeftConfigProps) {
const [menuSelect, setMenuSelect] = useState('0');
const [leftRender, setLeftRender] = useState<ReactNode | null>(null);
const leftMapRenderListCategory = useMemo(() => {
return props.config.getConfig().leftRenderListCategory;
}, [props.config]);
const [search, setSearch] = useState('');
useEffect(() => {
let cache: LeftRegistComponentMapItem[] = [];
const type = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.type;
const isCustom = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.custom;
if (!isCustom) {
const config = props.config.getConfig();
cache = config.leftAllRegistMap.filter((k) => k.type === type);
cache.forEach((v) => props.config.asyncRegistComponent(v.component));
setLeftRender(
<div className={styles.leftco}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '10px',
}}
>
<div
style={{
width: 100,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: 32,
lineHeight: '32px',
marginRight: '10px',
fontSize: '14px',
fontFamily: 'PingFangSC-Medium, PingFang SC',
fontWeight: 600,
userSelect: 'none',
}}
>
{leftMapRenderListCategory[parseInt(menuSelect, 10)].displayName}
</div>
<Input
style={{
borderRadius: '40px',
}}
allowClear
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
prefix={<SearchOutlined />}
/>
</div>
{search &&
search !== '' &&
cache
.reduce<LeftRegistComponentMapItem[]>((prev, next) => {
//筛选搜索条件name或者displayName存在即显示
if (next.displayName.includes(search) || next.component.includes(search)) {
prev.push(next);
}
return prev;
}, [])
.map((v, index) => (
<div className={styles.coitem} key={index} {...dragEventResolve(v)}>
<div className={styles.redbox}>
{v.imgCustom ? v.imgCustom : <img src={v.img}></img>}
</div>
<div
style={{
textAlign: 'center',
lineHeight: '20px',
height: '20px',
overflow: 'hidden',
}}
>
{v.displayName}
</div>
</div>
))}
{(!search || search === '') &&
cache.map((v, index) => (
<div className={styles.coitem} key={index} {...dragEventResolve(v)}>
<div className={styles.redbox}>
{v.imgCustom ? v.imgCustom : <img src={v.img}></img>}
</div>
<div
style={{
textAlign: 'center',
lineHeight: '20px',
height: '20px',
overflow: 'hidden',
}}
>
{v.displayName}
</div>
</div>
))}
</div>
);
} else {
const render = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.customRender;
setLeftRender(<div className={styles.leftco}>{render}</div>);
}
}, [menuSelect, props.config, leftMapRenderListCategory, search]);
const [isCollapse, setCollapse] = useState(false);
const [renderCollapse, setRenderCollaspe] = useState(false);
return (
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Menu style={{ flex: 1 }} defaultSelectedKeys={[menuSelect]} mode="vertical">
{leftMapRenderListCategory.map((v, i) => {
return (
<Menu.Item key={i} onClick={() => setMenuSelect(i + '')} icon={v.icon}></Menu.Item>
);
})}
</Menu>
<Menu selectedKeys={[]}>
<Menu.Item
key="1"
onClick={() =>
setCollapse((pre) => {
if (pre) {
setTimeout(() => {
setRenderCollaspe(false);
}, 300);
return !pre;
} else {
setRenderCollaspe(true);
return !pre;
}
})
}
className={styles.menu_footer}
icon={isCollapse ? <DoubleRightOutlined /> : <DoubleLeftOutlined />}
></Menu.Item>
</Menu>
</div>
<div
className={`${styles.yhLeftrender} ant-menu scrollbar`}
style={{
width: isCollapse ? 0 : 270,
paddingRight: isCollapse ? 0 : 7, // 这个是滚动条宽度
overflowX: 'hidden',
}}
>
{!renderCollapse && leftRender}
</div>
</div>
);
}
export default LeftConfig;

View File

@@ -0,0 +1,112 @@
import { IStoreData } from '../core/store/storetype';
import React, { useMemo } from 'react';
import Blocks from './blocks';
import UserConfig from '../config';
import * as ReactDOM from 'react-dom';
import { deepCopy } from '../core/utils';
interface ModalRenderProps {
data: IStoreData;
name: string; //传递的modal名字
config: UserConfig; //需要拿到componentRegister
parentDom: HTMLDivElement;
rootDom: HTMLDivElement;
}
export const unmountMap: Map<string, Function> = new Map();
export function ModalRender(props: ModalRenderProps) {
//先获取数据
const storeData: IStoreData = useMemo(() => {
const z = props.data.modalMap[props.name];
if (z) {
const data = deepCopy(z);
//需要把第一个mask扔了手动写一个
data.block.shift();
return data;
}
return { block: [] };
}, [props.data.modalMap, props.name]);
const { parentDom, rootDom } = props;
//这里还要添加个关闭函数,
const unmount = useMemo(() => {
return () => {
if (parentDom && rootDom) {
ReactDOM.unmountComponentAtNode(parentDom);
rootDom.removeChild(parentDom);
rootDom.parentElement?.removeChild(rootDom);
}
};
}, [parentDom, rootDom]);
unmountMap.set(props.name, unmount);
return (
<>
<div
className="yh-container-modal"
style={{
height: `100%`,
width: `100%`,
position: 'fixed',
overflow: 'hidden',
}}
>
{storeData.block.map((v) => {
return <Blocks key={v.id} config={props.config} data={v} context={'preview'}></Blocks>;
})}
<div
onClick={() => {
unmount();
}}
style={{
backgroundColor: '#716f6f9e',
width: '100%',
height: '100%',
}}
></div>
</div>
</>
);
}
let wrap: HTMLDivElement | null;
export const createModal = (name: string, data: IStoreData, config: UserConfig) => {
if (wrap) {
wrap = null;
}
if (!wrap) {
wrap = document.createElement('div');
wrap.style.cssText = `line-height:
1.5;text-align:
center;color: #333;
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
position: fixed;
z-index: 100000;
width: 100%;
height:100%;
top:0;
left: 0;`;
if (wrap) {
document.body.appendChild(wrap);
}
}
const divs = document.createElement('div');
wrap.appendChild(divs);
ReactDOM.render(
<ModalRender
name={name}
data={data}
config={config}
parentDom={divs}
rootDom={wrap}
></ModalRender>,
divs
);
};

View File

@@ -0,0 +1,55 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:40:37
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:44:45
* @FilePath: \DooringV2\packages\dooringx-lib\src\components\preview.tsx
*/
import Container from './container';
import React, { ReactElement, ReactNode, useEffect, useState } from 'react';
import UserConfig from '../config';
function Preview(props: { config: UserConfig; loadText?: ReactNode }): ReactElement {
const isEdit = props.config.getStoreChanger().isEdit();
/// 这里需要在渲染组件之前必须把所有config加载完成否则会导致先运行的函数无法运行
const [loading, setLoading] = useState(true);
useEffect(() => {
// 链接数据
props.config
.getDataCenter()
.initAddToDataMap(props.config.getStore().getData(), props.config.getStoreChanger());
// 链接事件
props.config
.getEventCenter()
.syncEventMap(props.config.getStore().getData(), props.config.getStoreChanger());
setTimeout(() => {
setLoading(false);
});
}, [props.config]);
if (isEdit) {
// 正常情况不会走这
const state = props.config.getStoreChanger().getOrigin()!.now;
return (
<>
<Container config={props.config} context="preview" state={state}></Container>
</>
);
} else {
if (loading) {
return <div>{props.loadText ? props.loadText : 'loading'}</div>;
} else {
return (
<>
<Container
config={props.config}
context="preview"
state={props.config.getStore().getData()}
></Container>
</>
);
}
}
}
export default Preview;

View File

@@ -0,0 +1,239 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:42:13
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:35:05
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\components\rightConfig.tsx
*/
import { CreateOptionsRes } from '../core/components/formTypes';
import { IBlockType, IStoreData } from '../core/store/storetype';
import { store } from '../runtime/store';
import { PropsWithChildren, useEffect, useMemo, useState } from 'react';
import React from 'react';
import { Tabs, Input, Row, Col } from 'antd';
import UserConfig from '../config';
import { RGBColor, SketchPicker } from 'react-color';
import { rgba2Obj } from '../core/utils';
import deepcopy from 'deepcopy';
interface RightConfigProps {
state: IStoreData;
config: UserConfig;
}
/**
*
* 这里一个需要异步拿取当前注册组件的配置项,另外需要异步加载所需的配置项。
* @param {*} props
* @returns
*/
function RightConfig(props: PropsWithChildren<RightConfigProps>) {
const [menuSelect, setMenuSelect] = useState('0');
const [current, setCurrent] = useState<IBlockType | null>(null);
const rightMapRenderListCategory = useMemo(() => {
return props.config.getConfig().rightRenderListCategory;
}, [props.config]);
useEffect(() => {
const fn = () => {
let item: IBlockType | undefined;
store.getData().block.some((v) => {
if (v.focus) {
item = v;
}
return v.focus === true;
});
if (item) {
setCurrent({ ...item });
} else {
setCurrent(null);
}
};
const unregist = store.subscribe(fn);
return () => {
unregist();
};
}, []);
const render = useMemo(() => {
return (type: string, current: IBlockType) => {
const fn = () => props.config.getComponentRegister().getComp(current.name);
const data = fn();
// 这里不可能拿不到组件,因为点击的那个组件已经渲染出来了
if (data) {
const renderList = data.props[type];
if (renderList) {
return renderList.map((v, i) => {
const Component = props.config.getFormRegister().formMap[v.type];
if (!Component) {
console.error(`you might forgot to regist form component ${v.type}`);
return null;
}
return (
<Component
key={i}
data={v as CreateOptionsRes<any, any>}
current={current}
config={props.config}
></Component>
);
});
} else {
return <div></div>;
}
}
return null;
};
}, [props.config]);
const initColor = useMemo(() => {
return props.config.getStoreChanger().isEdit()
? rgba2Obj(props.config.getStoreChanger().getOrigin()?.now.globalState.containerColor)
: rgba2Obj(props.config.getStore().getData().globalState.containerColor);
}, [props.config]);
const [color, setColor] = useState<RGBColor>(initColor);
const [colorPickerVisible, setColorPickerVisible] = useState(false);
const initTitle = useMemo(() => {
const title = props.config.getStoreChanger().isEdit()
? props.config.getStoreChanger().getOrigin()?.now.globalState.title
: props.config.getStore().getData().globalState.title;
return title;
}, [props.config]);
const [title, setTitle] = useState(initTitle);
const customGlobal = props.config.getConfig().rightGlobalCustom;
return (
<div
className="ant-menu"
style={{
height: '100%',
width: '400px',
overflow: 'auto',
padding: '0 10px',
lineHeight: 1.5715,
}}
>
{current && (
<Tabs
activeKey={menuSelect}
style={{ width: '100%' }}
onChange={(e) => {
setMenuSelect(e);
}}
>
{rightMapRenderListCategory.map((v, i) => {
return (
<Tabs.TabPane tab={v.icon} key={i + ''}>
<div
className="scrollbar"
style={{
height: 'calc(100vh - 110px)',
overflow: 'auto',
}}
>
{v.custom && v.customRender && v.customRender(v.type, current)}
{!v.custom && render(v.type, current)}
</div>
</Tabs.TabPane>
);
})}
</Tabs>
)}
{!current && !customGlobal && (
<div style={{ padding: '20px' }}>
<Row style={{ padding: '10 0 20px 0', fontWeight: 'bold' }}></Row>
<Row style={{ padding: '10px 0' }}>
<Col span={6}></Col>
<Col span={18}>
<Input
value={title}
onChange={(e) => {
const val = e.target.value;
setTitle(val);
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const originData: IStoreData = deepcopy(
props.config.getStoreChanger().getOrigin()!.now
);
originData.globalState.title = val;
props.config.getStoreChanger().updateOrigin(originData);
} else {
const originData = deepcopy(props.config.getStore().getData());
originData.globalState.title = val;
props.config.getStore().setData(originData);
}
}}
/>
</Col>
</Row>
<Row style={{ padding: '10px 0' }}>
<Col span={6}></Col>
<Col span={18}>
{
<div style={{ position: 'relative' }}>
<div
onClick={() => {
setColorPickerVisible((pre) => !pre);
}}
style={{
background: '#fff',
borderRadius: '1px',
boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
cursor: 'pointer',
display: 'inline-block',
}}
>
<div
style={{
width: '20px',
height: '20px',
borderRadius: '2px',
background: `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`,
}}
/>
</div>
{colorPickerVisible && (
<>
<div style={{ position: 'absolute', zIndex: 2000 }}>
<SketchPicker
color={color}
onChange={(c) => {
const newcolor = c.rgb;
setColor(newcolor);
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const originData: IStoreData = deepcopy(
props.config.getStoreChanger().getOrigin()!.now
);
originData.globalState.containerColor = `rgba(${newcolor.r}, ${newcolor.g}, ${newcolor.b}, ${newcolor.a})`;
props.config.getStoreChanger().updateOrigin(originData);
} else {
const originData = deepcopy(props.config.getStore().getData());
originData.globalState.containerColor = `rgba(${newcolor.r}, ${newcolor.g}, ${newcolor.b}, ${newcolor.a})`;
props.config.getStore().setData(originData);
}
}}
></SketchPicker>
</div>
<div
style={{
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
zIndex: 1000,
}}
onClick={() => setColorPickerVisible(false)}
/>
</>
)}
</div>
}
</Col>
</Row>
</div>
)}
{!current && customGlobal && customGlobal}
</div>
);
}
export default RightConfig;

View File

@@ -0,0 +1,68 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-21 22:17:29
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 18:24:27
* @FilePath: \dooringv2\src\components\wrapperMove\event.ts
*/
import { store } from '../../runtime/store';
import { RefObject } from 'react';
import { containerResizer } from '../../core/resizeHandler/containerResizer';
import { contextMenuState } from '../../core/contextMenu';
export interface WrapperMoveStateProps {
isDrag: boolean;
startX: number;
startY: number;
needX: number;
needY: number;
ref: null | RefObject<HTMLDivElement>;
}
export const wrapperMoveState: WrapperMoveStateProps = {
isDrag: false,
startX: 0,
startY: 0,
needX: 0,
needY: 0,
ref: null,
};
export const wrapperEvent = (ref: RefObject<HTMLDivElement>) => {
return {
onMouseDown: (e: React.MouseEvent) => {
// e.preventDefault();// 不能使用preventDefault 否则弹窗输入框焦点无法触发
contextMenuState.unmountContextMenu();
if (e.target !== ref.current) {
} else {
wrapperMoveState.isDrag = true;
wrapperMoveState.startX = e.clientX;
wrapperMoveState.startY = e.clientY;
if (ref.current) {
ref.current.style.cursor = 'grab';
wrapperMoveState.ref = ref;
}
}
},
onMouseMove: (e: React.MouseEvent) => {
e.preventDefault();
if (wrapperMoveState.isDrag) {
const diffX = e.clientX - wrapperMoveState.startX;
const diffY = e.clientY - wrapperMoveState.startY;
wrapperMoveState.needX = wrapperMoveState.needX + diffX;
wrapperMoveState.needY = wrapperMoveState.needY + diffY;
wrapperMoveState.startX = e.clientX;
wrapperMoveState.startY = e.clientY;
store.forceUpdate();
}
containerResizer.onMouseMove(e);
},
};
};
export const wrapperMoveMouseUp = () => {
if (wrapperMoveState.ref && wrapperMoveState.ref.current) {
wrapperMoveState.ref.current.style.cursor = 'default';
}
containerResizer.onMouseUp();
wrapperMoveState.isDrag = false;
};

View File

@@ -0,0 +1,44 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:58:51
* @FilePath: \dooring-v2\src\core\wrapperMove\index.tsx
*/
import { AllHTMLAttributes, CSSProperties, PropsWithChildren, useRef } from 'react';
import { wrapperEvent } from './event';
import { onWheelEvent } from '../../core/scale';
import React from 'react';
export interface ContainerWrapperProps extends AllHTMLAttributes<HTMLDivElement> {
classNames?: string;
style?: CSSProperties;
}
function ContainerWrapper(props: PropsWithChildren<ContainerWrapperProps>) {
const { children, style, classNames, ...rest } = props;
const ref = useRef<HTMLDivElement>(null);
return (
<div
className={`ant-menu ${classNames}`}
ref={ref}
style={{
backgroundColor: '#f0f0f0',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flex: 1,
position: 'relative',
overflow: 'hidden',
...style,
}}
{...wrapperEvent(ref)}
{...onWheelEvent}
{...rest}
>
{children}
</div>
);
}
export default ContainerWrapper;

View File

@@ -0,0 +1,531 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-25 21:16:58
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:42:22
* @FilePath: \DooringV2\packages\dooringx-lib\src\config\index.tsx
*/
import { IBlockType, IStoreData } from '../core/store/storetype';
import { store } from '../runtime/store';
import { formRegister, componentRegister, commander, storeChanger } from '../runtime';
import { ComponentClass, FunctionComponent, ReactNode } from 'react';
import { ComponentItemFactory } from '../core/components/abstract';
import { marklineConfig } from '../core/markline/marklineConfig';
import { CommanderItem } from '../core/command/commanderType';
import { contextMenuState } from '../core/contextMenu';
import { formComponentRegisterFn } from '../core/components/formComponentRegister';
import { deepCopy } from '../core/utils';
import { LeftRegistComponentMapItem } from '../core/crossDrag';
import { FunctionCenterType } from '../core/functionCenter';
import { EventCenter } from '../core/eventCenter';
import { DataCenter } from '../core/dataCenter';
import { createModal, unmountMap } from '../components/modalRender';
import { scaleState } from '../core/scale/state';
import { CommanderItemFactory } from '../core/command/abstract';
import MmodalMask from '../core/components/defaultFormComponents/modalMask';
import MmodalContainer from '../core/components/defaultFormComponents/modalContainer';
// 组件部分
/**
*
* @urlFn 组件异步加载函数
* @component 组件默认导出
* @export
* @interface CacheComponentValueType
*/
export interface CacheComponentValueType {
urlFn?: () => Promise<any>;
component?: ComponentItemFactory;
}
export type CacheComponentType = Record<string, CacheComponentValueType> | {};
export type AsyncCacheComponentType = Record<string, () => Promise<any>>;
/**
*
*
* @export 左侧的图标 custom 自定义渲染
* @interface LeftMapRenderListPropsItemCategory
*/
export interface LeftMapRenderListPropsItemCategory {
type: string;
icon: ReactNode;
custom?: boolean;
customRender?: ReactNode;
displayName?: string;
}
/**
*
*
* @export 右侧的图标配置
* @interface RightMapRenderListPropsItemCategory
*/
export interface RightMapRenderListPropsItemCategory {
type: string;
icon: ReactNode;
custom?: boolean;
customRender?: (type: string, current: IBlockType) => ReactNode;
}
// 设置部分
export interface InitConfig {
/**
* 初始化store
* @type {IStoreData[]}
* @memberof InitConfig
*/
initStoreData: IStoreData[];
/**
* 左边tab页组件渲染包括异步路径 { type: 'basic', component: 'button', img: 'http://xxxx/1.jpg' ,url:'' },
* @memberof InitConfig
*/
leftAllRegistMap: LeftRegistComponentMapItem[];
/**
* 左边tab页图标配置
* type icon custom customRender
* @memberof InitConfig
*/
leftRenderListCategory: LeftMapRenderListPropsItemCategory[];
/**
* 右边tab页图标配置
* type icon custom customRender
* @memberof InitConfig
*/
rightRenderListCategory: RightMapRenderListPropsItemCategory[];
/**
*
* 右侧全局自定义
* @type {ReactNode}
* @memberof InitConfig
*/
rightGlobalCustom: ReactNode;
/**
* 组件加载缓存判定,用来设置不异步加载的组件
* @memberof InitConfig
*/
initComponentCache: CacheComponentType;
/**
*
* 内置函数配置
* @memberof InitConfig
*/
initFunctionMap: FunctionCenterType;
/**
*
* 内置数据中心配置数据
* @memberof InitConfig
*/
initDataCenterMap: Record<string, any>;
/**
*
* commander 指令集合
* @type {Array<CommanderItemFactory>}
* @memberof InitConfig
*/
initCommandModule: Array<CommanderItemFactory>;
/**
*
* 右侧配置项
* @type {(Record<
* string,
* FunctionComponent<any> | ComponentClass<any, any>
* >)}
* @memberof InitConfig
*/
initFormComponents: Record<string, FunctionComponent<any> | ComponentClass<any, any>>;
}
export const defaultStore: IStoreData = {
container: {
width: 375,
height: 667,
},
block: [],
modalMap: {},
dataSource: {
defaultKey: 'defaultValue',
},
globalState: {
containerColor: 'rgba(255,255,255,1)',
title: 'dooring',
},
};
export const defaultConfig: InitConfig = {
initStoreData: [defaultStore],
leftAllRegistMap: [],
leftRenderListCategory: [],
rightGlobalCustom: null,
rightRenderListCategory: [],
initComponentCache: {
modalMask: { component: MmodalMask }, // 这2个组件不配置显示
modalContainer: { component: MmodalContainer },
},
initFunctionMap: {
: {
fn: (_ctx, next, config, args) => {
const modalName = args['_modal'];
const storeData = config.getStore().getData();
createModal(modalName, storeData, config);
next();
},
config: [
{
name: '弹窗名称',
data: ['modal'],
options: {
receive: '_modal',
multi: false,
},
},
],
},
: {
fn: (_ctx, next, _config, args) => {
const modalName = args['_modal'];
const fn = unmountMap.get(modalName);
fn ? fn() : '';
next();
},
config: [
{
name: '弹窗名称',
data: ['modal'],
options: {
receive: '_modal',
multi: false,
},
},
],
},
},
initDataCenterMap: {},
initCommandModule: [],
initFormComponents: {},
};
/**
*
* 部分无法合并属性如果b传了会以b为准
* initstore不合并
* leftallregistmap合并
* leftRenderListCategory合并
* rightRenderListCategory合并
* rightGlobalCustom 不合并
* initComponentCache合并
* initFunctionMap合并
* initDataCenterMap合并
* initCommandModule合并
* initFormComponents合并
*
* @export InitConfig
*/
export function userConfigMerge(a: Partial<InitConfig>, b?: Partial<InitConfig>): InitConfig {
const mergeConfig: InitConfig = {
initStoreData: [defaultStore],
leftAllRegistMap: [],
leftRenderListCategory: [],
rightRenderListCategory: [],
initComponentCache: {},
initFunctionMap: {},
initDataCenterMap: {},
initCommandModule: [],
rightGlobalCustom: null,
initFormComponents: {},
};
if (!b) {
return userConfigMerge(mergeConfig, a);
}
mergeConfig.initStoreData = b.initStoreData
? [...b.initStoreData]
: a.initStoreData
? [...a.initStoreData]
: [defaultStore];
mergeConfig.rightGlobalCustom = b.rightGlobalCustom ? b.rightGlobalCustom : a.rightGlobalCustom;
mergeConfig.leftAllRegistMap = b.leftAllRegistMap
? a.leftAllRegistMap
? [...a.leftAllRegistMap, ...b.leftAllRegistMap]
: [...b.leftAllRegistMap]
: a.leftAllRegistMap
? [...a.leftAllRegistMap]
: [];
mergeConfig.leftRenderListCategory = b.leftRenderListCategory
? a.leftRenderListCategory
? [...a.leftRenderListCategory, ...b.leftRenderListCategory]
: [...b.leftRenderListCategory]
: a.leftRenderListCategory
? [...a.leftRenderListCategory]
: [...defaultConfig.leftRenderListCategory];
mergeConfig.rightRenderListCategory = b.rightRenderListCategory
? a.rightRenderListCategory
? [...a.rightRenderListCategory, ...b.rightRenderListCategory]
: [...b.rightRenderListCategory]
: a.rightRenderListCategory
? [...a.rightRenderListCategory]
: [...defaultConfig.rightRenderListCategory];
mergeConfig.initComponentCache = {
...a.initComponentCache,
...b.initComponentCache,
};
mergeConfig.initFunctionMap = {
...a.initFunctionMap,
...b.initFunctionMap,
};
mergeConfig.initFormComponents = {
...a.initFormComponents,
...b.initFormComponents,
};
mergeConfig.initDataCenterMap = {
...a.initDataCenterMap,
...b.initDataCenterMap,
};
mergeConfig.initCommandModule = b.initCommandModule
? a.initCommandModule
? [...a.initCommandModule, ...b.initCommandModule]
: [...b.initCommandModule]
: a.initCommandModule
? [...a.initCommandModule]
: [];
return mergeConfig;
}
/**
*
*
* @export 用户配置项
* @class UserConfig
*/
export class UserConfig {
public initConfig: InitConfig;
public store = store;
public componentRegister = componentRegister;
public componentCache = {};
public asyncComponentUrlMap = {} as AsyncCacheComponentType;
public marklineConfig = marklineConfig;
public commanderRegister = commander;
public contextMenuState = contextMenuState;
public formRegister = formRegister;
public storeChanger = storeChanger;
public eventCenter: EventCenter;
public dataCenter: DataCenter;
public scaleState = scaleState;
constructor(initConfig?: Partial<InitConfig>) {
const mergeConfig = userConfigMerge(defaultConfig, initConfig);
this.initConfig = mergeConfig;
this.eventCenter = new EventCenter({}, mergeConfig.initFunctionMap);
this.dataCenter = new DataCenter(mergeConfig.initDataCenterMap);
this.init();
// 右侧配置项注册 初始注册组件暂时固定
}
toRegist() {
const modules = this.initConfig.initFormComponents;
formComponentRegisterFn(formRegister, modules);
const cache = this.initConfig.initComponentCache;
this.componentCache = cache;
// 拿到组件缓存后先同步加载map上组件
Object.values(cache).forEach((v) => {
if ((v as CacheComponentValueType).component) {
this.registComponent((v as CacheComponentValueType).component!);
}
});
// 异步组件注册地址
this.initConfig.leftAllRegistMap.forEach((v) => {
if (v.urlFn) {
//@ts-ignore
this.asyncComponentUrlMap[v.component] = v.urlFn;
}
});
// 注册画布上组件
this.store.getData().block.forEach((v) => {
this.asyncRegistComponent(v.name);
});
// 注册data
this.dataCenter = new DataCenter(this.initConfig.initDataCenterMap);
//数据需要加上store上的
this.dataCenter.initAddToDataMap(this.store.getData(), this.storeChanger);
// 修改事件与数据初始
this.eventCenter = new EventCenter({}, this.initConfig.initFunctionMap);
// 注册画布事件
this.eventCenter.syncEventMap(this.store.getData(), this.storeChanger);
}
init() {
this.store.resetToInitData(deepCopy(this.initConfig.initStoreData), true);
this.toRegist();
}
getStoreJSON() {
return JSON.stringify(this.store.getData());
}
parseStoreJson(json: string) {
return JSON.parse(json);
}
resetData(data: IStoreData[]) {
this.store.resetToInitData(data, true);
this.toRegist();
}
getScaleState() {
return this.scaleState;
}
getDataCenter() {
return this.dataCenter;
}
getEventCenter() {
return this.eventCenter;
}
getStoreChanger() {
return this.storeChanger;
}
getConfig() {
return this.initConfig;
}
getStore() {
return this.store;
}
getComponentRegister() {
return this.componentRegister;
}
getContextMenuState() {
return this.contextMenuState;
}
getFormRegister() {
return this.formRegister;
}
getCommanderRegister() {
return this.commanderRegister;
}
/**
*
* 以默认设置重置配置项
* @param {Partial<InitConfig>} v
* @memberof UserConfig
*/
resetConfig(v: Partial<InitConfig>) {
const mergeConfig = userConfigMerge(defaultConfig, v);
this.initConfig = mergeConfig;
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加左侧tab页
* @memberof UserConfig
*/
addLeftCategory(v: LeftMapRenderListPropsItemCategory[]) {
const obj = {} as InitConfig;
obj.leftRenderListCategory = v;
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加右侧tab页
* @memberof UserConfig
*/
addRightCategory(v: RightMapRenderListPropsItemCategory[]) {
const obj = {} as InitConfig;
obj.rightRenderListCategory = v;
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加组件map
* @memberof UserConfig
*/
addCoRegistMap(v: LeftRegistComponentMapItem) {
const obj = {} as InitConfig;
obj.leftAllRegistMap = [v];
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
*会重置配置,请修改配置后增加
* 异步修改config 重置store
* @memberof UserConfig
*/
setConfig(v: Partial<InitConfig>) {
this.initConfig = userConfigMerge(this.initConfig, v);
this.init();
store.forceUpdate();
}
/**
*
* 同步注册指令
* @param {CommanderItem} command
* @memberof UserConfig
*/
registCommander(command: CommanderItem) {
this.commanderRegister.register(command);
}
/**
*
* 用于修改markline配置
* @returns
* @memberof UserConfig
*/
getMarklineConfig() {
return this.marklineConfig;
}
getComponentCache() {
return this.componentCache;
}
/**
*
* 同步注册组件,不会检测缓存是否存在
* @param {ComponentItemFactory} item
* @memberof UserConfig
*/
registComponent(item: ComponentItemFactory) {
this.componentRegister.register(item);
}
/**
*
* 异步注册组件,会判定缓存是否存在
* @param {string} name
* @memberof UserConfig
*/
async asyncRegistComponent(name: string) {
//判定缓存
if (
!(this.componentCache as Record<string, CacheComponentValueType>)[name] &&
this.asyncComponentUrlMap[name]
) {
const chunk = await this.asyncComponentUrlMap[name]();
const chunkDefault = chunk.default;
this.componentRegister.register(chunkDefault);
(this.componentCache as Record<string, CacheComponentValueType>)[name] = {
component: chunkDefault,
};
this.componentRegister.emitEvent(name);
}
}
}
export default UserConfig;

View File

@@ -0,0 +1,19 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:39:30
* @FilePath: \dooring-v2\src\core\command\abstract.ts
*/
import { CommanderItem } from './commanderType';
export class CommanderItemFactory implements CommanderItem {
constructor(
public name: CommanderItem['name'],
public keyboard: CommanderItem['keyboard'],
public excute: CommanderItem['excute'],
public display: CommanderItem['display'],
public init: CommanderItem['init'] = () => {},
public destroy: CommanderItem['destroy'] = () => {}
) {}
}

View File

@@ -0,0 +1,17 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:39:43
* @FilePath: \dooring-v2\src\core\command\commanderType.ts
*/
import Store from '../store';
export interface CommanderItem {
init: () => void;
display: string;
name: string;
keyboard: string;
excute: (store: Store, options?: any) => void;
destroy: () => void;
}

View File

@@ -0,0 +1,84 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:42:15
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\command\index.ts
*/
import Store from '../store';
import { CommanderItem } from './commanderType';
import { keycodeFilter } from './keycode';
class CommanderWrapper {
constructor(public store: Store, public commandMap: Record<string, CommanderItem> = {}) {}
getList() {
return this.commandMap;
}
register(item: CommanderItem) {
item.init();
if (this.commandMap[item.name]) {
// console.error(`${item.name} commander has registed`);
return;
}
this.commandMap[item.name] = item;
//注册快捷键快捷键执行调用excute
const remove = this.registerKeyBoard(item);
//改写销毁方法
const origindestroy = item.destroy;
const newdestroy = () => {
remove();
origindestroy();
};
item.destroy = newdestroy;
}
registerKeyBoard(current: CommanderItem) {
if (current.keyboard.length === 0) {
return () => {};
}
const onKeydown = (e: KeyboardEvent) => {
if (document.activeElement !== document.body && e.target !== document.body) {
return;
}
const { shiftKey, altKey, ctrlKey, metaKey, key } = e;
const keyString: string[] = [];
if (ctrlKey || metaKey) keyString.push('Control');
if (shiftKey) keyString.push('Shift');
if (altKey) keyString.push('Alt');
if (key !== 'Control' && key !== 'Shift' && key !== 'Alt') {
const res = keycodeFilter(key);
if (res !== undefined) {
keyString.push(res);
} else {
keyString.push(key);
}
}
const keyname = keyString.join('+');
if (current.keyboard === keyname) {
current.excute(this.store);
e.stopPropagation();
e.preventDefault();
}
};
window.addEventListener('keydown', onKeydown);
return () => window.removeEventListener('keydown', onKeydown);
}
unRegister(name: string) {
if (!this.commandMap[name]) {
console.error(`${name} commander not found`);
return;
}
const item = this.commandMap[name];
item.destroy();
delete this.commandMap[item.name];
}
exec(name: string, options?: any) {
if (!this.commandMap[name]) {
console.error(`${name} commander not found`);
return;
}
this.commandMap[name].excute(this.store, options);
}
}
export default CommanderWrapper;

View File

@@ -0,0 +1,225 @@
export default {
Cancel: 3,
Help: 6,
Backspace: 8,
Tab: 9,
Clear: 12,
Enter: 13,
Shift: 16,
Control: 17,
Alt: 18,
Pause: 19,
CapsLock: 20,
Escape: 27,
Convert: 28,
NonConvert: 29,
Accept: 30,
ModeChange: 31,
' ': 32,
PageUp: 33,
PageDown: 34,
End: 35,
Home: 36,
ArrowLeft: 37,
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40,
Select: 41,
Print: 42,
Execute: 43,
PrintScreen: 44,
Insert: 45,
Delete: 46,
0: 48,
')': 48,
1: 49,
'!': 49,
2: 50,
'@': 50,
3: 51,
'#': 51,
4: 52,
$: 52,
5: 53,
'%': 53,
6: 54,
'^': 54,
7: 55,
'&': 55,
8: 56,
'*': 56,
9: 57,
'(': 57,
a: 65,
A: 65,
b: 66,
B: 66,
c: 67,
C: 67,
d: 68,
D: 68,
e: 69,
E: 69,
f: 70,
F: 70,
g: 71,
G: 71,
h: 72,
H: 72,
i: 73,
I: 73,
j: 74,
J: 74,
k: 75,
K: 75,
l: 76,
L: 76,
m: 77,
M: 77,
n: 78,
N: 78,
o: 79,
O: 79,
p: 80,
P: 80,
q: 81,
Q: 81,
r: 82,
R: 82,
s: 83,
S: 83,
t: 84,
T: 84,
u: 85,
U: 85,
v: 86,
V: 86,
w: 87,
W: 87,
x: 88,
X: 88,
y: 89,
Y: 89,
z: 90,
Z: 90,
OS: 91,
ContextMenu: 93,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
F13: 124,
F14: 125,
F15: 126,
F16: 127,
F17: 128,
F18: 129,
F19: 130,
F20: 131,
F21: 132,
F22: 133,
F23: 134,
F24: 135,
NumLock: 144,
ScrollLock: 145,
VolumeMute: 181,
VolumeDown: 182,
VolumeUp: 183,
';': 186,
':': 186,
'=': 187,
'+': 187,
',': 188,
'<': 188,
'-': 189,
_: 189,
'.': 190,
'>': 190,
'/': 191,
'?': 191,
'`': 192,
'~': 192,
'[': 219,
'{': 219,
'\\': 220,
'|': 220,
']': 221,
'}': 221,
"'": 222,
'"': 222,
Meta: 224,
AltGraph: 225,
Attn: 246,
CrSel: 247,
ExSel: 248,
EraseEof: 249,
Play: 250,
ZoomOut: 251,
};
const keymap: Record<string, string> = {
a: 'a',
A: 'a',
b: 'b',
B: 'b',
c: 'c',
C: 'c',
d: 'd',
D: 'd',
e: 'e',
E: 'e',
f: 'f',
F: 'f',
g: 'g',
G: 'g',
h: 'h',
H: 'h',
i: 'i',
I: 'i',
j: 'j',
J: 'j',
k: 'k',
K: 'k',
l: 'l',
L: 'l',
m: 'm',
M: 'm',
n: 'n',
N: 'n',
o: 'o',
O: 'o',
p: 'p',
P: 'p',
q: 'q',
Q: 'q',
r: 'r',
R: 'r',
s: 's',
S: 's',
t: 't',
T: 't',
u: 'u',
U: 'u',
v: 'v',
V: 'v',
w: 'w',
W: 'w',
x: 'x',
X: 'x',
y: 'y',
Y: 'y',
z: 'z',
Z: 'z',
};
export const keycodeFilter = (key: string) => {
return keymap[key];
};

View File

@@ -0,0 +1,20 @@
/*
* @Author: yehuozhili
* @Date: 2021-07-04 14:18:18
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 14:21:33
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\command\runtime.ts
*/
import CommanderWrapper from '.';
import { CommanderItemFactory } from './abstract';
export const registCommandFn = (module: CommanderItemFactory[], commander: CommanderWrapper) => {
module.forEach((v) => {
commander.register(v);
});
};
export const unRegistCommandFn = (module: CommanderItemFactory[], commander: CommanderWrapper) => {
module.forEach((v) => {
commander.unRegister(v.name);
});
};

View File

@@ -0,0 +1,22 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-04 23:14:00
* @FilePath: \dooringv2\src\core\components\abstract.ts
*/
import { ComponentItem } from './componentItem';
export class ComponentItemFactory implements ComponentItem {
constructor(
public name: ComponentItem['name'],
public display: ComponentItem['display'],
public props: ComponentItem['props'],
public initData: ComponentItem['initData'],
public render: ComponentItem['render'],
public resize: ComponentItem['resize'] = true,
public needPosition: ComponentItem['needPosition'] = true,
public init: ComponentItem['init'] = () => {},
public destroy: ComponentItem['destroy'] = () => {}
) {}
}

View File

@@ -0,0 +1,35 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:45:21
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\components\componentItem.ts
*/
import UserConfig from '../../config';
import Store from '../store';
import { IBlockType } from '../store/storetype';
import { CreateOptionsResAll } from './formTypes';
/**
*
* 包装部分配置,渲染配置,条件渲染,属性
* @export
* @interface ComponentItem
*/
export interface ComponentItem {
init: () => void;
name: string; // map上key名
display: string; //显示名称
resize: boolean;
needPosition: boolean; //是否要使用拖拽的点
initData: Partial<IBlockType>; //初始值
props: Record<string, CreateOptionsResAll[]>; // 配置属性
render: (data: IBlockType, context: any, store: Store, config: UserConfig) => JSX.Element;
destroy: () => void;
}
export type ComponentRenderConfigProps = {
data: IBlockType;
context: any;
store: Store;
config: UserConfig;
};

View File

@@ -0,0 +1,34 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 16:36:42
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\components\createBlock.ts
*/
import { IBlockType } from '../store/storetype';
import { createUid } from '../utils';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ComponentItem } from './componentItem';
export function createBlock(top: number, left: number, ComponentItem: ComponentItem): IBlockType {
return {
id: createUid(ComponentItem.name),
name: ComponentItem.name,
top,
left,
zIndex: ComponentItem.initData.zIndex || 0,
props: ComponentItem.initData.props || {},
resize: ComponentItem.initData.resize || ComponentItem.resize,
focus: false,
position: ComponentItem.initData.position || 'absolute',
display: ComponentItem.initData.display || 'block',
width: ComponentItem.initData.width,
height: ComponentItem.initData.height,
syncList: ComponentItem.initData.syncList || [],
canDrag: ComponentItem.initData.canDrag ?? true,
eventMap: ComponentItem.initData.eventMap || {},
functionList: ComponentItem.initData.functionList || [],
animate: ComponentItem.initData.animate || {},
fixed: ComponentItem.initData.fixed || false,
};
}

View File

@@ -0,0 +1,34 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 19:21:36
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:56:07
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\components\defaultFormComponents\modalContainer.tsx
*/
import React from 'react';
import { ComponentItemFactory } from '../abstract';
const MmodalContainer = new ComponentItemFactory(
'modalContainer',
'模态框容器',
{},
{
props: {},
width: 300,
height: 300,
},
(data) => {
return (
<div
style={{
zIndex: data.zIndex,
width: data.width,
height: data.height,
backgroundColor: 'white',
}}
></div>
);
}
);
export default MmodalContainer;

View File

@@ -0,0 +1,59 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-04 20:35:11
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:55:53
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\components\defaultFormComponents\modalMask.tsx
*/
import { SaveOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import React from 'react';
import { ComponentItemFactory } from '../abstract';
const MmodalMask = new ComponentItemFactory(
'modalMask',
'模态框遮罩',
{},
{
props: {},
position: 'fixed',
top: 0,
left: 0,
zIndex: 999,
width: '100%',
height: '100%',
canDrag: false,
},
(_, context, store, config) => {
const container = store.getData().container;
return (
<div
style={{
width: context === 'preview' ? '100%' : container.width,
height: context === 'preview' ? '100%' : container.height,
backgroundColor: '#716f6f9e',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{context === 'edit' && (
<Button
type="primary"
shape="circle"
title="save"
style={{ position: 'absolute', right: '10px', top: '10px' }}
icon={<SaveOutlined></SaveOutlined>}
onClick={() => {
config.getStoreChanger().closeModal(config.getStore());
}}
></Button>
)}
</div>
);
},
true,
false
);
export default MmodalMask;

View File

@@ -0,0 +1,70 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: chentianshang
* @LastEditTime: 2021-07-06 09:53:29
* @FilePath: /DooringV2/packages/dooring-v2-lib/src/core/components/formComponentRegister.ts
*/
import { ComponentClass, FunctionComponent } from 'react';
import { CreateOptionsRes } from './formTypes';
export interface ContainerConfigItem {
type: string;
option: CreateOptionsRes<any, any>;
}
export const formComponentRegisterFn = (
formComponent: FormComponentRegister,
modules: Record<string, FunctionComponent<any> | ComponentClass<any, any>>
) => {
Object.keys(modules).forEach((v) => {
formComponent.register(v, modules[v]);
});
};
/**
*
* 拿到form组件地址和状态
* 获取配置container配置项和普通组件配置项
* @export
* @class FormComponentRegister
*/
export class FormComponentRegister {
constructor(
public formMap: Record<string, FunctionComponent<any> | ComponentClass<any, any>> = {},
public listener: Function[] = [],
public eventMap: Record<string, Function[]> = {},
public containerConfig: Array<ContainerConfigItem> = []
) {}
getMap() {
return this.formMap;
}
getComp(name: string) {
return this.formMap[name];
}
getConfig() {
return this.containerConfig;
}
setConfig(config: Array<ContainerConfigItem>) {
this.containerConfig = config;
}
/**
*
* 同步注册方法
* @memberof FormComponentRegister
*/
register(name: string, ele: FunctionComponent<any> | ComponentClass<any, any>) {
this.formMap[name] = ele;
}
emit() {
this.listener.forEach((v) => v());
}
on(event: string, fn: Function) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push(fn);
return () => this.eventMap[event].filter((v) => v !== fn);
}
}

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:45:02
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\components\formTypes.ts
*/
export interface CreateOptionsResAll {
type: string;
option: any;
}
export interface CreateOptionsRes<T, K extends keyof T> {
type: keyof T;
option: T[K];
}
export function createPannelOptions<T, K extends keyof T>(
type: K,
option: T[K]
): CreateOptionsRes<T, K> {
return {
type,
option,
};
}

View File

@@ -0,0 +1,79 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:43:53
* @FilePath: \dooring-v2\src\core\components\index.ts
*/
import { ComponentItem } from './componentItem';
/**
*
* 注册组件需要异步的,由注册时效果决定。
* 主要是存放所有已注册组件。可以在其render时提供对应context
* @class ComponentRegister
*/
class ComponentRegister {
constructor(
public componentMap: Record<string, ComponentItem> = {},
public componentList: ComponentItem[] = [],
public listener: Function[] = [],
public eventMap: Record<string, Function[]> = {}
) {}
getMap() {
return this.componentMap;
}
getList() {
return this.componentList;
}
getComp(name: string) {
return this.componentMap[name];
}
subscribe(fn: Function) {
this.listener.push(fn);
return () => this.listener.filter((v) => v !== fn);
}
emit() {
this.listener.forEach((v) => v());
}
on(event: string, fn: Function) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push(fn);
return () => this.eventMap[event].filter((v) => v !== fn);
}
emitEvent(event: string) {
if (!this.eventMap[event]) {
return;
}
this.eventMap[event].forEach((v) => v());
}
register(item: ComponentItem) {
if (this.componentMap[item.name]) {
// console.error(`${item.name} component has registed`);
return;
}
this.componentMap[item.name] = item;
this.componentList.push(item);
this.emit();
item.init();
}
unRegister(name: string) {
if (!this.componentMap[name]) {
console.error(`${name} component not found`);
return;
}
const item = this.componentMap[name];
item.destroy();
this.emit();
this.componentList = this.componentList.filter((v) => v !== item);
delete this.componentMap[item.name];
}
}
export default ComponentRegister;

View File

@@ -0,0 +1,132 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 22:46:15
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\contextMenu\index.tsx
*/
import { Button } from 'antd';
import React, { RefObject, useState } from 'react';
import { ReactElement } from 'react';
import ReactDOM, { unmountComponentAtNode } from 'react-dom';
import { scaleState } from '../scale/state';
import { isMac } from '../utils';
const ContextMenu = () => {
const handleclick = () => {
unmountContextMenu();
};
const forceUpdate = useState(0)[1];
contextMenuState.forceUpdate = () => {
forceUpdate((pre) => pre + 1);
};
return (
<div
style={{
left: contextMenuState.left,
top: contextMenuState.top,
position: 'fixed',
background: 'rgb(24, 23, 23)',
}}
>
<Button
onClick={() => {
handleclick();
}}
>
</Button>
</div>
);
};
export interface ContextMenuStateProps {
left: number;
top: number;
menu: HTMLElement | null;
parent: HTMLDivElement | null;
contextMenu: ReactElement;
unmountContextMenu: () => void;
observer: null | MutationObserver;
initLeft: number;
initTop: number;
forceUpdate: Function;
state: boolean;
}
export const contextMenuState: ContextMenuStateProps = {
left: 0,
top: 0,
menu: null,
parent: null,
contextMenu: <ContextMenu />,
unmountContextMenu: unmountContextMenu,
observer: null,
initLeft: 0,
initTop: 0,
forceUpdate: () => {},
state: false,
};
export function contextMenuEvent(
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
ref: RefObject<HTMLDivElement>
) {
e.preventDefault();
contextMenuState.unmountContextMenu();
const config: MutationObserverInit = {
attributes: true,
};
const callback: MutationCallback = (mutationsList) => {
if (isMac()) {
//mac 有bug
contextMenuState.unmountContextMenu();
} else {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes') {
const scale = scaleState.value;
const curLeft = parseFloat((mutation.target as HTMLDivElement).style.left);
const curTop = parseFloat((mutation.target as HTMLDivElement).style.top);
const diffL = (curLeft - contextMenuState.initLeft) * scale;
const diffT = (curTop - contextMenuState.initTop) * scale;
contextMenuState.initLeft = curLeft;
contextMenuState.initTop = curTop;
contextMenuState.left = contextMenuState.left + diffL;
contextMenuState.top = contextMenuState.top + diffT;
contextMenuState.forceUpdate();
}
}
}
};
contextMenuState.state = true;
contextMenuState.observer = new MutationObserver(callback);
if (ref.current) {
//记录初始值
contextMenuState.initTop = parseFloat(ref.current.style.top);
contextMenuState.initLeft = parseFloat(ref.current.style.left);
contextMenuState.observer.observe(ref.current, config);
}
contextMenuState.left = e.clientX;
contextMenuState.top = e.clientY;
if (!contextMenuState.menu) {
contextMenuState.menu = document.createElement('div');
document.body && document.body.appendChild(contextMenuState.menu);
}
if (!contextMenuState.parent) {
contextMenuState.parent = document.createElement('div');
}
contextMenuState.menu.appendChild(contextMenuState.parent);
ReactDOM.render(contextMenuState.contextMenu, contextMenuState.parent);
}
export function unmountContextMenu() {
contextMenuState.state = false;
if (contextMenuState.observer) {
contextMenuState.observer.disconnect();
}
if (contextMenuState.menu && contextMenuState.parent) {
unmountComponentAtNode(contextMenuState.parent);
contextMenuState.menu.removeChild(contextMenuState.parent);
contextMenuState.parent = null;
}
}
export default ContextMenu;

View File

@@ -0,0 +1,84 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 15:21:54
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\crossDrag\index.ts
*/
import { store } from '../../runtime/store';
import { componentRegister } from '../../runtime';
import { DragEvent, ReactNode } from 'react';
import { createBlock } from '../components/createBlock';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
/**
*
* @export
* @interface LeftRegistComponentMapItem
* @img 图片地址
* @urlFn 组件异步加载函数
*/
export interface LeftRegistComponentMapItem {
type: string;
component: string;
img: string;
imgCustom?: ReactNode;
displayName: string;
urlFn?: () => Promise<any>;
}
let currentDrag: LeftRegistComponentMapItem | null = null;
export const dragEventResolve = function (item: LeftRegistComponentMapItem) {
return {
draggable: true,
onDragStart: () => {
currentDrag = item;
},
onDragOver: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
},
onDrop: () => {},
onDragEnd: () => {},
};
};
export const containerDragResolve = {
onDragStart: () => {},
onDragOver: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
},
onDrop: (e: DragEvent<HTMLDivElement>) => {
const offsetX = e.nativeEvent.offsetX;
const offestY = e.nativeEvent.offsetY;
//drop后修改store
if (currentDrag) {
// 还需要拿到注册的组件状态
const origin = componentRegister.getComp(currentDrag.component);
if (!origin) {
console.log(currentDrag.component, 'wait the chunk pull compeletely and retry');
return;
}
const target = e.target as HTMLElement;
let newblock: IBlockType;
if (!origin.needPosition) {
newblock = createBlock(
origin.initData.top ?? offestY,
origin.initData.left ?? offsetX,
origin
);
} else {
if (target.id !== 'yh-container') {
newblock = createBlock(offestY + target.offsetTop, offsetX + target.offsetLeft, origin);
} else {
newblock = createBlock(offestY, offsetX, origin);
}
}
const data = deepCopy(store.getData());
data.block.push(newblock);
store.setData({ ...data });
}
currentDrag = null;
},
onDragEnd: () => {},
};

View File

@@ -0,0 +1,123 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-13 11:20:55
* @LastEditors: yehuozhili
* @LastEditTime: 2021-05-04 20:34:36
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\dataCenter\index.ts
*/
import UserConfig from '../../config';
import { IStoreData } from '../store/storetype';
import { StoreChanger } from '../storeChanger';
/**
*
* 用来管理页面数据,包括全局数据,做全局设置变量时可以加上
* 使用Record<string,any>结构,每个组件的数据需要抛出并设定键进行通信。
* @export
* @class DataCenter
*/
export class DataCenter {
public asyncMap: Record<string, Function> = {};
constructor(public dataMap: Record<string, any> = {}) {}
/**
*
* 拿到map
* @return {*}
* @memberof DataCenter
*/
getDataMap() {
return this.dataMap;
}
/**
*
* 用于设置map数据
* 在异步注册时会触发get的回调动态不需要持久化
* @memberof DataCenter
*/
setToMap(data: Record<string, any>) {
this.dataMap = Object.assign(this.dataMap, data);
Object.keys(data).forEach((v) => {
if (this.asyncMap[v]) {
this.asyncMap[v]();
delete this.asyncMap[v];
}
});
}
/**
*
* 静态设置map 和异步无关 静态需要持久化datacenter存入store
* 该更新不放在redo undo处
* @param {Record<string, any>} data
* @memberof DataCenter
*/
staticSetToMap(data: Record<string, any>, config: UserConfig) {
this.dataMap = data;
const storeChanger = config.getStoreChanger();
const store = config.getStore();
const storeCurrentData = store.getData();
const sign = storeChanger.isEdit();
if (sign) {
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
currentData.dataSource = data;
}
} else {
storeCurrentData.dataSource = data;
}
}
/**
*
* 初始收集使用 -> to datacenter
* @param {IStoreData} data
* @memberof DataCenter
*/
initAddToDataMap(data: IStoreData, storeChanger: StoreChanger) {
const sign = storeChanger.isEdit();
//这里只能初始触发,一般不会走编辑状态,否则逻辑可能会有问题
if (sign) {
// 编辑状态收集orgin
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
this.dataMap = currentData.dataSource;
}
} else {
this.dataMap = data.dataSource;
}
}
/**
*
* 获取值可异步
* @param {string} name
* @memberof DataCenter
*/
getValue(name: string) {
const value = this.dataMap[name];
if (value) {
return Promise.resolve(value);
}
return new Promise((resolve) => {
this.asyncMap[name] = () => {
resolve(this.getValue(name));
};
});
}
/**
*
* 获取值不可异步
* @param {string} name
* @memberof DataCenter
*/
get(name: string) {
const value = this.dataMap[name];
return value;
}
}

View File

@@ -0,0 +1,72 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-08 20:22:43
* @LastEditors: yehuozhili
* @LastEditTime: 2021-06-28 16:08:56
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\eventCenter\eventQuene.ts
*/
import { EventCenterMapItem, EventCenterUserSelect } from '.';
import UserConfig from '../../config';
import { FunctionCenterFunction } from '../functionCenter';
export class EventQuene {
available: number;
waiters: Array<{
fn: FunctionCenterFunction;
args: any;
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
};
iname: EventCenterMapItem;
}>;
context: Record<string, any>;
config: UserConfig;
constructor(available: number = 1, config: UserConfig, context = {}) {
this.available = available;
this.waiters = [];
this._continue = this._continue.bind(this);
this.context = context;
this.config = config;
}
take(
task: FunctionCenterFunction,
args: Record<string, any>,
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
},
iname: EventCenterMapItem
) {
if (this.available > 0) {
this.available--;
task(this.context, this.leave.bind(this), this.config, args, eventList, iname);
} else {
this.waiters.push({ fn: task, args: args, eventList, iname });
}
}
leave() {
this.available++;
if (this.waiters.length > 0) {
this._continue();
}
}
_continue() {
if (this.available > 0) {
this.available--;
let task = this.waiters.shift();
if (task?.fn) {
task.fn(
this.context,
this.leave.bind(this),
this.config,
task.args,
task.eventList,
task.iname
);
}
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-06 19:33:17
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 14:41:48
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\eventCenter\index.ts
*/
import UserConfig from '../../config';
import { FunctionCenter, FunctionCenterType } from '../functionCenter';
import { FunctionDataType } from '../functionCenter/config';
import { IStoreData } from '../store/storetype';
import { StoreChanger } from '../storeChanger';
import { EventQuene } from './eventQuene';
// 每个组件制作时可以抛出多个事件事件名为id+自定义name
// 每个组件可以抛出多个函数,存在函数中心
export interface EventCenterMapItem {
name: string; // 函数名
args: Record<string, any>; // 输入参数都会变成对象传来,
data: Record<string, FunctionDataType>; // 用户选的种类 键是每个配置项名
}
export interface EventCenterUserSelect {
uid: string;
value: string;
detail: Record<string, any>;
}
export type EventCenterMapType = Record<
string,
{
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
}
>;
export class EventCenter {
/**
* 该map需要存入store,值为函数的key的数组
* @param {Record<string, Array<string>>} [eventMap={}]
* @memberof EventCenter
*/
public functionCenter: FunctionCenter;
constructor(public eventMap: EventCenterMapType = {}, configFunction?: FunctionCenterType) {
this.functionCenter = new FunctionCenter(configFunction);
}
getFunctionCenter() {
return this.functionCenter;
}
getEventMap() {
return this.eventMap;
}
resetEventMap() {
this.eventMap = {};
}
/**
*
* 重置map进行收集事件 主要就是收集eventMap字段
* 这个应该优化在换store情况下。
* @param {IStoreData} data
* @memberof EventCenter
*/
syncEventMap(data: IStoreData, storeChanger: StoreChanger) {
// 需要判断是否在弹窗状态。如果在弹窗状态数据以storeChanger为准否则就以store为准
const sign = storeChanger.isEdit();
this.eventMap = {};
if (sign) {
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
// 收集源block数据
currentData.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
//收集源modal数据
Object.keys(currentData.modalMap).forEach((v) => {
currentData.modalMap[v].block.forEach((k) => {
this.eventMap = Object.assign(this.eventMap, k.eventMap);
});
});
//收集当前modal数据
data.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
}
} else {
data.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
Object.keys(data.modalMap).forEach((v) => {
data.modalMap[v].block.forEach((k) => {
this.eventMap = Object.assign(this.eventMap, k.eventMap);
});
});
}
}
/**
*
* 手动更新状态eventMap
* @param {string} name
* @memberof EventCenter
*/
manualUpdateMap(name: string, displayName: string, arr?: Array<EventCenterMapItem>) {
if (!this.eventMap[name]) {
this.eventMap[name] = {
arr: [],
displayName: displayName,
userSelect: [],
};
}
if (arr && this.eventMap[name].displayName) {
this.eventMap[name].arr = arr;
} else if (arr && this.eventMap[name]) {
this.eventMap[name] = {
displayName,
arr,
userSelect: [],
};
}
}
/**
*
* 执行事件链
* @param {string} name
* @memberof EventCenter
*/
async runEventQueue(name: string, config: UserConfig) {
const eventList = this.eventMap[name];
if (!eventList) {
console.error(`未查询到该事件${name}`);
return;
}
const arr = new EventQuene(1, config);
//如果组件异步加载,那么函数会过段时间载入,等同于异步函数
// 函数中心需要处理未找到时的异步处理情况
if (Array.isArray(eventList.arr)) {
for (let i of eventList.arr) {
const fn = await this.functionCenter.getFunction(i.name);
arr.take(fn, i.args, eventList, i);
}
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:10:16
* @FilePath: \dooring-v2\src\core\focusHandler\index.tsx
*/
import { store } from '../../runtime/store';
import { innerDragState } from '../innerDrag/state';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import { selectRangeMouseDown } from '../selectRange';
import { unmountContextMenu } from '../contextMenu';
import { focusState } from './state';
export function containerFocusRemove() {
const onMouseDown = (e: React.MouseEvent) => {
const clonedata = deepCopy(store.getData());
const newBlock = clonedata.block.map((v: IBlockType) => {
v.focus = false;
return v;
});
focusState.blocks = [];
store.setData({ ...clonedata, block: newBlock });
if (!innerDragState.item) {
selectRangeMouseDown(e);
}
unmountContextMenu();
};
return {
onMouseDown,
};
}
export function blockFocus(e: React.MouseEvent, item: IBlockType) {
const clonedata = deepCopy(store.getData());
if (e.shiftKey) {
const newBlock = clonedata.block.map((v: IBlockType) => {
if (v.id === item.id) {
v.focus = true;
focusState.blocks.push(item);
}
return v;
});
store.setData({ ...clonedata, block: newBlock });
} else {
let blocks: IBlockType[] = [];
const newBlock = clonedata.block.map((v: IBlockType) => {
if (v.id === item.id) {
blocks.push(item);
v.focus = true;
} else {
v.focus = false;
}
return v;
});
focusState.blocks = blocks;
store.setData({ ...clonedata, block: newBlock });
}
}

View File

@@ -0,0 +1,14 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 12:06:20
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:06:50
* @FilePath: \dooring-v2\src\core\focusHandler\state.ts
*/
import { IBlockType } from '../store/storetype';
export interface FocusStateType {
blocks: IBlockType[];
}
export const focusState: FocusStateType = {
blocks: [],
};

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-25 10:03:21
* @LastEditors: yehuozhili
* @LastEditTime: 2021-06-28 19:29:21
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\functionCenter\config.ts
*/
// 设定函数配置项格式,
export type FunctionDataType = keyof FunctionDataMap;
export type FunctionNameType = string;
export type FuncitonOptionConfigType = {
receive: string;
multi: boolean;
};
export interface FunctionDataMap {
dataSource: FuncitonOptionConfigType;
modal: FuncitonOptionConfigType;
input: FuncitonOptionConfigType;
ctx: FuncitonOptionConfigType;
}
// data 如果是''则在datasource,input,ctx选择
export type FunctionConfigType = {
name: FunctionNameType; // 会放到左侧展示 唯一!
data: FunctionDataType[];
options: FuncitonOptionConfigType;
}[];

View File

@@ -0,0 +1,128 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-08 19:59:01
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 01:46:17
* @FilePath: \dooringv2\packages\dooringx-lib\src\core\functionCenter\index.ts
*/
import UserConfig from '../../config';
import { EventCenterMapItem, EventCenterUserSelect } from '../eventCenter';
import { FunctionConfigType } from './config';
/**
*
* ctx可在事件链中传递函数完成后调用next执行下一个函数
* @export
*/
export type FunctionCenterFunction = (
ctx: Record<string, any>,
next: Function,
config: UserConfig,
args: Record<string, any>,
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
},
iname: EventCenterMapItem
) => void;
export type FunctionCenterType = Record<
string,
{ fn: FunctionCenterFunction; config: FunctionConfigType }
>;
/**
*
* 初始化时可以加载初始已配好的函数
* @export
* @class FunctionCenter
*/
export class FunctionCenter {
/**
*
* 该map 用于获取函数未获取到时,异步拉取
* @memberof FunctionCenter
*/
public asyncMap: Record<string, Function> = {};
public configMap: Record<string, FunctionConfigType> = {};
public funcitonMap: Record<string, FunctionCenterFunction> = {};
constructor(public initConfig: FunctionCenterType = {}) {
this.init(initConfig);
}
init(initConfig: FunctionCenterType) {
this.reset();
this.funcitonMap = Object.keys(initConfig).reduce<Record<string, FunctionCenterFunction>>(
(prev, next) => {
prev[next] = initConfig[next].fn;
return prev;
},
{}
);
this.configMap = Object.keys(initConfig).reduce<Record<string, FunctionConfigType>>(
(prev, next) => {
prev[next] = initConfig[next].config;
return prev;
},
{}
);
}
reset() {
this.funcitonMap = {};
this.configMap = {};
}
getFunctionMap() {
return this.funcitonMap;
}
getConfigMap() {
return this.configMap;
}
/**
*
* 注册函数,同名覆盖,返回删除函数
* @param {string} name
* @param {FunctionCenterFunction} fn
* @return {*}
* @memberof FunctionCenter
*/
register(name: string, fn: FunctionCenterFunction, config: FunctionConfigType) {
// 注册时需要通知asyncmap已经拿到
this.funcitonMap[name] = fn;
if (this.asyncMap[name]) {
this.asyncMap[name]();
}
this.configMap[name] = config;
return () => {
delete this.funcitonMap[name];
delete this.configMap[name];
};
}
/**
*
* 获取函数,包含异步获取函数
* @param {string} name
* @return {*} {Promise<FunctionCenterFunction>}
* @memberof FunctionCenter
*/
getFunction(name: string): Promise<FunctionCenterFunction> {
//如果拿不到,可能是异步,进行监听回调
const fn = this.funcitonMap[name];
if (fn) {
return Promise.resolve(fn);
}
return new Promise((resolve) => {
console.warn(`waiting the function now ${name} `);
this.asyncMap[name] = () => {
delete this.asyncMap[name];
resolve(this.getFunction(name));
};
});
}
}

View File

@@ -0,0 +1,124 @@
import { store } from '../../runtime/store';
import { RefObject } from 'react';
import { blockFocus, containerFocusRemove } from '../focusHandler';
import { marklineConfig } from '../markline/marklineConfig';
import { resizerMouseMove, resizerMouseUp } from '../resizeHandler';
import { scaleState } from '../scale/state';
import { selectRangeMouseMove, selectData, selectRangeMouseUp } from '../selectRange';
import { IBlockType } from '../store/storetype';
import { deepCopy, isMac } from '../utils';
import { wrapperMoveMouseUp } from '../../components/wrapperMove/event';
import { contextMenuState } from '../contextMenu';
import { innerDragState } from './state';
export const innerDrag = function (item: IBlockType, ref: RefObject<HTMLDivElement>) {
return {
onMouseDown: (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!item.canDrag) {
containerFocusRemove().onMouseDown(e);
return;
}
blockFocus(e, item);
if (item.id && innerDragState.lastClick && item.id !== innerDragState.lastClick.id) {
contextMenuState.unmountContextMenu();
}
innerDragState.lastClick = item;
if (item.position === 'static') {
return;
}
if (ref.current) {
ref.current.style.cursor = 'move';
ref.current.style.willChange = 'left,right,width,height';
}
innerDragState.startX = e.clientX;
innerDragState.startY = e.clientY;
innerDragState.item = item;
innerDragState.isDrag = true;
innerDragState.ref = ref;
innerDragState.current = store.getIndex();
},
};
};
export const innerContainerDrag = function () {
let lastblock: null | IBlockType;
const onMouseMove = (e: React.MouseEvent) => {
e.preventDefault();
if (isMac() && contextMenuState.state) {
//mac有bug
return;
}
const id = innerDragState.item?.id;
if (id && innerDragState.isDrag) {
const current = store.getData().block.find((v) => v.id === id);
if (current?.position === 'static') {
return;
}
let { clientX: moveX, clientY: moveY } = e;
const { startX, startY } = innerDragState;
const scale = scaleState.value;
let durX = (moveX - startX) / scale;
let durY = (moveY - startY) / scale;
let newblock: IBlockType[];
if (lastblock !== innerDragState.item) {
const cloneblock: IBlockType[] = deepCopy(store.getData().block);
lastblock = innerDragState.item;
newblock = cloneblock.map((v) => {
if (v.focus && v.position !== 'static') {
v.left = v.left + durX;
v.top = v.top + durY;
}
return v;
});
} else {
newblock = store.getData().block.map((v) => {
if (v.focus && v.position !== 'static') {
v.left = v.left + durX;
v.top = v.top + durY;
}
return v;
});
}
innerDragState.startX = moveX;
innerDragState.startY = moveY;
store.setData({ ...store.getData(), block: newblock });
}
resizerMouseMove(e);
if (selectData.selectDiv) {
selectRangeMouseMove(e);
}
};
return {
onMouseMove,
};
};
export const innerContainerDragUp = function () {
const onMouseUp = (e: React.MouseEvent) => {
e.preventDefault();
wrapperMoveMouseUp();
selectRangeMouseUp(e);
if (innerDragState.ref && innerDragState.ref.current) {
innerDragState.ref.current.style.cursor = 'default';
innerDragState.ref.current.style.willChange = 'auto';
}
resizerMouseUp();
if (innerDragState.current) {
const endindex = store.getIndex();
store.getStoreList().splice(innerDragState.current, endindex - innerDragState.current);
store.setIndex(innerDragState.current);
}
innerDragState.ref = null;
innerDragState.isDrag = false;
innerDragState.item = null;
innerDragState.current = 0;
marklineConfig.marklineUnfocus = null;
store.forceupdate();
};
return {
onMouseUp,
};
};

View File

@@ -0,0 +1,30 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 12:09:11
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:09:46
* @FilePath: \dooring-v2\src\core\innerDrag\state.ts
*/
import { RefObject } from 'react';
import { IBlockType } from '../store/storetype';
export interface innerDragStateType {
startX: number;
startY: number;
item: null | IBlockType;
isDrag: boolean;
ref: RefObject<HTMLDivElement> | null;
current: number;
lastClick: null | IBlockType;
}
export const innerDragState: innerDragStateType = {
startX: 0,
startY: 0,
item: null,
isDrag: false,
ref: null,
current: 0,
lastClick: null,
};

View File

@@ -0,0 +1,79 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:50
* @FilePath: \dooring-v2\src\core\markline\calcRender.ts
*/
import { store } from '../../runtime/store';
import { innerDragState } from '../innerDrag/state';
import { scaleState } from '../scale/state';
import { grideModeRender, gridModeDisplay } from './gridMode';
import { switchMarklineDisplay } from './normalMode';
import { resizeCurrentCalculate } from './resizeMarkline';
import { marklineConfig } from './marklineConfig';
export interface LinesTypes {
x: number[];
y: number[];
}
export function marklineCalRender() {
//focus可能好几个做对比的是拖拽那个
const lines: LinesTypes = { x: [], y: [] };
if (innerDragState.item?.position === 'static' || innerDragState.item?.position === 'relative') {
return lines;
}
const item = innerDragState.item;
const ref = innerDragState.ref;
if (item && ref && ref.current && innerDragState.isDrag) {
const focus = store.getData().block.find((v) => v.id === item.id)!;
if (!marklineConfig.marklineUnfocus) {
marklineConfig.marklineUnfocus = store
.getData()
.block.filter(
(v) => v.focus === false && v.position !== 'static' && v.position !== 'relative'
);
}
const { width, height } = ref.current.getBoundingClientRect();
// left 和top 被深拷贝过,最新的值需要即时获取
const left = focus?.left;
const top = focus?.top;
if (typeof left !== 'number' || typeof top !== 'number') {
return lines; //莫名可能没有这2值
}
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
marklineConfig.marklineUnfocus.forEach((v) => {
let l = v?.left;
let t = v?.top;
if (typeof l !== 'number' || typeof t !== 'number') {
console.warn(`${v} component miss top or left`);
} else {
// 如果不是由外层容器决定的则没有这2属性
const w = v.width;
const h = v.height;
// 只有满足要求的才进行push
if (marklineConfig.mode === 'normal') {
switchMarklineDisplay(l, t, w, h, left, top, wwidth, wheight, lines, focus);
}
}
});
if (marklineConfig.mode === 'grid' && marklineConfig.isAbsorb) {
gridModeDisplay(left, top, focus);
}
}
if (marklineConfig.mode === 'grid') {
grideModeRender(lines);
}
resizeCurrentCalculate(lines);
return lines;
}

View File

@@ -0,0 +1,82 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:43
* @FilePath: \dooring-v2\src\core\markline\gridMode.ts
*/
import { store } from '../../runtime/store';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import { LinesTypes } from './calcRender';
import { marklineConfig } from './marklineConfig';
export function gridModeDisplay(left: number, top: number, focus: IBlockType) {
// 有吸附走吸附只吸top和left宽高不需要
// 无吸附拖拽时显示所有网格。
const container = store.getData().container;
const containerWidth = container.width;
const containerHeight = container.height;
const indent = marklineConfig.gridIndent;
const diff = marklineConfig.indent;
// 网格按照宽高除以下,每间隔一定距离给个线
// 横线
for (let i = 0; i < containerHeight; i++) {
const tmp = i * indent;
if (Math.abs(top - tmp) < diff) {
focus.top = tmp;
break;
} else if (tmp + diff > top) {
break;
}
}
// 竖线
for (let i = 0; i < containerWidth; i++) {
const tmp = i * indent;
if (Math.abs(left - tmp) < diff) {
focus.left = tmp;
break;
} else if (tmp + diff > left) {
break;
}
}
}
export interface lastGridStatusProps {
lastWidth: number;
lastHeight: number;
lastIndent: number;
lastLine: LinesTypes;
}
export const lastGridStatus: lastGridStatusProps = {
lastWidth: 0,
lastHeight: 0,
lastIndent: 0,
lastLine: { x: [], y: [] },
};
export function grideModeRender(lines: LinesTypes) {
const container = store.getData().container;
const containerWidth = container.width;
const containerHeight = container.height;
const indent = marklineConfig.gridIndent;
if (
lastGridStatus.lastWidth === containerWidth &&
lastGridStatus.lastHeight === containerHeight &&
lastGridStatus.lastIndent === indent
) {
lines.x = lastGridStatus.lastLine.x;
lines.y = lastGridStatus.lastLine.y;
} else {
for (let i = 0; i < containerWidth; i++) {
lines.x.push(i * indent);
}
for (let i = 0; i < containerHeight; i++) {
lines.y.push(i * indent);
}
lastGridStatus.lastLine = deepCopy(lines);
lastGridStatus.lastWidth = containerWidth;
lastGridStatus.lastHeight = containerHeight;
lastGridStatus.lastIndent = indent;
}
}

View File

@@ -0,0 +1,80 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:45:22
* @FilePath: \dooring-v2\src\core\markline\index.tsx
*/
import React from 'react';
import { useMemo } from 'react';
import { IBlockType } from '../store/storetype';
import { marklineCalRender } from './calcRender';
// 主要逻辑需要注入组件内拖拽
export interface MarklineConfigType {
indent: number;
isAbsorb: boolean;
mode: 'normal' | 'grid';
gridIndent: number;
resizeIndent: number;
marklineUnfocus: null | IBlockType[];
}
// 间隔距离执行吸附
export const marklineConfig: MarklineConfigType = {
indent: 2,
isAbsorb: true,
mode: 'normal',
gridIndent: 50,
resizeIndent: 0,
marklineUnfocus: null,
};
export function MarklineX(props: any) {
return (
<div
className="yh-markline"
style={{
borderTop: '1px dashed black',
position: 'absolute',
width: '100%',
top: props.top,
display: props.display,
zIndex: 9999,
}}
></div>
);
}
export function MarklineY(props: any) {
return (
<div
className="yh-markline"
style={{
borderLeft: '1px dashed black',
position: 'absolute',
height: '100%',
left: props.left,
display: props.display,
zIndex: 9999,
}}
></div>
);
}
export function NormalMarkLineRender() {
const lines = marklineCalRender();
const render = useMemo(() => {
return (
<>
{lines.x.map((v, i) => {
return <MarklineX key={i} top={v}></MarklineX>;
})}
{lines.y.map((v, i) => {
return <MarklineY key={i} left={v}></MarklineY>;
})}
</>
);
}, [lines]);
return <>{render}</>;
}

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 11:49:13
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:31
* @FilePath: \dooring-v2\src\core\markline\marklineConfig.ts
*/
import { IBlockType } from '../store/storetype';
export interface MarklineConfigType {
indent: number;
isAbsorb: boolean;
mode: 'normal' | 'grid';
gridIndent: number;
resizeIndent: number;
marklineUnfocus: null | IBlockType[];
}
// 间隔距离执行吸附
export const marklineConfig: MarklineConfigType = {
indent: 2,
isAbsorb: true,
mode: 'normal',
gridIndent: 50,
resizeIndent: 0,
marklineUnfocus: null,
};

View File

@@ -0,0 +1,279 @@
import { IBlockType } from '../store/storetype';
import { LinesTypes } from './calcRender';
import { marklineConfig } from './marklineConfig';
export function switchMarklineDisplay(
l: number,
t: number,
w: number | string | undefined,
h: number | string | undefined,
left: number,
top: number,
width: number,
height: number,
lines: LinesTypes,
focus: IBlockType
) {
// 做吸附只能选择一个接近的吸,所以只匹配一个即可。
// 头对头
if (marklineConfig.isAbsorb) {
if (Math.abs(top - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t;
}
// 中对头
else if (Math.abs(top + height / 2 - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t - height / 2;
}
// 尾对头
else if (Math.abs(top + height - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t - height;
} else if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2;
}
// 中对中
else if (Math.abs(t + h / 2 - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2 - height / 2;
}
// 尾对中
else if (Math.abs(t + h / 2 - top - height) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2 - height;
}
// 头对尾
else if (Math.abs((t + h - top) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h;
}
// 中对尾
else if (Math.abs(t + h - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h - height / 2;
}
// 尾对尾
else if (Math.abs((t + h - top - height) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h - height;
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l;
}
// 中对头
else if (Math.abs(left + width / 2 - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l - width / 2;
}
// 尾对头
else if (Math.abs(left + width - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l - width;
} else if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2;
}
// 中对中
else if (Math.abs(l + w / 2 - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2 - width / 2;
}
// 尾对中
else if (Math.abs(l + w / 2 - left - width) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2 - width;
}
// 头对尾
else if (Math.abs((l + w - left) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w;
}
// 中对尾
else if (Math.abs(l + w - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w - width / 2;
}
// 尾对尾
else if (Math.abs((l + w - left - width) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w - width;
}
}
} else {
if (Math.abs(top - t) < marklineConfig.indent) {
lines.x.push(t);
}
// 中对头
if (Math.abs(top + height / 2 - t) < marklineConfig.indent) {
lines.x.push(t);
}
// 尾对头
if (Math.abs(top + height - t) < marklineConfig.indent) {
lines.x.push(t);
}
if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 中对中
if (Math.abs(t + h / 2 - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 尾对中
if (Math.abs(t + h / 2 - top - height) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 头对尾
if (Math.abs((t + h - top) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
// 中对尾
if (Math.abs(t + h - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
// 尾对尾
if (Math.abs((t + h - top - height) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) < marklineConfig.indent) {
lines.y.push(l);
}
// 中对头
if (Math.abs(left + width / 2 - l) < marklineConfig.indent) {
lines.y.push(l);
}
// 尾对头
if (Math.abs(left + width - l) < marklineConfig.indent) {
lines.y.push(l);
}
if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 中对中
if (Math.abs(l + w / 2 - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 尾对中
if (Math.abs(l + w / 2 - left - width) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 头对尾
if (Math.abs((l + w - left) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
// 中对尾
if (Math.abs(l + w - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
// 尾对尾
if (Math.abs((l + w - left - width) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
}
}
}
export function switchMarklineResizeDisplay(
l: number,
t: number,
w: number | string | undefined,
h: number | string | undefined,
left: number,
top: number,
width: number,
height: number,
lines: LinesTypes
) {
// 头对头
if (Math.abs(top - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
// 中对头
if (Math.abs(top + height / 2 - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
// 尾对头
if (Math.abs(top + height - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 中对中
if (Math.abs(t + h / 2 - top - height / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 尾对中
if (Math.abs(t + h / 2 - top - height) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 头对尾
if (Math.abs((t + h - top) / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
// 中对尾
if (Math.abs(t + h - top - height / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
// 尾对尾
if (Math.abs((t + h - top - height) / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
// 中对头
if (Math.abs(left + width / 2 - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
// 尾对头
if (Math.abs(left + width - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 中对中
if (Math.abs(l + w / 2 - left - width / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 尾对中
if (Math.abs(l + w / 2 - left - width) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 头对尾
if (Math.abs((l + w - left) / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
// 中对尾
if (Math.abs(l + w - left - width / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
// 尾对尾
if (Math.abs((l + w - left - width) / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-18 11:52:38
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:58
* @FilePath: \dooring-v2\src\core\markline\resizeMarkline.ts
*/
import { store } from '../../runtime/store';
import { resizeState } from '../resizeHandler';
import { scaleState } from '../scale/state';
import { LinesTypes } from './calcRender';
import { switchMarklineResizeDisplay } from './normalMode';
import { marklineConfig } from './marklineConfig';
export function resizeCurrentCalculate(lines: LinesTypes) {
const id = resizeState.item?.id;
if (resizeState.ref?.current && id) {
const newblock = store.getData().block;
const unfocus = newblock.filter((v) => v.id !== id);
const { width, height } = resizeState.ref.current.getBoundingClientRect();
const focus = store.getData().block.find((v) => v.id === id)!;
const { left, top } = focus;
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
unfocus.forEach((v) => {
const { left: l, top: t } = v;
// 如果不是由外层容器决定的则没有这2属性
const w = v.width;
const h = v.height;
const ww = w && typeof w === 'number' ? w / scale : w;
const wh = h && typeof h === 'number' ? h / scale : h;
// 只有满足要求的才进行push
if (marklineConfig.mode === 'normal') {
switchMarklineResizeDisplay(l, t, ww, wh, left, top, wwidth, wheight, lines);
}
});
}
}

View File

@@ -0,0 +1,46 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-09 15:19:36
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:52:57
* @FilePath: \dooring-v2\src\core\resizeHandler\containerResizer.ts
*/
import { store } from '../../runtime/store';
import { scaleState } from '../scale/state';
import { IStoreData } from '../store/storetype';
import { deepCopy } from '../utils';
export const containerState = {
isDrag: false,
startY: 0,
startIndex: 0,
};
export const containerResizer = {
onMousedown: (e: React.MouseEvent) => {
containerState.isDrag = true;
containerState.startY = e.clientY;
containerState.startIndex = store.getIndex();
},
onMouseMove: (e: React.MouseEvent) => {
if (containerState.isDrag) {
const scale = scaleState.value;
const diff = ((e.clientY - containerState.startY) / scale) * 2;
const clonedata: IStoreData = deepCopy(store.getData());
const height = clonedata.container.height;
let tmpHeight = height + diff < 600 ? 600 : height + diff;
clonedata.container.height = tmpHeight;
store.setData(clonedata);
containerState.startY = e.clientY;
}
},
onMouseUp: () => {
if (containerState.isDrag) {
containerState.isDrag = false;
const endIndex = store.getIndex();
store.getStoreList().splice(containerState.startIndex, endIndex - containerState.startIndex);
store.setIndex(containerState.startIndex);
}
},
};

View File

@@ -0,0 +1,204 @@
import { store } from '../../runtime/store';
import { RefObject, useMemo } from 'react';
import { scaleState } from '../scale/state';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import React from 'react';
import classnames from 'classnames';
import styles from '../../index.less';
interface BlockResizerProps {
data: IBlockType;
rect: RefObject<HTMLDivElement>;
}
interface resizeStateType {
startX: number;
startY: number;
item: null | IBlockType;
isResize: boolean;
direction: directionType;
ref: RefObject<HTMLDivElement> | null;
current: number;
}
type directionType =
| 'top'
| 'topleft'
| 'topright'
| 'left'
| 'bottomleft'
| 'bottom'
| 'bottomright'
| 'right';
export const resizeState: resizeStateType = {
startX: 0,
startY: 0,
item: null,
isResize: false,
direction: 'bottom',
ref: null,
current: 0,
};
const onMouseDown = (
e: React.MouseEvent,
direction: directionType,
item: IBlockType,
ref: RefObject<HTMLDivElement>
) => {
e.stopPropagation();
resizeState.isResize = true;
resizeState.item = item;
resizeState.startX = e.clientX;
resizeState.startY = e.clientY;
resizeState.direction = direction;
resizeState.ref = ref;
resizeState.current = store.getIndex();
};
export const resizerMouseUp = () => {
resizeState.isResize = false;
resizeState.item = null;
if (resizeState.current) {
const endindex = store.getIndex();
store.getStoreList().splice(resizeState.current, endindex - resizeState.current);
store.setIndex(resizeState.current);
}
resizeState.current = 0;
};
const changePosition = (v: IBlockType, durX: number, durY: number) => {
const direction = resizeState.direction;
const { width, height } = resizeState.ref!.current!.getBoundingClientRect();
const scale = scaleState.value;
let tmpy = height / scale - durY;
let tmpx = width / scale - durX;
switch (direction) {
case 'right':
v.width = width / scale + durX;
break;
case 'bottom':
v.height = height / scale + durY;
break;
case 'left':
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
break;
case 'top':
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
break;
case 'bottomright':
v.width = width / scale + durX;
v.height = height / scale + durY;
break;
case 'topright':
v.width = width / scale + durX;
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
break;
case 'topleft':
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
break;
case 'bottomleft':
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
v.height = height / scale + durY;
break;
default:
break;
}
};
export const resizerMouseMove = (e: React.MouseEvent) => {
//根据direction修改位置
if (resizeState.isResize && resizeState.item) {
let { clientX: moveX, clientY: moveY } = e;
const { startX, startY } = resizeState;
const scale = scaleState.value;
let durX = (moveX - startX) / scale;
let durY = (moveY - startY) / scale;
const clonedata = deepCopy(store.getData());
const newblock: IBlockType[] = clonedata.block.map((v: IBlockType) => {
if (v.id === resizeState.item!.id) {
changePosition(v, durX, durY);
}
return v;
});
resizeState.startX = moveX;
resizeState.startY = moveY;
store.setData({ ...clonedata, block: newblock });
}
};
export function BlockResizer(props: BlockResizerProps) {
const render = useMemo(() => {
if (props.data.focus && props.data.resize) {
return (
<>
<div
className={classnames(styles.resizepoint, styles.top)}
onMouseDown={(e) => {
onMouseDown(e, 'top', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.topleft)}
onMouseDown={(e) => {
onMouseDown(e, 'topleft', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.left)}
onMouseDown={(e) => {
onMouseDown(e, 'left', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.topright)}
onMouseDown={(e) => {
onMouseDown(e, 'topright', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottomleft)}
onMouseDown={(e) => {
onMouseDown(e, 'bottomleft', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottom)}
onMouseDown={(e) => {
onMouseDown(e, 'bottom', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.right)}
onMouseDown={(e) => {
onMouseDown(e, 'right', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottomright)}
onMouseDown={(e) => {
onMouseDown(e, 'bottomright', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
</>
);
} else {
return null;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.data.focus, props.data.resize]);
return <>{render}</>;
}

View File

@@ -0,0 +1,5 @@
import { unmountContextMenu } from '../contextMenu';
export const scaleCancelFn = () => {
unmountContextMenu();
};

View File

@@ -0,0 +1,54 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 22:18:43
* @FilePath: \dooringv2\src\core\scale\index.ts
*/
import { store } from '../../runtime/store';
import Store from '../store';
import { scaleCancelFn } from './cancel';
import { scaleState } from './state';
export const onWheelEvent = {
onWheel: (e: React.WheelEvent<HTMLDivElement>) => {
const dom = document.querySelector('.ant-modal-mask');
if (dom) {
//出现弹窗禁止滚动
return;
}
if (e.deltaY > 0) {
scaleCancelFn();
if (scaleState.value < scaleState.maxValue) {
scaleState.value = scaleState.value + 0.1;
store.forceUpdate();
}
} else {
scaleCancelFn();
//往上滚缩小
if (scaleState.value > scaleState.minValue) {
scaleState.value = scaleState.value - 0.1;
store.forceUpdate();
}
}
},
};
export const scaleFn = {
increase(number: number = 0.1, store: Store) {
if (scaleState.value < scaleState.maxValue) {
scaleCancelFn();
scaleState.value = scaleState.value + number;
store.forceUpdate();
}
return scaleState.value;
},
decrease(number: number = 0.1, store: Store) {
scaleCancelFn();
if (scaleState.value > scaleState.minValue) {
scaleState.value = scaleState.value - number;
store.forceUpdate();
}
return scaleState.value;
},
};

View File

@@ -0,0 +1,12 @@
/*
* @Author: yehuozhili
* @Date: 2021-07-07 10:28:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 23:16:31
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\scale\state.ts
*/
export const scaleState = {
value: 0.8,
maxValue: 1.3,
minValue: 0.4,
};

View File

@@ -0,0 +1,100 @@
import { store } from '../../runtime/store';
import { IStoreData } from '../store/storetype';
import { deepCopy } from '../utils';
import { focusState } from '../focusHandler/state';
import { scaleState } from '../scale/state';
import style from '../../index.less';
export interface SelectDataProps {
selectDiv: HTMLDivElement | null;
posx: number;
posy: number;
startX: number;
startY: number;
}
export const selectData: SelectDataProps = {
selectDiv: null,
posx: 0,
posy: 0,
startX: 0,
startY: 0,
};
export function selectRangeMouseDown(e: React.MouseEvent) {
if (!selectData.selectDiv) {
selectData.selectDiv = document.createElement('div');
}
if (selectData.selectDiv) {
selectData.startX = e.nativeEvent.offsetX;
selectData.startY = e.nativeEvent.offsetY;
selectData.posx = e.clientX;
selectData.posy = e.clientY;
selectData.selectDiv.className = style.yhTempDiv;
selectData.selectDiv.style.left = e.clientX + 'px';
selectData.selectDiv.style.top = e.clientY + 'px';
selectData.selectDiv.style.position = 'fixed';
document.body.appendChild(selectData.selectDiv);
selectData.selectDiv.onmouseup = (e) => selectRangeMouseUp(e);
selectData.selectDiv.onmousemove = (e) => selectRangeMouseMove(e);
}
}
export function selectRangeMouseMove(ev: React.MouseEvent | MouseEvent) {
if (selectData.selectDiv) {
selectData.selectDiv.style.left = Math.min(ev.clientX, selectData.posx) + 'px';
selectData.selectDiv.style.top = Math.min(ev.clientY, selectData.posy) + 'px';
selectData.selectDiv.style.width = Math.abs(selectData.posx - ev.clientX) + 'px';
selectData.selectDiv.style.height = Math.abs(selectData.posy - ev.clientY) + 'px';
}
}
function typeGuard(e: React.MouseEvent | MouseEvent): e is React.MouseEvent {
return !(e instanceof Event);
}
function selectFocus(left: number, top: number, width: number, height: number) {
if (width === 0 || height === 0) {
return;
}
const clonedata: IStoreData = deepCopy(store.getData());
const blocks = clonedata.block;
let change = false;
const maxleft = left + width;
const maxtop = top + height;
blocks.forEach((v) => {
const l = v.left;
const t = v.top;
if (l >= left && l <= maxleft && t >= top && t <= maxtop) {
change = true;
v.focus = true;
focusState.blocks.push(v);
}
});
if (change) {
store.setData(clonedata);
}
}
export function selectRangeMouseUp(e: React.MouseEvent | MouseEvent) {
if (selectData.selectDiv) {
// 这里需要判定区域
// 如果是react触发 left和top就是起始值和终止值的最小值
// 如果是原生触发left和top是起始点减去其宽高
let left = 0;
let top = 0;
const { width, height } = selectData.selectDiv.getBoundingClientRect();
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
if (typeGuard(e)) {
left = Math.min(e.nativeEvent.offsetX, selectData.startX);
top = Math.min(e.nativeEvent.offsetY, selectData.startY);
} else {
left = selectData.startX - wwidth;
top = selectData.startY - wheight;
}
selectFocus(left, top, wwidth, wheight);
selectData.selectDiv.parentNode!.removeChild(selectData.selectDiv);
selectData.selectDiv = null;
}
}

View File

@@ -0,0 +1,143 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-19 17:07:10
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\store\index.ts
*/
import { IStoreData } from './storetype';
import { storeChangerState } from '../storeChanger/state';
export const initialData: IStoreData = {
container: {
width: 375,
height: 600,
},
block: [],
modalMap: {},
dataSource: {},
globalState: {},
};
class Store {
constructor(
public storeDataList: IStoreData[] = [initialData],
public listeners: Array<Function> = [],
public current: number = 0,
public forceupdate: Function = () => {}
) {}
getData() {
return this.storeDataList[this.current];
}
getStoreList() {
return this.storeDataList;
}
getListeners() {
return this.listeners;
}
getIndex() {
return this.current;
}
/**
*
* 注意重置需要注册事件
* @param {IStoreData[]} initData
* @param {boolean} [check=false]
* @memberof Store
*/
resetToInitData(initData: IStoreData[], check = false) {
this.storeDataList = initData;
this.current = 0;
//如果是编辑模式,需要修改
if (storeChangerState.modalEditName !== '' && check) {
storeChangerState.modalEditName = '';
}
this.emit();
}
/**
*
* 注意重置需要注册事件
* @param {IStoreData[]} initData
* @param {number} current
* @param {boolean} [check=false]
* @memberof Store
*/
resetToCustomData(initData: IStoreData[], current: number, check = false) {
this.storeDataList = initData;
this.current = current;
//如果是编辑模式,需要修改
if (storeChangerState.modalEditName !== '' && check) {
storeChangerState.modalEditName = '';
}
this.emit();
}
resetListeners() {
this.listeners = [];
}
replaceList(list: IStoreData[]) {
this.storeDataList = list;
}
setForceUpdate(fn: Function) {
this.forceupdate = fn;
}
forceUpdate() {
this.forceupdate();
}
setIndex(num: number) {
this.current = num;
}
redo() {
const maxLength = this.storeDataList.length;
if (this.current + 1 < maxLength) {
this.current = this.current + 1;
this.emit();
}
}
undo() {
if (this.current > 0) {
this.current = this.current - 1;
this.emit();
}
}
cleanRedundant(index: number) {
this.storeDataList = this.storeDataList.slice(0, index + 1);
}
setData(data: IStoreData) {
// 如果current不是最后那个说明后面的被undo过的如果要新增那么需要清除之前的
let flag = true;
if (this.current + 1 !== this.storeDataList.length) {
this.cleanRedundant(this.current);
flag = false;
}
this.current = this.current + 1;
this.storeDataList[this.current] = data;
if (flag && this.current + 1 !== this.storeDataList.length) {
this.storeDataList.length = this.current + 1;
}
this.emit();
}
emit() {
this.listeners.forEach((fn) => {
fn(this.getData());
});
}
subscribe(listener: Function) {
this.listeners.push(listener);
return () => (this.listeners = this.listeners.filter((v) => v !== listener));
}
}
export default Store;

View File

@@ -0,0 +1,48 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 17:00:07
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\store\storetype.ts
*/
import { EventCenterMapType } from '../eventCenter';
export interface IStoreData {
container: {
width: number;
height: number;
};
block: Array<IBlockType>;
modalMap: Record<string, IStoreData>;
dataSource: Record<string, any>;
globalState: Record<string, any>;
}
export interface IBlockType {
id: string;
name: string;
top: number;
left: number;
zIndex: number;
position: 'absolute' | 'relative' | 'fixed' | 'static' | 'sticky';
width?: number | string;
height?: number | string;
display?: 'inline-block' | 'block' | 'inline';
focus: boolean;
resize: boolean;
canDrag: boolean;
props: Record<string, any>;
syncList: Array<string>;
eventMap: EventCenterMapType; //调用的event 与对应的函数名 如果要增加参数则类型不能是Array<string>,需要[{name:string,...args}]
functionList: Array<string>; //抛出的函数名
animate: {
animate?: string; //动画名
animationIterationCount?: any;
speed?: //动画速度
'animate__slow' | 'animate__slower' | 'animate__fast' | 'animate__faster' | '';
delay?: //首次延迟
'animate__delay-2s' | 'animate__delay-3s' | 'animate__delay-4s' | 'animate__delay-5s' | '';
};
fixed: boolean; // 用于制作fixed组件
}

View File

@@ -0,0 +1,239 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 14:55:31
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 16:59:54
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\storeChanger\index.ts
*/
import { message } from 'antd';
import Store from '../store';
import { IStoreData } from '../store/storetype';
import { createUid, deepCopy } from '../utils';
import { storeChangerState } from './state';
export type StoreChangerMap = Record<
'ORIGIN',
{
data: Array<IStoreData>;
current: number;
now: IStoreData;
} | null
>;
function createDefaultModalBlock(): IStoreData['block'] {
return [
{
id: createUid('modal-mask'),
name: 'modalMask',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
props: {},
resize: true,
focus: false,
position: 'fixed',
display: 'block',
syncList: [],
canDrag: false,
eventMap: {},
functionList: [],
animate: {},
fixed: false,
},
{
id: createUid('modal-container'),
name: 'modalContainer',
top: 100,
left: 35,
zIndex: 0,
props: {},
resize: true,
focus: true,
position: 'absolute',
display: 'block',
width: 300,
height: 300,
syncList: [],
canDrag: true,
eventMap: {},
functionList: [],
animate: {},
fixed: false,
},
];
}
// 用来存储主store
const ORIGIN = 'ORIGIN';
const defaultModalStore: () => IStoreData = () => {
const newblock = createDefaultModalBlock();
return {
container: {
width: 375,
height: 600,
},
block: newblock,
modalMap: {},
dataSource: {},
globalState: {},
};
};
export class StoreChanger {
public map: StoreChangerMap;
constructor() {
this.map = { ORIGIN: null };
}
getState() {
return storeChangerState;
}
getOrigin() {
return this.map[ORIGIN];
}
isEdit() {
if (storeChangerState.modalEditName !== '') {
return true;
}
return false;
}
isInModalMap(store: Store, name: string) {
const modalNameList = Object.keys(store.getData().modalMap);
if (modalNameList.includes(name)) {
return true;
}
return false;
}
initStoreChanger() {
storeChangerState.modalEditName = '';
this.map = { ORIGIN: null };
}
/**
*
* 更新origin内容用于编辑模式下更新全局属性
* 需要判断是否在编辑模式,否则会报错
* @memberof StoreChanger
*/
updateOrigin(data: IStoreData) {
const origin = this.getOrigin();
if (origin!.data.length === origin!.current + 1) {
//说明为末尾,
origin!.data.push(data);
} else {
//替换下一个索引
origin!.data[origin!.current + 1] = data;
}
origin!.now = data;
origin!.current = origin!.current + 1;
}
/**
*
* 保存现阶段store将store替换为新modal数据
* @memberof StoreChanger
*/
newModalMap(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后编辑其他弹窗');
return;
}
//新建modal name不能重名否则直接报错
const sign2 = this.isInModalMap(store, name);
if (sign2) {
message.error(`已有重名弹窗:${name}`);
return;
}
storeChangerState.modalEditName = name;
this.map[ORIGIN] = {
data: store.getStoreList(),
current: store.getIndex(),
now: store.getStoreList()[store.getIndex()],
};
store.resetToInitData([defaultModalStore()]);
}
/**
*
* 存储modal到主store的map中切换主store
* @param {Store} store
* @memberof StoreChanger
*/
closeModal(store: Store) {
const sign = this.isEdit();
if (!sign) {
message.error('您并没有正在编辑弹窗');
return;
}
const main = this.map[ORIGIN];
const tmpModalData = deepCopy(store.getData());
if (main) {
store.resetToCustomData(main.data, main.current);
const cloneData: IStoreData = deepCopy(store.getData());
cloneData.modalMap[storeChangerState.modalEditName] = tmpModalData;
store.setData(cloneData);
storeChangerState.modalEditName = '';
}
}
/**
*
* 在已经保存的map中获取如果正在编辑别的弹窗则报错。
* @param {Store} store store必须为主store
* @param {string} name
* @memberof StoreChanger
*/
updateModal(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后编辑其他弹窗');
return;
}
const sign2 = this.isInModalMap(store, name);
if (!sign2) {
message.error(`未找到该弹窗:${name}`);
return;
}
storeChangerState.modalEditName = name;
const modalData = store.getData().modalMap[name];
this.map[ORIGIN] = {
data: store.getStoreList(),
current: store.getIndex(),
now: store.getStoreList()[store.getIndex()],
};
store.resetToInitData([modalData]);
}
/**
*
* 删除弹窗,不能处于编辑弹窗状态
* @param {Store} store
* @param {string} name
* @returns
* @memberof StoreChanger
*/
removeModal(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后删除其他弹窗');
return;
}
const sign2 = this.isInModalMap(store, name);
if (!sign2) {
message.error(`未找到该弹窗:${name}`);
return;
}
const cloneData: IStoreData = deepCopy(store.getData());
delete cloneData.modalMap[name];
store.setData(cloneData);
}
}

View File

@@ -0,0 +1,11 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 15:40:04
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 15:40:47
* @FilePath: \dooringv2\src\core\storeChanger\state.ts
*/
export const storeChangerState = {
modalEditName: '',
};

Some files were not shown because too many files have changed in this diff Show More