Skip to content

Commit

Permalink
Add emptyValue property to StyleManager stack type. Closes #5583
Browse files Browse the repository at this point in the history
  • Loading branch information
artf committed Feb 14, 2024
1 parent 11c2e1b commit 54bfd0f
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 43 deletions.
35 changes: 21 additions & 14 deletions src/style_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ import StyleableModel, { StyleProps } from '../domain_abstract/model/StyleableMo
import { CustomPropertyView } from './view/PropertyView';
import { PropertySelectProps } from './model/PropertySelect';
import { PropertyNumberProps } from './model/PropertyNumber';
import { PropertyStackProps } from './model/PropertyStack';
import PropertyStack, { PropertyStackProps } from './model/PropertyStack';
import PropertyComposite from './model/PropertyComposite';

export type PropertyTypes = PropertyStackProps | PropertySelectProps | PropertyNumberProps;

Expand Down Expand Up @@ -785,27 +786,29 @@ export default class StyleManager extends ItemManagerModule<
});
}

__upProp(prop: any, style: StyleProps, parentStyles: any[], opts: any) {
__upProp(prop: Property, style: StyleProps, parentStyles: any[], opts: any) {
const name = prop.getName();
const value = style[name];
const hasVal = propDef(value);
const isStack = prop.getType() === 'stack';
const isComposite = prop.getType() === 'composite';
const opt = { ...opts, __up: true };
const canUpdate = !isComposite && !isStack;
let newLayers = isStack ? prop.__getLayersFromStyle(style) : [];
let newProps = isComposite ? prop.__getPropsFromStyle(style) : {};
const propStack = prop as PropertyStack;
const propComp = prop as PropertyComposite;
let newLayers = isStack ? propStack.__getLayersFromStyle(style) : [];
let newProps = isComposite ? propComp.__getPropsFromStyle(style) : {};
let newValue = hasVal ? value : null;
let parentTarget: any = null;

if ((isStack && newLayers === null) || (isComposite && newProps === null)) {
const method = isStack ? '__getLayersFromStyle' : '__getPropsFromStyle';
const parentItem = parentStyles.filter(p => prop[method](p.style) !== null)[0];
const parentItem = parentStyles.filter(p => propStack[method](p.style) !== null)[0];

if (parentItem) {
newValue = parentItem.style[name];
parentTarget = parentItem.target;
const val = prop[method](parentItem.style);
const val = propStack[method](parentItem.style);
if (isStack) {
newLayers = val;
} else {
Expand All @@ -823,22 +826,26 @@ export default class StyleManager extends ItemManagerModule<
}

prop.__setParentTarget(parentTarget);
canUpdate && prop.__getFullValue() !== newValue && prop.upValue(newValue, opt);
isStack && prop.__setLayers(newLayers || []);
canUpdate && prop.__getFullValue() !== newValue && prop.upValue(newValue as string, opt);
if (isStack) {
propStack.__setLayers(newLayers || [], {
isEmptyValue: propStack.isEmptyValueStyle(style),
});
}
if (isComposite) {
const props = prop.getProperties();
const props = propComp.getProperties();

// Detached has to be treathed as separate properties
if (prop.isDetached()) {
const newStyle = prop.__getPropsFromStyle(style, { byName: true }) || {};
if (propComp.isDetached()) {
const newStyle = propComp.__getPropsFromStyle(style, { byName: true }) || {};
const newParentStyles = parentStyles.map(p => ({
...p,
style: prop.__getPropsFromStyle(p.style, { byName: true }) || {},
style: propComp.__getPropsFromStyle(p.style, { byName: true }) || {},
}));
props.map((pr: any) => this.__upProp(pr, newStyle, newParentStyles, opts));
} else {
prop.__setProperties(newProps || {}, opt);
prop.getProperties().map((pr: any) => pr.__setParentTarget(parentTarget));
propComp.__setProperties(newProps || {}, opt);
propComp.getProperties().map(pr => pr.__setParentTarget(parentTarget));
}
}
}
Expand Down
104 changes: 81 additions & 23 deletions src/style_manager/model/PropertyStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type FromStyleDataStack = Omit<FromStyleData, 'property' | 'separator'> & {
separatorLayers: RegExp;
};

export type OptionStyleStack = OptionsStyle & { number?: { min?: number; max?: number } };
export type OptionStyleStack = OptionsStyle & { number?: { min?: number; max?: number }; __clear?: boolean };

/** @private */
export interface PropertyStackProps extends Omit<PropertyCompositeProps, 'toStyle' | 'fromStyle'> {
Expand All @@ -50,13 +50,28 @@ export interface PropertyStackProps extends Omit<PropertyCompositeProps, 'toStyl
* Custom logic for creating layer labels.
*/
layerLabel?: (layer: Layer, data: { index: number; values: LayerValues; property: PropertyStack }) => string;

/**
* Empty value to apply when all layers are removed.
* @default 'unset'
* @example
* // use simple string
* emptyValue: 'inherit',
* // or a function for a custom style object
* emptyValue: () => ({
* color: 'unset',
* width: 'auto'
* }),
*/
emptyValue?: string | ((data: { property: PropertyStack }) => PropValues);

toStyle?: (values: PropValues, data: ToStyleDataStack) => ReturnType<ToStyle>;
fromStyle?: (style: StyleProps, data: FromStyleDataStack) => ReturnType<FromStyle>;
parseLayer?: (data: { value: string; values: PropValues }) => PropValues;
emptyValue?: string | ((data: { property: PropertyStack }) => PropValues);
selectedLayer?: Layer;
prepend?: boolean;
__layers?: PropValues[];
isEmptyValue?: boolean;
}

/**
Expand All @@ -76,6 +91,17 @@ export interface PropertyStackProps extends Omit<PropertyCompositeProps, 'toStyl
* return `A: ${values['prop-a']} B: ${values['prop-b']}`;
* }
* ```
* @property {String|Function} [emptyValue='unset'] Empty value to apply when all layers are removed.
* \n
* ```js
* // use simple string
* emptyValue: 'inherit',
* // or a function for a custom style object
* emptyValue: () => ({
* color: 'unset',
* width: 'auto'
* }),
* ```
*
*/
export default class PropertyStack extends PropertyComposite<PropertyStackProps> {
Expand Down Expand Up @@ -109,16 +135,24 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
PropertyComposite.callInit(this, props, opts);
}

get layers() {
return this.get('layers') as unknown as Layers;
}

/**
* Get all available layers.
* @returns {Array<[Layer]>}
*/
getLayers() {
return this.__getLayers().models;
return this.layers.models;
}

__getLayers() {
return this.get('layers') as unknown as Layers;
/**
* Check if the property has layers.
* @returns {Boolean}
*/
hasLayers() {
return this.getLayers().length > 0;
}

/**
Expand All @@ -133,7 +167,7 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
* const layerLast = property.getLayer(layers.length - 1);
*/
getLayer(index = 0): Layer | undefined {
return this.__getLayers().at(index) || undefined;
return this.layers.at(index) || undefined;
}

/**
Expand Down Expand Up @@ -177,11 +211,12 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
* property.moveLayer(layer, 0);
*/
moveLayer(layer: Layer, index = 0) {
const { layers } = this;
const currIndex = layer ? layer.getIndex() : -1;

if (currIndex >= 0 && isNumber(index) && index >= 0 && index < this.getLayers().length && currIndex !== index) {
if (currIndex >= 0 && isNumber(index) && index >= 0 && index < layers.length && currIndex !== index) {
this.removeLayer(layer);
this.__getLayers().add(layer, { at: index });
layers.add(layer, { at: index });
}
}

Expand All @@ -202,7 +237,7 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
const value = props[key];
values[key] = isUndefined(value) ? prop.getDefaultValue() : value;
});
const layer = this.__getLayers().push({ values } as any, opts);
const layer = this.layers.push({ values } as any, opts);

return layer;
}
Expand All @@ -216,7 +251,7 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
* property.removeLayer(layer);
*/
removeLayer(layer: Layer) {
return this.__getLayers().remove(layer);
return this.layers.remove(layer);
}

/**
Expand Down Expand Up @@ -344,6 +379,14 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
return isString(sep) ? new RegExp(`${sep}(?![^\\(]*\\))`) : sep;
}

/**
* Check if the property is with an empty value.
* @returns {Boolean}
*/
hasEmptyValue() {
return !this.hasLayers() && !!this.attributes.isEmptyValue;
}

__upProperties(prop: Property, opts: any = {}) {
const layer = this.getSelectedLayer();
if (!layer) return;
Expand All @@ -362,7 +405,7 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
}

__upTargetsStyleProps(opts = {}) {
this.__upTargetsStyle(this.getStyleFromLayers(), opts);
this.__upTargetsStyle(this.getStyleFromLayers(opts), opts);
}

__upTargetsStyle(style: StyleProps, opts: any) {
Expand Down Expand Up @@ -394,16 +437,17 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
return this;
}

__setLayers(newLayers: PropValues[] = []) {
const layers = this.__getLayers();
__setLayers(newLayers: LayerValues[] = [], opts: { isEmptyValue?: boolean } = {}) {
const { layers } = this;
const layersNew = newLayers.map(values => ({ values }));

if (layers.length === layersNew.length) {
layersNew.map((layer, n) => layers.at(n)?.upValues(layer.values));
} else {
this.__getLayers().reset(layersNew);
layers.reset(layersNew);
}

this.set({ isEmptyValue: !!opts.isEmptyValue });
this.__upSelected({ noEvent: true });
}

Expand Down Expand Up @@ -431,13 +475,14 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
}, {} as PropValues);
}

__getLayersFromStyle(style: StyleProps = {}) {
__getLayersFromStyle(style: StyleProps = {}): LayerValues[] | null {
if (!this.__styleHasProps(style)) return null;
if (this.isEmptyValueStyle(style)) return [];

const name = this.getName();
const props = this.getProperties();
const sep = this.getLayerSeparator();
const fromStyle = this.get('fromStyle');
const { fromStyle } = this.attributes;
let result = fromStyle ? fromStyle(style, { property: this, name, separatorLayers: sep }) : [];

if (!fromStyle) {
Expand Down Expand Up @@ -473,7 +518,6 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>

getStyleFromLayers(opts: OptionStyleStack = {}) {
let result: StyleProps = {};
const { emptyValue } = this.attributes;
const name = this.getName();
const layers = this.getLayers();
const props = this.getProperties();
Expand Down Expand Up @@ -508,14 +552,20 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>

return {
...result,
...this.getEmptyValueStyle(),
...(opts.__clear ? {} : this.getEmptyValueStyle()),
};
}

getEmptyValueStyle() {
isEmptyValueStyle(style: StyleProps = {}) {
const emptyStyle = this.getEmptyValueStyle({ force: true });
const props = keys(emptyStyle);
return !!props.length && props.every(prop => emptyStyle[prop] === style[prop]);
}

getEmptyValueStyle(opts: { force?: boolean } = {}) {
const { emptyValue } = this.attributes;

if (emptyValue && !this.getLayers().length) {
if (emptyValue && (!this.hasLayers() || opts.force)) {
const name = this.getName();
const props = this.getProperties();
const result = isString(emptyValue) ? emptyValue : emptyValue({ property: this });
Expand Down Expand Up @@ -561,21 +611,29 @@ export default class PropertyStack extends PropertyComposite<PropertyStackProps>
hasValue(opts: { noParent?: boolean } = {}) {
const { noParent } = opts;
const parentValue = noParent && this.getParentTarget();
return this.getLayers().length > 0 && !parentValue;
return (this.hasLayers() || this.hasEmptyValue()) && !parentValue;
}

/**
* Extended
* @private
*/
clear(opts = {}) {
this.__getLayers().reset();
this.__upTargetsStyleProps(opts);
this.layers.reset();
this.__upTargetsStyleProps({ ...opts, __clear: true });
PropertyBase.prototype.clear.call(this);
return this;
}

__canClearProp() {
return false;
}

/**
* @deprecated
* @private
*/
__getLayers() {
return this.layers;
}
}
5 changes: 3 additions & 2 deletions src/style_manager/view/PropertyStackView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export default class PropertyStackView extends PropertyCompositeView {

init() {
const { model } = this;
this.listenTo(model.__getLayers(), 'change reset', this.updateStatus);
this.listenTo(model.layers, 'change reset', this.updateStatus);
this.listenTo(model, 'change:isEmptyValue', this.updateStatus);
}

addLayer() {
Expand Down Expand Up @@ -75,7 +76,7 @@ export default class PropertyStackView extends PropertyCompositeView {
propsView.render();

const layersView = new LayersView({
collection: model.__getLayers(),
collection: model.layers,
// @ts-ignore
config,
propertyView: this,
Expand Down
10 changes: 6 additions & 4 deletions test/specs/style_manager/model/Properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,12 +737,14 @@ describe('StyleManager properties logic', () => {

describe('emptyValue', () => {
test('Removing all layers with empty value as string', () => {
compTypeProp.set('emptyValue', 'unset'), compTypeProp.removeLayerAt(1);
compTypeProp.set('emptyValue', 'unset');
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
const res = { [propTest]: 'unset' };
expect(compTypeProp.isEmptyValueStyle(res)).toBe(true);
expect(compTypeProp.isEmptyValueStyle({})).toBe(false);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({
[propTest]: 'unset',
});
expect(rule1.getStyle()).toEqual(res);
});

test('Removing all layers with empty value as string (detached)', () => {
Expand Down

0 comments on commit 54bfd0f

Please sign in to comment.