import React, { useMemo, useReducer, useEffect, useState } from "react"; import type { IDemoOpts, IDemoOperation, } from "@alicloud/console-toolkit-docs-shared"; import "./codemirror.less"; import "codemirror/mode/javascript/javascript.js"; import "codemirror/addon/edit/matchbrackets.js"; import { Controlled as CodeMirror } from "react-codemirror2"; import AnimateHeight from "react-animate-height"; import styles from "./index.scoped.less"; import Codesandbox from "./codesandbox"; import { useEvalCode } from "./useEvalCode"; // @ts-ignore import buildTimeDemoOpts from "/@demoOpts"; import FullView from "./FullView"; /** * demo面板操作栏,比如展示源码、在CodeSandbox打开等 */ export function useOperations( originalCode: string, imports: string[], meta: any = {}, opts: IDemoOpts = {}, demoDeps, children: React.ReactNode, DemoWrapper: undefined | React.ComponentType, loadOpts: any ) { const [state, dispatch] = useReducer(reducer, initialState); const resolvedOpts = { canFullScreen: (() => { // 可以通过demo自己的meta注释来定制canFullScreen配置,优先级最高 if (meta?.canFullScreen !== undefined) { return String(meta?.canFullScreen) !== "false" ? true : false; } return opts.canFullScreen ?? buildTimeDemoOpts.canFullScreen ?? false; })() as boolean, }; // 插件可以修改demo代码 const originalCode2 = useMemo(() => { let res = originalCode; if (typeof buildTimeDemoOpts.modifyDisplayCode === "function") { res = buildTimeDemoOpts.modifyDisplayCode({ code: res, meta, imports, }); } if (typeof opts.modifyDisplayCode === "function") { res = opts.modifyDisplayCode({ code: res, meta, imports }); } return res; }, [ opts.modifyDisplayCode, buildTimeDemoOpts.modifyDisplayCode, originalCode, meta, imports, ]); // 用户可以在源码面板修改demo代码 const [currentCode, setCurrentCode] = useState(originalCode2); // 上游code状态更新时,同步到本组件状态 useEffect(() => { setCurrentCode(originalCode2); }, [originalCode2]); // 如果用户在操作面板修改过代码, // 那么使用EvalCode来实时执行代码 const { renderEvalCode, transformedCode } = useEvalCode({ code: currentCode, deps: demoDeps, enable: currentCode !== originalCode2, }); // 插件可以配置操作栏 const operations = useMemo(() => { let res = defaultOperations; // 构建者提供的操作栏配置 if (typeof buildTimeDemoOpts.extraOperations === "function") { res = [ ...res, ...buildTimeDemoOpts.extraOperations({ code: originalCode2, meta, imports, }), ]; } // 加载者提供的操作栏配置 if (typeof opts.extraOperations === "function") { res = [ ...res, ...opts.extraOperations({ code: originalCode2, meta, imports }), ]; } const ctx = { demoOpts: opts, buildTimeDemoOpts, demoMeta: meta, resolvedOpts, }; return res.filter((operation) => { // 操作可以根据上下文信息来决定自己是否开启 if (typeof operation.enabled === "function") { return operation.enabled(ctx); } return true; }); }, [ opts.extraOperations, buildTimeDemoOpts.extraOperations, originalCode2, meta, imports, ]); const demoView = (() => { // 如果用户在操作面板修改过代码, // 那么使用EvalCode来实时执行代码 // 否则,会fallback到打包的渲染代码 let ret = renderEvalCode ?? children; // 如果用户配置了DemoWrapper,那么要用它包裹一下demo if (DemoWrapper) { ret = {ret}; } return ret; })(); const expandPanel = (() => { const { operation, height } = (() => { if (state.current !== "none") { // 有面板操作的时候,且该操作有面板视图 // 则渲染该面板视图 const operation = operations.find( (item) => item.name === state.current ); return { operation, height: operation?.View ? "auto" : 0, }; } else { // 没有面板操作的时候,渲染上一个操作,撑开操作面板高度 // 以便高度收起的动画能够进行 const operation = operations.find( (item) => item.name === state.previous ); return { operation, height: 0, }; } })(); return ( {operation?.View ? ( ) : (
)} ); })(); const operationsView = ( <>
{operations.map(({ icon, name, onClick }) => { return ( { dispatch(name); if (typeof onClick === "function") { onClick(); } }} > {icon()} ); })}
{/* 全屏查看弹窗 */} { dispatch("full-view"); }} > {demoView} {expandPanel} ); return { demoView, operationsView, resolvedOpts }; } const defaultOperations: IDemoOperation[] = [ { name: "code", icon: () => { // 图标来自阿里云控制台图标库 return ( ); }, View: ({ code, setCode }) => { return ( { // https://github.com/codemirror/CodeMirror/issues/2469#issuecomment-376064308 setTimeout(() => { editor.setSize("", `${code.split('\n').length * 18 + 10}px`); editor.refresh(); }, 10); }} onBeforeChange={(editor, data, value) => { setCode(value); }} /> ); }, }, { name: "full-view", enabled: ({ demoOpts, buildTimeDemoOpts, demoMeta, resolvedOpts }) => { return resolvedOpts.canFullScreen; }, icon: () => ( // 图标库:https://www.iconfont.cn/search/index?searchType=icon&q=expand ), // View可以不传,表示点击该操作图标不展开面板 View: undefined, }, { name: "codesandbox", icon: () => { return ( ); }, View: ({ code, imports, meta, opts }) => { return ( ); }, }, ]; const initialState = { current: "none", previous: "none" }; function reducer( state: { current: string; previous: string }, action: string ): { current: string; previous: string } { // 第二次点击同一个操作按钮时收起面板 if (state.current === action) { return { current: "none", previous: state.current, }; } return { current: action, previous: state.current, }; }