mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
53 Commits
d474c2a47e
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca3944a2fc | ||
![]() |
15f0210572 | ||
![]() |
4a76115684 | ||
![]() |
8bd7f69c30 | ||
![]() |
9652b1e292 | ||
![]() |
ebec6e3b9e | ||
![]() |
b40199d669 | ||
![]() |
3ef4257953 | ||
![]() |
a67bb9c9bc | ||
![]() |
2e71b55f50 | ||
![]() |
57d9c6c4d6 | ||
![]() |
6de95f0b27 | ||
![]() |
c0176c7de4 | ||
![]() |
787b584bcf | ||
![]() |
c33814f63f | ||
![]() |
0ea05689ea | ||
![]() |
f8a46fd9c1 | ||
![]() |
8de642c3d3 | ||
![]() |
a9d4e19823 | ||
![]() |
7259d58894 | ||
![]() |
41f502e841 | ||
![]() |
b54aa51847 | ||
![]() |
979fe73c19 | ||
![]() |
94f12b6c2b | ||
![]() |
e04d3d2ff0 | ||
![]() |
a215445199 | ||
![]() |
a5bc734982 | ||
![]() |
865282ccd4 | ||
![]() |
13e5c2c4cd | ||
![]() |
ef614a15e9 | ||
![]() |
92ffcc8773 | ||
![]() |
9aef64e410 | ||
![]() |
b1dd950c9c | ||
![]() |
de1272c12f | ||
![]() |
13a0d266b9 | ||
![]() |
c5d643a61c | ||
![]() |
da2364fd76 | ||
![]() |
a89fd28c10 | ||
![]() |
26534d617f | ||
![]() |
0c3d9b1e98 | ||
![]() |
05ee5046d7 | ||
![]() |
34c86457db | ||
![]() |
35de1fe1b5 | ||
![]() |
62a4595b51 | ||
![]() |
bab4cb0d23 | ||
![]() |
e872d90eac | ||
![]() |
d995ed96e2 | ||
![]() |
30d588dde4 | ||
![]() |
73af8de8e6 | ||
![]() |
ac2e589a76 | ||
![]() |
3dc7331b2f | ||
![]() |
499b1988c3 | ||
![]() |
f2cba25435 |
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String* InitialInstance::badStringProperyValue = NULL;
|
||||
gd::String* InitialInstance::badStringPropertyValue = NULL;
|
||||
|
||||
InitialInstance::InitialInstance()
|
||||
: objectName(""),
|
||||
@@ -34,6 +34,7 @@ InitialInstance::InitialInstance()
|
||||
depth(0),
|
||||
locked(false),
|
||||
sealed(false),
|
||||
keepRatio(true),
|
||||
persistentUuid(UUID::MakeUuid4()) {}
|
||||
|
||||
void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
@@ -58,6 +59,7 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
SetLayer(element.GetStringAttribute("layer"));
|
||||
SetLocked(element.GetBoolAttribute("locked", false));
|
||||
SetSealed(element.GetBoolAttribute("sealed", false));
|
||||
SetShouldKeepRatio(element.GetBoolAttribute("keepRatio", false));
|
||||
|
||||
persistentUuid = element.GetStringAttribute("persistentUuid");
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
@@ -120,6 +122,7 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
|
||||
if (IsLocked()) element.SetAttribute("locked", IsLocked());
|
||||
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
|
||||
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
|
||||
|
||||
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
|
||||
element.SetStringAttribute("persistentUuid", persistentUuid);
|
||||
@@ -188,10 +191,10 @@ double InitialInstance::GetRawDoubleProperty(const gd::String& name) const {
|
||||
|
||||
const gd::String& InitialInstance::GetRawStringProperty(
|
||||
const gd::String& name) const {
|
||||
if (!badStringProperyValue) badStringProperyValue = new gd::String("");
|
||||
if (!badStringPropertyValue) badStringPropertyValue = new gd::String("");
|
||||
|
||||
const auto& it = stringProperties.find(name);
|
||||
return it != stringProperties.end() ? it->second : *badStringProperyValue;
|
||||
return it != stringProperties.end() ? it->second : *badStringPropertyValue;
|
||||
}
|
||||
|
||||
void InitialInstance::SetRawDoubleProperty(const gd::String& name,
|
||||
|
@@ -206,6 +206,17 @@ class GD_CORE_API InitialInstance {
|
||||
*/
|
||||
void SetSealed(bool enable = true) { sealed = enable; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the dimensions (width, height and depth) should keep
|
||||
* the same ratio.
|
||||
*/
|
||||
bool ShouldKeepRatio() const { return keepRatio; };
|
||||
|
||||
/**
|
||||
* \brief Define if instance's dimensions should keep the same ratio.
|
||||
*/
|
||||
void SetShouldKeepRatio(bool enable = true) { keepRatio = enable; }
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Variable management
|
||||
@@ -340,11 +351,13 @@ class GD_CORE_API InitialInstance {
|
||||
gd::VariablesContainer initialVariables; ///< Instance specific variables
|
||||
bool locked; ///< True if the instance is locked
|
||||
bool sealed; ///< True if the instance is sealed
|
||||
bool keepRatio; ///< True if the instance's dimensions
|
||||
/// should keep the same ratio.
|
||||
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
|
||||
///< useful for hot reloading.
|
||||
/// useful for hot reloading.
|
||||
|
||||
static gd::String*
|
||||
badStringProperyValue; ///< Empty string returned by GetRawStringProperty
|
||||
badStringPropertyValue; ///< Empty string returned by GetRawStringProperty
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -3134,14 +3134,51 @@ module.exports = {
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
const minScaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(minScaleRatio)) {
|
||||
this._defaultWidth = modelWidth;
|
||||
this._defaultHeight = modelHeight;
|
||||
this._defaultDepth = modelDepth;
|
||||
} else {
|
||||
if (widthRatio === minScaleRatio) {
|
||||
this._defaultWidth = originalWidth;
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else if (heightRatio === minScaleRatio) {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
|
||||
this._defaultWidth = scaleRatio * modelWidth;
|
||||
this._defaultHeight = scaleRatio * modelHeight;
|
||||
this._defaultDepth = scaleRatio * modelDepth;
|
||||
this._defaultHeight = originalHeight;
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = originalDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
13
Extensions/JsExtensionTypes.d.ts
vendored
13
Extensions/JsExtensionTypes.d.ts
vendored
@@ -111,6 +111,19 @@ class Rendered3DInstance {
|
||||
*/
|
||||
static toRad(angleInDegrees: number): number;
|
||||
|
||||
/**
|
||||
* Applies ratio to value without intermediary value to avoid precision issues.
|
||||
*/
|
||||
static applyRatio({
|
||||
oldReferenceValue,
|
||||
newReferenceValue,
|
||||
valueToApplyTo,
|
||||
}: {
|
||||
oldReferenceValue: number;
|
||||
newReferenceValue: number;
|
||||
valueToApplyTo: number;
|
||||
}): number;
|
||||
|
||||
/**
|
||||
* Called when the scene editor is rendered.
|
||||
*/
|
||||
|
@@ -1175,6 +1175,8 @@ interface InitialInstance {
|
||||
void SetLocked(boolean lock);
|
||||
boolean IsSealed();
|
||||
void SetSealed(boolean seal);
|
||||
boolean ShouldKeepRatio();
|
||||
void SetShouldKeepRatio(boolean keepRatio);
|
||||
long GetZOrder();
|
||||
void SetZOrder(long zOrder);
|
||||
[Const, Ref] DOMString GetLayer();
|
||||
@@ -1855,7 +1857,7 @@ interface BehaviorMetadata {
|
||||
|
||||
[Ref] Behavior Get();
|
||||
BehaviorsSharedData GetSharedDataInstance();
|
||||
|
||||
|
||||
[Value] MapStringPropertyDescriptor GetProperties();
|
||||
[Value] MapStringPropertyDescriptor GetSharedProperties();
|
||||
};
|
||||
|
2
GDevelop.js/types.d.ts
vendored
2
GDevelop.js/types.d.ts
vendored
@@ -1025,6 +1025,8 @@ export class InitialInstance extends EmscriptenObject {
|
||||
setLocked(lock: boolean): void;
|
||||
isSealed(): boolean;
|
||||
setSealed(seal: boolean): void;
|
||||
shouldKeepRatio(): boolean;
|
||||
setShouldKeepRatio(keepRatio: boolean): void;
|
||||
getZOrder(): number;
|
||||
setZOrder(zOrder: number): void;
|
||||
getLayer(): string;
|
||||
|
@@ -19,6 +19,8 @@ declare class gdInitialInstance {
|
||||
setLocked(lock: boolean): void;
|
||||
isSealed(): boolean;
|
||||
setSealed(seal: boolean): void;
|
||||
shouldKeepRatio(): boolean;
|
||||
setShouldKeepRatio(keepRatio: boolean): void;
|
||||
getZOrder(): number;
|
||||
setZOrder(zOrder: number): void;
|
||||
getLayer(): string;
|
||||
|
@@ -356,6 +356,55 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#DDD1FF",
|
||||
"comment": "Palette/Purple/10"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#C9B6FC",
|
||||
"comment": "Palette/Purple/20"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "#32323B",
|
||||
"comment": "Palette/Grey/80"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -0,0 +1,440 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import { type Schema, type Instance } from '.';
|
||||
import { type ResourceKind } from '../ResourcesList/ResourceSource';
|
||||
import { type Field } from '.';
|
||||
import MeasurementUnitDocumentation from '../PropertiesEditor/MeasurementUnitDocumentation';
|
||||
|
||||
const createField = (
|
||||
name: string,
|
||||
property: gdPropertyDescriptor,
|
||||
getProperties: (instance: Instance) => any,
|
||||
onUpdateProperty: (
|
||||
instance: Instance,
|
||||
propertyName: string,
|
||||
newValue: string
|
||||
) => void,
|
||||
object: ?gdObject
|
||||
): ?Field => {
|
||||
const propertyDescription = property.getDescription();
|
||||
const getLabel = (instance: Instance) => {
|
||||
const propertyName = getProperties(instance)
|
||||
.get(name)
|
||||
.getLabel();
|
||||
if (propertyName) return propertyName;
|
||||
return (
|
||||
name.charAt(0).toUpperCase() +
|
||||
name
|
||||
.slice(1)
|
||||
.split(/(?=[A-Z])/)
|
||||
.join(' ')
|
||||
);
|
||||
};
|
||||
const getDescription = () => propertyDescription;
|
||||
const getEndAdornment = (instance: Instance) => {
|
||||
const property = getProperties(instance).get(name);
|
||||
const measurementUnit = property.getMeasurementUnit();
|
||||
return {
|
||||
label: getMeasurementUnitShortLabel(measurementUnit),
|
||||
tooltipContent: (
|
||||
<MeasurementUnitDocumentation
|
||||
label={measurementUnit.getLabel()}
|
||||
description={measurementUnit.getDescription()}
|
||||
elementsWithWords={measurementUnit.getElementsWithWords()}
|
||||
/>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const valueType = property.getType().toLowerCase();
|
||||
if (valueType === 'number') {
|
||||
return {
|
||||
name,
|
||||
valueType,
|
||||
getValue: (instance: Instance): number => {
|
||||
return (
|
||||
parseFloat(
|
||||
getProperties(instance)
|
||||
.get(name)
|
||||
.getValue()
|
||||
) || 0
|
||||
); // Consider a missing value as 0 to avoid propagating NaN.
|
||||
},
|
||||
setValue: (instance: Instance, newValue: number) => {
|
||||
onUpdateProperty(instance, name, '' + newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
getEndAdornment,
|
||||
};
|
||||
} else if (valueType === 'string' || valueType === '') {
|
||||
return {
|
||||
name,
|
||||
valueType: 'string',
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'boolean') {
|
||||
return {
|
||||
name,
|
||||
valueType,
|
||||
getValue: (instance: Instance): boolean => {
|
||||
return (
|
||||
getProperties(instance)
|
||||
.get(name)
|
||||
.getValue() === 'true'
|
||||
);
|
||||
},
|
||||
setValue: (instance: Instance, newValue: boolean) => {
|
||||
onUpdateProperty(instance, name, newValue ? '1' : '0');
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'choice') {
|
||||
// Choice is a "string" (with a selector for the user in the UI)
|
||||
const choices = property
|
||||
.getExtraInfo()
|
||||
.toJSArray()
|
||||
.map(value => ({ value, label: value }));
|
||||
return {
|
||||
name,
|
||||
valueType: 'string',
|
||||
getChoices: () => choices,
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'behavior') {
|
||||
const behaviorType =
|
||||
property.getExtraInfo().size() > 0 ? property.getExtraInfo().at(0) : '';
|
||||
return {
|
||||
name,
|
||||
valueType: 'string',
|
||||
getChoices: () => {
|
||||
return !object || behaviorType === ''
|
||||
? []
|
||||
: object
|
||||
.getAllBehaviorNames()
|
||||
.toJSArray()
|
||||
.map(name =>
|
||||
object.getBehavior(name).getTypeName() === behaviorType
|
||||
? name
|
||||
: null
|
||||
)
|
||||
.filter(Boolean)
|
||||
.map(value => ({ value, label: value }));
|
||||
},
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'resource') {
|
||||
// Resource is a "string" (with a selector in the UI)
|
||||
const extraInfos = property.getExtraInfo().toJSArray();
|
||||
// $FlowFixMe - assume the passed resource kind is always valid.
|
||||
const kind: ResourceKind = extraInfos[0] || '';
|
||||
// $FlowFixMe - assume the passed resource kind is always valid.
|
||||
const fallbackKind: ResourceKind = extraInfos[1] || '';
|
||||
return {
|
||||
name,
|
||||
valueType: 'resource',
|
||||
resourceKind: kind,
|
||||
fallbackResourceKind: fallbackKind,
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'color') {
|
||||
return {
|
||||
name,
|
||||
valueType: 'color',
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else if (valueType === 'textarea') {
|
||||
return {
|
||||
name,
|
||||
valueType: 'textarea',
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
.getValue();
|
||||
},
|
||||
setValue: (instance: Instance, newValue: string) => {
|
||||
onUpdateProperty(instance, name, newValue);
|
||||
},
|
||||
getLabel,
|
||||
getDescription,
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
`A property with type=${valueType} could not be mapped to a field. Ensure that this type is correct and understood by the IDE.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const propertyKeywordCouples: Array<Array<string>> = [
|
||||
['X', 'Y', 'Z'],
|
||||
['Width', 'Height', 'Depth'],
|
||||
['Top', 'Bottom'],
|
||||
['Left', 'Right'],
|
||||
['Front', 'Back'],
|
||||
['Up', 'Down'],
|
||||
['Min', 'Max'],
|
||||
['Low', 'High'],
|
||||
['Color', 'Opacity'],
|
||||
['Horizontal', 'Vertical'],
|
||||
['Acceleration', 'Deceleration'],
|
||||
['Duration', 'Easing'],
|
||||
['EffectName', 'EffectProperty'],
|
||||
['Gravity', 'MaxFallingSpeed'],
|
||||
['JumpSpeed', 'JumpSustainTime'],
|
||||
['XGrabTolerance', 'YGrabOffset'],
|
||||
['MaxSpeed', 'SlopeMaxAngle'],
|
||||
];
|
||||
|
||||
const uncapitalize = str => {
|
||||
if (!str) return str;
|
||||
return str[0].toLowerCase() + str.substr(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true when the property exists and should be displayed.
|
||||
*
|
||||
* @param properties The properties
|
||||
* @param name The property name
|
||||
* @param visibility `true` when only deprecated properties must be displayed
|
||||
* and `false` when only not deprecated ones must be displayed
|
||||
*/
|
||||
const isPropertyVisible = (
|
||||
properties: gdMapStringPropertyDescriptor,
|
||||
name: string,
|
||||
visibility: 'All' | 'Basic' | 'Advanced' | 'Deprecated'
|
||||
): boolean => {
|
||||
if (!properties.has(name)) {
|
||||
return false;
|
||||
}
|
||||
const property = properties.get(name);
|
||||
if (property.isHidden()) {
|
||||
return false;
|
||||
}
|
||||
if (visibility === 'All') {
|
||||
return true;
|
||||
}
|
||||
if (visibility === 'Deprecated') {
|
||||
return property.isDeprecated();
|
||||
}
|
||||
if (visibility === 'Advanced') {
|
||||
return property.isAdvanced();
|
||||
}
|
||||
if (visibility === 'Basic') {
|
||||
return !property.isAdvanced() && !property.isDeprecated();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform a MapStringPropertyDescriptor to a schema that can be used
|
||||
* in CompactPropertiesEditor.
|
||||
* This method is similar to PropertiesMapToSchema.propertiesMapToSchema but returns
|
||||
* fields compatible with CompactPropertiesEditor.
|
||||
*
|
||||
* @param properties The properties to use
|
||||
* @param getProperties The function called to read again the properties
|
||||
* @param onUpdateProperty The function called to update a property of an object
|
||||
*/
|
||||
const propertiesMapToSchema = (
|
||||
properties: gdMapStringPropertyDescriptor,
|
||||
getProperties: (instance: Instance) => any,
|
||||
onUpdateProperty: (
|
||||
instance: Instance,
|
||||
propertyName: string,
|
||||
newValue: string
|
||||
) => void,
|
||||
object: ?gdObject,
|
||||
visibility: 'All' | 'Basic' | 'Advanced' | 'Deprecated' = 'All'
|
||||
): Schema => {
|
||||
const propertyNames = properties.keys();
|
||||
// Aggregate field by groups to be able to build field groups with a title.
|
||||
const fieldsByGroups = new Map<string, Array<Field>>();
|
||||
const alreadyHandledProperties = new Set<string>();
|
||||
mapFor(0, propertyNames.size(), i => {
|
||||
const name = propertyNames.at(i);
|
||||
const property = properties.get(name);
|
||||
if (!isPropertyVisible(properties, name, visibility)) {
|
||||
return null;
|
||||
}
|
||||
if (alreadyHandledProperties.has(name)) return null;
|
||||
|
||||
const groupName = property.getGroup() || '';
|
||||
let fields = fieldsByGroups.get(groupName);
|
||||
if (!fields) {
|
||||
fields = [];
|
||||
fieldsByGroups.set(groupName, fields);
|
||||
}
|
||||
|
||||
// Search a property couple that can be put in a row.
|
||||
let field: ?Field = null;
|
||||
for (const propertyKeywords of propertyKeywordCouples) {
|
||||
const rowPropertyNames: string[] = [];
|
||||
for (let index = 0; index < propertyKeywords.length; index++) {
|
||||
const keyword = propertyKeywords[index];
|
||||
|
||||
if (name.includes(keyword)) {
|
||||
const rowAllPropertyNames = propertyKeywords.map(otherKeyword =>
|
||||
name.replace(keyword, otherKeyword)
|
||||
);
|
||||
for (const rowPropertyName of rowAllPropertyNames) {
|
||||
if (isPropertyVisible(properties, rowPropertyName, visibility)) {
|
||||
rowPropertyNames.push(rowPropertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
const uncapitalizeKeyword = uncapitalize(keyword);
|
||||
if (name.startsWith(uncapitalizeKeyword)) {
|
||||
const rowAllPropertyNames = propertyKeywords.map(otherKeyword =>
|
||||
name.replace(uncapitalizeKeyword, uncapitalize(otherKeyword))
|
||||
);
|
||||
for (const rowPropertyName of rowAllPropertyNames) {
|
||||
if (isPropertyVisible(properties, rowPropertyName, visibility)) {
|
||||
rowPropertyNames.push(rowPropertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rowPropertyNames.length > 1) {
|
||||
const rowProperties = rowPropertyNames.map(name =>
|
||||
properties.get(name)
|
||||
);
|
||||
if (
|
||||
rowProperties.every(
|
||||
property => property.getGroup() === rowProperties[0].getGroup()
|
||||
)
|
||||
) {
|
||||
const rowFields: Field[] = [];
|
||||
for (
|
||||
let index = 0;
|
||||
index < rowProperties.length && index < rowPropertyNames.length;
|
||||
index++
|
||||
) {
|
||||
const rowProperty = rowProperties[index];
|
||||
const rowPropertyName = rowPropertyNames[index];
|
||||
|
||||
const field = createField(
|
||||
rowPropertyName,
|
||||
rowProperty,
|
||||
getProperties,
|
||||
onUpdateProperty,
|
||||
object
|
||||
);
|
||||
|
||||
if (field) {
|
||||
rowFields.push(field);
|
||||
}
|
||||
}
|
||||
if (rowFields.length === rowProperties.length) {
|
||||
field = {
|
||||
name: rowPropertyNames.join('-'),
|
||||
type: 'row',
|
||||
children: rowFields,
|
||||
};
|
||||
rowPropertyNames.forEach(propertyName => {
|
||||
alreadyHandledProperties.add(propertyName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!field) {
|
||||
field = createField(
|
||||
name,
|
||||
property,
|
||||
getProperties,
|
||||
onUpdateProperty,
|
||||
object
|
||||
);
|
||||
}
|
||||
if (field) {
|
||||
fields.push(field);
|
||||
}
|
||||
});
|
||||
if (fieldsByGroups.size === 0) {
|
||||
return [];
|
||||
}
|
||||
const defaultGroupField = fieldsByGroups.get('');
|
||||
if (fieldsByGroups.size === 1 && defaultGroupField) {
|
||||
// Avoid to create a blank title
|
||||
return defaultGroupField;
|
||||
}
|
||||
// Create a group for the default one too because it would look weird with the indentation.
|
||||
const groupNames = [...fieldsByGroups.keys()].sort((a, b) =>
|
||||
a.localeCompare(b)
|
||||
);
|
||||
return groupNames.map(groupName => ({
|
||||
name: groupName,
|
||||
type: 'column',
|
||||
title: groupName,
|
||||
// The group actually always exists here.
|
||||
children: fieldsByGroups.get(groupName) || [],
|
||||
}));
|
||||
};
|
||||
|
||||
const exponents = ['⁰', '¹', '²', '³', '⁴', '⁵'];
|
||||
|
||||
export const getMeasurementUnitShortLabel = (
|
||||
measurementUnit: gdMeasurementUnit
|
||||
): string => {
|
||||
return mapFor(0, measurementUnit.getElementsCount(), i => {
|
||||
const baseUnit = measurementUnit.getElementBaseUnit(i);
|
||||
const power = measurementUnit.getElementPower(i);
|
||||
const absPower = Math.abs(power);
|
||||
const showPower = power < 0 || (absPower > 1 && absPower < 6);
|
||||
return (
|
||||
baseUnit.getSymbol() +
|
||||
(power < 0 ? '⁻' : '') +
|
||||
(showPower ? exponents[absPower] : '')
|
||||
);
|
||||
}).join(' · ');
|
||||
};
|
||||
|
||||
export default propertiesMapToSchema;
|
791
newIDE/app/src/CompactPropertiesEditor/index.js
Normal file
791
newIDE/app/src/CompactPropertiesEditor/index.js
Normal file
@@ -0,0 +1,791 @@
|
||||
// @flow
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import ResourceSelectorWithThumbnail from '../ResourcesList/ResourceSelectorWithThumbnail';
|
||||
import SelectOption from '../UI/SelectOption';
|
||||
import { type MenuItemTemplate } from '../UI/Menu/Menu.flow';
|
||||
import {
|
||||
type ResourceKind,
|
||||
type ResourceManagementProps,
|
||||
} from '../ResourcesList/ResourceSource';
|
||||
import {
|
||||
ResponsiveLineStackLayout,
|
||||
ColumnStackLayout,
|
||||
LineStackLayout,
|
||||
} from '../UI/Layout';
|
||||
import CompactSelectField from '../UI/CompactSelectField';
|
||||
import CompactSemiControlledTextField from '../UI/CompactSemiControlledTextField';
|
||||
import CompactSemiControlledNumberField from '../UI/CompactSemiControlledNumberField';
|
||||
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
|
||||
import { Line, marginsSize } from '../UI/Grid';
|
||||
import Text from '../UI/Text';
|
||||
import useForceUpdate from '../Utils/UseForceUpdate';
|
||||
import Edit from '../UI/CustomSvgIcons/Edit';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import VerticallyCenterWithBar from '../UI/VerticallyCenterWithBar';
|
||||
import GDevelopThemeContext from '../UI/Theme/GDevelopThemeContext';
|
||||
import { textEllipsisStyle } from '../UI/TextEllipsis';
|
||||
|
||||
// An "instance" here is the objects for which properties are shown
|
||||
export type Instance = Object; // This could be improved using generics.
|
||||
export type Instances = Array<Instance>;
|
||||
|
||||
// "Value" fields are fields displayed in the properties.
|
||||
export type ValueFieldCommonProperties = {|
|
||||
name: string,
|
||||
getLabel?: Instance => string,
|
||||
getDescription?: Instance => string,
|
||||
getExtraDescription?: Instance => string,
|
||||
disabled?: boolean | ((instances: Array<gdInitialInstance>) => boolean),
|
||||
onEditButtonBuildMenuTemplate?: (i18n: I18nType) => Array<MenuItemTemplate>,
|
||||
onEditButtonClick?: () => void,
|
||||
|};
|
||||
|
||||
// "Primitive" value fields are "simple" fields.
|
||||
export type PrimitiveValueField =
|
||||
| {|
|
||||
valueType: 'number',
|
||||
getValue: Instance => number,
|
||||
setValue: (instance: Instance, newValue: number) => void,
|
||||
// TODO: support this attribute.
|
||||
getEndAdornment?: Instance => {|
|
||||
label: string,
|
||||
tooltipContent: React.Node,
|
||||
|},
|
||||
getEndAdornmentIcon?: () => React.Node,
|
||||
onClickEndAdornment?: Instance => void,
|
||||
renderLeftIcon?: (className?: string) => React.Node,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'string',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
getChoices?: ?() => Array<{|
|
||||
value: string,
|
||||
label: string,
|
||||
labelIsUserDefined?: boolean,
|
||||
|}>,
|
||||
getEndAdornmentIcon?: () => React.Node,
|
||||
onClickEndAdornment?: Instance => void,
|
||||
renderLeftIcon?: (className?: string) => React.Node,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'boolean',
|
||||
getValue: Instance => boolean,
|
||||
setValue: (instance: Instance, newValue: boolean) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'enumIcon',
|
||||
renderIcon: (value: any) => React.Node,
|
||||
getValue: Instance => any,
|
||||
isHighlighted: (value: any) => boolean,
|
||||
setValue: (instance: Instance, newValue: any) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'color',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'textarea',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|};
|
||||
|
||||
// "Resource" fields are showing a resource selector.
|
||||
type ResourceField = {|
|
||||
valueType: 'resource',
|
||||
resourceKind: ResourceKind,
|
||||
fallbackResourceKind?: ResourceKind,
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
renderLeftIcon?: (className?: string) => React.Node,
|
||||
...ValueFieldCommonProperties,
|
||||
|};
|
||||
|
||||
type Title = {|
|
||||
name: string,
|
||||
renderLeftIcon: (className?: string) => React.Node,
|
||||
getValue?: Instance => string,
|
||||
nonFieldType: 'title',
|
||||
defaultValue?: string,
|
||||
|};
|
||||
|
||||
type SectionTitle = {|
|
||||
name: string,
|
||||
nonFieldType: 'sectionTitle',
|
||||
getValue: typeof undefined,
|
||||
|};
|
||||
|
||||
type VerticalCenterWithBar = {|
|
||||
name: string,
|
||||
nonFieldType: 'verticalCenterWithBar',
|
||||
child: PrimitiveValueField,
|
||||
|};
|
||||
|
||||
type ActionButton = {|
|
||||
label: string,
|
||||
disabled: 'onValuesDifferent',
|
||||
getValue: Instance => string,
|
||||
nonFieldType: 'button',
|
||||
getIcon?: ({| fontSize: string |}) => React.Node,
|
||||
onClick: (instance: Instance) => void,
|
||||
|};
|
||||
|
||||
// A value field is a primitive or a resource.
|
||||
export type ValueField = PrimitiveValueField | ResourceField;
|
||||
|
||||
// A field can be a primitive, a resource or a list of fields
|
||||
export type Field =
|
||||
| PrimitiveValueField
|
||||
| ResourceField
|
||||
| SectionTitle
|
||||
| Title
|
||||
| ActionButton
|
||||
| VerticalCenterWithBar
|
||||
| {|
|
||||
name: string,
|
||||
type: 'row' | 'column',
|
||||
preventWrap?: boolean,
|
||||
title?: ?string,
|
||||
children: Array<Field>,
|
||||
|};
|
||||
|
||||
// The schema is the tree of all fields.
|
||||
export type Schema = Array<Field>;
|
||||
|
||||
type Props = {|
|
||||
onInstancesModified?: Instances => void,
|
||||
instances: Instances,
|
||||
schema: Schema,
|
||||
mode?: 'column' | 'row',
|
||||
preventWrap?: boolean,
|
||||
|
||||
// If set, render the "extra" description content from fields
|
||||
// (see getExtraDescription).
|
||||
renderExtraDescriptionText?: (extraDescription: string) => string,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
|
||||
// Optional context:
|
||||
project?: ?gdProject,
|
||||
resourceManagementProps?: ?ResourceManagementProps,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
columnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
fieldContainer: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
field: {
|
||||
flex: 1,
|
||||
width: 'auto',
|
||||
},
|
||||
subHeader: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
container: { flex: 1, minWidth: 0 },
|
||||
separator: {
|
||||
marginRight: -marginsSize,
|
||||
marginLeft: -marginsSize,
|
||||
marginTop: marginsSize,
|
||||
borderTop: '1px solid black',
|
||||
},
|
||||
};
|
||||
|
||||
export const Separator = () => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...styles.separator,
|
||||
borderColor: gdevelopTheme.listItem.separatorColor,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getDisabled = ({
|
||||
instances,
|
||||
field,
|
||||
}: {|
|
||||
instances: Instances,
|
||||
field: ValueField,
|
||||
|}): boolean => {
|
||||
return typeof field.disabled === 'boolean'
|
||||
? field.disabled
|
||||
: typeof field.disabled === 'function'
|
||||
? field.disabled(instances)
|
||||
: false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value for the given field across all instances.
|
||||
* If one of the instances doesn't share the same value, returns the default value.
|
||||
* If there is no instances, returns the default value.
|
||||
* If the field does not have a `getValue` method, returns `null`.
|
||||
*/
|
||||
const getFieldValue = ({
|
||||
instances,
|
||||
field,
|
||||
defaultValue,
|
||||
}: {|
|
||||
instances: Instances,
|
||||
field: ValueField | ActionButton | SectionTitle | Title,
|
||||
defaultValue?: any,
|
||||
|}): any => {
|
||||
if (!instances[0]) {
|
||||
console.log(
|
||||
'getFieldValue was called with an empty list of instances (or containing undefined). This is a bug that should be fixed'
|
||||
);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const { getValue } = field;
|
||||
if (!getValue) return null;
|
||||
|
||||
let value = getValue(instances[0]);
|
||||
for (var i = 1; i < instances.length; ++i) {
|
||||
if (value !== getValue(instances[i])) {
|
||||
if (typeof defaultValue !== 'undefined') value = defaultValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const getFieldLabel = ({
|
||||
instances,
|
||||
field,
|
||||
}: {|
|
||||
instances: Instances,
|
||||
field: ValueField,
|
||||
|}): any => {
|
||||
if (!instances[0]) {
|
||||
console.log(
|
||||
'CompactPropertiesEditor._getFieldLabel was called with an empty list of instances (or containing undefined). This is a bug that should be fixed'
|
||||
);
|
||||
return field.name;
|
||||
}
|
||||
|
||||
if (field.getLabel) return field.getLabel(instances[0]);
|
||||
|
||||
return field.name;
|
||||
};
|
||||
|
||||
const CompactPropertiesEditor = ({
|
||||
onInstancesModified,
|
||||
instances,
|
||||
schema,
|
||||
mode,
|
||||
renderExtraDescriptionText,
|
||||
unsavedChanges,
|
||||
project,
|
||||
resourceManagementProps,
|
||||
preventWrap,
|
||||
}: Props) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const _onInstancesModified = React.useCallback(
|
||||
(instances: Instances) => {
|
||||
// This properties editor is dealing with fields that are
|
||||
// responsible to update their state (see field.setValue).
|
||||
|
||||
if (unsavedChanges) unsavedChanges.triggerUnsavedChanges();
|
||||
if (onInstancesModified) onInstancesModified(instances);
|
||||
forceUpdate();
|
||||
},
|
||||
[unsavedChanges, onInstancesModified, forceUpdate]
|
||||
);
|
||||
|
||||
const getFieldDescription = React.useCallback(
|
||||
(field: ValueField): ?string => {
|
||||
if (!instances[0]) {
|
||||
console.log(
|
||||
'CompactPropertiesEditor._getFieldDescription was called with an empty list of instances (or containing undefined). This is a bug that should be fixed'
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const descriptions: Array<string> = [];
|
||||
if (field.getDescription)
|
||||
descriptions.push(field.getDescription(instances[0]));
|
||||
if (renderExtraDescriptionText && field.getExtraDescription)
|
||||
descriptions.push(
|
||||
renderExtraDescriptionText(field.getExtraDescription(instances[0]))
|
||||
);
|
||||
|
||||
return descriptions.join('\n') || undefined;
|
||||
},
|
||||
[instances, renderExtraDescriptionText]
|
||||
);
|
||||
|
||||
const renderInputField = React.useCallback(
|
||||
(field: ValueField) => {
|
||||
if (field.name === 'PLEASE_ALSO_SHOW_EDIT_BUTTON_THANKS') return null; // This special property was used in GDevelop 4 IDE to ask for a Edit button to be shown, ignore it.
|
||||
|
||||
if (field.valueType === 'boolean') {
|
||||
return 'TODO';
|
||||
// const { setValue } = field;
|
||||
// const description = getFieldDescription(field);
|
||||
// return (
|
||||
// <InlineCheckbox
|
||||
// label={
|
||||
// !description ? (
|
||||
// getFieldLabel({ instances, field })
|
||||
// ) : (
|
||||
// <React.Fragment>
|
||||
// <Line noMargin>{getFieldLabel({ instances, field })}</Line>
|
||||
// <FormHelperText style={{ display: 'inline' }}>
|
||||
// <MarkdownText source={description} />
|
||||
// </FormHelperText>
|
||||
// </React.Fragment>
|
||||
// )
|
||||
// }
|
||||
// key={field.name}
|
||||
// id={field.name}
|
||||
// checked={getFieldValue({ instances, field })}
|
||||
// onCheck={(event, newValue) => {
|
||||
// instances.forEach(i => setValue(i, !!newValue));
|
||||
// _onInstancesModified(instances);
|
||||
// }}
|
||||
// disabled={getDisabled({ instances, field })}
|
||||
// />
|
||||
// );
|
||||
} else if (field.valueType === 'number') {
|
||||
const { setValue, onClickEndAdornment } = field;
|
||||
// TODO: Support end adornment
|
||||
// const endAdornment = getEndAdornment && getEndAdornment(instances[0]);
|
||||
|
||||
return (
|
||||
<CompactSemiControlledNumberField
|
||||
value={getFieldValue({ instances, field })}
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
onChange={newValue => {
|
||||
// If the value is not a number, the user is probably still typing, adding a dot or a comma.
|
||||
// So don't update the value, it will be reverted if they leave the field.
|
||||
if (isNaN(newValue)) return;
|
||||
instances.forEach(i => setValue(i, newValue));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
disabled={getDisabled({ instances, field })}
|
||||
renderLeftIcon={field.renderLeftIcon}
|
||||
leftIconTooltip={getFieldLabel({ instances, field })}
|
||||
renderEndAdornmentOnHover={field.getEndAdornmentIcon}
|
||||
onClickEndAdornment={() => {
|
||||
if (!onClickEndAdornment) return;
|
||||
instances.forEach(i => onClickEndAdornment(i));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
useLeftIconAsNumberControl
|
||||
// endAdornment={
|
||||
// endAdornment && (
|
||||
// <Tooltip title={endAdornment.tooltipContent}>
|
||||
// <InputAdornment position="end">
|
||||
// {endAdornment.label}
|
||||
// </InputAdornment>
|
||||
// </Tooltip>
|
||||
// )
|
||||
// }
|
||||
/>
|
||||
);
|
||||
} else if (field.valueType === 'color') {
|
||||
return 'TODO';
|
||||
// const { setValue } = field;
|
||||
// return (
|
||||
// <Column key={field.name} expand noMargin>
|
||||
// <ColorField
|
||||
// id={field.name}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
// disableAlpha
|
||||
// fullWidth
|
||||
// color={getFieldValue({ instances, field })}
|
||||
// onChange={color => {
|
||||
// const rgbString =
|
||||
// color.length === 0 ? '' : rgbOrHexToRGBString(color);
|
||||
// instances.forEach(i => setValue(i, rgbString));
|
||||
// _onInstancesModified(instances);
|
||||
// }}
|
||||
// />
|
||||
// </Column>
|
||||
// );
|
||||
} else if (field.valueType === 'enumIcon') {
|
||||
const value = getFieldValue({ instances, field });
|
||||
return (
|
||||
<IconButton
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
size="small"
|
||||
tooltip={getFieldLabel({ instances, field })}
|
||||
selected={field.isHighlighted(value)}
|
||||
onClick={event => {
|
||||
instances.forEach(i => field.setValue(i, !value));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
>
|
||||
{field.renderIcon(value)}
|
||||
</IconButton>
|
||||
);
|
||||
} else if (field.valueType === 'textarea') {
|
||||
return 'TODO';
|
||||
// const { setValue } = field;
|
||||
// return (
|
||||
// <SemiControlledTextField
|
||||
// key={field.name}
|
||||
// id={field.name}
|
||||
// onChange={text => {
|
||||
// instances.forEach(i => setValue(i, text || ''));
|
||||
// _onInstancesModified(instances);
|
||||
// }}
|
||||
// value={getFieldValue({ instances, field })}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// floatingLabelFixed
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
// multiline
|
||||
// style={styles.field}
|
||||
// />
|
||||
// );
|
||||
} else if (field.valueType === 'resource') {
|
||||
return 'TODO';
|
||||
} else {
|
||||
const {
|
||||
// TODO: Still support onEditButtonClick & onEditButtonBuildMenuTemplate ?
|
||||
// onEditButtonBuildMenuTemplate,
|
||||
// onEditButtonClick,
|
||||
setValue,
|
||||
onClickEndAdornment,
|
||||
} = field;
|
||||
return (
|
||||
<CompactSemiControlledTextField
|
||||
value={getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: '(Multiple values)',
|
||||
})}
|
||||
id={field.name}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
onChange={newValue => {
|
||||
instances.forEach(i => setValue(i, newValue || ''));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
disabled={getDisabled({ instances, field })}
|
||||
renderLeftIcon={field.renderLeftIcon}
|
||||
leftIconTooltip={getFieldLabel({ instances, field })}
|
||||
renderEndAdornmentOnHover={field.getEndAdornmentIcon || undefined}
|
||||
onClickEndAdornment={() => {
|
||||
if (!onClickEndAdornment) return;
|
||||
instances.forEach(i => onClickEndAdornment(i));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
[instances, _onInstancesModified]
|
||||
);
|
||||
|
||||
const renderSelectField = React.useCallback(
|
||||
(field: ValueField) => {
|
||||
if (!field.getChoices || !field.getValue) return;
|
||||
|
||||
const children = field
|
||||
.getChoices()
|
||||
.map(({ value, label, labelIsUserDefined }) => (
|
||||
<SelectOption
|
||||
key={value}
|
||||
value={value}
|
||||
label={label}
|
||||
shouldNotTranslate={labelIsUserDefined}
|
||||
/>
|
||||
));
|
||||
|
||||
if (field.valueType === 'number') {
|
||||
const { setValue } = field;
|
||||
return (
|
||||
<CompactSelectField
|
||||
value={getFieldValue({ instances, field })}
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
onChange={(newValue: string) => {
|
||||
instances.forEach(i => setValue(i, parseFloat(newValue) || 0));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
disabled={field.disabled}
|
||||
>
|
||||
{children}
|
||||
</CompactSelectField>
|
||||
);
|
||||
} else if (field.valueType === 'string') {
|
||||
const { setValue } = field;
|
||||
return (
|
||||
<CompactSelectField
|
||||
value={getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: '(Multiple values)',
|
||||
})}
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
// floatingLabelText={getFieldLabel({ instances, field })}
|
||||
// helperMarkdownText={getFieldDescription(field)}
|
||||
onChange={(newValue: string) => {
|
||||
instances.forEach(i => setValue(i, newValue || ''));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
disabled={getDisabled({ instances, field })}
|
||||
renderLeftIcon={field.renderLeftIcon}
|
||||
leftIconTooltip={getFieldLabel({ instances, field })}
|
||||
>
|
||||
{children}
|
||||
</CompactSelectField>
|
||||
);
|
||||
}
|
||||
},
|
||||
[instances, _onInstancesModified]
|
||||
);
|
||||
|
||||
const renderButton = React.useCallback(
|
||||
(field: ActionButton) => {
|
||||
let disabled = false;
|
||||
if (field.disabled === 'onValuesDifferent') {
|
||||
const DIFFERENT_VALUES = 'DIFFERENT_VALUES';
|
||||
disabled =
|
||||
getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: DIFFERENT_VALUES,
|
||||
}) === DIFFERENT_VALUES;
|
||||
}
|
||||
return (
|
||||
<FlatButton
|
||||
key={`button-${field.label}`}
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={
|
||||
field.getIcon ? (
|
||||
field.getIcon({ fontSize: 'small' })
|
||||
) : (
|
||||
<Edit fontSize="small" />
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
label={field.label}
|
||||
onClick={() => {
|
||||
field.onClick(instances[0]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[instances]
|
||||
);
|
||||
|
||||
const renderResourceField = (field: ResourceField) => {
|
||||
if (!project || !resourceManagementProps) {
|
||||
console.error(
|
||||
'You tried to display a resource field in a PropertiesEditor that does not support display resources. If you need to display resources, pass additional props (project, resourceManagementProps).'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { setValue } = field;
|
||||
return (
|
||||
<ResourceSelectorWithThumbnail
|
||||
key={field.name}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
resourceKind={field.resourceKind}
|
||||
fallbackResourceKind={field.fallbackResourceKind}
|
||||
resourceName={getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: '(Multiple values)', //TODO
|
||||
})}
|
||||
onChange={newValue => {
|
||||
instances.forEach(i => setValue(i, newValue));
|
||||
_onInstancesModified(instances);
|
||||
}}
|
||||
floatingLabelText={getFieldLabel({ instances, field })}
|
||||
helperMarkdownText={getFieldDescription(field)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVerticalCenterWithBar = (field: Field) =>
|
||||
field.child && field.child.getValue ? (
|
||||
<VerticallyCenterWithBar key={field.name}>
|
||||
{renderInputField(field.child)}
|
||||
</VerticallyCenterWithBar>
|
||||
) : (
|
||||
'TODO'
|
||||
);
|
||||
|
||||
const renderContainer =
|
||||
mode === 'row'
|
||||
? (fields: React.Node) =>
|
||||
preventWrap ? (
|
||||
<LineStackLayout noMargin alignItems="center" expand>
|
||||
{fields}
|
||||
</LineStackLayout>
|
||||
) : (
|
||||
<ResponsiveLineStackLayout noMargin alignItems="center" expand>
|
||||
{fields}
|
||||
</ResponsiveLineStackLayout>
|
||||
)
|
||||
: (fields: React.Node) => (
|
||||
<ColumnStackLayout noMargin expand>
|
||||
{fields}
|
||||
</ColumnStackLayout>
|
||||
);
|
||||
|
||||
const renderTitle = React.useCallback(
|
||||
(field: Title) => {
|
||||
const { getValue, renderLeftIcon } = field;
|
||||
|
||||
let additionalText = null;
|
||||
|
||||
if (getValue) {
|
||||
let selectedInstancesValue = getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: field.defaultValue || 'Multiple Values',
|
||||
});
|
||||
if (!!selectedInstancesValue) additionalText = selectedInstancesValue;
|
||||
}
|
||||
|
||||
if (!!additionalText) {
|
||||
return (
|
||||
<LineStackLayout
|
||||
alignItems="center"
|
||||
key={`section-title-${field.name}`}
|
||||
expand
|
||||
>
|
||||
{renderLeftIcon()}
|
||||
<Text displayInlineAsSpan noMargin>
|
||||
{field.name}
|
||||
</Text>
|
||||
<Text allowSelection displayInlineAsSpan size="body2" noMargin>
|
||||
-
|
||||
</Text>
|
||||
<Text
|
||||
allowSelection
|
||||
displayInlineAsSpan
|
||||
size="body2"
|
||||
noMargin
|
||||
style={textEllipsisStyle}
|
||||
>
|
||||
{additionalText}
|
||||
</Text>
|
||||
</LineStackLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LineStackLayout key={`title-${field.name}`}>
|
||||
{renderLeftIcon()}
|
||||
<Text displayInlineAsSpan size="sub-title" noMargin>
|
||||
{field.name}
|
||||
</Text>
|
||||
</LineStackLayout>
|
||||
);
|
||||
},
|
||||
[instances]
|
||||
);
|
||||
const renderSectionTitle = React.useCallback((field: SectionTitle) => {
|
||||
return [
|
||||
<Line key={`section-title-${field.name}`}>
|
||||
<Text displayInlineAsSpan size="sub-title" noMargin>
|
||||
{field.name}
|
||||
</Text>
|
||||
</Line>,
|
||||
];
|
||||
}, []);
|
||||
|
||||
return renderContainer(
|
||||
schema.map(field => {
|
||||
if (!!field.nonFieldType) {
|
||||
if (field.nonFieldType === 'title') {
|
||||
return renderTitle(field);
|
||||
} else if (field.nonFieldType === 'sectionTitle') {
|
||||
return renderSectionTitle(field);
|
||||
} else if (field.nonFieldType === 'button') {
|
||||
return renderButton(field);
|
||||
} else if (field.nonFieldType === 'verticalCenterWithBar') {
|
||||
return renderVerticalCenterWithBar(field);
|
||||
}
|
||||
return null;
|
||||
} else if (field.children) {
|
||||
if (field.type === 'row') {
|
||||
const contentView = (
|
||||
<React.Fragment key={field.name}>
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
schema={field.children}
|
||||
instances={instances}
|
||||
mode="row"
|
||||
unsavedChanges={unsavedChanges}
|
||||
onInstancesModified={onInstancesModified}
|
||||
preventWrap={field.preventWrap}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
if (field.title) {
|
||||
return [
|
||||
<Separator key={field.name + '-separator'} />,
|
||||
<Text key={field.name + '-title'} size="sub-title" noMargin>
|
||||
{field.title}
|
||||
</Text>,
|
||||
contentView,
|
||||
];
|
||||
}
|
||||
return contentView;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.name} style={styles.container}>
|
||||
<React.Fragment key={field.name}>
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
schema={field.children}
|
||||
instances={instances}
|
||||
mode="column"
|
||||
unsavedChanges={unsavedChanges}
|
||||
onInstancesModified={onInstancesModified}
|
||||
preventWrap={field.preventWrap}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
} else if (field.valueType === 'resource') {
|
||||
return renderResourceField(field);
|
||||
} else {
|
||||
if (field.getChoices && field.getValue) return renderSelectField(field);
|
||||
if (field.getValue) return renderInputField(field);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactPropertiesEditor;
|
@@ -0,0 +1,569 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { type Schema } from '../../CompactPropertiesEditor';
|
||||
import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow';
|
||||
import enumerateLayers from '../../LayersList/EnumerateLayers';
|
||||
|
||||
import Angle from '../../UI/CustomSvgIcons/Angle';
|
||||
import Layers from '../../UI/CustomSvgIcons/Layers';
|
||||
import LetterX from '../../UI/CustomSvgIcons/LetterX';
|
||||
import LetterY from '../../UI/CustomSvgIcons/LetterY';
|
||||
import LetterH from '../../UI/CustomSvgIcons/LetterH';
|
||||
import LetterW from '../../UI/CustomSvgIcons/LetterW';
|
||||
import Depth from '../../UI/CustomSvgIcons/Depth';
|
||||
import LetterZ from '../../UI/CustomSvgIcons/LetterZ';
|
||||
import Instance from '../../UI/CustomSvgIcons/Instance';
|
||||
import Link from '../../UI/CustomSvgIcons/Link';
|
||||
import Unlink from '../../UI/CustomSvgIcons/Unlink';
|
||||
import RemoveCircle from '../../UI/CustomSvgIcons/RemoveCircle';
|
||||
import Lock from '../../UI/CustomSvgIcons/Lock';
|
||||
import LockOpen from '../../UI/CustomSvgIcons/LockOpen';
|
||||
import Restore from '../../UI/CustomSvgIcons/Restore';
|
||||
import Object3d from '../../UI/CustomSvgIcons/Object3d';
|
||||
import Object2d from '../../UI/CustomSvgIcons/Object2d';
|
||||
|
||||
/**
|
||||
* Applies ratio to value without intermediary value to avoid precision issues.
|
||||
*/
|
||||
const applyRatio = ({
|
||||
oldReferenceValue,
|
||||
newReferenceValue,
|
||||
valueToApplyTo,
|
||||
}: {|
|
||||
oldReferenceValue: number,
|
||||
newReferenceValue: number,
|
||||
valueToApplyTo: number,
|
||||
|}) => {
|
||||
return (newReferenceValue / oldReferenceValue) * valueToApplyTo;
|
||||
};
|
||||
|
||||
const getEditObjectButton = ({
|
||||
i18n,
|
||||
onEditObjectByName,
|
||||
is3DInstance,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
onEditObjectByName: (name: string) => void,
|
||||
is3DInstance: boolean,
|
||||
|}) => ({
|
||||
label: i18n._(t`Edit object`),
|
||||
disabled: 'onValuesDifferent',
|
||||
nonFieldType: 'button',
|
||||
getIcon: is3DInstance
|
||||
? props => <Object3d {...props} />
|
||||
: props => <Object2d {...props} />,
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
onClick: (instance: gdInitialInstance) =>
|
||||
onEditObjectByName(instance.getObjectName()),
|
||||
});
|
||||
|
||||
const getRotationXAndRotationYFields = ({ i18n }: {| i18n: I18nType |}) => [
|
||||
{
|
||||
name: 'RotationX',
|
||||
getLabel: () => i18n._(t`Rotation (X)`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getRotationX(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setRotationX(newValue),
|
||||
renderLeftIcon: className => <LetterX className={className} />,
|
||||
},
|
||||
{
|
||||
name: 'RotationY',
|
||||
getLabel: () => i18n._(t`Rotation (Y)`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getRotationY(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setRotationY(newValue),
|
||||
renderLeftIcon: className => <LetterY className={className} />,
|
||||
},
|
||||
];
|
||||
const getRotationZField = ({
|
||||
i18n,
|
||||
label,
|
||||
Icon,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
label: MessageDescriptor,
|
||||
Icon: React.ComponentType<any>,
|
||||
|}) => ({
|
||||
name: 'Angle',
|
||||
getLabel: () => i18n._(label),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getAngle(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setAngle(newValue),
|
||||
renderLeftIcon: className => <Icon className={className} />,
|
||||
});
|
||||
const getXAndYFields = ({ i18n }: {| i18n: I18nType |}): Schema => [
|
||||
{
|
||||
name: 'X',
|
||||
getLabel: () => i18n._(t`X`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getX(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setX(newValue),
|
||||
renderLeftIcon: className => <LetterX className={className} />,
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
getLabel: () => i18n._(t`Y`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getY(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setY(newValue),
|
||||
renderLeftIcon: className => <LetterY className={className} />,
|
||||
},
|
||||
];
|
||||
const getZField = ({ i18n }: {| i18n: I18nType |}) => ({
|
||||
name: 'Z',
|
||||
getLabel: () => i18n._(t`Z`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getZ(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setZ(newValue),
|
||||
renderLeftIcon: className => <LetterZ className={className} />,
|
||||
});
|
||||
const getLayerField = ({
|
||||
i18n,
|
||||
layout,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
layout: gdLayout,
|
||||
|}) => ({
|
||||
name: 'Layer',
|
||||
getLabel: () => i18n._(t`Layer`),
|
||||
valueType: 'string',
|
||||
getChoices: () => enumerateLayers(layout),
|
||||
getValue: (instance: gdInitialInstance) => instance.getLayer(),
|
||||
setValue: (instance: gdInitialInstance, newValue: string) =>
|
||||
instance.setLayer(newValue),
|
||||
renderLeftIcon: className => <Layers className={className} />,
|
||||
});
|
||||
const getZOrderField = ({ i18n }: {| i18n: I18nType |}) => ({
|
||||
name: 'Z Order',
|
||||
getLabel: () => i18n._(t`Z Order`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getZOrder(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setZOrder(newValue),
|
||||
renderLeftIcon: className => <LetterZ className={className} />,
|
||||
});
|
||||
|
||||
const getTitleRow = ({ i18n }: {| i18n: I18nType |}) => ({
|
||||
name: 'Title',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: [
|
||||
{
|
||||
name: i18n._(t`Instance`),
|
||||
renderLeftIcon: className => (
|
||||
<Instance className={className} fontSize="small" />
|
||||
),
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
nonFieldType: 'title',
|
||||
defaultValue: i18n._(t`Different objects`),
|
||||
},
|
||||
{
|
||||
name: 'Lock instance',
|
||||
getLabel: (instance: gdInitialInstance) =>
|
||||
instance.isSealed()
|
||||
? i18n._(t`Free instance`)
|
||||
: instance.isLocked()
|
||||
? i18n._(t`Prevent selection in the editor`)
|
||||
: i18n._(t`Lock position/angle in the editor`),
|
||||
valueType: 'enumIcon',
|
||||
renderIcon: value =>
|
||||
value === 'sealed' ? (
|
||||
<RemoveCircle fontSize="small" />
|
||||
) : value === 'locked' ? (
|
||||
<Lock fontSize="small" />
|
||||
) : (
|
||||
<LockOpen fontSize="small" />
|
||||
),
|
||||
isHighlighted: value => value === 'locked' || value === 'sealed',
|
||||
getValue: (instance: gdInitialInstance) =>
|
||||
instance.isSealed()
|
||||
? 'sealed'
|
||||
: instance.isLocked()
|
||||
? 'locked'
|
||||
: 'free',
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) => {
|
||||
if (instance.isSealed()) {
|
||||
instance.setSealed(false);
|
||||
instance.setLocked(false);
|
||||
return;
|
||||
}
|
||||
if (instance.isLocked()) {
|
||||
instance.setSealed(true);
|
||||
return;
|
||||
}
|
||||
instance.setLocked(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const getWidthField = ({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
getInstanceWidth: gdInitialInstance => number,
|
||||
getInstanceHeight: gdInitialInstance => number,
|
||||
getInstanceDepth: gdInitialInstance => number,
|
||||
forceUpdate: () => void,
|
||||
|}) => ({
|
||||
name: 'Width',
|
||||
getLabel: () => i18n._(t`Width`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceWidth,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
const shouldKeepRatio = instance.shouldKeepRatio();
|
||||
const newWidth = Math.max(newValue, 0);
|
||||
if (shouldKeepRatio) {
|
||||
const initialWidth = getInstanceWidth(instance) || 1;
|
||||
instance.setCustomWidth(newWidth);
|
||||
instance.setCustomHeight(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialWidth,
|
||||
newReferenceValue: newWidth,
|
||||
valueToApplyTo: getInstanceHeight(instance),
|
||||
})
|
||||
);
|
||||
instance.setCustomDepth(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialWidth,
|
||||
newReferenceValue: newWidth,
|
||||
valueToApplyTo: getInstanceDepth(instance),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
instance.setCustomWidth(newWidth);
|
||||
instance.setCustomHeight(getInstanceHeight(instance));
|
||||
instance.setCustomDepth(getInstanceDepth(instance));
|
||||
}
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
renderLeftIcon: className => <LetterW className={className} />,
|
||||
getEndAdornmentIcon: className => <Restore className={className} />,
|
||||
onClickEndAdornment: (instance: gdInitialInstance) => {
|
||||
instance.setHasCustomSize(false);
|
||||
instance.setHasCustomDepth(false);
|
||||
forceUpdate();
|
||||
},
|
||||
});
|
||||
const getHeightField = ({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
getInstanceWidth: gdInitialInstance => number,
|
||||
getInstanceHeight: gdInitialInstance => number,
|
||||
getInstanceDepth: gdInitialInstance => number,
|
||||
forceUpdate: () => void,
|
||||
|}) => ({
|
||||
name: 'Height',
|
||||
getLabel: () => i18n._(t`Height`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceHeight,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
const shouldKeepRatio = instance.shouldKeepRatio();
|
||||
const newHeight = Math.max(newValue, 0);
|
||||
if (shouldKeepRatio) {
|
||||
const initialHeight = getInstanceHeight(instance) || 1;
|
||||
instance.setCustomWidth(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialHeight,
|
||||
newReferenceValue: newHeight,
|
||||
valueToApplyTo: getInstanceWidth(instance),
|
||||
})
|
||||
);
|
||||
instance.setCustomHeight(newHeight);
|
||||
instance.setCustomDepth(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialHeight,
|
||||
newReferenceValue: newHeight,
|
||||
valueToApplyTo: getInstanceDepth(instance),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
instance.setCustomWidth(getInstanceWidth(instance));
|
||||
instance.setCustomHeight(newHeight);
|
||||
instance.setCustomDepth(getInstanceDepth(instance));
|
||||
}
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
renderLeftIcon: className => <LetterH className={className} />,
|
||||
getEndAdornmentIcon: className => <Restore className={className} />,
|
||||
onClickEndAdornment: (instance: gdInitialInstance) => {
|
||||
instance.setHasCustomSize(false);
|
||||
instance.setHasCustomDepth(false);
|
||||
forceUpdate();
|
||||
},
|
||||
});
|
||||
const getDepthField = ({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
getInstanceWidth: gdInitialInstance => number,
|
||||
getInstanceHeight: gdInitialInstance => number,
|
||||
getInstanceDepth: gdInitialInstance => number,
|
||||
forceUpdate: () => void,
|
||||
|}) => ({
|
||||
name: 'Depth',
|
||||
getLabel: () => i18n._(t`Depth`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceDepth,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
const shouldKeepRatio = instance.shouldKeepRatio();
|
||||
const newDepth = Math.max(newValue, 0);
|
||||
if (shouldKeepRatio) {
|
||||
const initialDepth = getInstanceDepth(instance) || 1;
|
||||
instance.setCustomWidth(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialDepth,
|
||||
newReferenceValue: newDepth,
|
||||
valueToApplyTo: getInstanceWidth(instance),
|
||||
})
|
||||
);
|
||||
instance.setCustomHeight(
|
||||
applyRatio({
|
||||
oldReferenceValue: initialDepth,
|
||||
newReferenceValue: newDepth,
|
||||
valueToApplyTo: getInstanceHeight(instance),
|
||||
})
|
||||
);
|
||||
instance.setCustomDepth(newDepth);
|
||||
} else {
|
||||
instance.setCustomWidth(getInstanceWidth(instance));
|
||||
instance.setCustomHeight(getInstanceHeight(instance));
|
||||
instance.setCustomDepth(newDepth);
|
||||
}
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
renderLeftIcon: className => <Depth className={className} />,
|
||||
getEndAdornmentIcon: className => <Restore className={className} />,
|
||||
onClickEndAdornment: (instance: gdInitialInstance) => {
|
||||
instance.setHasCustomSize(false);
|
||||
instance.setHasCustomDepth(false);
|
||||
forceUpdate();
|
||||
},
|
||||
});
|
||||
const getCustomSizeField = ({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
getInstanceWidth: gdInitialInstance => number,
|
||||
getInstanceHeight: gdInitialInstance => number,
|
||||
getInstanceDepth: gdInitialInstance => number,
|
||||
forceUpdate: () => void,
|
||||
|}) => ({
|
||||
name: 'Keep ratio',
|
||||
getLabel: () => i18n._(t`Keep ratio`),
|
||||
valueType: 'enumIcon',
|
||||
isHighlighted: value => value,
|
||||
renderIcon: value =>
|
||||
value ? <Link fontSize="small" /> : <Unlink fontSize="small" />,
|
||||
getValue: (instance: gdInitialInstance) => instance.shouldKeepRatio(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) =>
|
||||
instance.setShouldKeepRatio(newValue),
|
||||
});
|
||||
|
||||
export const makeSchema = ({
|
||||
is3DInstance,
|
||||
i18n,
|
||||
forceUpdate,
|
||||
onEditObjectByName,
|
||||
onGetInstanceSize,
|
||||
layout,
|
||||
}: {|
|
||||
is3DInstance: boolean,
|
||||
i18n: I18nType,
|
||||
forceUpdate: () => void,
|
||||
onEditObjectByName: (name: string) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
layout: gdLayout,
|
||||
|}): Schema => {
|
||||
const getInstanceWidth = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomSize()
|
||||
? instance.getCustomWidth()
|
||||
: onGetInstanceSize(instance)[0];
|
||||
|
||||
const getInstanceHeight = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomSize()
|
||||
? instance.getCustomHeight()
|
||||
: onGetInstanceSize(instance)[1];
|
||||
|
||||
const getInstanceDepth = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomDepth()
|
||||
? instance.getCustomDepth()
|
||||
: onGetInstanceSize(instance)[2];
|
||||
|
||||
if (is3DInstance) {
|
||||
return [
|
||||
getTitleRow({ i18n }),
|
||||
getEditObjectButton({ i18n, onEditObjectByName, is3DInstance }),
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: [...getXAndYFields({ i18n }), getZField({ i18n })],
|
||||
},
|
||||
{
|
||||
name: 'Size',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: [
|
||||
{
|
||||
name: 'Custom size',
|
||||
type: 'column',
|
||||
children: [
|
||||
getWidthField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
getHeightField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
getDepthField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'verticalCenterCustomSize',
|
||||
nonFieldType: 'verticalCenterWithBar',
|
||||
child: getCustomSizeField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
getLayerField({ i18n, layout }),
|
||||
{
|
||||
name: 'Rotation',
|
||||
type: 'row',
|
||||
title: i18n._(t`Rotation`),
|
||||
preventWrap: true,
|
||||
children: [
|
||||
...getRotationXAndRotationYFields({ i18n }),
|
||||
getRotationZField({ i18n, label: t`Z`, Icon: LetterZ }),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
getTitleRow({ i18n }),
|
||||
getEditObjectButton({ i18n, onEditObjectByName, is3DInstance }),
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: getXAndYFields({ i18n }),
|
||||
},
|
||||
getZOrderField({ i18n }),
|
||||
{
|
||||
name: 'custom-size-row',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: [
|
||||
getWidthField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
{
|
||||
name: 'height-and-custom-size',
|
||||
type: 'row',
|
||||
preventWrap: true,
|
||||
children: [
|
||||
getHeightField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
getCustomSizeField({
|
||||
i18n,
|
||||
getInstanceWidth,
|
||||
getInstanceHeight,
|
||||
getInstanceDepth,
|
||||
forceUpdate,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
getRotationZField({ i18n, label: t`Angle`, Icon: Angle }),
|
||||
getLayerField({ i18n, layout }),
|
||||
];
|
||||
};
|
||||
|
||||
export const reorderInstanceSchemaForCustomProperties = (
|
||||
schema: Schema,
|
||||
i18n: I18nType
|
||||
): Schema => {
|
||||
const newSchema = [...schema];
|
||||
const animationFieldIndex = newSchema.findIndex(
|
||||
field => field.name && field.name === 'animation'
|
||||
);
|
||||
if (animationFieldIndex === -1) return newSchema;
|
||||
|
||||
const [animationField] = newSchema.splice(animationFieldIndex, 1);
|
||||
newSchema.unshift({
|
||||
name: 'Animation',
|
||||
type: 'row',
|
||||
title: i18n._(t`Animation`),
|
||||
children: [animationField],
|
||||
});
|
||||
return newSchema;
|
||||
};
|
@@ -0,0 +1,217 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import * as React from 'react';
|
||||
import Background from '../../UI/Background';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import CompactPropertiesEditor, {
|
||||
Separator,
|
||||
} from '../../CompactPropertiesEditor';
|
||||
import propertiesMapToSchema from '../../CompactPropertiesEditor/PropertiesMapToCompactSchema';
|
||||
import { type Schema } from '../../CompactPropertiesEditor';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import { Line, Column } from '../../UI/Grid';
|
||||
import Text from '../../UI/Text';
|
||||
import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
|
||||
import ScrollView from '../../UI/ScrollView';
|
||||
import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder';
|
||||
import VariablesList, {
|
||||
type HistoryHandler,
|
||||
} from '../../VariablesList/VariablesList';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
import useForceUpdate from '../../Utils/UseForceUpdate';
|
||||
import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import {
|
||||
makeSchema,
|
||||
reorderInstanceSchemaForCustomProperties,
|
||||
} from './CompactPropertiesSchema';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
layout: gdLayout,
|
||||
instances: Array<gdInitialInstance>,
|
||||
onEditObjectByName: string => void,
|
||||
onInstancesModified?: (Array<gdInitialInstance>) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
editInstanceVariables: gdInitialInstance => void,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
i18n: I18nType,
|
||||
historyHandler?: HistoryHandler,
|
||||
|};
|
||||
|
||||
export type CompactInstancePropertiesEditorInterface = {|
|
||||
forceUpdate: () => void,
|
||||
|};
|
||||
|
||||
const CompactInstancePropertiesEditor = ({
|
||||
instances,
|
||||
i18n,
|
||||
project,
|
||||
layout,
|
||||
unsavedChanges,
|
||||
historyHandler,
|
||||
onEditObjectByName,
|
||||
onGetInstanceSize,
|
||||
editInstanceVariables,
|
||||
onInstancesModified,
|
||||
}: Props) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const schemaFor2D: Schema = React.useMemo(
|
||||
() =>
|
||||
makeSchema({
|
||||
i18n,
|
||||
is3DInstance: false,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
layout,
|
||||
forceUpdate,
|
||||
}),
|
||||
[i18n, onGetInstanceSize, onEditObjectByName, layout, forceUpdate]
|
||||
);
|
||||
|
||||
const schemaFor3D: Schema = React.useMemo(
|
||||
() =>
|
||||
makeSchema({
|
||||
i18n,
|
||||
is3DInstance: true,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
layout,
|
||||
forceUpdate,
|
||||
}),
|
||||
[i18n, onGetInstanceSize, onEditObjectByName, layout, forceUpdate]
|
||||
);
|
||||
|
||||
// TODO: multiple instances support.
|
||||
const instance = instances[0];
|
||||
|
||||
const { object, instanceSchema } = React.useMemo<{|
|
||||
object?: gdObject,
|
||||
instanceSchema?: Schema,
|
||||
|}>(
|
||||
() => {
|
||||
if (!instance) return { object: undefined, instanceSchema: undefined };
|
||||
|
||||
const associatedObjectName = instance.getObjectName();
|
||||
const object = getObjectByName(project, layout, associatedObjectName);
|
||||
const properties = instance.getCustomProperties(project, layout);
|
||||
if (!object) return { object: undefined, instanceSchema: undefined };
|
||||
|
||||
const is3DInstance = gd.MetadataProvider.getObjectMetadata(
|
||||
project.getCurrentPlatform(),
|
||||
object.getType()
|
||||
).isRenderedIn3D();
|
||||
const instanceSchemaForCustomProperties = propertiesMapToSchema(
|
||||
properties,
|
||||
(instance: gdInitialInstance) =>
|
||||
instance.getCustomProperties(project, layout),
|
||||
(instance: gdInitialInstance, name, value) =>
|
||||
instance.updateCustomProperty(name, value, project, layout)
|
||||
);
|
||||
|
||||
const reorderedInstanceSchemaForCustomProperties = reorderInstanceSchemaForCustomProperties(
|
||||
instanceSchemaForCustomProperties,
|
||||
i18n
|
||||
);
|
||||
|
||||
return {
|
||||
object,
|
||||
instanceSchema: is3DInstance
|
||||
? schemaFor3D.concat(reorderedInstanceSchemaForCustomProperties)
|
||||
: schemaFor2D.concat(reorderedInstanceSchemaForCustomProperties),
|
||||
};
|
||||
},
|
||||
[project, layout, instance, schemaFor2D, schemaFor3D, i18n]
|
||||
);
|
||||
|
||||
if (!object || !instance || !instanceSchema) return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
componentTitle={<Trans>Instance properties</Trans>}
|
||||
scope="scene-editor-instance-properties"
|
||||
>
|
||||
<ScrollView
|
||||
autoHideScrollbar
|
||||
key={instances
|
||||
.map((instance: gdInitialInstance) => '' + instance.ptr)
|
||||
.join(';')}
|
||||
>
|
||||
<Column expand noMargin id="instance-properties-editor">
|
||||
<Column>
|
||||
<CompactPropertiesEditor
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={instanceSchema}
|
||||
instances={instances}
|
||||
onInstancesModified={onInstancesModified}
|
||||
/>
|
||||
<Separator />
|
||||
<Line alignItems="center" justifyContent="space-between">
|
||||
<Text size="sub-title" noMargin>
|
||||
<Trans>Instance Variables</Trans>
|
||||
</Text>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
editInstanceVariables(instance);
|
||||
}}
|
||||
>
|
||||
<ShareExternal fontSize="small" />
|
||||
</IconButton>
|
||||
</Line>
|
||||
</Column>
|
||||
{object ? (
|
||||
<VariablesList
|
||||
directlyStoreValueChangesWhileEditing
|
||||
inheritedVariablesContainer={object.getVariables()}
|
||||
variablesContainer={instance.getVariables()}
|
||||
size="small"
|
||||
onComputeAllVariableNames={() =>
|
||||
object
|
||||
? EventsRootVariablesFinder.findAllObjectVariables(
|
||||
project.getCurrentPlatform(),
|
||||
project,
|
||||
layout,
|
||||
object
|
||||
)
|
||||
: []
|
||||
}
|
||||
historyHandler={historyHandler}
|
||||
/>
|
||||
) : null}
|
||||
</Column>
|
||||
</ScrollView>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const CompactInstancePropertiesEditorContainer = React.forwardRef<
|
||||
Props,
|
||||
CompactInstancePropertiesEditorInterface
|
||||
>((props, ref) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Background minWidth={0}>
|
||||
{!props.instances || !props.instances.length ? (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Click on an instance in the scene to display its properties
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
) : (
|
||||
<CompactInstancePropertiesEditor {...props} />
|
||||
)}
|
||||
</Background>
|
||||
);
|
||||
});
|
||||
|
||||
export default CompactInstancePropertiesEditorContainer;
|
@@ -17,9 +17,9 @@ describe('EnumerateObjects', () => {
|
||||
allObjectsList,
|
||||
} = enumerateObjects(project, testLayout);
|
||||
|
||||
expect(containerObjectsList).toHaveLength(20);
|
||||
expect(containerObjectsList).toHaveLength(21);
|
||||
expect(projectObjectsList).toHaveLength(2);
|
||||
expect(allObjectsList).toHaveLength(22);
|
||||
expect(allObjectsList).toHaveLength(23);
|
||||
});
|
||||
|
||||
it('can enumerate objects with a filter on object type', () => {
|
||||
|
@@ -44,10 +44,25 @@ export default class Rendered3DInstance {
|
||||
/**
|
||||
* Convert an angle from degrees to radians.
|
||||
*/
|
||||
static toRad(angleInDegrees: number) {
|
||||
static toRad(angleInDegrees: number): number {
|
||||
return (angleInDegrees / 180) * Math.PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies ratio to value without intermediary value to avoid precision issues.
|
||||
*/
|
||||
static applyRatio({
|
||||
oldReferenceValue,
|
||||
newReferenceValue,
|
||||
valueToApplyTo,
|
||||
}: {|
|
||||
oldReferenceValue: number,
|
||||
newReferenceValue: number,
|
||||
valueToApplyTo: number,
|
||||
|}): number {
|
||||
return (newReferenceValue / oldReferenceValue) * valueToApplyTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the scene editor is rendered.
|
||||
*/
|
||||
|
@@ -10,9 +10,6 @@ import EditorMosaic, {
|
||||
type EditorMosaicInterface,
|
||||
} from '../../UI/EditorMosaic';
|
||||
import InstancesEditor from '../../InstancesEditor';
|
||||
import InstancePropertiesEditor, {
|
||||
type InstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/InstancePropertiesEditor';
|
||||
import LayersList, { type LayersListInterface } from '../../LayersList';
|
||||
import FullSizeInstancesEditorWithScrollbars from '../../InstancesEditor/FullSizeInstancesEditorWithScrollbars';
|
||||
import CloseButton from '../../UI/EditorMosaic/CloseButton';
|
||||
@@ -31,6 +28,9 @@ import {
|
||||
type SceneEditorsDisplayProps,
|
||||
type SceneEditorsDisplayInterface,
|
||||
} from '../EditorsDisplay.flow';
|
||||
import CompactInstancePropertiesEditorContainer, {
|
||||
type CompactInstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
|
||||
const initialMosaicEditorNodes = {
|
||||
direction: 'row',
|
||||
@@ -93,7 +93,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
} = React.useContext(PreferencesContext);
|
||||
const selectedInstances = props.instancesSelection.getSelectedInstances();
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?InstancePropertiesEditorInterface>(
|
||||
const instancesPropertiesEditorRef = React.useRef<?CompactInstancePropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
@@ -242,7 +242,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<InstancePropertiesEditor
|
||||
<CompactInstancePropertiesEditorContainer
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
layout={layout}
|
||||
|
@@ -5,9 +5,6 @@ import { Trans } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
import InstancesEditor from '../../InstancesEditor';
|
||||
import InstancePropertiesEditor, {
|
||||
type InstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/InstancePropertiesEditor';
|
||||
import LayersList, { type LayersListInterface } from '../../LayersList';
|
||||
import ObjectsList, { type ObjectsListInterface } from '../../ObjectsList';
|
||||
import ObjectGroupsList, {
|
||||
@@ -31,6 +28,9 @@ import {
|
||||
type SceneEditorsDisplayProps,
|
||||
} from '../EditorsDisplay.flow';
|
||||
import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import CompactInstancePropertiesEditorContainer, {
|
||||
type CompactInstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
|
||||
export const swipeableDrawerContainerId = 'swipeable-drawer-container';
|
||||
|
||||
@@ -66,7 +66,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
const screenType = useScreenType();
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?InstancePropertiesEditorInterface>(
|
||||
const instancesPropertiesEditorRef = React.useRef<?CompactInstancePropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
@@ -328,7 +328,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
{selectedEditorId === 'properties' && (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<InstancePropertiesEditor
|
||||
<CompactInstancePropertiesEditorContainer
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
layout={layout}
|
||||
|
@@ -15,6 +15,7 @@ const styles = {
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
maxWidth?: boolean,
|
||||
minWidth?: number,
|
||||
width?: number | string,
|
||||
/** Sometimes required on Safari */
|
||||
noFullHeight?: boolean,
|
||||
@@ -34,6 +35,7 @@ const Background = (props: Props) => (
|
||||
width: props.width ? props.width : undefined,
|
||||
flex: props.noExpand ? undefined : 1,
|
||||
...(props.maxWidth ? styles.maxWidth : undefined),
|
||||
minWidth: props.minWidth,
|
||||
}}
|
||||
background="dark"
|
||||
>
|
||||
|
@@ -0,0 +1,139 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.compactSelectField {
|
||||
border-radius: 4px;
|
||||
color: var(--theme-text-default-color);
|
||||
background-color: var(--theme-text-field-default-background-color);
|
||||
transition: box-shadow 0.1s;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Style dropdown options on Windows */
|
||||
.compactSelectField select option {
|
||||
color: var(--theme-text-default-color);
|
||||
background-color: var(--theme-text-field-default-background-color);
|
||||
}
|
||||
|
||||
.container.disabled .compactSelectField {
|
||||
color: var(--theme-text-field-disabled-color);
|
||||
}
|
||||
|
||||
.container.errored:not(.disabled) .compactSelectField {
|
||||
border: none;
|
||||
outline: 1px solid var(--theme-text-field-default-error);
|
||||
}
|
||||
.container.errored:not(.disabled):hover .compactSelectField {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
.container.errored:not(.disabled):focus-within .compactSelectField {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
|
||||
.compactSelectField::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container:not(.disabled):not(.errored):hover .compactSelectField::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-hover-border-color);
|
||||
}
|
||||
.container:not(.disabled):not(.errored):focus-within
|
||||
.compactSelectField::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-active-border-color);
|
||||
}
|
||||
|
||||
.compactSelectField select {
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
border-radius: inherit; /* Needed for InAppTutorialElementHighlighter to adapt its border-radius to the input container */
|
||||
}
|
||||
|
||||
.compactSelectField select::placeholder {
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
}
|
||||
|
||||
.arrowContainer {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s linear;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
flex: 1;
|
||||
mask-size: 18px;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' fill-rule='evenodd' clip-rule='evenodd' d='M5.49303 6.80023C5.69538 6.61233 6.01175 6.62405 6.19965 6.8264L7.99992 8.76515L9.80019 6.8264C9.98809 6.62405 10.3045 6.61233 10.5068 6.80023C10.7092 6.98813 10.7209 7.3045 10.533 7.50685L8.36632 9.84019C8.27171 9.94207 8.13896 9.99996 7.99992 9.99996C7.86089 9.99996 7.72813 9.94207 7.63352 9.84019L5.46686 7.50685C5.27896 7.3045 5.29067 6.98813 5.49303 6.80023Z'/%3E%3C/svg%3E");
|
||||
background-color: var(--theme-text-secondary-color);
|
||||
transition: background-color 0.1s linear;
|
||||
}
|
||||
|
||||
.container.disabled .arrow {
|
||||
background-color: var(--theme-text-disabled-color);
|
||||
}
|
||||
|
||||
.container:not(.disabled) .arrowContainer {
|
||||
background-color: none;
|
||||
}
|
||||
.container:not(.disabled):hover .arrowContainer {
|
||||
background-color: var(--theme-text-field-end-adornment-icon-background-color);
|
||||
}
|
||||
.container:not(.disabled):focus-within .arrowContainer {
|
||||
background-color: var(--theme-text-field-end-adornment-icon-background-color);
|
||||
}
|
||||
.container:not(.disabled):hover .arrow {
|
||||
background-color: var(--theme-text-default-color);
|
||||
}
|
||||
.container:not(.disabled):focus-within .arrow {
|
||||
background-color: var(--theme-text-default-color);
|
||||
}
|
||||
|
||||
.leftIconContainer {
|
||||
display: flex;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* svg tag is needed to be first priority compared to Material UI Custom SVG icon classes*/
|
||||
svg.leftIcon {
|
||||
font-size: 18px;
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
transition: color 0.1s linear;
|
||||
}
|
||||
|
||||
.container.disabled .leftIcon {
|
||||
color: var(--theme-text-field-disabled-color);
|
||||
}
|
||||
|
||||
.container:not(.disabled):hover .leftIcon {
|
||||
color: var(--theme-text-default-color);
|
||||
}
|
||||
.container:not(.disabled):focus-within .leftIcon {
|
||||
color: var(--theme-text-default-color);
|
||||
}
|
83
newIDE/app/src/UI/CompactSelectField/index.js
Normal file
83
newIDE/app/src/UI/CompactSelectField/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import classNames from 'classnames';
|
||||
import classes from './CompactSelectField.module.css';
|
||||
import { tooltipEnterDelay } from '../Tooltip';
|
||||
type Props = {|
|
||||
onChange: string => void,
|
||||
value: string,
|
||||
id?: string,
|
||||
disabled?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: string,
|
||||
children: React.Node,
|
||||
renderLeftIcon?: (className: string) => React.Node,
|
||||
leftIconTooltip?: React.Node,
|
||||
|};
|
||||
|
||||
const CompactSelectField = ({
|
||||
onChange,
|
||||
value,
|
||||
id,
|
||||
disabled,
|
||||
errored,
|
||||
placeholder,
|
||||
children,
|
||||
renderLeftIcon,
|
||||
leftIconTooltip,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.container]: true,
|
||||
[classes.disabled]: disabled,
|
||||
[classes.errored]: errored,
|
||||
})}
|
||||
>
|
||||
{renderLeftIcon && (
|
||||
<Tooltip
|
||||
title={leftIconTooltip}
|
||||
enterDelay={tooltipEnterDelay}
|
||||
placement="bottom"
|
||||
PopperProps={{
|
||||
modifiers: {
|
||||
offset: {
|
||||
enabled: true,
|
||||
/**
|
||||
* It does not seem possible to get the tooltip closer to the anchor
|
||||
* when positioned on top. So it is positioned on bottom with a negative offset.
|
||||
*/
|
||||
offset: '0,-20',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className={classes.leftIconContainer}>
|
||||
{renderLeftIcon(classes.leftIcon)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.compactSelectField]: true,
|
||||
})}
|
||||
>
|
||||
<select
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={e => onChange(e.currentTarget.value)}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
<div className={classNames({ [classes.arrowContainer]: true })}>
|
||||
<span className={classNames({ [classes.arrow]: true })} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactSelectField;
|
@@ -0,0 +1,14 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.container .error {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: var(--theme-error-color);
|
||||
padding: 2px 0px;
|
||||
}
|
73
newIDE/app/src/UI/CompactSemiControlledNumberField/index.js
Normal file
73
newIDE/app/src/UI/CompactSemiControlledNumberField/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import CompactTextField from '../CompactTextField';
|
||||
import classes from './CompactSemiControlledNumberField.module.css';
|
||||
|
||||
type Props = {|
|
||||
id?: string,
|
||||
value: number,
|
||||
onChange: number => void,
|
||||
commitOnBlur?: boolean,
|
||||
disabled?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: string,
|
||||
renderLeftIcon?: (className: string) => React.Node,
|
||||
leftIconTooltip?: React.Node,
|
||||
useLeftIconAsNumberControl?: boolean,
|
||||
renderEndAdornmentOnHover?: (className: string) => React.Node,
|
||||
onClickEndAdornment?: () => void,
|
||||
|
||||
errorText?: React.Node,
|
||||
|};
|
||||
|
||||
const CompactSemiControlledNumberField = ({
|
||||
value,
|
||||
onChange,
|
||||
errorText,
|
||||
commitOnBlur,
|
||||
...otherProps
|
||||
}: Props) => {
|
||||
const [focused, setFocused] = React.useState<boolean>(false);
|
||||
const [temporaryValue, setTemporaryValue] = React.useState<?number>(null);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<CompactTextField
|
||||
type="number"
|
||||
value={focused ? temporaryValue : value}
|
||||
onChange={(valueAsString, reason) => {
|
||||
const newValue = parseFloat(valueAsString);
|
||||
const isNewValueValid = !Number.isNaN(newValue);
|
||||
if (isNewValueValid) {
|
||||
setTemporaryValue(newValue);
|
||||
if (reason === 'keyInput') {
|
||||
if (!commitOnBlur) onChange(newValue);
|
||||
} else {
|
||||
onChange(newValue);
|
||||
}
|
||||
} else {
|
||||
setTemporaryValue(null);
|
||||
}
|
||||
}}
|
||||
onFocus={event => {
|
||||
setFocused(true);
|
||||
setTemporaryValue(value);
|
||||
}}
|
||||
onBlur={event => {
|
||||
const newValue = parseFloat(event.currentTarget.value);
|
||||
const isNewValueValid = !Number.isNaN(newValue);
|
||||
if (isNewValueValid) {
|
||||
onChange(newValue);
|
||||
}
|
||||
setFocused(false);
|
||||
setTemporaryValue(null);
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
{errorText && <div className={classes.error}>{errorText}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactSemiControlledNumberField;
|
@@ -0,0 +1,14 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.container .error {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: var(--theme-error-color);
|
||||
padding: 2px 0px;
|
||||
}
|
58
newIDE/app/src/UI/CompactSemiControlledTextField/index.js
Normal file
58
newIDE/app/src/UI/CompactSemiControlledTextField/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import CompactTextField from '../CompactTextField';
|
||||
import classes from './CompactSemiControlledTextField.module.css';
|
||||
|
||||
type Props = {|
|
||||
id?: string,
|
||||
value: string,
|
||||
onChange: string => void,
|
||||
commitOnBlur?: boolean,
|
||||
disabled?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: string,
|
||||
renderLeftIcon?: (className: string) => React.Node,
|
||||
leftIconTooltip?: React.Node,
|
||||
renderEndAdornmentOnHover?: (className: string) => React.Node,
|
||||
onClickEndAdornment?: () => void,
|
||||
|
||||
errorText?: React.Node,
|
||||
|};
|
||||
|
||||
const CompactSemiControlledTextField = ({
|
||||
value,
|
||||
onChange,
|
||||
errorText,
|
||||
commitOnBlur,
|
||||
...otherProps
|
||||
}: Props) => {
|
||||
const [focused, setFocused] = React.useState<boolean>(false);
|
||||
const [text, setText] = React.useState<string>('');
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<CompactTextField
|
||||
type="text"
|
||||
value={focused ? text : value}
|
||||
onFocus={event => {
|
||||
setFocused(true);
|
||||
setText(value);
|
||||
}}
|
||||
onChange={newValue => {
|
||||
setText(newValue);
|
||||
if (!commitOnBlur) onChange(newValue);
|
||||
}}
|
||||
onBlur={event => {
|
||||
onChange(event.currentTarget.value);
|
||||
setFocused(false);
|
||||
setText('');
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
{errorText && <div className={classes.error}>{errorText}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactSemiControlledTextField;
|
159
newIDE/app/src/UI/CompactTextField/CompactTextField.module.css
Normal file
159
newIDE/app/src/UI/CompactTextField/CompactTextField.module.css
Normal file
@@ -0,0 +1,159 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.compactTextField {
|
||||
border-radius: 4px;
|
||||
color: var(--theme-text-default-color);
|
||||
background-color: var(--theme-text-field-default-background-color);
|
||||
transition: box-shadow 0.1s;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.container.disabled .compactTextField {
|
||||
color: var(--theme-text-field-disabled-color);
|
||||
}
|
||||
|
||||
.container.errored:not(.disabled) .compactTextField {
|
||||
border: none;
|
||||
outline: 1px solid var(--theme-text-field-default-error);
|
||||
}
|
||||
.container.errored:not(.disabled):hover .compactTextField {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
.container.errored:not(.disabled):focus-within .compactTextField {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
|
||||
.compactTextField::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container:not(.disabled):not(.errored):hover .compactTextField::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-hover-border-color);
|
||||
}
|
||||
.container:not(.disabled):not(.errored):focus-within .compactTextField::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-active-border-color);
|
||||
}
|
||||
|
||||
.compactTextField input {
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
caret-color: var(--theme-text-field-active-caret-color);
|
||||
min-width: 0px;
|
||||
border-radius: inherit; /* Needed for InAppTutorialElementHighlighter to adapt its border-radius to the input container */
|
||||
}
|
||||
.compactTextField.withEndAdornment input {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.compactTextField input::placeholder {
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
}
|
||||
|
||||
.leftIconContainer {
|
||||
display: flex;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.container:not(.disabled) .leftIconContainer.control {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
/* svg tag is needed to be first priority compared to Material UI Custom SVG icon classes*/
|
||||
svg.leftIcon {
|
||||
font-size: 20px;
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
transition: color 0.1s linear;
|
||||
}
|
||||
|
||||
.container.disabled .leftIcon {
|
||||
color: var(--theme-text-field-disabled-color);
|
||||
}
|
||||
|
||||
.container:not(.disabled):hover .leftIcon {
|
||||
color: var(--theme-text-default-color);
|
||||
}
|
||||
.container:not(.disabled):focus-within .leftIcon {
|
||||
color: var(--theme-text-default-color);
|
||||
}
|
||||
|
||||
.endAdornmentButton {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
display: none;
|
||||
padding: 1px;
|
||||
border-radius: 4px;
|
||||
background-color: unset;
|
||||
}
|
||||
/* svg tag is needed to be first priority compared to Material UI Custom SVG icon classes*/
|
||||
svg.endAdornmentIcon {
|
||||
font-size: 15px;
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
transition: color 0.1s linear;
|
||||
}
|
||||
.container:not(.disabled):hover .endAdornmentButton,
|
||||
.container:not(.disabled):focus-within .endAdornmentButton {
|
||||
display: flex;
|
||||
}
|
||||
.container:not(.disabled):hover .endAdornmentButton:hover,
|
||||
.container:not(.disabled):focus-within .endAdornmentButton:hover,
|
||||
.container:not(.disabled):hover .endAdornmentButton:focus,
|
||||
.container:not(.disabled):focus-within .endAdornmentButton:focus {
|
||||
background-color: var(--theme-text-field-end-adornment-icon-background-color);
|
||||
}
|
||||
.container:not(.disabled):hover .endAdornmentButton:hover .endAdornmentIcon,
|
||||
.container:not(.disabled):focus-within
|
||||
.endAdornmentButton:hover
|
||||
.endAdornmentIcon,
|
||||
.container:not(.disabled):hover .endAdornmentButton:focus .endAdornmentIcon,
|
||||
.container:not(.disabled):focus-within
|
||||
.endAdornmentButton:focus
|
||||
.endAdornmentIcon {
|
||||
color: var(--theme-text-default-color);
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
.compactTextField input::-webkit-outer-spin-button,
|
||||
.compactTextField input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
.compactTextField input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
66
newIDE/app/src/UI/CompactTextField/UseClickDragAsControl.js
Normal file
66
newIDE/app/src/UI/CompactTextField/UseClickDragAsControl.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = {|
|
||||
onChange: number => void,
|
||||
onGetInitialValue: () => number,
|
||||
|};
|
||||
|
||||
const useClickDragAsControl = ({ onChange, onGetInitialValue }: Props) => {
|
||||
const clickDragOrigin = React.useRef<?{|
|
||||
initialValue: number,
|
||||
accumulatedValue: number,
|
||||
previouslyChangedValue: ?number,
|
||||
|}>(null);
|
||||
|
||||
const start = React.useCallback(
|
||||
(e: MouseEvent) => {
|
||||
const target = e.currentTarget;
|
||||
if (target instanceof Element) {
|
||||
target.requestPointerLock();
|
||||
clickDragOrigin.current = {
|
||||
initialValue: onGetInitialValue(),
|
||||
accumulatedValue: 0,
|
||||
previouslyChangedValue: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
[onGetInitialValue]
|
||||
);
|
||||
|
||||
const onMove = React.useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!clickDragOrigin.current) return;
|
||||
|
||||
const { initialValue, previouslyChangedValue } = clickDragOrigin.current;
|
||||
clickDragOrigin.current.accumulatedValue +=
|
||||
e.movementX /
|
||||
// Sensitivity setting
|
||||
2;
|
||||
const newValue =
|
||||
Math.round(clickDragOrigin.current.accumulatedValue) + initialValue;
|
||||
if (newValue !== previouslyChangedValue) {
|
||||
if (!clickDragOrigin.current) return;
|
||||
clickDragOrigin.current.previouslyChangedValue = newValue;
|
||||
onChange(newValue);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const clear = React.useCallback(() => {
|
||||
if (clickDragOrigin.current) {
|
||||
document.exitPointerLock();
|
||||
clickDragOrigin.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
onMouseDown: start,
|
||||
onMouseMove: onMove,
|
||||
onMouseUp: clear,
|
||||
};
|
||||
};
|
||||
|
||||
export default useClickDragAsControl;
|
163
newIDE/app/src/UI/CompactTextField/index.js
Normal file
163
newIDE/app/src/UI/CompactTextField/index.js
Normal file
@@ -0,0 +1,163 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import classNames from 'classnames';
|
||||
import classes from './CompactTextField.module.css';
|
||||
import { tooltipEnterDelay } from '../Tooltip';
|
||||
import useClickDragAsControl from './UseClickDragAsControl';
|
||||
import { makeTimestampedId } from '../../Utils/TimestampedId';
|
||||
import { toFixedWithoutTrailingZeros } from '../../Utils/Mathematics';
|
||||
|
||||
type ValueProps =
|
||||
| {|
|
||||
type?: 'text',
|
||||
value: string,
|
||||
onChange: (newValue: string, reason: 'keyInput') => void,
|
||||
|}
|
||||
| {|
|
||||
type: 'number',
|
||||
value: ?number, // null value corresponds to an empty input.
|
||||
onChange: (newValue: number, reason: 'keyInput' | 'iconControl') => void,
|
||||
|};
|
||||
|
||||
type OtherProps = {|
|
||||
onBlur?: ({
|
||||
currentTarget: {
|
||||
value: string,
|
||||
},
|
||||
}) => void,
|
||||
onFocus?: ({
|
||||
currentTarget: {
|
||||
value: string,
|
||||
},
|
||||
preventDefault: () => void,
|
||||
}) => void,
|
||||
|};
|
||||
|
||||
export type CompactTextFieldProps = {|
|
||||
...ValueProps,
|
||||
...OtherProps,
|
||||
id?: string,
|
||||
disabled?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: string,
|
||||
renderLeftIcon?: (className: string) => React.Node,
|
||||
leftIconTooltip?: React.Node,
|
||||
useLeftIconAsNumberControl?: boolean,
|
||||
renderEndAdornmentOnHover?: (className: string) => React.Node,
|
||||
onClickEndAdornment?: () => void,
|
||||
|};
|
||||
|
||||
const CompactTextField = ({
|
||||
type,
|
||||
value,
|
||||
onChange,
|
||||
id,
|
||||
disabled,
|
||||
errored,
|
||||
placeholder,
|
||||
renderLeftIcon,
|
||||
leftIconTooltip,
|
||||
useLeftIconAsNumberControl,
|
||||
renderEndAdornmentOnHover,
|
||||
onClickEndAdornment,
|
||||
onBlur,
|
||||
onFocus,
|
||||
}: CompactTextFieldProps) => {
|
||||
const idToUse = React.useRef<string>(id || makeTimestampedId());
|
||||
const controlProps = useClickDragAsControl({
|
||||
// $FlowExpectedError - Click drag controls should not be used if value type is not number.
|
||||
onChange: value => onChange(value, 'iconControl'),
|
||||
// $FlowExpectedError
|
||||
onGetInitialValue: () => value,
|
||||
});
|
||||
|
||||
const onBlurInput = React.useCallback(
|
||||
event => {
|
||||
if (onBlur) onBlur(event);
|
||||
},
|
||||
[onBlur]
|
||||
);
|
||||
const onFocusInput = React.useCallback(
|
||||
event => {
|
||||
if (onFocus) onFocus(event);
|
||||
},
|
||||
[onFocus]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.container]: true,
|
||||
[classes.disabled]: disabled,
|
||||
[classes.errored]: errored,
|
||||
})}
|
||||
>
|
||||
{renderLeftIcon && (
|
||||
<Tooltip
|
||||
title={leftIconTooltip}
|
||||
enterDelay={tooltipEnterDelay}
|
||||
placement="bottom"
|
||||
PopperProps={{
|
||||
modifiers: {
|
||||
offset: {
|
||||
enabled: true,
|
||||
/**
|
||||
* It does not seem possible to get the tooltip closer to the anchor
|
||||
* when positioned on top. So it is positioned on bottom with a negative offset.
|
||||
*/
|
||||
offset: '0,-20',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.leftIconContainer]: true,
|
||||
[classes.control]: !!useLeftIconAsNumberControl,
|
||||
})}
|
||||
{...(useLeftIconAsNumberControl ? controlProps : {})}
|
||||
>
|
||||
<label htmlFor={idToUse.current} className={classes.label}>
|
||||
{renderLeftIcon(classes.leftIcon)}
|
||||
</label>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.compactTextField]: true,
|
||||
[classes.withEndAdornment]: !!renderEndAdornmentOnHover,
|
||||
})}
|
||||
>
|
||||
<input
|
||||
id={idToUse.current}
|
||||
type={type || 'text'}
|
||||
disabled={disabled}
|
||||
value={
|
||||
value === null
|
||||
? ''
|
||||
: typeof value === 'number'
|
||||
? toFixedWithoutTrailingZeros(value, 2)
|
||||
: value
|
||||
}
|
||||
onChange={e => onChange(e.currentTarget.value, 'keyInput')}
|
||||
placeholder={placeholder}
|
||||
onBlur={onBlurInput}
|
||||
onFocus={onFocusInput}
|
||||
/>
|
||||
{renderEndAdornmentOnHover && (
|
||||
<button
|
||||
onClick={onClickEndAdornment}
|
||||
className={classes.endAdornmentButton}
|
||||
>
|
||||
{renderEndAdornmentOnHover(classes.endAdornmentIcon)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactTextField;
|
13
newIDE/app/src/UI/CustomSvgIcons/Angle.js
Normal file
13
newIDE/app/src/UI/CustomSvgIcons/Angle.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.8335 2.66666C3.8335 2.39051 3.60964 2.16666 3.3335 2.16666C3.05735 2.16666 2.8335 2.39051 2.8335 2.66666V12C2.8335 12.2761 3.05735 12.5 3.3335 12.5H12.6668C12.943 12.5 13.1668 12.2761 13.1668 12C13.1668 11.7238 12.943 11.5 12.6668 11.5H8.46948C8.32936 10.3516 7.72143 9.26415 6.89538 8.4381C6.06933 7.61205 4.98186 7.00412 3.8335 6.86401V2.66666ZM6.18828 9.14521C5.51647 8.4734 4.67669 8.00907 3.8335 7.87393V11.5H7.45955C7.32441 10.6568 6.86008 9.81701 6.18828 9.14521Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
62
newIDE/app/src/UI/CustomSvgIcons/Depth.js
Normal file
62
newIDE/app/src/UI/CustomSvgIcons/Depth.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M1.3335 13.3333H14.6668H1.3335Z" fill="currentColor" />
|
||||
<path
|
||||
d="M1.3335 13.3333H14.6668"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.3335 2.66666H12.6668"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 10.6733L2.00667 10.6659"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14.0067 10.6733L14 10.6659"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.6665 8.00667L2.67317 7.99927"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.3397 8.00667L13.333 7.99927"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.3335 5.33999L3.34016 5.33258"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.6732 5.33999L12.6665 5.33258"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 4.66666V11.3333M8 4.66666L7 5.66666M8 4.66666L9 5.66666M8 11.3333L6 9.33332M8 11.3333L10 9.33332"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
13
newIDE/app/src/UI/CustomSvgIcons/Instance.js
Normal file
13
newIDE/app/src/UI/CustomSvgIcons/Instance.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.00016 3.93256L3.93273 8L8.00016 12.0674L12.0676 8L8.00016 3.93256ZM7.46279 3.05572C7.60537 2.91331 7.79865 2.83333 8.00016 2.83333C8.20168 2.83333 8.39495 2.91331 8.53753 3.05572L12.9442 7.46241C13.0866 7.60499 13.1668 7.79848 13.1668 8C13.1668 8.20151 13.0868 8.39478 12.9444 8.53736L8.53775 12.9441C8.39517 13.0865 8.20168 13.1667 8.00016 13.1667C7.79865 13.1667 7.60537 13.0867 7.46279 12.9443L3.05611 8.53759C2.9137 8.395 2.8335 8.20151 2.8335 8C2.8335 7.79848 2.91348 7.60521 3.05589 7.46263L7.46279 3.05572Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/LetterH.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/LetterH.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="17" height="20" viewBox="0 0 16 20" fill="none">
|
||||
<path
|
||||
d="M10.29 15V10.478H6.23V15H4.9V5.354H6.23V9.386H10.29V5.354H11.62V15H10.29Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/LetterW.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/LetterW.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="17" height="20" viewBox="0 0 17 20" fill="none">
|
||||
<path
|
||||
d="M13.214 5.354L11.254 15H9.546L7.768 6.922L5.976 15H4.31L2.35 5.354H3.652L5.206 13.838L7.068 5.354H8.482L10.386 13.838L11.996 5.354H13.214Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/LetterX.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/LetterX.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="12" height="20" viewBox="0 0 12 20" fill="none">
|
||||
<path
|
||||
d="M6.55 9.792L9.49 15H7.978L5.752 10.73L3.498 15H2.07L4.968 9.862L2.322 5.354H3.834L5.78 8.98L7.74 5.354H9.168L6.55 9.792Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/LetterY.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/LetterY.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="17" height="20" viewBox="0 0 17 20" fill="none">
|
||||
<path
|
||||
d="M12.13 5.354L9.022 11.29V15H7.678V11.304L4.57 5.354H6.04L8.392 10.128L10.744 5.354H12.13Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/LetterZ.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/LetterZ.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="20" viewBox="0 0 16 20" fill="none">
|
||||
<path
|
||||
d="M10.678 5.354V6.432L5.904 13.866H10.678L10.524 15H4.42V13.936L9.236 6.474H4.924V5.354H10.678Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
25
newIDE/app/src/UI/CustomSvgIcons/Link.js
Normal file
25
newIDE/app/src/UI/CustomSvgIcons/Link.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.6537 5.19282C4.64208 3.3546 6.12275 1.85505 7.96096 1.84338C9.7992 1.83171 11.2988 3.31241 11.3104 5.15065L11.3179 6.32914C11.3196 6.60528 11.0972 6.83055 10.8211 6.8323C10.5449 6.83405 10.3197 6.61161 10.3179 6.33548L10.3104 5.15699C10.3023 3.87101 9.25325 2.83519 7.96732 2.84336C6.68136 2.85153 5.64556 3.90054 5.65368 5.18649C5.65368 5.18649 5.65368 5.18649 5.65368 5.18649L5.66115 6.36498C5.6629 6.64112 5.44046 6.86639 5.16432 6.86814C4.88819 6.86989 4.66292 6.64745 4.66117 6.37132L4.6537 5.19282Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.17889 9.16792C5.45503 9.16617 5.6803 9.38861 5.68205 9.66474L5.68952 10.8432C5.68952 10.8432 5.68952 10.8432 5.68952 10.8432C5.69769 12.1292 6.74671 13.165 8.03266 13.1569C9.3186 13.1487 10.3544 12.0997 10.3463 10.8137L10.3388 9.63524C10.3371 9.35911 10.5595 9.13383 10.8356 9.13208C11.1118 9.13034 11.337 9.35277 11.3388 9.62891L11.3463 10.8074C11.3579 12.6456 9.87721 14.1452 8.03898 14.1568C6.20076 14.1685 4.70121 12.6878 4.68954 10.8496L4.68207 9.67108C4.68032 9.39494 4.90276 9.16967 5.17889 9.16792Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.98337 5.37884C8.25951 5.37709 8.48478 5.59953 8.48653 5.87567L8.51341 10.1182C8.51516 10.3944 8.29272 10.6196 8.01659 10.6214C7.74045 10.6231 7.51518 10.4007 7.51343 10.1246L7.48655 5.882C7.4848 5.60586 7.70724 5.38059 7.98337 5.37884Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
11
newIDE/app/src/UI/CustomSvgIcons/Restore.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/Restore.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M2.5 8C2.5 4.96499 4.97482 2.5 8.03379 2.5C10.1843 2.5 12.0485 3.71928 12.9644 5.5H10.8667C10.5905 5.5 10.3667 5.72386 10.3667 6C10.3667 6.27614 10.5905 6.5 10.8667 6.5H13.8C13.9045 6.5 14.0097 6.48361 14.1103 6.45026C14.2109 6.41694 14.3084 6.36614 14.3952 6.29671C14.4821 6.22717 14.5585 6.13862 14.6136 6.03235C14.6689 5.92544 14.7 5.8053 14.7 5.68V2.86667C14.7 2.59052 14.4761 2.36667 14.2 2.36667C13.9239 2.36667 13.7 2.59052 13.7 2.86667V4.76132C12.5718 2.81196 10.4562 1.5 8.03379 1.5C4.42784 1.5 1.5 4.40741 1.5 8C1.5 11.5926 4.42784 14.5 8.03379 14.5C11.3074 14.5 14.0208 12.1042 14.4944 8.97482C14.5357 8.70178 14.3478 8.44695 14.0748 8.40563C13.8018 8.36431 13.5469 8.55215 13.5056 8.82518C13.1055 11.469 10.8096 13.5 8.03379 13.5C4.97482 13.5 2.5 11.035 2.5 8Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
19
newIDE/app/src/UI/CustomSvgIcons/Unlink.js
Normal file
19
newIDE/app/src/UI/CustomSvgIcons/Unlink.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.65394 5.19279C4.64233 3.35457 6.12299 1.85502 7.96121 1.84335C9.79944 1.83168 11.299 3.31238 11.3107 5.15062L11.3181 6.32911C11.3199 6.60525 11.0974 6.83052 10.8213 6.83227C10.5452 6.83402 10.3199 6.61158 10.3182 6.33544L10.3107 5.15696C10.3025 3.87098 9.2535 2.83516 7.96756 2.84333C6.68161 2.8515 5.6458 3.90051 5.65392 5.18646L5.66139 6.36495C5.66314 6.64109 5.4407 6.86636 5.16457 6.86811C4.88843 6.86986 4.66316 6.64742 4.66141 6.37128L4.65394 5.19279Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.17914 9.16789C5.45528 9.16614 5.68055 9.38858 5.6823 9.66471L5.68976 10.8432C5.69793 12.1291 6.74695 13.165 8.0329 13.1568C9.31884 13.1487 10.3547 12.0997 10.3465 10.8137L10.3391 9.63521C10.3373 9.35908 10.5597 9.1338 10.8359 9.13205C11.112 9.1303 11.3373 9.35274 11.339 9.62888L11.3465 10.8074C11.3581 12.6456 9.87746 14.1452 8.03922 14.1568C6.20101 14.1684 4.70146 12.6878 4.68978 10.8495L4.68232 9.67105C4.68057 9.39491 4.903 9.16964 5.17914 9.16789Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
@@ -31,6 +31,10 @@ export const Line = (props: {|
|
||||
// all the height (if set to flex: 1) and to *not* grow
|
||||
// larger than the parent.
|
||||
minHeight: props.useFullHeight ? '0' : undefined,
|
||||
// Setting min-width to 0 prevents the line to keep a minimum width
|
||||
// in some rare cases (CompactInstancePropertiesEditor when in the
|
||||
// mosaic editor for instance).
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
@@ -439,6 +439,52 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#99c4f8"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#7fb6f6"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "rgba(255, 255, 255, 0.09)"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -512,6 +512,55 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#DDD1FF",
|
||||
"comment": "Palette/Purple/10"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#C9B6FC",
|
||||
"comment": "Palette/Purple/20"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "#32323B",
|
||||
"comment": "Palette/Grey/80"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -500,6 +500,55 @@
|
||||
"badge-color": {
|
||||
"value": "#2B8CFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#7046EC",
|
||||
"comment": "Palette/Purple/40"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#9979F1",
|
||||
"comment": "Palette/Purple/30"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "#D9D9DE",
|
||||
"comment": "Palette/Grey/20"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF5E3B",
|
||||
"comment": "Palette/Red/50"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(79, 40, 205, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -430,6 +430,52 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#D8DEE9"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#7e9abc"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "rgba(255, 255, 255, 0.09)"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -445,6 +445,52 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#D6DEEC"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#a9d3f6"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "rgba(255, 255, 255, 0.09)"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -431,6 +431,52 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#6e6a86"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#decff1"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "rgba(255, 255, 255, 0.09)"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -432,6 +432,52 @@
|
||||
"badge-color": {
|
||||
"value": "#6BAFFF"
|
||||
}
|
||||
},
|
||||
"text-field": {
|
||||
"active": {
|
||||
"error": {
|
||||
"value": "#FFC2B4",
|
||||
"comment": "Palette/Red/20"
|
||||
},
|
||||
"border-color": {
|
||||
"value": "#444444"
|
||||
},
|
||||
"caret-color": {
|
||||
"value": "#9ac9db"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"placeholder": {
|
||||
"color": {
|
||||
"value": "#A6A6AB",
|
||||
"comment": "Palette/Grey/40"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"background-color": {
|
||||
"value": "rgba(255, 255, 255, 0.09)"
|
||||
},
|
||||
"error": {
|
||||
"value": "#FF8569",
|
||||
"comment": "Palette/Red/40"
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"border-color": {
|
||||
"value": "#7F7F85",
|
||||
"comment": "Palette/Grey/50"
|
||||
}
|
||||
},
|
||||
"endAdornmentIcon": {
|
||||
"background-color": {
|
||||
"value": "rgba(221, 209, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
48
newIDE/app/src/UI/VerticallyCenterWithBar.js
Normal file
48
newIDE/app/src/UI/VerticallyCenterWithBar.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import GDevelopThemeContext from './Theme/GDevelopThemeContext';
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'stretch',
|
||||
alignItems: 'center',
|
||||
},
|
||||
verticalBar: {
|
||||
flex: 1,
|
||||
},
|
||||
childrenContainer: {
|
||||
flex: 0,
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
|};
|
||||
|
||||
const VerticallyCenterWithBar = (props: Props) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<div
|
||||
style={{
|
||||
...styles.verticalBar,
|
||||
borderLeft: `1px solid ${gdevelopTheme.palette.secondary}`,
|
||||
}}
|
||||
/>
|
||||
<div style={styles.childrenContainer}>{props.children}</div>
|
||||
<div
|
||||
style={{
|
||||
...styles.verticalBar,
|
||||
borderLeft: `1px solid ${gdevelopTheme.palette.secondary}`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerticallyCenterWithBar;
|
@@ -351,4 +351,48 @@ export const makeTestExtensions = (gd: libGDevelop) => {
|
||||
platform.addNewExtension(extension);
|
||||
extension.delete(); // Release the extension as it was copied inside gd.JsPlatform
|
||||
}
|
||||
{
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'FakeScene3D',
|
||||
'Fake 3D',
|
||||
'Fake support for 3D in GDevelop.',
|
||||
'',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('General');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata('3D')
|
||||
.setIcon('res/conditions/3d_box.svg');
|
||||
const Cube3DObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
Cube3DObject.getProperties = function(objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
return objectProperties;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
Cube3DObject.getInitialInstanceProperties = function(
|
||||
content,
|
||||
instance,
|
||||
project,
|
||||
layout
|
||||
) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
|
||||
extension
|
||||
.addObject(
|
||||
'Cube3DObject',
|
||||
'3D Box',
|
||||
'A box with images for each face',
|
||||
'JsPlatform/Extensions/3d_box.svg',
|
||||
Cube3DObject
|
||||
)
|
||||
.setCategoryFullName('General')
|
||||
.markAsRenderedIn3D();
|
||||
platform.addNewExtension(extension);
|
||||
extension.delete(); // Release the extension as it was copied inside gd.JsPlatform
|
||||
}
|
||||
};
|
||||
|
@@ -10,6 +10,7 @@ export type TestProject = {|
|
||||
panelSpriteObject: gdObject,
|
||||
spriteObjectConfiguration: gdSpriteObject,
|
||||
emptySpriteObjectConfiguration: gdSpriteObject,
|
||||
cube3dObject: gdObject,
|
||||
customObject: gdObject,
|
||||
spriteObject: gdObject,
|
||||
emptySpriteObject: gdObject,
|
||||
@@ -21,6 +22,7 @@ export type TestProject = {|
|
||||
group2: gdObjectGroup,
|
||||
group4WithLongsNames: gdObjectGroup,
|
||||
testLayoutInstance1: gdInitialInstance,
|
||||
testLayoutInstance2: gdInitialInstance,
|
||||
testInstruction: gdInstruction,
|
||||
testExternalEvents1: gdExternalEvents,
|
||||
testExternalEvents2: gdExternalEvents,
|
||||
@@ -213,6 +215,12 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
|
||||
'MyTiledSpriteObject',
|
||||
0
|
||||
);
|
||||
const cube3dObject = testLayout.insertNewObject(
|
||||
project,
|
||||
'FakeScene3D::Cube3DObject',
|
||||
'CubeObject',
|
||||
0
|
||||
);
|
||||
const panelSpriteObject = testLayout.insertNewObject(
|
||||
project,
|
||||
'PanelSpriteObject::PanelSprite',
|
||||
@@ -386,6 +394,14 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
|
||||
testLayoutInstance1.setX(10);
|
||||
testLayoutInstance1.setY(15);
|
||||
|
||||
const testLayoutInstance2 = testLayout
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
testLayoutInstance2.setX(120);
|
||||
testLayoutInstance2.setY(-15);
|
||||
testLayoutInstance2.setZ(32);
|
||||
testLayoutInstance2.setObjectName(cube3dObject.getName());
|
||||
|
||||
const testSpriteObjectInstance = testLayout
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
@@ -880,6 +896,7 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
|
||||
tiledSpriteObjectConfiguration: tiledSpriteObject.getConfiguration(),
|
||||
panelSpriteObject,
|
||||
customObject,
|
||||
cube3dObject,
|
||||
spriteObject,
|
||||
spriteObjectConfiguration,
|
||||
emptySpriteObject,
|
||||
@@ -892,6 +909,7 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
|
||||
group2,
|
||||
group4WithLongsNames,
|
||||
testLayoutInstance1,
|
||||
testLayoutInstance2,
|
||||
testInstruction,
|
||||
testExternalEvents1,
|
||||
testExternalEvents2,
|
||||
|
75
newIDE/app/src/stories/ElementHighlighterProvider.js
Normal file
75
newIDE/app/src/stories/ElementHighlighterProvider.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { ColumnStackLayout, ResponsiveLineStackLayout } from '../UI/Layout';
|
||||
import Toggle from '../UI/Toggle';
|
||||
import InAppTutorialElementHighlighter from '../InAppTutorial/InAppTutorialElementHighlighter';
|
||||
import CompactSelectField from '../UI/CompactSelectField';
|
||||
import { Column } from '../UI/Grid';
|
||||
|
||||
type Props = {|
|
||||
elements: {| label: string, id: string |}[],
|
||||
children: React.Node,
|
||||
|};
|
||||
|
||||
const ElementHighlighterProvider = (props: Props) => {
|
||||
const [
|
||||
shouldHighlightField,
|
||||
setShouldHighlightField,
|
||||
] = React.useState<boolean>(false);
|
||||
const [
|
||||
elementToHighlightId,
|
||||
setElementToHighlightId,
|
||||
] = React.useState<?string>(props.elements[0] ? props.elements[0].id : null);
|
||||
const [elementToHighlight, setElementToHighlight] = React.useState<any>(null);
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!shouldHighlightField) {
|
||||
setElementToHighlight(null);
|
||||
return;
|
||||
}
|
||||
const element = elementToHighlightId
|
||||
? document.getElementById(elementToHighlightId)
|
||||
: null;
|
||||
setElementToHighlight(element);
|
||||
},
|
||||
[elementToHighlightId, shouldHighlightField]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColumnStackLayout noMargin>
|
||||
{props.children}
|
||||
<ResponsiveLineStackLayout noMargin expand noColumnMargin>
|
||||
<Column expand noMargin justifyContent="center">
|
||||
<Toggle
|
||||
label="Highlight field"
|
||||
labelPosition="right"
|
||||
toggled={shouldHighlightField}
|
||||
onToggle={(e, active) => setShouldHighlightField(active)}
|
||||
/>
|
||||
</Column>
|
||||
<Column expand noMargin justifyContent="center">
|
||||
<CompactSelectField
|
||||
value={elementToHighlightId || ''}
|
||||
onChange={setElementToHighlightId}
|
||||
>
|
||||
{props.elements.map(element => (
|
||||
<option
|
||||
label={element.label}
|
||||
value={element.id}
|
||||
key={element.id}
|
||||
/>
|
||||
))}
|
||||
</CompactSelectField>
|
||||
</Column>
|
||||
</ResponsiveLineStackLayout>
|
||||
</ColumnStackLayout>
|
||||
{elementToHighlight && (
|
||||
<InAppTutorialElementHighlighter element={elementToHighlight} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ElementHighlighterProvider;
|
@@ -0,0 +1,59 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
// Keep first as it creates the `global.gd` object:
|
||||
import { testProject } from '../../GDevelopJsInitializerDecorator';
|
||||
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
import CompactInstancePropertiesEditor from '../../../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
import SerializedObjectDisplay from '../../SerializedObjectDisplay';
|
||||
import DragAndDropContextProvider from '../../../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
|
||||
export default {
|
||||
title: 'LayoutEditor/CompactInstancePropertiesEditor',
|
||||
component: CompactInstancePropertiesEditor,
|
||||
decorators: [paperDecorator],
|
||||
};
|
||||
|
||||
export const Instance2d = () => (
|
||||
<DragAndDropContextProvider>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SerializedObjectDisplay object={testProject.testLayout}>
|
||||
<CompactInstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={testProject.project}
|
||||
layout={testProject.testLayout}
|
||||
instances={[testProject.testSpriteObjectInstance]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
onGetInstanceSize={() => [100, 101, 102]}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
)}
|
||||
</I18n>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
|
||||
export const Instance3d = () => (
|
||||
<DragAndDropContextProvider>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SerializedObjectDisplay object={testProject.testLayout}>
|
||||
<CompactInstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={testProject.project}
|
||||
layout={testProject.testLayout}
|
||||
instances={[testProject.testLayoutInstance2]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
onGetInstanceSize={() => [100, 101, 102]}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
)}
|
||||
</I18n>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
@@ -10,6 +10,7 @@ import { testProject } from '../../GDevelopJsInitializerDecorator';
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
import InstancePropertiesEditor from '../../../InstancesEditor/InstancePropertiesEditor';
|
||||
import SerializedObjectDisplay from '../../SerializedObjectDisplay';
|
||||
import DragAndDropContextProvider from '../../../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
|
||||
export default {
|
||||
title: 'LayoutEditor/InstancePropertiesEditor',
|
||||
@@ -17,20 +18,42 @@ export default {
|
||||
decorators: [paperDecorator],
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SerializedObjectDisplay object={testProject.testLayout}>
|
||||
<InstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={testProject.project}
|
||||
layout={testProject.testLayout}
|
||||
instances={[testProject.testLayoutInstance1]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
onGetInstanceSize={() => [100, 101, 102]}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
)}
|
||||
</I18n>
|
||||
export const Instance2d = () => (
|
||||
<DragAndDropContextProvider>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SerializedObjectDisplay object={testProject.testLayout}>
|
||||
<InstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={testProject.project}
|
||||
layout={testProject.testLayout}
|
||||
instances={[testProject.testSpriteObjectInstance]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
onGetInstanceSize={() => [100, 101, 102]}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
)}
|
||||
</I18n>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
|
||||
export const Instance3d = () => (
|
||||
<DragAndDropContextProvider>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SerializedObjectDisplay object={testProject.testLayout}>
|
||||
<InstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={testProject.project}
|
||||
layout={testProject.testLayout}
|
||||
instances={[testProject.testLayoutInstance2]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
onGetInstanceSize={() => [100, 101, 102]}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
)}
|
||||
</I18n>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
|
@@ -0,0 +1,99 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
import muiDecorator from '../../ThemeDecorator';
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
|
||||
import CompactSelectField from '../../../UI/CompactSelectField';
|
||||
import { ColumnStackLayout } from '../../../UI/Layout';
|
||||
import Layers from '../../../UI/CustomSvgIcons/Layers';
|
||||
import ElementHighlighterProvider from '../../ElementHighlighterProvider';
|
||||
|
||||
export default {
|
||||
title: 'UI Building Blocks/CompactSelectField',
|
||||
component: CompactSelectField,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
const options = [
|
||||
<option>First option</option>,
|
||||
<option>Segundo</option>,
|
||||
<option>Troisième option</option>,
|
||||
];
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = React.useState<string>('');
|
||||
const [value1, setValue1] = React.useState<string>('');
|
||||
const [value2, setValue2] = React.useState<string>('');
|
||||
const [value3, setValue3] = React.useState<string>('');
|
||||
const [value4, setValue4] = React.useState<string>('');
|
||||
const [value5, setValue5] = React.useState<string>('');
|
||||
return (
|
||||
<ElementHighlighterProvider
|
||||
elements={[
|
||||
{ label: 'With icon', id: 'with-icon' },
|
||||
{ label: 'Without icon', id: 'without-icon' },
|
||||
]}
|
||||
>
|
||||
<ColumnStackLayout expand>
|
||||
<CompactSelectField value={value} onChange={setValue} id="without-icon">
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField value={value1} onChange={setValue1} errored>
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField value={value2} onChange={setValue2}>
|
||||
{[
|
||||
<option style={{ display: 'none' }}>Select an option</option>,
|
||||
...options,
|
||||
]}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
>
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField
|
||||
value={value3}
|
||||
onChange={setValue3}
|
||||
renderLeftIcon={className => <Layers className={className} />}
|
||||
leftIconTooltip={'Layer'}
|
||||
id="with-icon"
|
||||
>
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField
|
||||
value={value4}
|
||||
onChange={setValue4}
|
||||
errored
|
||||
renderLeftIcon={className => <Layers className={className} />}
|
||||
leftIconTooltip={'Layer'}
|
||||
>
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField
|
||||
value={value5}
|
||||
onChange={setValue5}
|
||||
renderLeftIcon={className => <Layers className={className} />}
|
||||
leftIconTooltip={'Layer'}
|
||||
>
|
||||
{[
|
||||
<option style={{ display: 'none' }}>Select an option</option>,
|
||||
...options,
|
||||
]}
|
||||
</CompactSelectField>
|
||||
<CompactSelectField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
renderLeftIcon={className => <Layers className={className} />}
|
||||
leftIconTooltip={'Layer'}
|
||||
>
|
||||
{options}
|
||||
</CompactSelectField>
|
||||
</ColumnStackLayout>
|
||||
</ElementHighlighterProvider>
|
||||
);
|
||||
};
|
@@ -0,0 +1,160 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import muiDecorator from '../../ThemeDecorator';
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
|
||||
import CompactSemiControlledNumberField from '../../../UI/CompactSemiControlledNumberField';
|
||||
import { ColumnStackLayout } from '../../../UI/Layout';
|
||||
import Angle from '../../../UI/CustomSvgIcons/Angle';
|
||||
import { Column } from '../../../UI/Grid';
|
||||
import ElementHighlighterProvider from '../../ElementHighlighterProvider';
|
||||
import Text from '../../../UI/Text';
|
||||
import Restore from '../../../UI/CustomSvgIcons/Restore';
|
||||
|
||||
export default {
|
||||
title: 'UI Building Blocks/CompactSemiControlledNumberField',
|
||||
component: CompactSemiControlledNumberField,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = React.useState<number>(45);
|
||||
const [value1, setValue1] = React.useState<number>(1);
|
||||
const [value2, setValue2] = React.useState<number>(25);
|
||||
const [value3, setValue3] = React.useState<number>(-12);
|
||||
const [value4, setValue4] = React.useState<number>(566560);
|
||||
const [value5, setValue5] = React.useState<number>(334);
|
||||
const [value6, setValue6] = React.useState<number>(334);
|
||||
|
||||
return (
|
||||
<ElementHighlighterProvider
|
||||
elements={[
|
||||
{ label: 'With icon', id: 'with-icon' },
|
||||
{ label: 'Without icon', id: 'without-icon' },
|
||||
{ label: 'With end adornment', id: 'with-end-adornment' },
|
||||
]}
|
||||
>
|
||||
<ColumnStackLayout expand useLargeSpacer>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
commitOnBlur
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
id="without-icon"
|
||||
/>
|
||||
|
||||
<div>Commits on blur: state value is {value}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
commitOnBlur
|
||||
value={value1}
|
||||
onChange={setValue1}
|
||||
errored
|
||||
errorText={'This value cannot be used'}
|
||||
/>
|
||||
|
||||
<div>Commits on blur: state value is {value1}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
commitOnBlur
|
||||
value={value2}
|
||||
onChange={setValue2}
|
||||
placeholder="With placeholder"
|
||||
/>
|
||||
|
||||
<div>Commits on blur: state value is {value2}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
disabled
|
||||
value={666}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<div>Disabled field</div>
|
||||
</Column>
|
||||
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
id="with-icon"
|
||||
value={value3}
|
||||
onChange={setValue3}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
useLeftIconAsNumberControl
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>State value is {value3}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
value={value4}
|
||||
onChange={setValue4}
|
||||
errored
|
||||
errorText={'An error occurred.'}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
useLeftIconAsNumberControl
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>State value is {value4}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
commitOnBlur
|
||||
value={value5}
|
||||
onChange={setValue5}
|
||||
placeholder="With placeholder"
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
useLeftIconAsNumberControl
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>Commits on blur: state value is {value5}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
disabled
|
||||
value={777}
|
||||
onChange={() => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
useLeftIconAsNumberControl
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>Disabled field</div>
|
||||
</Column>
|
||||
<Text>With end adornment</Text>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
value={value6}
|
||||
onChange={setValue6}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
useLeftIconAsNumberControl
|
||||
id="with-end-adornment"
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
<div>State value is {value6}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledNumberField
|
||||
disabled
|
||||
value={45.1}
|
||||
onChange={() => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle disabled'}
|
||||
useLeftIconAsNumberControl
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
<div>Disabled field</div>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</ElementHighlighterProvider>
|
||||
);
|
||||
};
|
@@ -0,0 +1,155 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import muiDecorator from '../../ThemeDecorator';
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
|
||||
import CompactSemiControlledTextField from '../../../UI/CompactSemiControlledTextField';
|
||||
import { ColumnStackLayout } from '../../../UI/Layout';
|
||||
import Angle from '../../../UI/CustomSvgIcons/Angle';
|
||||
import { Column } from '../../../UI/Grid';
|
||||
import ElementHighlighterProvider from '../../ElementHighlighterProvider';
|
||||
import Text from '../../../UI/Text';
|
||||
import Restore from '../../../UI/CustomSvgIcons/Restore';
|
||||
|
||||
export default {
|
||||
title: 'UI Building Blocks/CompactSemiControlledTextField',
|
||||
component: CompactSemiControlledTextField,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = React.useState<string>('');
|
||||
const [value1, setValue1] = React.useState<string>('');
|
||||
const [value2, setValue2] = React.useState<string>('');
|
||||
const [value3, setValue3] = React.useState<string>('');
|
||||
const [value4, setValue4] = React.useState<string>('');
|
||||
const [value5, setValue5] = React.useState<string>('');
|
||||
const [value6, setValue6] = React.useState<string>('');
|
||||
|
||||
return (
|
||||
<ElementHighlighterProvider
|
||||
elements={[
|
||||
{ label: 'With icon', id: 'with-icon' },
|
||||
{ label: 'Without icon', id: 'without-icon' },
|
||||
{ label: 'With end adornment', id: 'with-end-adornment' },
|
||||
]}
|
||||
>
|
||||
<ColumnStackLayout expand useLargeSpacer>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
commitOnBlur
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
id="without-icon"
|
||||
/>
|
||||
|
||||
<div>State value is {value}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
commitOnBlur
|
||||
value={value1}
|
||||
onChange={setValue1}
|
||||
errored
|
||||
errorText={'This value cannot be used'}
|
||||
/>
|
||||
|
||||
<div>State value is {value1}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
commitOnBlur
|
||||
value={value2}
|
||||
onChange={setValue2}
|
||||
placeholder="With placeholder"
|
||||
/>
|
||||
|
||||
<div>State value is {value2}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<div>Disabled field</div>
|
||||
</Column>
|
||||
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
id="with-icon"
|
||||
commitOnBlur
|
||||
value={value3}
|
||||
onChange={setValue3}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>State value is {value3}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
commitOnBlur
|
||||
value={value4}
|
||||
onChange={setValue4}
|
||||
errored
|
||||
errorText={'An error occurred.'}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>State value is {value4}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
commitOnBlur
|
||||
value={value5}
|
||||
onChange={setValue5}
|
||||
placeholder="With placeholder"
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>State value is {value5}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<div>Disabled field</div>
|
||||
</Column>
|
||||
<Text>With end adornment</Text>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
value={value6}
|
||||
onChange={setValue6}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
id="with-end-adornment"
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
<div>State value is {value6}</div>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<CompactSemiControlledTextField
|
||||
disabled
|
||||
value={'Disabled field'}
|
||||
onChange={valueAsString => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle disabled'}
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</ElementHighlighterProvider>
|
||||
);
|
||||
};
|
@@ -0,0 +1,143 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import muiDecorator from '../../ThemeDecorator';
|
||||
import paperDecorator from '../../PaperDecorator';
|
||||
|
||||
import CompactTextField from '../../../UI/CompactTextField';
|
||||
import { ColumnStackLayout } from '../../../UI/Layout';
|
||||
import Angle from '../../../UI/CustomSvgIcons/Angle';
|
||||
import Text from '../../../UI/Text';
|
||||
import ElementHighlighterProvider from '../../ElementHighlighterProvider';
|
||||
import Restore from '../../../UI/CustomSvgIcons/Restore';
|
||||
|
||||
export default {
|
||||
title: 'UI Building Blocks/CompactTextField',
|
||||
component: CompactTextField,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = React.useState<string>('');
|
||||
const [value1, setValue1] = React.useState<string>('');
|
||||
const [value2, setValue2] = React.useState<string>('');
|
||||
const [value3, setValue3] = React.useState<string>('');
|
||||
const [value4, setValue4] = React.useState<string>('');
|
||||
const [value5, setValue5] = React.useState<string>('');
|
||||
const [value8, setValue8] = React.useState<string>('');
|
||||
const [value6, setValue6] = React.useState<number>(0);
|
||||
const [value7, setValue7] = React.useState<number>(0);
|
||||
return (
|
||||
<ElementHighlighterProvider
|
||||
elements={[
|
||||
{ label: 'Text with icon', id: 'with-icon' },
|
||||
{ label: 'text without icon', id: 'without-icon' },
|
||||
{ label: 'Number with icon', id: 'number-with-icon' },
|
||||
{ label: 'Text with end adornment', id: 'text-with-end-adornment' },
|
||||
]}
|
||||
>
|
||||
<ColumnStackLayout expand>
|
||||
<CompactTextField value={value} onChange={setValue} id="without-icon" />
|
||||
<CompactTextField value={value1} onChange={setValue1} errored />
|
||||
<CompactTextField
|
||||
value={value2}
|
||||
onChange={setValue2}
|
||||
placeholder="With placeholder"
|
||||
/>
|
||||
<CompactTextField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<CompactTextField
|
||||
value={value3}
|
||||
onChange={setValue3}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
id="with-icon"
|
||||
/>
|
||||
<CompactTextField
|
||||
value={value4}
|
||||
onChange={setValue4}
|
||||
errored
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<CompactTextField
|
||||
value={value5}
|
||||
onChange={setValue5}
|
||||
placeholder="With placeholder"
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<CompactTextField
|
||||
disabled
|
||||
value={'disabled field'}
|
||||
onChange={() => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
/>
|
||||
<Text>Numbers</Text>
|
||||
<CompactTextField
|
||||
type="number"
|
||||
value={value6}
|
||||
onChange={valueAsString => {
|
||||
if (!valueAsString) setValue6(valueAsString);
|
||||
else setValue6(parseFloat(valueAsString) || 0);
|
||||
}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
useLeftIconAsNumberControl
|
||||
id="number-with-icon"
|
||||
/>
|
||||
<CompactTextField
|
||||
type="number"
|
||||
value={value7}
|
||||
onChange={valueAsString => {
|
||||
if (!valueAsString) setValue7(valueAsString);
|
||||
else setValue7(parseFloat(valueAsString) || 0);
|
||||
}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
placeholder="80"
|
||||
useLeftIconAsNumberControl
|
||||
/>
|
||||
<CompactTextField
|
||||
type="number"
|
||||
disabled
|
||||
value={45}
|
||||
onChange={valueAsString => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle disabled'}
|
||||
useLeftIconAsNumberControl
|
||||
/>
|
||||
<Text>With end adornment</Text>
|
||||
<CompactTextField
|
||||
value={value8}
|
||||
onChange={setValue8}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle'}
|
||||
useLeftIconAsNumberControl
|
||||
id="text-with-end-adornment"
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
<CompactTextField
|
||||
disabled
|
||||
value={'Disabled field'}
|
||||
onChange={valueAsString => {}}
|
||||
renderLeftIcon={className => <Angle className={className} />}
|
||||
leftIconTooltip={'Angle disabled'}
|
||||
useLeftIconAsNumberControl
|
||||
renderEndAdornmentOnHover={className => (
|
||||
<Restore className={className} />
|
||||
)}
|
||||
onClickEndAdornment={action('onClickEndAdornment')}
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
</ElementHighlighterProvider>
|
||||
);
|
||||
};
|
Reference in New Issue
Block a user