* 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 { computed, reactive, watch } from 'vue'
import { useCanvas, useHistory, useProperties as useProps, getOptions } from '@opentiny/tiny-engine-meta-register'
import { formatString } from '@opentiny/tiny-engine-common/js/ast'
import { constants, utils } from '@opentiny/tiny-engine-utils'
import { parser, stringify, getSelectorArr } from './parser'
const { EXPRESSION_TYPE } = constants
const { generateRandomLetters, parseExpression, objectCssToString } = utils
const state = reactive({
style: {},
styleContent: formatString(`:root {\n \n}`, 'css'),
cssContent: '',
pageCssObject: {},
currentClassSelector: '',
existClassSelectors: [],
className: {
classNameList: '',
mouseState: ''
},
cssParseList: [],
selectors: [],
styleObject: {},
currentClassNameList: [],
currentIdList: [],
selectorOptionLists: [],
schemaUpdateKey: 0,
inlineBtnText: '编辑行内样式',
lineStyleDisable: true,
propertiesList: '',
bindModelValue: null
})
const getCurrentClassSelector = () => {
let res = `${state.className.classNameList}`
const mouseState = state.className.mouseState
if (mouseState) {
res += `:${mouseState}`
}
return res
}
export const genRandomClassNames = (componentName: any) => {
return `.${componentName}-${generateRandomLetters(5)}`.toLowerCase()
}
const getPropsFromExpression = (propValue: { value: string }) => {
let res: any[] = []
try {
const expressRes = parseExpression(propValue?.value)
if (Array.isArray(expressRes)) {
res = expressRes
.map((item) => {
if (typeof item === 'string') {
return item
}
if (typeof item === 'object') {
return Object.keys(item)
}
return null
})
.flat()
.filter(Boolean)
} else if (typeof expressRes === 'string' && expressRes) {
res = [expressRes]
}
} catch (e) {
}
return res
}
const parseClassOrIdProps = (propValue: any) => {
if (typeof propValue === 'string' && propValue) {
return propValue.split(' ').filter(Boolean)
}
const res = []
if (propValue?.type === EXPRESSION_TYPE.JS_EXPRESSION) {
return getPropsFromExpression(propValue)
}
return res
}
const getClassNameAndIdList = (schema: { props: { className: any; id: any } }) => {
let classNameList: any[] = []
let idList: any[] = []
if (!schema) {
return {
classNameList,
idList
}
}
const classNameStr = schema?.props?.className
const idStr = schema?.props?.id
classNameList = parseClassOrIdProps(classNameStr)
idList = parseClassOrIdProps(idStr)
return {
classNameList,
idList
}
}
export const initStylePanelWatch = () => {
watch(
() => [
useCanvas().getCurrentSchema?.(),
state.schemaUpdateKey,
useProps().propsUpdateKey?.value,
useCanvas()?.getSchema?.()
],
([curSchema], [oldCurSchema] = []) => {
const { getCurrentSchema, getSchema } = useCanvas()
let schema = getCurrentSchema()
if (!schema || Object.keys(schema).length === 0) {
schema = getSchema?.()
}
if (!schema) {
return
}
const { classNameList, idList } = getClassNameAndIdList(schema)
state.currentClassNameList = classNameList.map((item) => `.${item}`)
state.currentIdList = idList.map((item) => `#${item}`)
if (curSchema !== oldCurSchema) {
state.className = {
classNameList: '',
mouseState: ''
}
state.style = {}
}
state.styleContent = formatString(`:root {\n ${schema?.props?.style || ''}\n}`, 'css')
},
{
deep: true
}
)
watch(
() => useCanvas().getPageSchema?.()?.css,
(value) => {
state.cssContent = objectCssToString(value) || ''
const { parseList, selectors, styleObject } = parser(state.cssContent)
state.cssParseList = parseList
state.selectors = selectors
state.styleObject = styleObject
}
)
watch(
() => [state.currentClassNameList, state.currentIdList, state.styleObject],
() => {
let list = []
const classNameListOptions = state.currentClassNameList.map((item) => ({ label: item, value: item }))
const idListOptions = state.currentIdList.map((item) => ({ label: item, value: item }))
list = list.concat(classNameListOptions, idListOptions)
Object.values(state.styleObject).forEach((value) => {
const selectorArr = getSelectorArr(value.pureSelector)
if (selectorArr.length <= 1) {
return
}
const isComboSelector = selectorArr.every(
(item) => state.currentClassNameList.includes(item) || state.currentIdList.includes(item)
)
if (isComboSelector) {
list.push({ label: value.pureSelector, value: value.pureSelector })
}
})
let defaultSelector = ''
let defaultMouseState = ''
const curClassName = state.className.classNameList
if (list.find(({ value }) => value === curClassName)) {
defaultSelector = curClassName
defaultMouseState = state.className.mouseState
} else if (list.length) {
defaultSelector = list.at(-1).value
}
state.selectorOptionLists = list
state.className = {
classNameList: defaultSelector,
mouseState: defaultMouseState
}
}
)
watch(
() => state.className,
() => {
const { classNameList, mouseState } = state.className
if (!classNameList) {
return
}
const matchStyles = Object.values(state.styleObject).filter(
(value) => value.pureSelector === classNameList && value.mouseState === mouseState
)
const style = matchStyles.length ? matchStyles[0].rules : {}
state.style = { ...style }
},
{
deep: true
}
)
}
export const updateGlobalStyleStr = (styleStr: string) => {
const { updateSchema } = useCanvas()
updateSchema({ css: styleStr })
state.schemaUpdateKey++
}
const updateGlobalStyle = (newSelector: string) => {
let currentSelector = getCurrentClassSelector()
const mouseState = state.className.mouseState
if (newSelector) {
currentSelector = newSelector
if (mouseState) {
currentSelector += `:${mouseState}`
}
}
state.styleObject[currentSelector] = {
...(state.styleObject[currentSelector] || {}),
rules: { ...state.style }
}
if (!Object.keys(state.style).length) {
delete state.styleObject[currentSelector]
}
const styleStr = formatString(stringify(state.cssParseList, state.styleObject), 'css')
updateGlobalStyleStr(styleStr)
}
const updateStyle = (properties: any) => {
const { canvasApi, getSchema: getCanvasPageSchema } = useCanvas()
const { getSchema } = useProps()
const { addHistory } = useHistory()
const { updateRect } = canvasApi.value
const schema = getSchema() || getCanvasPageSchema()
const pageOptions = getOptions('engine.plugins.appmanage')
const materialsOptions = getOptions('engine.plugins.materials')
const baseClassGroup = [
`.${pageOptions.pageBaseStyle.className}`,
`.${materialsOptions.blockBaseStyle.className}`,
`.${materialsOptions.componentBaseStyle.className}`
]
schema.props = schema.props || {}
if (properties) {
Object.entries(properties).forEach(([key, value]) => {
state.style[key] = value
})
}
const currentSelector = getCurrentClassSelector()
let randomClassName = ''
const classNames = schema.props.className || ''
if ((!currentSelector || baseClassGroup.includes(currentSelector)) && typeof classNames === 'string') {
randomClassName = genRandomClassNames(schema?.componentName || 'component')
let newClassNames = randomClassName.slice(1)
if (classNames) {
newClassNames = `${classNames} ${newClassNames}`
}
schema.props.className = newClassNames
state.className.classNameList = randomClassName
}
updateGlobalStyle(randomClassName)
addHistory()
updateRect()
}
export default () => {
return {
state,
updateStyle,
initStylePanelWatch
}
}
const getTextOfValue = (value: string) => {
const basicValueMap = {
auto: 'auto',
none: 'none'
}
if (basicValueMap[value] || /^\d+(\.\d+)?%$/.test(value)) {
return value
}
return String(Number.parseInt(value) || '')
}
* 根据 style 对象生成样式属性对象 properties
* styleName: {
* name, // 属性名
* text, // 界面显示的值
* value, // 属性原始值
* setting // 属性是否已设置值
* }
*/
export const useProperties = ({ names, parseNumber }) => {
const properties = computed(() => {
const newProperties = {}
if (Array.isArray(names) && state.style) {
names.forEach((name) => {
const value = state.style[name]
let text = value || ''
if (parseNumber) {
text = getTextOfValue(value)
}
newProperties[name] = {
name,
text,
value,
setting: Boolean(value)
}
})
}
return newProperties
})
const getProperty = (styleName: string) => properties.value[styleName]
const getSettingFlag = (styleName: string) => Boolean(properties.value[styleName]?.setting)
const getPropertyText = (styleName: string) => properties.value[styleName]?.text
const getPropertyValue = (styleName: string) => properties.value[styleName]?.value
return {
properties,
getProperty,
getSettingFlag,
getPropertyText,
getPropertyValue
}
}