forked from opentiny/tiny-engine
feat: Modify the theme switching logic and add a new theme template to CLI. (#1143)
1.修改主题切换逻辑,默认值改成从注册表中的themes获取。 只有亮色和暗色两种默认主题的情况下,保持原来的逻辑,点击直接切换主题 用户添加了自定义主题,出现选择列表,在列表中切换主题 2.添加新主题模板 在终端中,用命令创建新主题,选择theme类型,添加主题模板,输入主题名称:demo-theme 在demo-theme下可以看到新创建的主题,cd demo-theme && pnpm i 将主题接入设计器
This commit is contained in:
parent
f53c455eba
commit
ee01af8ecb
|
|
@ -70,11 +70,17 @@ export default {
|
|||
themes: [
|
||||
{
|
||||
id: 'engine.theme.light',
|
||||
title: '亮色主题'
|
||||
text: '浅色主题',
|
||||
type: 'light',
|
||||
icon: 'light',
|
||||
oppositeTheme: 'dark'
|
||||
},
|
||||
{
|
||||
id: 'engine.theme.dark',
|
||||
title: '暗色主题'
|
||||
text: '深色主题',
|
||||
type: 'dark',
|
||||
icon: 'dark',
|
||||
oppositeTheme: 'light'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,3 +30,10 @@ npx @opentiny/tiny-engine-cli@latest create-platform my-designer
|
|||
```sh
|
||||
npx @opentiny/tiny-engine-cli@latest create-plugin my-plugin
|
||||
```
|
||||
|
||||
|
||||
### create a tiny-engine theme
|
||||
|
||||
```sh
|
||||
npx @opentiny/tiny-engine-cli@latest create-theme my-theme
|
||||
```
|
||||
|
|
@ -33,6 +33,13 @@ npx @opentiny/tiny-engine-cli@latest create-platform my-designer
|
|||
npx @opentiny/tiny-engine-cli@latest create-plugin my-tiny-engine-plugin
|
||||
```
|
||||
|
||||
### 创建主题
|
||||
|
||||
```sh
|
||||
# 创建名为 my-tiny-engine-theme 的 tiny-engine 主题
|
||||
npx @opentiny/tiny-engine-cli@latest create-theme my-tiny-engine-theme
|
||||
```
|
||||
|
||||
TODO:
|
||||
|
||||
- [ ] 插件开发文档&平台二次开发文档
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { cwd } from 'node:process'
|
|||
import path from 'node:path'
|
||||
import fs from 'fs-extra'
|
||||
import chalk from 'chalk'
|
||||
import { generateConfig, generatePackageJson } from './generateConfig'
|
||||
import { generateConfig, generatePackageJson, generateThemeMeta } from './generateConfig'
|
||||
|
||||
const logger = console
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ export function createPlatform(name, options = {}) {
|
|||
fs.copySync(templatePath, destPath)
|
||||
|
||||
const configContent = generateConfig(mergedOptions)
|
||||
const pkgContent = generatePackageJson(name, mergedOptions, templatePath)
|
||||
const pkgContent = generatePackageJson(name, templatePath)
|
||||
|
||||
fs.outputFileSync(path.resolve(destPath, 'engine.config.js'), configContent)
|
||||
fs.outputJSONSync(path.resolve(destPath, 'package.json'), pkgContent, { spaces: 2 })
|
||||
|
|
@ -62,3 +62,23 @@ export function createPlugin(name) {
|
|||
chalk.green(`create finish, run the follow command to start project: \ncd ${name} && npm install && npm run dev`)
|
||||
)
|
||||
}
|
||||
|
||||
export function createTheme(name, themeName) {
|
||||
const sourcePath = path.join(__dirname, '../template/theme/')
|
||||
const destPath = path.join(cwd(), name)
|
||||
fs.copySync(sourcePath, destPath)
|
||||
|
||||
const configContent = generateThemeMeta(themeName)
|
||||
|
||||
const themePath = path.resolve(destPath, 'src/common.less')
|
||||
fs.outputFileSync(path.resolve(destPath, 'meta.js'), configContent)
|
||||
const content = fs.readFileSync(themePath, 'utf-8')
|
||||
const outputContent = content.replace(/data-theme='custom'/g, `data-theme='${themeName}'`)
|
||||
fs.writeFileSync(themePath, outputContent, 'utf-8')
|
||||
const pkgContent = generatePackageJson(name, sourcePath)
|
||||
fs.outputJSONSync(path.resolve(destPath, 'package.json'), pkgContent, { spaces: 2 })
|
||||
|
||||
logger.log(
|
||||
chalk.green(`create finish, run the follow command to start project: \ncd ${name} && npm install && npm run dev`)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,24 @@ export default {
|
|||
}
|
||||
|
||||
// 根据参数修改 package.json
|
||||
export const generatePackageJson = (name, options, templatePath) => {
|
||||
export const generatePackageJson = (name, templatePath) => {
|
||||
const templatePackageJson = fs.readJSONSync(path.resolve(templatePath, 'package.json'))
|
||||
|
||||
templatePackageJson.name = name
|
||||
|
||||
return templatePackageJson
|
||||
}
|
||||
|
||||
// 根据参数生成 config 文件内容
|
||||
export const generateThemeMeta = (themeName = 'custom') => {
|
||||
const metaContent = `
|
||||
export default {
|
||||
id: 'engine.theme.${themeName}',
|
||||
text: '自定义主题',
|
||||
type: '${themeName}',
|
||||
icon: 'dark'
|
||||
}
|
||||
`
|
||||
|
||||
return metaContent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,35 @@
|
|||
*/
|
||||
import { Command, Option } from 'commander'
|
||||
import { input, select } from '@inquirer/prompts'
|
||||
import { createPlatform, createPlugin } from './commands/create.js'
|
||||
import { createPlatform, createPlugin, createTheme } from './commands/create.js'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
const messageMap = {
|
||||
theme: {
|
||||
message:
|
||||
'Please enter the theme ID (used to uniquely identify the theme in code or configuration, such as "custom"). 请输入主题ID(用于代码或配置中唯一标识该主题,如“custom”)',
|
||||
validateMessage: 'theme ID can not be empty. 主题ID不允许为空。'
|
||||
},
|
||||
project: {
|
||||
message: 'please enter the project name. 请输入项目名称',
|
||||
validateMessage: 'project name can not be empty. 项目名称不允许为空。'
|
||||
}
|
||||
}
|
||||
|
||||
const getName = async (type) => {
|
||||
return await input({
|
||||
message: type ? messageMap.theme.message : messageMap.project.message,
|
||||
validate: (inputName) => {
|
||||
if (!inputName) {
|
||||
return type ? messageMap.theme.validateMessage : messageMap.project.validateMessage
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
program
|
||||
.command('create-platform <name>')
|
||||
.description('create a new tiny-engine platform 创建一个新的tiny-engine低代码平台')
|
||||
|
|
@ -34,6 +59,14 @@ program
|
|||
createPlugin(name)
|
||||
})
|
||||
|
||||
program
|
||||
.command('create-theme <name>')
|
||||
.description('create a new tiny-engine theme 创建一个新的 tiny-engine 主题')
|
||||
.action(async (name) => {
|
||||
const themeName = await getName('theme')
|
||||
createTheme(name, themeName)
|
||||
})
|
||||
|
||||
program
|
||||
.command('create')
|
||||
.description('create a new tiny-engine platform or plugin by prompt 根据提示创建一个新的 tiny-engine 插件')
|
||||
|
|
@ -50,27 +83,26 @@ program
|
|||
name: 'plugin',
|
||||
value: 'plugin',
|
||||
description: 'create a new tiny-engine plugin 创建一个新的 tiny-engine 插件'
|
||||
},
|
||||
{
|
||||
name: 'theme',
|
||||
value: 'theme',
|
||||
description: 'create a new tiny-engine theme 创建一个新的 tiny-engine 主题'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const projectName = await input({
|
||||
message: 'please enter the project name. 请输入项目名称',
|
||||
validate: (inputName) => {
|
||||
if (!inputName) {
|
||||
return 'project name can not be empty. 项目名称不允许为空。'
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
})
|
||||
const projectName = await getName()
|
||||
|
||||
const typeMapper = {
|
||||
platform: createPlatform,
|
||||
plugin: createPlugin
|
||||
plugin: createPlugin,
|
||||
theme: createTheme
|
||||
}
|
||||
|
||||
typeMapper[type](projectName)
|
||||
const themeName = type === 'theme' ? await getName(type) : ''
|
||||
|
||||
typeMapper[type](projectName, themeName)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# tiny-engine Theme demo
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
import './src/common.less'
|
||||
import './src/styles/vars.less'
|
||||
import metaData from './meta.js'
|
||||
|
||||
export default {
|
||||
...metaData,
|
||||
// 插件暴露的 api,可以提供其他 api 进行调用,如果无需暴露,可为空
|
||||
apis: {}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// 描述该插件的相关元信息
|
||||
export default {
|
||||
id: 'engine.theme.custom',
|
||||
text: '自定义主题',
|
||||
type: 'custom',
|
||||
icon: 'dark'
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "theme-custom",
|
||||
"version": "2.0.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// 通过修改common的变量控制主题颜色
|
||||
:root[data-theme='custom'] {
|
||||
--te-common-text-primary: var(--te-base-red-80);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// 自定义某个模块的变量
|
||||
:root {
|
||||
--te-styles-common-text-color-primary: var(--te-base-blue-80);
|
||||
}
|
||||
|
||||
// 自定义区块管理分组删除气泡
|
||||
.block-category-option-popper-wrapper {
|
||||
--te-block-popper-content-text-color: var(--te-base-blue-80);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import path from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
publicDir: false,
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, './index.js'),
|
||||
fileName: () => 'index.js',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
banner: 'import "./style.css"'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1,27 +1,54 @@
|
|||
<template>
|
||||
<div class="toolbar-theme-switch">
|
||||
<toolbar-base :content="baseContent" :icon="baseIcon" :options="optionsData" @click-api="toChangeTheme">
|
||||
<template v-if="position === 'collapse'">
|
||||
<div class="toolbar-theme-switch-radio">
|
||||
<div class="toolbar-theme-switch-radio-title">主题</div>
|
||||
<tiny-radio-group v-model="state.theme" :options="THEME_DATA" class="theme-radio-group" @change="themeChange">
|
||||
</tiny-radio-group>
|
||||
<tiny-popover
|
||||
width="130"
|
||||
trigger="manual"
|
||||
v-model="showpopover"
|
||||
:visible-arrow="false"
|
||||
popper-class="theme-popover"
|
||||
>
|
||||
<div class="theme-list">
|
||||
<div
|
||||
v-for="item in THEME_DATA"
|
||||
:key="item.type"
|
||||
:class="['theme-item', { active: state.theme === item.type }]"
|
||||
@click="themeItemChange(item.type)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<toolbar-base :content="baseContent" :icon="baseIcon" :options="optionsData" @click-api="changeThemeType">
|
||||
<template v-if="position === 'collapse'">
|
||||
<div class="toolbar-theme-switch-radio">
|
||||
<div class="toolbar-theme-switch-radio-title">主题</div>
|
||||
<tiny-radio-group
|
||||
v-model="state.theme"
|
||||
:options="radioThemeList"
|
||||
:vertical="themeShowType ? false : true"
|
||||
class="theme-radio-group"
|
||||
@change="themeChange"
|
||||
>
|
||||
</tiny-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
</toolbar-base>
|
||||
</template>
|
||||
</toolbar-base>
|
||||
</tiny-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { ToolbarBase } from '@opentiny/tiny-engine-common'
|
||||
import { RadioGroup } from '@opentiny/vue'
|
||||
import { TinyRadioGroup, TinyPopover } from '@opentiny/vue'
|
||||
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolbarBase,
|
||||
TinyRadioGroup: RadioGroup
|
||||
TinyRadioGroup,
|
||||
TinyPopover
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
|
|
@ -46,27 +73,67 @@ export default {
|
|||
|
||||
return options
|
||||
})
|
||||
const radioThemeList = computed(() => {
|
||||
return THEME_DATA.value.map((item) => ({ ...item, label: item.type }))
|
||||
})
|
||||
const baseContent = computed(() => (props.position === COLLAPSE ? '' : state.themeLabel))
|
||||
const baseIcon = computed(() => (props.position === COLLAPSE ? '' : state.theme))
|
||||
const baseIcon = computed(() => (props.position === COLLAPSE ? '' : state.themeIcon))
|
||||
const showpopover = ref(false)
|
||||
|
||||
const themeShowType = computed(() => {
|
||||
let filterList = THEME_DATA.value.filter((item) => ['light', 'dark'].includes(item.type)) || []
|
||||
return THEME_DATA.value.length === filterList.length
|
||||
})
|
||||
|
||||
const toChangeTheme = () => {
|
||||
const theme = getTheme(state.theme).oppositeTheme
|
||||
themeChange(theme)
|
||||
}
|
||||
|
||||
const changeThemeType = () => {
|
||||
if (props.position === COLLAPSE) {
|
||||
return
|
||||
}
|
||||
if (themeShowType.value) {
|
||||
toChangeTheme()
|
||||
} else {
|
||||
showpopover.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const theme = getTheme(state.theme).oppositeTheme
|
||||
const themeItemChange = (theme) => {
|
||||
themeChange(theme)
|
||||
showpopover.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
THEME_DATA,
|
||||
state,
|
||||
optionsData,
|
||||
radioThemeList,
|
||||
baseContent,
|
||||
baseIcon,
|
||||
toChangeTheme,
|
||||
themeChange
|
||||
themeChange,
|
||||
showpopover,
|
||||
themeShowType,
|
||||
themeItemChange,
|
||||
changeThemeType
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.theme-list {
|
||||
.theme-item {
|
||||
padding: 4px 16px;
|
||||
margin: 0 -16px;
|
||||
&:hover {
|
||||
background-color: var(--te-toolbar-theme-popover-list-item-bg-color-hover);
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background-color: var(--te-toolbar-theme-popover-list-item-bg-color-active);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,34 @@
|
|||
import { reactive } from 'vue'
|
||||
import { defineService, getMetaApi, getMergeMeta, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
|
||||
import { reactive, ref } from 'vue'
|
||||
import {
|
||||
defineService,
|
||||
getMetaApi,
|
||||
getMergeMeta,
|
||||
META_SERVICE,
|
||||
getMergeRegistry
|
||||
} from '@opentiny/tiny-engine-meta-register'
|
||||
import { setGlobalMonacoEditorTheme } from '@opentiny/tiny-engine-common'
|
||||
|
||||
const THEME_DATA = [
|
||||
{
|
||||
text: '浅色模式',
|
||||
label: 'light',
|
||||
oppositeTheme: 'dark'
|
||||
},
|
||||
{
|
||||
text: '深色模式',
|
||||
label: 'dark',
|
||||
oppositeTheme: 'light'
|
||||
}
|
||||
]
|
||||
let THEME_DATA = ref([])
|
||||
|
||||
const DEFAULT_THEME = THEME_DATA[0]
|
||||
let DEFAULT_THEME = null
|
||||
|
||||
const themeState = reactive({
|
||||
theme: DEFAULT_THEME.label,
|
||||
themeLabel: DEFAULT_THEME.text
|
||||
theme: '',
|
||||
themeLabel: '',
|
||||
themeIcon: ''
|
||||
})
|
||||
|
||||
const getThemeData = () => THEME_DATA
|
||||
const getThemeState = () => themeState
|
||||
|
||||
const getTheme = (theme) => {
|
||||
return THEME_DATA.find((item) => theme === item.label) || DEFAULT_THEME
|
||||
return THEME_DATA.value.find((item) => theme === item.type) || DEFAULT_THEME
|
||||
}
|
||||
|
||||
const themeChange = (theme) => {
|
||||
themeState.theme = getTheme(theme).label
|
||||
themeState.theme = getTheme(theme).type
|
||||
themeState.themeLabel = getTheme(themeState.theme).text
|
||||
themeState.themeIcon = getTheme(themeState.theme).icon
|
||||
document.documentElement.setAttribute('data-theme', themeState.theme)
|
||||
|
||||
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
|
||||
|
|
@ -45,9 +42,10 @@ export default defineService({
|
|||
type: 'MetaService',
|
||||
init: () => {
|
||||
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
|
||||
THEME_DATA.value = getMergeRegistry('themes')
|
||||
DEFAULT_THEME = THEME_DATA.value[0]
|
||||
const theme =
|
||||
localStorage.getItem(`tiny-engine-theme-${appId}`) || getMergeMeta('engine.config').theme || DEFAULT_THEME.label
|
||||
|
||||
localStorage.getItem(`tiny-engine-theme-${appId}`) || getMergeMeta('engine.config').theme || DEFAULT_THEME.type
|
||||
themeChange(theme)
|
||||
},
|
||||
apis: () => ({
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
.toolbar-theme-switch {
|
||||
--te-toolbar-theme-switch-radio-title: var(--te-common-text-secondary);
|
||||
}
|
||||
.theme-list {
|
||||
--te-toolbar-theme-popover-list-item-bg-color-hover: var(--te-common-bg-container);
|
||||
--te-toolbar-theme-popover-list-item-bg-color-active: var(--te-common-bg-container);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ export default defineConfig({
|
|||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
banner: 'import "./style.css"'
|
||||
},
|
||||
external: ['vue', /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue