完成代码沙盒组件
parent
f3bbec5536
commit
1b2a31a833
|
@ -13,7 +13,7 @@
|
|||
|
||||
- @bd/core 针对`web components`的核心封装库, 以数据驱动, 可以更方便的开发 wc 组件
|
||||
|
||||
### 开发进度 && 计划 (32/54)
|
||||
### 开发进度 && 计划 (33/54)
|
||||
|
||||
- [x] `wc-card` 卡片组件
|
||||
- [x] `wc-space` 间隔组件
|
||||
|
@ -69,6 +69,7 @@
|
|||
- [ ] `wc-table` 表格组件
|
||||
- [ ] `wc-result` 结果反馈组件
|
||||
- [ ] `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 => {
|
||||
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) => {
|
||||
let tmp = {
|
||||
label: it.label,
|
||||
|
|
Loading…
Reference in New Issue