完成代码沙盒组件
parent
f3bbec5536
commit
1b2a31a833
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
- @bd/core 针对`web components`的核心封装库, 以数据驱动, 可以更方便的开发 wc 组件
|
- @bd/core 针对`web components`的核心封装库, 以数据驱动, 可以更方便的开发 wc 组件
|
||||||
|
|
||||||
### 开发进度 && 计划 (32/54)
|
### 开发进度 && 计划 (33/54)
|
||||||
|
|
||||||
- [x] `wc-card` 卡片组件
|
- [x] `wc-card` 卡片组件
|
||||||
- [x] `wc-space` 间隔组件
|
- [x] `wc-space` 间隔组件
|
||||||
|
@ -69,6 +69,7 @@
|
||||||
- [ ] `wc-table` 表格组件
|
- [ ] `wc-table` 表格组件
|
||||||
- [ ] `wc-result` 结果反馈组件
|
- [ ] `wc-result` 结果反馈组件
|
||||||
- [ ] `wc-empty` 空状态组件
|
- [ ] `wc-empty` 空状态组件
|
||||||
|
- [x] `wc-sandbox` 代码沙盒组件
|
||||||
|
|
||||||
### 测试预览
|
### 测试预览
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
/**
|
||||||
|
* {选项卡组件}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2023/03/06 15:17:25
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
bind,
|
||||||
|
Component,
|
||||||
|
nextTick,
|
||||||
|
styleMap,
|
||||||
|
classMap
|
||||||
|
} from '@bd/core'
|
||||||
|
import '../icon/index.js'
|
||||||
|
import { gzip } from '//jscdn.ink/@bytedo/gzip/2.1.0/gzip.js'
|
||||||
|
|
||||||
|
class Sandbox extends Component {
|
||||||
|
static props = {
|
||||||
|
tab: { type: String, default: 'preview', attribute: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid var(--color-plain-3);
|
||||||
|
font-size: 14px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navs {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
color: var(--color-dark-1);
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
box-shadow: inset 0 -1px 0 0px var(--color-plain-3);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 120px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-right: 1px solid var(--color-plain-3);
|
||||||
|
border-bottom: 1px solid var(--color-plain-3);
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
--size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover:not([disabled]),
|
||||||
|
&.active {
|
||||||
|
color: var(--color-teal-1);
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #fff;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
#cache = {
|
||||||
|
preview: { disabled: false, code: '' },
|
||||||
|
javascript: { disabled: true, code: '' },
|
||||||
|
html: { disabled: true, code: '' },
|
||||||
|
css: { disabled: true, code: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
#created = false
|
||||||
|
|
||||||
|
selectTab(ev) {
|
||||||
|
let elem = ev.target
|
||||||
|
let key
|
||||||
|
|
||||||
|
if (elem === ev.currentTarget) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.tagName === 'LABEL') {
|
||||||
|
key = elem.dataset.key
|
||||||
|
|
||||||
|
if (key === this.tab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#cache[key].disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#cache[this.tab].panel
|
||||||
|
.$animate(true)
|
||||||
|
.then(_ => this.#cache[key].panel.$animate())
|
||||||
|
|
||||||
|
this.tab = key
|
||||||
|
} else {
|
||||||
|
window.open('https://bd-js.github.io/playground.html#' + gzip(this.code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get code() {
|
||||||
|
let { javascript, html, css } = this.#cache
|
||||||
|
let code = { js: javascript.code, html: html.code, css: css.code }
|
||||||
|
return JSON.stringify(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
created() {
|
||||||
|
bind(this.root, 'slotchange', ev => {
|
||||||
|
let slot = ev.target.assignedNodes().pop()
|
||||||
|
|
||||||
|
// 移除不合法的子组件
|
||||||
|
|
||||||
|
if (slot.tagName !== 'WC-LANG') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = slot.getAttribute('slot')
|
||||||
|
|
||||||
|
if (lang) {
|
||||||
|
this.#cache[lang].disabled = false
|
||||||
|
this.#cache[lang].code = slot.code
|
||||||
|
this.#cache[lang].panel = slot
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatePreview(lang)
|
||||||
|
this.$requestUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePreview(lang) {
|
||||||
|
let doc = this.$refs.preview.contentWindow.document
|
||||||
|
if (this.#created) {
|
||||||
|
switch (lang) {
|
||||||
|
case 'css':
|
||||||
|
doc.head.querySelector('style').innerText = this.#cache.css.code
|
||||||
|
break
|
||||||
|
case 'javascript':
|
||||||
|
doc.head.querySelector('script[type="module"]').innerText =
|
||||||
|
this.#cache.javascript.code
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'html':
|
||||||
|
doc.body.innerHTML = this.#cache.html.code
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let html = `<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/dist/css/reset-basic.css">
|
||||||
|
<style>${this.#cache.css.code}</style>
|
||||||
|
<style>body {padding:32px;}</style>
|
||||||
|
<script type="importmap">{"imports":{"es.shim":"//jscdn.ink/es.shim/2.1.1/index.js","vue":"//jscdn.ink/vue/3.2.47/vue.esm-browser.prod.js","vue-router":"//jscdn.ink/@bytedo/vue-router/4.1.6/vue-router.js","fetch":"//jscdn.ink/@bytedo/fetch/2.1.5/next.js","@bd/core":"//jscdn.ink/@bd/core/1.9.0/index.js"}}</script>
|
||||||
|
<script type="module">${this.#cache.javascript.code}</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${this.#cache.html.code}
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
try {
|
||||||
|
doc.open()
|
||||||
|
doc.write(html)
|
||||||
|
doc.close()
|
||||||
|
this.#created = true
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
//
|
||||||
|
this.#cache.preview.panel = this.$refs.preview
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let labels = ['preview', 'javascript', 'html', 'css']
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<header class="navs" ref="navs" @click=${this.selectTab}>
|
||||||
|
${labels.map(
|
||||||
|
it => html`
|
||||||
|
<label
|
||||||
|
class=${classMap({
|
||||||
|
label: true,
|
||||||
|
active: it === this.tab
|
||||||
|
})}
|
||||||
|
data-key=${it}
|
||||||
|
disabled=${this.#cache[it].disabled}
|
||||||
|
>
|
||||||
|
${it}
|
||||||
|
</label>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<wc-icon
|
||||||
|
title="在playground中打开"
|
||||||
|
class="open"
|
||||||
|
name="fly"
|
||||||
|
size="s"
|
||||||
|
></wc-icon>
|
||||||
|
</header>
|
||||||
|
<div class="content">
|
||||||
|
<iframe ref="preview" #animation=${{}}></iframe>
|
||||||
|
<slot name="javascript"></slot>
|
||||||
|
<slot name="html"></slot>
|
||||||
|
<slot name="css"></slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Lang extends Component {
|
||||||
|
static animation = {}
|
||||||
|
|
||||||
|
static props = {
|
||||||
|
code: { type: String, default: '', attribute: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #f7f8fb;
|
||||||
|
color: var(--color-dark-1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
css`
|
||||||
|
.code-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 6px 0;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-dark-1);
|
||||||
|
cursor: text;
|
||||||
|
counter-reset: code;
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 0 8px 0 45px;
|
||||||
|
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 5px;
|
||||||
|
text-align: right;
|
||||||
|
color: var(--color-grey-1);
|
||||||
|
content: counter(code);
|
||||||
|
counter-increment: code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
]
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
var txt = this.innerHTML || this.textContent
|
||||||
|
txt = txt.trim().replace(/^[\r\n]|\s{2,}$/g, '')
|
||||||
|
if (txt.startsWith('<xmp>') && txt.endsWith('</xmp>')) {
|
||||||
|
txt = txt.slice(5, -6).trim()
|
||||||
|
}
|
||||||
|
if (txt) {
|
||||||
|
this.textContent = ''
|
||||||
|
nextTick(_ => {
|
||||||
|
this.code = txt.replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="code-block">
|
||||||
|
${this.code.split('\n').map(s => html`<code>${s}</code>`)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sandbox.reg('sandbox')
|
||||||
|
Lang.reg('lang')
|
|
@ -484,6 +484,19 @@ class Tabs extends Component {
|
||||||
bind(this.root, 'slotchange', ev => {
|
bind(this.root, 'slotchange', ev => {
|
||||||
let children = ev.target.assignedNodes()
|
let children = ev.target.assignedNodes()
|
||||||
|
|
||||||
|
// 移除不合法的子组件
|
||||||
|
for (let it of children) {
|
||||||
|
if (it.tagName === 'WC-TAB') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
it.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.labels = children.map((it, i) => {
|
this.labels = children.map((it, i) => {
|
||||||
let tmp = {
|
let tmp = {
|
||||||
label: it.label,
|
label: it.label,
|
||||||
|
|
Loading…
Reference in New Issue