import React from "react"; import path from "path"; import { observable, computed, makeObservable } from "mobx"; import { isValid, strToColor16 } from "eez-studio-shared/color"; import { ClassInfo, PropertyInfo, registerClass, IEezObject, EezObject, InheritedValue, PropertyType, getProperty, MessageType, PropertyProps, getParent, IMessage, getKey } from "project-editor/core/object"; import { getChildOfObject, isAnyPropertyModified, Message, propertyInvalidValueMessage, propertyNotFoundMessage, updateObject, propertyNotSetMessage, createObject, isEezObjectArray, getAncestorOfType, getClassInfo } from "project-editor/store"; import { isDashboardProject, isNotDashboardProject, isV1Project, isV3OrNewerProject } from "project-editor/project/project-type-traits"; import { getProjectStore } from "project-editor/store"; import { validators } from "eez-studio-shared/validation"; import { showGenericDialog } from "eez-studio-ui/generic-dialog"; import { getAllProjectsWithThemes, getThemedColor, ITheme } from "project-editor/features/style/theme"; import type { Font } from "project-editor/features/font/font"; import { Project, findFont, findStyle, findBitmap } from "project-editor/project/project"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { MenuItem } from "@electron/remote"; import type { ProjectEditorFeature } from "project-editor/store/features"; import { makeExpressionProperty, type Widget } from "project-editor/flow/component"; import { checkExpression } from "project-editor/flow/expression"; import type { IFlowContext } from "project-editor/flow/flow-interfaces"; //////////////////////////////////////////////////////////////////////////////// const propertyMenu = (props: PropertyProps): Electron.MenuItem[] => { let menuItems: Electron.MenuItem[] = []; if (isDashboardProject(props.objects[0])) { const cssAttributeName = props.propertyInfo.cssAttributeName; if (cssAttributeName) { menuItems.push( new MenuItem({ label: "Help", click: () => { openCssHelpPage(cssAttributeName); } }) ); } } return menuItems; }; const backgroundColorPropertyMenu = ( props: PropertyProps ): Electron.MenuItem[] => { let menuItems: Electron.MenuItem[] = []; menuItems.push( new MenuItem({ label: "Transparent", click: () => { props.objects.forEach(object => updateObject(object, { [props.propertyInfo.name]: "transparent" }) ); } }) ); if (isDashboardProject(props.objects[0])) { menuItems.push( new MenuItem({ label: "Help", click: () => { openCssHelpPage("background-color"); } }) ); } return menuItems; }; //////////////////////////////////////////////////////////////////////////////// const conditionalStyleConditionProperty = makeExpressionProperty( { name: "condition", type: PropertyType.MultilineText }, "boolean" ); export class ConditionalStyle extends EezObject { style: string; condition: string; static classInfo: ClassInfo = { properties: [ { name: "style", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "allStyles" }, conditionalStyleConditionProperty ], check: ( conditionalStyleItem: ConditionalStyle, messages: IMessage[] ) => { if ( conditionalStyleItem.style && !findStyle( ProjectEditor.getProject(conditionalStyleItem), conditionalStyleItem.style ) ) { messages.push( propertyNotFoundMessage(conditionalStyleItem, "style") ); } try { checkExpression( getAncestorOfType( conditionalStyleItem, ProjectEditor.WidgetClass.classInfo )!, conditionalStyleItem.condition ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid condition: ${err}`, getChildOfObject(conditionalStyleItem, "condition") ) ); } }, defaultValue: {}, listLabel: (conditionalStyleItem: ConditionalStyle, collapsed) => !collapsed ? ( "" ) : ( <> {conditionalStyleItem.style}: {conditionalStyleItem.condition} ) }; override makeEditable() { super.makeEditable(); makeObservable(this, { style: observable, condition: observable }); } } //////////////////////////////////////////////////////////////////////////////// const idProperty: PropertyInfo = { name: "id", type: PropertyType.Number, isOptional: true, unique: true, inheritable: false, disabled: style => isWidgetParentOfStyle(style) || isDashboardProject(style), defaultValue: undefined }; function styleNameUnique( parent: IEezObject, oldIdentifier: string | undefined ) { return (object: any, ruleName: string) => { const newIdentifer = object[ruleName]; if (oldIdentifier != undefined && newIdentifer == oldIdentifier) { return null; } if ( findStyle(ProjectEditor.getProject(parent), newIdentifer) == undefined ) { return null; } return "Not an unique name"; }; } const nameProperty: PropertyInfo = { name: "name", type: PropertyType.String, unique: ( style: Style, parent: Style | Project, propertyInfo?: PropertyInfo ) => { const oldIdentifier = propertyInfo ? getProperty(style, propertyInfo.name) : undefined; return styleNameUnique(parent, oldIdentifier); }, disabled: isWidgetParentOfStyle }; const descriptionProperty: PropertyInfo = { name: "description", type: PropertyType.MultilineText, disabled: isWidgetParentOfStyle }; const useStyleProperty: PropertyInfo = { name: "useStyle", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "allStyles", propertyMenu: (props: PropertyProps): Electron.MenuItem[] => { const projectStore = getProjectStore(props.objects[0]); let menuItems: Electron.MenuItem[] = []; if (isAnyPropertyModified(props)) { menuItems.push( new MenuItem({ label: "Reset All Modifications", click: () => { const propertyValues: any = {}; properties.forEach(propertyInfo => { if (propertyInfo.inheritable) { propertyValues[propertyInfo.name] = undefined; } }); props.updateObject(propertyValues); } }) ); if (props.objects.length === 1) { const object = props.objects[0] as Style; menuItems.push( new MenuItem({ label: "Create New Style", click: () => { return showGenericDialog({ dialogDefinition: { title: "New Style", fields: [ { name: "name", type: "string", validators: [ validators.required, validators.unique( {}, projectStore.project .allStyles ) ] } ] }, values: {} }).then(result => { projectStore.undoManager.setCombineCommands( true ); const stylePropertyValues: any = {}; const objectPropertyValues: any = {}; properties.forEach(propertyInfo => { stylePropertyValues[propertyInfo.name] = getProperty(object, propertyInfo.name); objectPropertyValues[propertyInfo.name] = undefined; }); stylePropertyValues.name = result.values.name; objectPropertyValues.useStyle = result.values.name; projectStore.addObject( projectStore.project.styles, createObject