mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-27 08:39:59 +08:00
docs(*): add english version documents
This commit is contained in:
parent
8e2d86b7a7
commit
00bc9c3650
@ -2,7 +2,7 @@
|
||||
<img src="./docs/images/logo.png" alt="logo" width="200" />
|
||||
</div>
|
||||
<div align="center">
|
||||
<h1>Sunmao-UI</h1>
|
||||
<h1>Sunmao</h1>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@ -34,6 +34,8 @@ Both the UI component library itself and the low-code editor support custom exte
|
||||
|
||||
You are in type safety both when developing Sunmao components and when using the Sunmao editor. Sunmao heavily uses Typescript and JSON schema for a great type system.
|
||||
|
||||
For more details, read [Sunmao: A truly extensible low-code UI framework](./docs/en/what-is-sunmao.md).
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Sunmao is a monorepo project that includes the following packages:
|
||||
@ -84,8 +86,8 @@ The user's responsibility is to use the Sunmao components encapsulated by develo
|
||||
|
||||
We have prepared two tutorials for different roles. The user only needs to read the user's tutorial, but the developer has to read both.
|
||||
|
||||
- [User's Tutorial](./docs/zh/user.md)
|
||||
- [Developer's Tutorial](./docs/zh/developer.md)
|
||||
- [User's Tutorial](./docs/en/user.md)
|
||||
- [Developer's Tutorial](./docs/en/developer.md)
|
||||
|
||||
## Online Demo
|
||||
|
||||
|
522
docs/en/component.md
Normal file
522
docs/en/component.md
Normal file
@ -0,0 +1,522 @@
|
||||
# Component Development Documentation
|
||||
|
||||
A *Component* consists of two parts, a *Spec* and an *Implementation*; if you think of a *Component* as a class, a *Spec* is the interface of the class and an *Implementation* is the implementation of the class.
|
||||
|
||||
- Spec: a data structure used to describe the meta information, parameters, state, and behavior of a Component
|
||||
- Implementation: The function that is specifically responsible for rendering HTML elements.
|
||||
|
||||
## Component Development Tutorial
|
||||
|
||||
Let's learn how to develop a *Component* by using an example of an Input Component. This Input Component has the following capabilities.
|
||||
|
||||
- Configurable parameters such as placeholder, disabled, etc.
|
||||
- Allow external access to the current value
|
||||
- Can emit events such as `onBlur`
|
||||
- Allow updating its values from outside.
|
||||
- Allow insert child components, such as prefixes and suffixes, etc.
|
||||
|
||||
### Writing a Component Spec
|
||||
|
||||
A *Spec* is essentially a JSON that describes the parameters, behavior, and other information of a *Component*. All of our above information will be reflected in the *Spec*.
|
||||
|
||||
First, let's look at this Input Component *Spec* example.
|
||||
|
||||
```javascript
|
||||
{
|
||||
version: "arco/v1",
|
||||
metadata: {
|
||||
name: "input",
|
||||
displayName: "Input",
|
||||
exampleProperties: {
|
||||
placeholder: "Input here",
|
||||
disabled: fasle,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
}),
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
},
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ["content"],
|
||||
events: ["onBlur"],
|
||||
},
|
||||
};
|
||||
```
|
||||
It may be confusing to face so many fields at first, let's explain the meaning of each field one by one.
|
||||
|
||||
> For the detailed type and description of each parameter, please refer to the API reference below.
|
||||
|
||||
#### Component Spec Metadata
|
||||
|
||||
`metadata` is the meta-information of a *Component*, including information such as name and etc.
|
||||
|
||||
First, let's take a look at the complete type definition of *Spec*:
|
||||
|
||||
#### Component Spec Properties
|
||||
|
||||
`properties` describe the parameter names and types that the Component can accept. Two parameters are defined here, `placeholder` and `disabled`, of type *String* and *Boolean* respectively.
|
||||
|
||||
````
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
})
|
||||
````
|
||||
|
||||
You may be unfamiliar with this way of declaring types. As mentioned earlier, *Spec* is essentially a JSON, but JSON can not declare types like Typescript, so when we want to declare types in *Spec*, we use [JSONSchema](https://json-schema.org/). JSONSchema itself is JSON but can be used to declare the type of a JSON data structure.
|
||||
|
||||
But handwritten JSONSchema is very difficult, so we recommend using libraries, like [TypeBox](https://github.com/sinclairzx81/typebox) to assist in generating JSONSchema.
|
||||
|
||||
#### Component Spec State
|
||||
|
||||
`state` describes the state exposed by the *Component*. The input Component will only expose a `value`. The definition is similar to `properties`.
|
||||
|
||||
```
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
})
|
||||
```
|
||||
|
||||
#### Component Spec Method
|
||||
|
||||
`methods` describe the methods exposed by the Component. Our Input is going to expose the `updateValue` method so that the outside world can update its value.
|
||||
|
||||
The value corresponding to the key of `updateValue` in *Spec* is the acceptable parameter of `updateValue`, which is also defined by TypeBox.
|
||||
|
||||
```
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
#### Other properties of Component Spec
|
||||
|
||||
`slots` represent the *Slots* reserved by the *Component*. Each *Slot* can insert child Components. `slotProps` represents additional props that this slot will pass to the child Component when the child Component renders.
|
||||
|
||||
`styleSlots` represent the slots reserved by Component to insert styles. Generally, each Component needs to reserve a `content` *styleSlot*.
|
||||
|
||||
`events` represent events that the Component can emit for the outside world to listen to.
|
||||
|
||||
```
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ["content"],
|
||||
events: ["onBlur"],
|
||||
```
|
||||
|
||||
#### Component Spec example explanation
|
||||
|
||||
Now let's look at the example at the beginning and explain it again.
|
||||
|
||||
````javascript
|
||||
const InputSpec = {
|
||||
version: 'arco/v1',
|
||||
metadata: {
|
||||
name: 'input',
|
||||
displayName: 'Input',
|
||||
exampleProperties: {
|
||||
placeholder: 'Input here',
|
||||
disabled: fade,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
}),
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
},
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ['content'],
|
||||
events: ['onBlur'],
|
||||
},
|
||||
};
|
||||
````
|
||||
|
||||
This *Spec* declares an Input Component. It is part of the *arco/v1* component library and named input. Its only identifier is *arco/v1/input*.
|
||||
|
||||
The properties of this *Component* are declared with TypeBox. Its properties contain two parameters, `placeholder` and `disabled`. It also exposes a state `value` to external access.
|
||||
|
||||
In terms of behavior, it has an `updateValue` method that can allow externals to update their own value. As an Input, it also emits the `onBlur` event.
|
||||
|
||||
It also has two slots for inserting child Components, representing `prefix` and `suffix` respectively. These two slots have no additional properties to pass. There is also a *styleSlot* for content where custom styles can be added.
|
||||
|
||||
This is all the logic of this Input component. Let's take a look at how to implement the *Implementation* of this *Component*.
|
||||
|
||||
### Component Implementation
|
||||
|
||||
After completing the *Spec* of *Component*, we will develop the specific implementation of *Component*, which we call *Component Implementation*. In theory, a *Spec* can correspond to many components, just as an interface can correspond to the implementation of multiple classes.
|
||||
|
||||
Component Implementation is responsible for the specific rendering work. It is essentially a function. Its parameters are more complicated, and we will introduce them one by one according to the actual needs of the Input Component.
|
||||
|
||||
> Currently, Component Implementation must be a React functional component. In the future, we plan to let Sunmao support components using any technology stack, as long as the function returns a DOM element.
|
||||
|
||||
#### Read parameters of Component
|
||||
|
||||
First, the Component Implementation should accept the `properties` defined in the Spec, that is, `placeholder` and `disabled`. We can get it directly from the parameters. Then, we pass this parameter to an input JSX element and return it.
|
||||
|
||||
```jsx
|
||||
const InputImpl = props => {
|
||||
const { disabled, placeholder } = props;
|
||||
|
||||
return <input disabled={disabled} placeholder={placeholder} />;
|
||||
};
|
||||
````
|
||||
|
||||
It's that simple! In fact, this is already a complete Component Implementation, but we still have many features to implement.
|
||||
|
||||
#### Expose the state of the Component
|
||||
|
||||
Our Input will expose its own state, which requires a Sunmao built-in function `mergeState`. This function will be automatically injected into the Component Implementation and can be read like `properties`, called as follows.
|
||||
|
||||
```tsx
|
||||
const InputComponent = props => {
|
||||
const { mergeState } = props;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
// When the value changes, call the mergeState method.
|
||||
// Each time mergeState is called, the latest value will be merged into the Sunmao state tree for other components to access.
|
||||
useEffect(() => {
|
||||
mergeState({
|
||||
value,
|
||||
});
|
||||
}, [mergeState, value]);
|
||||
|
||||
return <input value={value} onChange={newVal => setValue(newVal)} />;
|
||||
};
|
||||
````
|
||||
|
||||
#### Expose Component's methods
|
||||
|
||||
Our Input also exposes its own method `updateValue`. This also requires the use of a built-in function `subscribeMethods`.
|
||||
|
||||
```typescript
|
||||
const InputComponent = props => {
|
||||
const { subscribeMethods } = props;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
// When the dom element is mounted, call subscribeMethods to register the updateValue method.
|
||||
// This way, the external Component can call updateValue to change the value of the input.
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
updateValue: ({ value: newValue }) => {
|
||||
setValue(newValue);
|
||||
},
|
||||
});
|
||||
}, [subscribeMethods]);
|
||||
|
||||
return <input value={value} />;
|
||||
};
|
||||
````
|
||||
|
||||
#### Emit Event
|
||||
|
||||
Our Input will also publish an `onBlur` event to notify other Components that it has lost focus. This requires another built-in parameter `callbackMap`.
|
||||
|
||||
```jsx
|
||||
const InputComponent = props => {
|
||||
const { callbackMap } = props;
|
||||
|
||||
// callbackMap already has the callback functions of the corresponding event, which can be called directly at the corresponding time.
|
||||
const onBlur = () => {
|
||||
if (callbackMap.onBlur) {
|
||||
callbackMap.onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
return <input onBlur={onBlur} />;
|
||||
};
|
||||
````
|
||||
|
||||
#### Reserve the position of Slot and StyleSlot
|
||||
|
||||
*Slot* and *StyleSlot* are slots that can insert custom content, and their positions need to be reserved in advance. They also need corresponding parameters, namely: `slotsElements` and `customStyle`. Both are js objects, and the corresponding content can be accessed through the names of slot and styleSlot.
|
||||
|
||||
The content of `slotsElements` is a function that takes `slotProps` and returns JSX elements. `slotProps` is optional and defined in the Spec.
|
||||
|
||||
`customStyle` is a map of styleSlot and CSS strings. Because it is a CSS string, it needs to be processed before it can be used. We recommend using `emotion` as a CSS-in-JS solution.
|
||||
|
||||
```tsx
|
||||
const InputImpl = props => {
|
||||
const { slotsElements, customStyle } = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{slotsElements.prefix()}
|
||||
<input className={css(customStyle.content)} />
|
||||
{slotsElements.suffix()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
````
|
||||
|
||||
> In fact, `customStyle` and `callbackMap` are actually parameters from Trait, but you don't need to know this for now. For details, please refer to the API documentation below.
|
||||
|
||||
#### Expose DOM elements to Sunmao
|
||||
|
||||
#### elementRef & getElement
|
||||
|
||||
Here is one last step, which has nothing to do with the logic of the Component itself, but Sunmao needs to get the DOM element of the Component runtime to get this component in the Editor. So this step needs to pass the DOM element of the Component to Sunmao.
|
||||
|
||||
Sunmao provides two methods to pass DOM elements: `elementRef` and `getElement`. The functions of the two methods are the same, but they are suitable for different scenarios. Just choose one implementation.
|
||||
|
||||
If the Component is implemented by React, it is more convenient to use `elementRef`, just pass `elementRef` to the `ref` property of the React component. If this method does not work, you can only use the generic `getElement` method to register the DOM element of the component.
|
||||
|
||||
```typescript
|
||||
const InputComponent = props => {
|
||||
const { getElement } = props;
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ele = ref.current?.dom;
|
||||
if (getElement && ele) {
|
||||
getElement(ele);
|
||||
}
|
||||
}, [getElement, ref]);
|
||||
|
||||
return <input ref={ref} />;
|
||||
};
|
||||
|
||||
// or
|
||||
|
||||
const InputComponent = props => {
|
||||
const { elementRef } = props;
|
||||
|
||||
return <input ref={elementRef} />;
|
||||
};
|
||||
````
|
||||
|
||||
#### Complete Component Implementation
|
||||
|
||||
Finally, we combine all the functions to implement all the logic of the Input Component Spec at the beginning.
|
||||
|
||||
```tsx
|
||||
const InputImpl = props => {
|
||||
const {
|
||||
disabled,
|
||||
placeholder,
|
||||
elementRef,
|
||||
slotsElements,
|
||||
customStyle,
|
||||
callbackMap,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
} = props;
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
mergeState({
|
||||
value,
|
||||
});
|
||||
}, [mergeState, value]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
updateValue: newValue => {
|
||||
setValue(newValue);
|
||||
},
|
||||
});
|
||||
}, [subscribeMethods]);
|
||||
|
||||
const onChange = e => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
if (callbackMap.onBlur) {
|
||||
callbackMap.onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{slotsElements.prefix()}
|
||||
<input
|
||||
ref={elementRef}
|
||||
className={css(customStyle.content)}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
{slotsElements.suffix()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
````
|
||||
|
||||
### Combine Spec and Implementation
|
||||
|
||||
After writing the *Spec* and *Implementation* of *Component*, the only step away from success is to encapsulate the two into a format acceptable to Sunmao runtime. This step is very simple, just call the `implementRuntimeComponent` function.
|
||||
|
||||
````javascript
|
||||
import { implementRuntimeComponent } from '@sunmao-ui/runtime';
|
||||
|
||||
const InputComponent = implementRuntimeComponent(InputSpec)(InputImpl);
|
||||
````
|
||||
|
||||
Finally, add this component to lib and pass it to `initSunmaoUI` when Sunmao starts up and you're done.
|
||||
|
||||
````javascript
|
||||
const lib: SunmaoLib = {
|
||||
components: [InputComponent],
|
||||
traits: [],
|
||||
modules: [],
|
||||
utilMethods: [],
|
||||
};
|
||||
````
|
||||
|
||||
## Component API Documentation
|
||||
|
||||
### Component Spec
|
||||
|
||||
The first-level fields of Spec are relatively straightforward.
|
||||
|
||||
| parameter name | type | description |
|
||||
| -------------- | ------------- | ------------------------------------------------------------ |
|
||||
| version | string | Component classification in Sunmao. The `version` of the same set of Components is usually the same. The format is "xxx/vx" , eg "`arco/v1`". |
|
||||
| kind | `"Component"` | Fixed, indicating that this is a Component Spec. |
|
||||
| metadata | | See below for details |
|
||||
| spec | | See below for details |
|
||||
|
||||
#### Metadata of Component Spec
|
||||
|
||||
Metadata contains the meta information of the Component.
|
||||
|
||||
| parameter name | type | remarks |
|
||||
| ----------------- | -------------------- | ------------------------------------------------------------ |
|
||||
| name | string | The name of the Component. The `version` and `name` of a Component together constitute the unique identifier of the Component. |
|
||||
| description | string? | |
|
||||
| displayName | string? | The name to display in the Component list in the Editor. |
|
||||
| exampleProperties | Record<string, any> | The initial `properties` for the Component when it was created in the Editor. |
|
||||
| annotations | Record<string, any>? | Some fields can be custom declared. |
|
||||
|
||||
#### Component Spec remaining fields
|
||||
|
||||
When defining `properties` and `state`, we use [JSONSchema](https://json-schema.org/). JSONSchema itself is JSON but can be used to declare the type of a JSON data structure. With type help, Sunmao can checksum and autocomplete for parameters and expressions.
|
||||
|
||||
| parameter name | type | remarks |
|
||||
| -------------- | --------------------------------------- | ------------------------------------------------------------ |
|
||||
| properties | JSONSchema | Parameters accepted by Component. |
|
||||
| state | JSONSchema | State exposed by Component. |
|
||||
| methods | Record<KMethodName, JSONSchema> | Method exposed by Component. The key is the name of the Method, and the value is the parameter of the Method. |
|
||||
| events | string[] | Events that the Component will emit. The array element is the name of the Event. |
|
||||
| slots | Record<string, {slotProps: JSONSchema}> | Component reserved slots that can be inserted into child Component. |
|
||||
| styleSlots | string[] | Component Reserved slots for adding styles. |
|
||||
|
||||
### Component Implementation Parameters
|
||||
|
||||
The parameter of Component Implementation is essentially an object, but it is actually a combination of several components. It can be roughly divided into:
|
||||
|
||||
- Properties declared in the Component Spec. This part is completely Component custom.
|
||||
- Sunmao Component API. This is what Sunmao injects into the Component.
|
||||
- Trait execution result. This is the result of the Trait passed to the component.
|
||||
- services. These are the individual service instances of the Sunmao runtime.
|
||||
|
||||
| Parameter Name | Type | Remarks | Source |
|
||||
| --------------- | ------------------------------------------------------------ | ---------------------------------------------------------- | -------- |
|
||||
| component | ComponentSchema | Component's Schema | API |
|
||||
| app | ApplicationSchema | Schema of the entire Application | API |
|
||||
| slotsElements | Record<string, (slotProps: any) => ReactElement[]> | List of child Component, see below for details | API |
|
||||
| mergeState | (partialState: object) => void | See below for details | API |
|
||||
| subscribeMethod | (methodsMap: Record<string, (params: object) => void>) => void | See below for details | API |
|
||||
| elementRef | React.Ref | see below | API |
|
||||
| getElement | (ele: HTMLElement) => void | See below for details | API |
|
||||
| services | object | Various service instances of Sunmao, see below for details | services |
|
||||
| customStyle | Record<string, string> | Custom style from Trait, see below | Trait |
|
||||
| callbackMap | Record<string, Function> | Callback function from Trait, see below for details | Trait |
|
||||
|
||||
#### Services
|
||||
|
||||
Services are instances of Sunmao's various services, including state management, event monitoring, component registration, and more. These Services are globally unique instances.
|
||||
|
||||
| parameter name | type | remarks |
|
||||
| ---------------- | ------------------------ | ------------------------------------------------------------ |
|
||||
| registry | Registry | All Sunmao Components, Traits, and Modules are registered on the Registry, where you can find their corresponding Spec and Implementation. |
|
||||
| stateManager | StateManager | StateManager manages Sunmao's global state store and also has the function of eval expression. |
|
||||
| globalHandlerMap | GlobalHandlerMap | GlobalHandlerMap manages all Component Method instances. |
|
||||
| apiService | ApiService | ApiService is the global event bus. |
|
||||
| eleMap | Map<string, HTMLElement> | eleMap stores all Component's DOM elements. |
|
||||
|
||||
> ⚠️ In general, you do not need to use these services. They may only be used when implementing some special requirements.
|
||||
|
||||
#### Sunmao Component API
|
||||
|
||||
##### `mergeState`
|
||||
|
||||
A Component can have its own local state, but if a Component exposes its own local state to other Sunmao components, the state must be merged into Sunmao's global state store through the `mergeState` function.
|
||||
|
||||
When `mergeState` is called, all expressions referencing the state are updated immediately, as are their corresponding components.
|
||||
|
||||
##### `subscribeMethods`
|
||||
|
||||
The function of `subscribeMethods` is to register the behavior of components in Sunmao in the form of functions for other components to call.
|
||||
|
||||
There is no limit to the Method registered by Component, it can accept custom parameters, and the parameter type should have been declared in Component Spec. Parameters will be passed by Sunmao when calling.
|
||||
|
||||
#### Trait execution result
|
||||
|
||||
All Trait execution results will be passed to Component as parameters. These parameters are generated according to the agreed interface. Trait and Component can only interact through this interface. **Components must handle the following parameters correctly**, otherwise, Components cannot interact with other Traits.
|
||||
|
||||
##### `customStyle`
|
||||
|
||||
In Sunmao, styles are expressed in CSS. customStyle is a map of styleSlot and CSS. You need to decide for yourself how to use CSS. Sunmao uses *emotion* as the runtime CSS-In-JS scheme, you can also choose your preferred library.
|
||||
|
||||
**We agree that a Component must implement at least one `content` styleSlot as the default styleSlot. **
|
||||
|
||||
##### `callbackMap`
|
||||
|
||||
`callbackMap` is how components expose events to the outside world. It is a Map of Event names and callback functions. If another Component listens to a Component's Event, the event callback function will be passed to the Component through `callbackMap`. You need to call this callback function in the code corresponding to the Event, so that other Component can successfully listen to the Component's Event.
|
||||
|
||||
#### Sunmao Runtime API
|
||||
|
||||
Sunmao does not limit the internal logic and implementation of Component, but there are some interfaces that must be implemented, otherwise Component will not be able to interact with Sunmao. These interfaces will be passed to Component Implementation as parameters, the parameters are as follows:
|
||||
|
||||
##### slotsElements
|
||||
|
||||
`slotsElements` is a list of child Components in each slot. A Component can declare its own Slot, and each Slot is where the child Component is inserted.
|
||||
|
||||
> If the Component has only one slot, we agree that the slot name is `content`.
|
||||
|
||||
##### elementRef & getElement
|
||||
|
||||
The role of these two APIs is to register the DOM elements rendered by the Component with Sunmao. Sunmao must obtain the DOM elements of each Component to implement some functions, such as the ability to highlight Components in the editor. Other Components and Traits can also use the Component's DOM elements to achieve functions.
|
13
docs/en/developer.md
Normal file
13
docs/en/developer.md
Normal file
@ -0,0 +1,13 @@
|
||||
## Sunmao Developer Tutorial
|
||||
|
||||
### [Installing Sunmao](./install.md)
|
||||
|
||||
### [Component Development](./component.md)
|
||||
|
||||
### [Trait Development](./trait.md)
|
||||
|
||||
### Advanced (TODO)
|
||||
|
||||
- [Expression](./expression)
|
||||
- `ExpressionOption` for Property
|
||||
- Life cycle
|
33
docs/en/expression.md
Normal file
33
docs/en/expression.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Expression expression design
|
||||
|
||||
# Supported usages
|
||||
|
||||
`'{{ value }}'` '100'
|
||||
|
||||
`'{{ value.toUppercase() }}'` 'ABC'
|
||||
|
||||
`'Hello, {{ value }}!'` 'Hello, world'
|
||||
|
||||
`'{{ $listItem.name }} is {{ $listItem.age }} years old'` 'Tom is 10 years old'
|
||||
|
||||
`'{{ $listItem.name }} is in {{ root.listTitle }} list'` 'Tom is in UserList list'
|
||||
|
||||
# nested expressions
|
||||
|
||||
Expressions support nesting, which is useful in the context of lists and modules. E.g:
|
||||
|
||||
`'{{ {{$listItem.value}}Input.value + {{$moduleId}}Fetch.value }}!'`
|
||||
|
||||
The parser will `eval` and concatenate strings from the inside out.
|
||||
|
||||
# some wrong usages
|
||||
|
||||
`{{ [1,2,3] }}string` (result will be `'[Object Object]'`).
|
||||
|
||||
# special keywords
|
||||
|
||||
`$i` `$listItem` and `$moduleId` are keywords, used in lists and modules.
|
||||
|
||||
# error handling
|
||||
|
||||
When the expression cannot be parsed or an error is reported during the parsing process, the expression itself will be returned directly.
|
114
docs/en/install.md
Normal file
114
docs/en/install.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Install Sunmao Tutorial
|
||||
|
||||
Sunmao is a low-code framework. You can use Sunmao's Cloud version, or you can deploy a Sunmao application yourself.
|
||||
|
||||
### Install Sunmao
|
||||
|
||||
Sunmao has two packages, runtime and editor. The runtime is the package used to run Sunmao applications, and the editor is used to run the Sunmao editor. The developer selects the corresponding package according to the needs of the scenario. If you are experiencing Sunmao for the first time, it is recommended to choose the editor.
|
||||
|
||||
No matter whether you choose runtime or editor, you need to install react, because Sunmao runs based on react.
|
||||
|
||||
#### Install Sunmao runtime
|
||||
|
||||
````
|
||||
yarn add @sunmao-ui/runtime
|
||||
yarn add react
|
||||
yarn add react-dom
|
||||
````
|
||||
|
||||
#### Install Sunmao editor
|
||||
|
||||
````
|
||||
yarn add @sunmao-ui/editor
|
||||
yarn add react
|
||||
yarn add react-dom
|
||||
````
|
||||
|
||||
### Start Sunmao
|
||||
|
||||
The runtime and editor are started in roughly the same way, but with different method names and parameters.
|
||||
|
||||
#### Start Sunmao runtime
|
||||
|
||||
````
|
||||
import { initSunmaoUI } from "@sunmao-ui/runtime";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
const { App } = initSunmaoUIEditor({});
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
````
|
||||
|
||||
#### Start Sunmao editor
|
||||
|
||||
````
|
||||
import { initSunmaoUIEditor } from "@sunmao-ui/editor";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
const { Editor } = initSunmaoUIEditor({});
|
||||
|
||||
ReactDOM.render(<Editor />, document.getElementById("root"));
|
||||
````
|
||||
|
||||
### Import Sunmao Lib
|
||||
|
||||
Sunmao Lib is a data structure composed of customized Sunmao Component, Trait, Module, and UtilMethod, and is an interface for developers to import custom component libraries.
|
||||
|
||||
````javascript
|
||||
const lib = {
|
||||
components: [],
|
||||
traits: [],
|
||||
modules: [],
|
||||
utilMethods: [],
|
||||
};
|
||||
````
|
||||
|
||||
When importing, just pass lib to `initSunmaoUI`.
|
||||
|
||||
````javascript
|
||||
const { App } = initSunmaoUIEditor({ libs: [lib] });
|
||||
````
|
||||
|
||||
For how to encapsulate custom Component, etc., you can refer to the Component development documentation.
|
||||
|
||||
### API
|
||||
|
||||
Sunmao's initialization function accepts some parameters. These parameters are optional and are used to implement some custom logic.
|
||||
|
||||
#### Parameters of `initSunmaoUI`
|
||||
|
||||
| Name | Type | Introduction |
|
||||
| ------------ | ------------------------------------ -------------------------------------------------- -------------- | ----------------------------------- -------------------- |
|
||||
| dependencies | Record<string, any> | Dependencies that can be used when the expression is evaluated, can be any JS variable. |
|
||||
| hooks | {<br /> didMount?: () => void;<br /> didUpdate?: () => void;<br /> didDomUpdate?: () => void;<br />} | Sunmao Application lifecycle hooks. |
|
||||
| libs | SunmaoLib[] | Used to load custom Component, Trait, Module, UtilMethod. |
|
||||
|
||||
#### The return result of `initSunmaoUI`
|
||||
|
||||
| Name | Type | Introduction |
|
||||
| -------- | --------------- | ------------------------ ------- |
|
||||
| App | React Component | The React component that renders the Sunmao app. |
|
||||
| Registry | Registry | Register custom Component, Trait. |
|
||||
|
||||
#### Parameters of `initSunmaoUIEditor`
|
||||
|
||||
| Name | Type | Introduction |
|
||||
| ------------------ | ------------------------------ -------------------------------------------------- --------------------- | ---------------------------- ------------------------------ |
|
||||
| App | React Component | The React component that renders the Sunmao app. |
|
||||
| widgets | ImplementedWidget[] | Custom editor widget. |
|
||||
| storageHandler | {<br />onSaveApp?: (app: Application) => void,<br />onSaveModules?: (module: Module[]) => void<br />} | callback function for persistent storage of Application and Module. |
|
||||
| defaultApplication | Application | Default Application Schema. |
|
||||
| defaultModules | Module[] | Default Module. |
|
||||
| runtimeProps | Sunmao Runtime Props | Same as `initSunmaoUI` parameter |
|
||||
|
||||
#### Return result of `initSunmaoUIEditor`
|
||||
|
||||
Same as the return result of `initSunmaoUI`.
|
||||
|
||||
### Parameters of App component
|
||||
|
||||
| Name | Type | Introduction |
|
||||
| ------- | ----------- | ----------------------------- ----- |
|
||||
| options | Application | Application Schema for Sunmao applications. |
|
251
docs/en/trait.md
Normal file
251
docs/en/trait.md
Normal file
@ -0,0 +1,251 @@
|
||||
# Trait development documentation
|
||||
|
||||
In this chapter you can learn how to implement a custom *Trait* through an example of a Timer Trait.
|
||||
|
||||
*Trait* development and *Component* have many similarities.
|
||||
|
||||
| | Trait | Component |
|
||||
| ------------------------------- | ----- | --------- |
|
||||
| Owned Sepc | ✅ | ✅ |
|
||||
| Has properties, state, methods | ✅ | ✅ |
|
||||
| Has Implementation | ✅ | ✅ |
|
||||
| Implementation is a function | ✅ | ✅ |
|
||||
|
||||
*Trait* is essentially a function. The role of Trait is to increase the capabilities of the Component. If the object-oriented analogy is still used, Trait is a decorator of Component. For example, adding styles, adding state, etc.
|
||||
|
||||
The main idea of Trait is to accept a series of parameters, run some logic, and return *TraitResult*, thereby enhancing the capabilities of the Component. Obviously, this is the logic of a pure function. Therefore, we recommend implementing Trait as a pure function. Even if it is not a pure function, the implementation needs to consider the result when the Trait is repeatedly executed, and try to avoid unexpected side effects.
|
||||
|
||||
> ⚠️ Since Trait is a pure function, React hooks cannot be used in Trait.
|
||||
|
||||
Since Trait doesn't have its own id, Trait and Component share id. That is to say, all the States and Methods exposed by the Trait will be added to the Component, and accessing and calling them is no different from what the Component implements itself.
|
||||
|
||||
## Trait development tutorial
|
||||
|
||||
Next, let's learn how to develop a Trait with an example. We want to implement a Timer Trait that has the following capabilities:
|
||||
|
||||
- Specify a timer for a period of time, and an alert will pop up after the timer expires
|
||||
- Provides the function of clearing the timer
|
||||
- You can choose whether to start a timer immediately after the Component is mounted
|
||||
- Get timer status (`waiting`, `finished`, `stopped`)
|
||||
|
||||
### Trait Spec
|
||||
|
||||
#### Trait Spec Example
|
||||
|
||||
First, let's write a Trait Spec and define some basic information for the Trait, such as version and name:
|
||||
|
||||
```jsx
|
||||
{
|
||||
version: 'custom/v1',
|
||||
metadata: {
|
||||
name: 'timer',
|
||||
description: 'Create a timer to alert, and could be clear.',
|
||||
},
|
||||
spec: {
|
||||
properties: PropsSpec, // see below for details
|
||||
state: StateSpec, // see below for details
|
||||
methods, // see below
|
||||
},
|
||||
}
|
||||
````
|
||||
|
||||
#### Trait Properties Spec
|
||||
|
||||
Next, we can start thinking about how to design our Timer Trait. First, we need to design the parameters that need to be passed into the Timer Trait, that is, the Spec of Properties.
|
||||
|
||||
First you need a `time` parameter to control the time of the timer, and a `content` parameter to display the content of the alert.
|
||||
|
||||
In addition, you need to control whether to trigger immediately after the Component is mounted, so there is also an `immediate` parameter.
|
||||
|
||||
So we can get our Props Spec:
|
||||
|
||||
```jsx
|
||||
const PropsSpec = Type.Object({
|
||||
time: Type.Number(),
|
||||
content: Type.String(),
|
||||
immediate: Type.Boolean(),
|
||||
});
|
||||
````
|
||||
|
||||
> Trait's Props Spec is also defined by TypeBox.
|
||||
|
||||
#### Trait State Spec
|
||||
|
||||
Since Timer Trait also needs to expose state, we also need to add State Spec to define the State exposed by Timer Trait and its type.
|
||||
|
||||
```jsx
|
||||
const StateSpec = Type.Object({
|
||||
status: Type.KeyOf(
|
||||
Type.Object({
|
||||
waiting: Type.Boolean(),
|
||||
finished: Type.Boolean(),
|
||||
stopped: Type.Boolean(),
|
||||
})
|
||||
),
|
||||
});
|
||||
````
|
||||
|
||||
#### Trait Methods Spec
|
||||
|
||||
We also need to provide Method to call externally to start or clear the timer, so we also need to define Method. Here we provide `start` and `stop` two methods.
|
||||
|
||||
```jsx
|
||||
const methods = [
|
||||
{
|
||||
name: 'start',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
{
|
||||
name: 'clear',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
];
|
||||
````
|
||||
|
||||
### Trait Factory & Implementation
|
||||
|
||||
After defining the complete Trait Spec, we can start implementing the logic of Timer Trait. The first thing to do is to implement a factory function of a Trait, which returns the Implementation of the real Trait.
|
||||
|
||||
In the factory function, we can declare some variables to store data, such as Timer Trait Here we need to create a Map to record the Timer corresponding to each Component so that it can be used to clear the timer.
|
||||
|
||||
> Since Trait is essentially a pure function and cannot have state, Trait needs a factory function. The factory function provides a closure to store state for the trait. It should be noted that for the same Trait, all Trait Implementation instances share the same js closure.
|
||||
|
||||
Let's take a look at the complete implementation code:
|
||||
|
||||
```jsx
|
||||
const TimerTraitFactory = () => {
|
||||
const map = new Map();
|
||||
|
||||
return props => {
|
||||
const {
|
||||
time,
|
||||
content,
|
||||
immediate,
|
||||
services,
|
||||
componentId,
|
||||
subscribeMethods,
|
||||
mergeState,
|
||||
} = props;
|
||||
// implement clear method
|
||||
const clear = () => {
|
||||
const timer = map.get(componentId);
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
mergeState({
|
||||
status: 'stopped',
|
||||
});
|
||||
map.delete(componentId);
|
||||
}
|
||||
};
|
||||
// implement start method
|
||||
const start = () => {
|
||||
clear();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
// When the timer expires, an alert pops up
|
||||
alert(content);
|
||||
mergeState({
|
||||
status: 'finished',
|
||||
});
|
||||
}, time || 0);
|
||||
|
||||
mergeState({
|
||||
status: 'waiting',
|
||||
});
|
||||
map.set(componentId, timer);
|
||||
};
|
||||
|
||||
// register the method to the component
|
||||
subscribeMethods({
|
||||
start,
|
||||
clear,
|
||||
});
|
||||
|
||||
// return Trait Result
|
||||
// Use the componentDidMount and componentDidUnmount lifecycles to start and clear timers
|
||||
return {
|
||||
props: {
|
||||
// start the timer after the Component hangs
|
||||
componentDidMount: [
|
||||
() => {
|
||||
if (immediate) {
|
||||
start();
|
||||
}
|
||||
},
|
||||
],
|
||||
componentDidUnmount: [
|
||||
() => {
|
||||
clear();
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
````
|
||||
|
||||
### Create Trait
|
||||
|
||||
Finally, generate the final Trait through the `implementRuntimeTrait` provided by `runtime`. So far, the complete implementation of Timer Trait is completed here.
|
||||
|
||||
```jsx
|
||||
export default implementRuntimeTrait({
|
||||
version: 'custom/v1',
|
||||
metadata: {
|
||||
name: 'timer',
|
||||
description: 'Create a timer to alert, and could be clear.',
|
||||
},
|
||||
spec: {
|
||||
properties: PropsSpec,
|
||||
state: StateSpec,
|
||||
methods,
|
||||
},
|
||||
})(TimerTraitFactory);
|
||||
````
|
||||
|
||||
## Trait API Documentation
|
||||
|
||||
Trait Spec and Component are basically the same, with only a few differences.
|
||||
|
||||
| parameter name | type | description |
|
||||
| -------- | --------- | ------------------------------ -------- |
|
||||
| version | string | |
|
||||
| kind | `"Trait"` | Fixed, indicating that this is a Trait Spec. |
|
||||
| metadata | | See below for details |
|
||||
| spec | | See below for details |
|
||||
|
||||
### Metadata of Trait Spec
|
||||
|
||||
The metadata content of Trait Spec is less than that of Component.
|
||||
|
||||
| parameter name | type | remarks |
|
||||
| ----------- | ------------------- | ------------ |
|
||||
| name | string | Trait's name |
|
||||
| annotations | Record<string, any> | |
|
||||
|
||||
### Spec field of Trait Spec
|
||||
|
||||
| parameter name | type | remarks |
|
||||
| ---------- | ------------------------------- | ---- |
|
||||
| properties | JSONSchema | |
|
||||
| state | JSONSchema | |
|
||||
| methods | Record<KMethodName, JSONSchema> | |
|
||||
|
||||
### Parameters of Trait Implementation
|
||||
|
||||
The parameters accepted by Trait Implementation are almost the same as Component, you can refer to the relevant chapter of Component.
|
||||
|
||||
### TraitResult
|
||||
|
||||
TraitResult is the most important part of Trait. It is the return result of the Trait function and will be passed directly to the Component as a parameter. For more details, please refer to the relevant chapters of the parameters of Component Implementation.
|
||||
|
||||
TraitResult is an object with the following properties:
|
||||
|
||||
| Parameter name | Type | Required | Description |
|
||||
| ------------------- | ------------------------ | ---- ---- | --------------------------------------------- ------------------- |
|
||||
| customStyle | Record<string, string> | No | Style map passed to the Component. The value of the object should be a CSS string. |
|
||||
| callbackMap | Record<string, Function> | No | Map of callback functions for Component. Mainly used for Event Trait. |
|
||||
| componentDidUnmount | Function[] | No | Lifecycle hook. Will be executed after the Component is unloaded. |
|
||||
| componentDidMount | Function[] | No | Lifecycle hook. Will be executed after the Component is mounted. |
|
||||
| componentDidUpdate | Function[] | No | Lifecycle hook. Will be executed after the Component is updated. |
|
113
docs/en/user.md
Normal file
113
docs/en/user.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Sunmao User Tutorials
|
||||
|
||||
## Basic concepts
|
||||
|
||||
### Application
|
||||
|
||||
Application is the biggest concept in Sunmao. An Application is a complete working front-end application.
|
||||
|
||||
### Component
|
||||
|
||||
Component is the smallest unit in Sunmao that is responsible for rendering UI.
|
||||
|
||||
Components are mostly common UI components, such as Button, Input, etc., but can also be custom highly encapsulated business logic components.
|
||||
|
||||
We can define the style and behavior of a Component by passing parameters to it.
|
||||
|
||||
### DataSource
|
||||
|
||||
DataSource is the component that carries data in SunmaoUI. DataSource can be of various types: global state, API request, LocalStorage ......
|
||||
|
||||
Essentially, DataSource is a Component, but it does not render the UI, it only fetches data.
|
||||
|
||||
### Trait
|
||||
|
||||
Trait is a unique Sunmao concept; a Trait can be understood as a feature that a Component has when it is added to a Component. For example, if a **State Trait** is added to a Component, then a state is added to that Component. For another example, if a **Style Trait** is added to a Component, then a custom style is added to the Component.
|
||||
|
||||
## Sunmao Development Guide
|
||||
|
||||
### How to layout?
|
||||
|
||||
Sunmao has a built-in Stack Component called `core/v1/stack`. With this component, you can adjust the spacing, orientation, and alignment of the Component ......
|
||||
|
||||
> The Stack layout is essentially a Flex layout.
|
||||
|
||||
When using the Stack layout, the order and parent-child relationship between Components are important. Here we will describe how to express the parent-child relationship of Components in Sunmao.
|
||||
|
||||
#### Slot
|
||||
|
||||
In Sunmao, the parent-child relationship is represented by the Slot.
|
||||
|
||||
A Slot is a slot where a Component inserts its child components. Each Slot can insert an unlimited number of child components. A Component can have more than one Slot, so the number of Slots and their names are defined by the Component. Generally, we agree that if a Component has only one Slot, the default name of that Slot is `content`.
|
||||
|
||||
In the following example.
|
||||
|
||||
`modal6` has two Slots, `content` and `footer`. Neither Slot currently has a Component, so it is rendered as Empty.
|
||||
|
||||
`button5` has no Slot, so there is no expanded arrow on the left.
|
||||
|
||||
`hstack4` has a Slot, which is `content`. Because it has only one slot, the slot name is omitted from the display.
|
||||
|
||||
`vstack2` has a Slot, which currently has a `text3` Component.
|
||||
|
||||
![componentTree.png](../images/componentTree.png)
|
||||
|
||||
### How do I configure parameters for a component?
|
||||
|
||||
First, select a component in the canvas or in the left column, then the right column will show the component's parameter form.![Screenshot 2022-05-07 5.37.44 PM](../images/componentForm.png)
|
||||
|
||||
If you need to enter complex logic or use the state of another Component, you'll need to use expressions.
|
||||
|
||||
#### expressions
|
||||
|
||||
For example, you want to decide whether to disable a button based on the value of an Input. Then, you need to set the `disabled` value of the button to `{{input.value ! == ''}}`. This is an expression. Where `input` is the ID of the object you want to access, and you must use the correct ID to access the object. `value` is the state exposed by Input.
|
||||
|
||||
The expression is a string enclosed by `{{ }}`. This string can be any legal Javascript code. Expressions support nesting and splicing with normal strings.
|
||||
|
||||
For more expression usage, please refer to: [Expression Expression Design](. /expression.md).
|
||||
|
||||
> For non-input controls on the form, you can switch this space to input by clicking the JS button next to the control, and then you can enter the expression.
|
||||
|
||||
### How do you manage data?
|
||||
|
||||
An application needs data in addition to UI. In Sunmao, the logic of data is generally managed by DataSource. sunmao has some common DataSource built in, such as State, LocalStorage, API and so on.
|
||||
|
||||
In the expression, you can access the value of the DataSource just like a normal Component.
|
||||
|
||||
| DataSource | Usage |
|
||||
| ------------ | -------------------------- |
|
||||
| state | Add global state |
|
||||
| localStorage | Save data to localStorage |
|
||||
| API | Sending HTTP requests |
|
||||
|
||||
### How do I modify the style?
|
||||
|
||||
To modify the style, you need to click the "+" button on the right side of the Style in the Component's form.
|
||||
|
||||
Then select the Style Slot you want to add style to, which is the HTML element you want to modify the style.
|
||||
|
||||
Finally, enter the CSS code in the black field and the style will take effect on the Component.
|
||||
|
||||
![Screenshot 2022-05-07 5.49.24 PM](../images/modifyStyle.png)
|
||||
|
||||
### How do I listen to the component's events?
|
||||
|
||||
Each Component has its own Event and Method; Event is the event that the Component will trigger, and Method is the behavior that the Component can be called on. For example, when a Button is clicked, it opens a Dialog, which requires the Button to emit an `onlick` Event and the Dialog to have an `openDialog ` Method.
|
||||
|
||||
#### Event Handler
|
||||
|
||||
To listen for an Event, you must add an Event Handler to the Component that emits the Event.
|
||||
|
||||
The Event Handler has these elements.
|
||||
|
||||
- The Event it is listening to
|
||||
- The id of the calling Component
|
||||
- The Method of the calling Component
|
||||
- The parameters passed when the Method is called
|
||||
- Some other configurations
|
||||
|
||||
> Multiple Event Handlers can be added to a Component.
|
||||
|
||||
The following is an example showing how to open a Dialog when a Button is clicked.
|
||||
|
||||
![Image.png](../images/eventHandler-en.png)
|
@ -1,4 +1,4 @@
|
||||
# sunmao-ui: A truly extensible low-code UI framework
|
||||
# Sunmao: A truly extensible low-code UI framework
|
||||
|
||||
Although more and more people are becoming interested in low-code development, they still have some reservations.
|
||||
|
||||
@ -8,37 +8,37 @@ That is reasonable because no user wants to be told that they cannot implement c
|
||||
|
||||
Some products, wisely, limit the use of low-code to specific areas, such as internal tools and landing pages, where users value productivity over flexibility. It works until developers want to boost their productivity in more general scenarios.
|
||||
|
||||
That's why we started working on sunmao-ui, an open-source low-code UI framework with a focus on extensibility.
|
||||
That's why we started working on Sunmao, an open-source low-code UI framework with a focus on extensibility.
|
||||
|
||||
## Design principles
|
||||
|
||||
Sunmao is a Chinese phrase '榫卯' that refers to one of the oldest and most durable wooden architectural techniques used in Chinese history. We love this name because it represents how we put together building blocks and make solid applications in sunmao-ui.
|
||||
Sunmao is a Chinese phrase '榫卯' that refers to one of the oldest and most durable wooden architectural techniques used in Chinese history. We love this name because it represents how we put together building blocks and make solid applications in Sunmao.
|
||||
|
||||
Several design principles are followed while developing sunmao-ui to ensure proper abstraction.
|
||||
Several design principles are followed while developing Sunmao to ensure proper abstraction.
|
||||
|
||||
### 1. Clarify the jobs of different roles
|
||||
|
||||
Sunmao-ui's aha moment came when we learned that persons who develop applications are normally separated into two groups: component developers and application builders.
|
||||
Sunmao's aha moment came when we learned that persons who develop applications are normally separated into two groups: component developers and application builders.
|
||||
|
||||
Component developers are concerned with code quality, performance, and user experience when creating reusable components. After creating a component in their favourite way, they can wrap it as a sunmao-ui component and register it to the platform.
|
||||
Component developers are concerned with code quality, performance, and user experience when creating reusable components. After creating a component in their favourite way, they can wrap it as a Sunmao component and register it to the platform.
|
||||
|
||||
Application builders pick reusable components and incorporate business logic into them. They can complete this task quickly and effectively in sunmao-ui with much less code.
|
||||
Application builders pick reusable components and incorporate business logic into them. They can complete this task quickly and effectively in Sunmao with much less code.
|
||||
|
||||
Applications are made every hour, but components are created significantly less frequently. With sunmao-roles ui's division, users may delegate component creation to senior frontend developers while junior frontend developers, backend developers, and even non-coders build and iterate apps.
|
||||
|
||||
### 2. Leverage the power of coding, not limit
|
||||
|
||||
As previously stated, sunmao-ui will not compel users to create applications only with simple components like buttons and inputs. Instead, component developers may register sophisticated, domain-specific components that meet your requirements and follow your design system.
|
||||
As previously stated, Sunmao will not compel users to create applications only with simple components like buttons and inputs. Instead, component developers may register sophisticated, domain-specific components that meet your requirements and follow your design system.
|
||||
|
||||
We also put a lot of effort to ensure that sunmao-ui's implementation does not impose any limit on users. For example, many low-code tools' GUI editors offer highlight when you hover over or select a component. Some of them implement this by wrapping the component with a `<div>` element and styling it.
|
||||
We also put a lot of effort to ensure that Sunmao's implementation does not impose any limit on users. For example, many low-code tools' GUI editors offer highlight when you hover over or select a component. Some of them implement this by wrapping the component with a `<div>` element and styling it.
|
||||
|
||||
However, this is unsatisfactory for sunmao-ui because it increases the number of DOM nodes, changes the hirarchy of DOM nodes, and prevents components from having inline layout.
|
||||
However, this is unsatisfactory for Sunmao because it increases the number of DOM nodes, changes the hirarchy of DOM nodes, and prevents components from having inline layout.
|
||||
|
||||
Although It took us longer to develop a solution that overcames all of the drawbacks, but we believe it was worthwhile because it does not limit the power of coding.
|
||||
|
||||
### 3. Extensible in every aspect
|
||||
|
||||
Sunmao-ui contains three layers: the core spec, the runtime, and the editor.
|
||||
Sunmao contains three layers: the core spec, the runtime, and the editor.
|
||||
|
||||
The schema of a component or an application is described in the core spec. Aside from the usual fields, users can add annotations and use them later in the runtime or editor.
|
||||
|
||||
@ -48,13 +48,13 @@ Users can also change how a component's property is inputted and displayed in th
|
||||
|
||||
### 4. Concentration rather than divergence
|
||||
|
||||
Instead of creating a full-stack low-code solution, we concentrate on the UI part, and sunmao-ui is currently just available on the web.
|
||||
Instead of creating a full-stack low-code solution, we concentrate on the UI part, and Sunmao is currently just available on the web.
|
||||
|
||||
Since we believe that backend technologies are fast improving these days ,creating a UI-only solution that can be utilized with any type of backend service would provide our users with the most flexibility.
|
||||
|
||||
## How does it work
|
||||
|
||||
We believe that web UI development is already quite mature, and sunmao-ui just adds a few necessary primitives for the low-code scenario, namely:
|
||||
We believe that web UI development is already quite mature, and Sunmao just adds a few necessary primitives for the low-code scenario, namely:
|
||||
|
||||
- reactive
|
||||
- events and methods
|
||||
@ -65,7 +65,7 @@ We believe that web UI development is already quite mature, and sunmao-ui just a
|
||||
|
||||
### Respond to the latest state
|
||||
|
||||
Reactive means when a state changes, all the UIs that depend on it will re-render automatically. Sunmao-ui creates a performant, reactive state store in which all components expose their state and can access the state of others.
|
||||
Reactive means when a state changes, all the UIs that depend on it will re-render automatically. Sunmao creates a performant, reactive state store in which all components expose their state and can access the state of others.
|
||||
|
||||
For example, we can access demo input's value state in the demo text component:
|
||||
|
||||
@ -81,7 +81,7 @@ Modern UI frameworks emphasize state-driven and declarative concepts, which are
|
||||
|
||||
If you want to switch to the dark theme when you click the theme button, all you need is an `onClick` event and a `changeTheme` method.
|
||||
|
||||
Sunmao-ui's spec lets every component declare the events they will dispatch and a set of immersive methods for interacting with itself from any other components.
|
||||
Sunmao's spec lets every component declare the events they will dispatch and a set of immersive methods for interacting with itself from any other components.
|
||||
|
||||
To add an event handler as follows:
|
||||
|
||||
@ -91,7 +91,7 @@ To add an event handler as follows:
|
||||
|
||||
When we develop an application, we usually end up with a hierarchical structure of components, such as a DOM tree.
|
||||
|
||||
Sunmao-ui's schema on the other hand, is straightforward to store and modify because we utilize an array to contain all the components without nesting. As a result, we introduce slots to aid in the organization of the hierarchy.
|
||||
Sunmao's schema on the other hand, is straightforward to store and modify because we utilize an array to contain all the components without nesting. As a result, we introduce slots to aid in the organization of the hierarchy.
|
||||
|
||||
To declare and implement slots in a component as follows:
|
||||
|
||||
@ -115,7 +115,7 @@ To insert customized styles to a style slot:
|
||||
|
||||
### Live in a type-safe world
|
||||
|
||||
We at sunmao-ui feel that type-safe will increase DX and efficiency for component developers as well as application builders. As a result, typescript and JSON schema are heavily used in our implementation.
|
||||
We at Sunmao feel that type-safe will increase DX and efficiency for component developers as well as application builders. As a result, typescript and JSON schema are heavily used in our implementation.
|
||||
|
||||
If you use typescript to create a component, we will infer types from the JSON schema, which will help you write safe code.
|
||||
|
||||
@ -133,14 +133,14 @@ We believe that providing props like `dataUrl`, `hidden`, or `handlers` to every
|
||||
|
||||
### A extensible GUI editor
|
||||
|
||||
Sunmao-ui's GUI editor will read all of the components' specs and generate a form based on its JSON schema description.
|
||||
Sunmao's GUI editor will read all of the components' specs and generate a form based on its JSON schema description.
|
||||
|
||||
If some of the form widgets require extensive customization, component developers can implement their own widgets and allow specific components to use them. A detailed design of the customized widgets can be found [here](https://github.com/webzard-io/sunmao-ui/issues/313).
|
||||
If some of the form widgets require extensive customization, component developers can implement their own widgets and allow specific components to use them. A detailed design of the customized widgets can be found [here](https://github.com/webzard-io/Sunmao/issues/313).
|
||||
|
||||
## Sunmao-ui is open
|
||||
## Sunmao is open
|
||||
|
||||
Sunmao-ui is an open-source project from day 1. But when we say 'open,' we don't just mean open-sourcing the code under the MIT license.
|
||||
Sunmao is an open-source project from day 1. But when we say 'open,' we don't just mean open-sourcing the code under the MIT license.
|
||||
|
||||
Although sunmao-ui starts as an internal project from SmartX, we chose to place all the proposals, discussions, and design decisions into our public repo rather than any internal channels. Because we believe that 'be open' is the cornerstone of sunmao-ui, and that this project will shine as more developers construct their own low-code solution on top of sunmao-ui.
|
||||
Although Sunmao starts as an internal project from SmartX, we chose to place all the proposals, discussions, and design decisions into our public repo rather than any internal channels. Because we believe that 'be open' is the cornerstone of Sunmao, and that this project will shine as more developers construct their own low-code solution on top of Sunmao.
|
||||
|
||||
If you're interested in sunmao-ui, please visit our Github repository and join the slack channel to stay connected with the community.
|
||||
If you're interested in Sunmao, please visit our Github repository and join the slack channel to stay connected with the community.
|
BIN
docs/images/eventHandler-en.png
Normal file
BIN
docs/images/eventHandler-en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
@ -2,7 +2,7 @@
|
||||
<img src="../images/logo.png" alt="logo" width="200" />
|
||||
</div>
|
||||
<div align="center">
|
||||
<h1>Sunmao-UI</h1>
|
||||
<h1>Sunmao</h1>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@ -33,6 +33,8 @@ Sunmao 内置了低代码工具的 GUI 编辑器,几乎囊括了一个完整
|
||||
|
||||
无论是在开发 Sunmao 组件时,还是在使用 Sunmao 编辑器时,你都处于类型安全之中。Sunmao 重度使用 Typescript 与 JSON schema 来实现极佳的类型系统。
|
||||
|
||||
更多详情,请见 [Sunmao:一个真正可扩展的低代码 UI 框架](./what-is-sunmao.md)
|
||||
|
||||
## 目录结构
|
||||
|
||||
Sunmao 是一个 monorepo 项目,其中分别包含下面这些包:
|
||||
|
@ -73,16 +73,16 @@ Spec 本质上是一个 JSON,它的作用是描述组件的参数、行为等
|
||||
|
||||
`properties` 描述了 Component 能够接受的参数名称和类型。这里定义了两个参数,`placeholder`和`disabled` ,类型分别是 String 和 Boolean。
|
||||
|
||||
你可能对这种声明类型的方法感到陌生。前文已经说过,Spec 本质是一个 JSON,但 JSON 不像 Typescript 可以声明类型,所以当我们要在 Spec 中声明类型时,我们使用 [JSONSchema](https://json-schema.org/)。JSONSchema 本身也是 JSON,但是可以用来声明一个 JSON 数据结构的类型。
|
||||
|
||||
但手写 JSONSchema 比较困难,所以我们推荐使用 [TypeBox](https://github.com/sinclairzx81/typebox) 库来辅助生成 JSONSchema。示例中的写法就是调用了 TypeBox。
|
||||
|
||||
```
|
||||
````
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
})
|
||||
```
|
||||
````
|
||||
|
||||
你可能对这种声明类型的方法感到陌生。前文已经说过,Spec 本质是一个 JSON,但 JSON 不像 Typescript 可以声明类型,所以当我们要在 Spec 中声明类型时,我们使用 [JSONSchema](https://json-schema.org/)。JSONSchema 本身也是 JSON,但是可以用来声明一个 JSON 数据结构的类型。
|
||||
|
||||
但手写 JSONSchema 比较困难,所以我们推荐使用 [TypeBox](https://github.com/sinclairzx81/typebox) 库来辅助生成 JSONSchema。示例中的写法就是调用了 TypeBox。
|
||||
|
||||
#### Component Spec State
|
||||
|
||||
@ -464,13 +464,13 @@ Component Implementation 的参数本质上一个 object,但是其实是由好
|
||||
|
||||
Services 是 Sunmao 的各种服务的实例,包括状态管理、事件监听、组件注册等等。这些 Service 都是全局唯一的实例。
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| registry | Registry | Registry 上注册了 Sunmao 所有的 Component、Trait、Module,您可以在其中它们所对应的 Spec 和渲染组件。 |
|
||||
| stateManager | StateManager | StateManager 管理着 Sunmao 的全局状态 Store,而且还具 eval 表达式的功能。 |
|
||||
| globalHandlerMap | GlobalHandlerMap | GlobalHandlerMap 管理着所有 Component 的 Method 实例。 |
|
||||
| apiService | ApiService | ApiService 是全局事件总线。 |
|
||||
| eleMap | Map<string, HTMLElement> | eleMap 存放所有 Component 的 DOM 元素。 |
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ---------------- | ------------------------ | ------------------------------------------------------------ |
|
||||
| registry | Registry | Registry 上注册了 Sunmao 所有的 Component、Trait、Module,您可以在其中找到它们所对应的 Spec 和 Implementation。 |
|
||||
| stateManager | StateManager | StateManager 管理着 Sunmao 的全局状态 Store,而且还具 eval 表达式的功能。 |
|
||||
| globalHandlerMap | GlobalHandlerMap | GlobalHandlerMap 管理着所有 Component 的 Method 实例。 |
|
||||
| apiService | ApiService | ApiService 是全局事件总线。 |
|
||||
| eleMap | Map<string, HTMLElement> | eleMap 存放所有 Component 的 DOM 元素。 |
|
||||
|
||||
> ⚠️ 一般情况下,您不需要使用这些服务。只有在实现一些特殊需求时,才可能会用到它们。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Sunmao 开发者教程
|
||||
|
||||
### 安装 Sunmao
|
||||
### [安装 Sunmao](./install.md)
|
||||
|
||||
### [Component 开发](./component.md)
|
||||
|
||||
@ -8,5 +8,6 @@
|
||||
|
||||
### 进阶(TODO)
|
||||
|
||||
- Property 的 ExpressionOption
|
||||
- [表达式](./expression)
|
||||
- Property 的 `ExpressionOption`
|
||||
- 生命周期
|
||||
|
@ -1,4 +1,4 @@
|
||||
# sunmao-ui:一个真正可扩展的低代码 UI 框架
|
||||
# Sunmao:一个真正可扩展的低代码 UI 框架
|
||||
|
||||
尽管现在越来越多的人开始对低代码开发感兴趣,但已有低代码方案的一些局限性仍然让大家有所保留。其中最常见的担忧莫过于低代码缺乏灵活性以及容易被厂商锁定。
|
||||
|
||||
@ -6,37 +6,37 @@
|
||||
|
||||
一些已有产品机智地将低代码的使用场景限定在了特定领域中,例如内部工具或者是官网,因为在这些场景中用户更关心开发效率而不是灵活性与定制能力。但当我们希望使用低代码在更多场景中提升效率时,这类产品就不能满足需求了。
|
||||
|
||||
因此,我们开始开发 sunmao-ui 这个项目,在开源开发的基础之上,我们专注于这个低代码 UI 框架的拓展性。
|
||||
因此,我们开始开发 Sunmao 这个项目,在开源开发的基础之上,我们专注于这个低代码 UI 框架的拓展性。
|
||||
|
||||
## 设计原则
|
||||
|
||||
sunmao-ui 的命名来源于中文榫卯,它是一种从古至今一直被使用的木结构技术,以精巧、稳固著称。我们非常喜欢这个名字,因为榫卯结构与我们将各类构建单元组合成一个稳固的应用的方式十分相似。
|
||||
Sunmao 的命名来源于中文榫卯,它是一种从古至今一直被使用的木结构技术,以精巧、稳固著称。我们非常喜欢这个名字,因为榫卯结构与我们将各类构建单元组合成一个稳固的应用的方式十分相似。
|
||||
|
||||
在开发的过程中,我们始终遵循以下设计原则,从而确保我们的抽象方式正确且一致。
|
||||
|
||||
### 1. 明确不同角色的职责
|
||||
|
||||
在开发 sunmao-ui 的过程中,我们首先将使用者划分为了组件开发者与应用构建者两个角色,角色的划分是我们后续设计中最重要的概念。
|
||||
在开发 Sunmao 的过程中,我们首先将使用者划分为了组件开发者与应用构建者两个角色,角色的划分是我们后续设计中最重要的概念。
|
||||
|
||||
组件开发者应该更加关注代码质量、性能以及用户体验等部分,并以此为标准创造出可复用的组件。当组件开发者以他们趁手的方式开发了一个新的组件,他们就可以将该组件封装为一个 sunmao-ui 组件并注册到组件库中。
|
||||
组件开发者应该更加关注代码质量、性能以及用户体验等部分,并以此为标准创造出可复用的组件。当组件开发者以他们趁手的方式开发了一个新的组件,他们就可以将该组件封装为一个 Sunmao 组件并注册到组件库中。
|
||||
|
||||
应用构建者则选用已有的组件,并实现应用相关的业务逻辑。结合组件与 sunmao-ui 的平台特性,应用构建者可以更高效地完成这一工作。
|
||||
应用构建者则选用已有的组件,并实现应用相关的业务逻辑。结合组件与 Sunmao 的平台特性,应用构建者可以更高效地完成这一工作。
|
||||
|
||||
之所以将角色进行划分,是因为每时每刻都有应用被开发,但组件迭代的频率则低得多。所以在 sunmao-ui 的帮助下,用户可以将组件开发的任务交给少量高级前端工程师完成,而将应用搭建的工作交由初级前端工程师、后端工程师、甚至是无代码开发经验的人完成。
|
||||
之所以将角色进行划分,是因为每时每刻都有应用被开发,但组件迭代的频率则低得多。所以在 Sunmao 的帮助下,用户可以将组件开发的任务交给少量高级前端工程师完成,而将应用搭建的工作交由初级前端工程师、后端工程师、甚至是无代码开发经验的人完成。
|
||||
|
||||
### 2. 发挥代码的威力,而不是限制
|
||||
|
||||
如之前所说,sunmao-ui 并不将用户局限于只能使用按钮、输入框等基础组件开发应用,而是可以由组件开发者注册各类复杂、适用于特定领域的组件,以此覆盖应用需求以及延续已有的视觉设计体系。
|
||||
如之前所说,Sunmao 并不将用户局限于只能使用按钮、输入框等基础组件开发应用,而是可以由组件开发者注册各类复杂、适用于特定领域的组件,以此覆盖应用需求以及延续已有的视觉设计体系。
|
||||
|
||||
在开发 sunmao-ui 的过程中,我们也投入了大量的精力来保证我们的实现不会给用户的应用带来限制。
|
||||
在开发 Sunmao 的过程中,我们也投入了大量的精力来保证我们的实现不会给用户的应用带来限制。
|
||||
|
||||
举个例子来说,许多低代码工具的可视化编辑器都提供了悬浮在组件上时进行高亮的效果。相当一部分编辑器在实现这个功能时都是通过在每个组件之外包裹一个 `<div>` 元素,并对该元素进行事件的监听与高亮样式的修改。但这样的实现方式有诸多弊端,例如增加了 DOM 节点数量、使组件无法被配置 inline 样式以及增加了隐式的嵌套关系,这些对于 sunmao-ui 来说都是不可接受的。
|
||||
举个例子来说,许多低代码工具的可视化编辑器都提供了悬浮在组件上时进行高亮的效果。相当一部分编辑器在实现这个功能时都是通过在每个组件之外包裹一个 `<div>` 元素,并对该元素进行事件的监听与高亮样式的修改。但这样的实现方式有诸多弊端,例如增加了 DOM 节点数量、使组件无法被配置 inline 样式以及增加了隐式的嵌套关系,这些对于 Sunmao 来说都是不可接受的。
|
||||
|
||||
尽管我们花费了更多时间才找到一个避免以上所有缺陷的实现方式,但我们相信这样的付出是值得的。因为只有这样,我们才能做到发挥代码的威力,赢得开发者的认可。
|
||||
|
||||
### 3. 各个层面的可扩展性
|
||||
|
||||
sunmao-ui 包含三个层级,分别是核心规范、运行时以及编辑器。
|
||||
Sunmao 包含三个层级,分别是核心规范、运行时以及编辑器。
|
||||
|
||||
组件与应用的 schema 都在核心规范中进行定义。除去常规的字段之外,用户还可以通过添加注解(annotations)的方式注入额外信息,并在运行时或编辑器中使用。
|
||||
|
||||
@ -50,9 +50,9 @@ sunmao-ui 包含三个层级,分别是核心规范、运行时以及编辑器
|
||||
|
||||
因为我们认为现如今的后端技术日新月异,一个 UI 低代码方案可以更灵活的与各类后端服务对接,这样才能够为使用者带来最大的灵活性。
|
||||
|
||||
## sunmao-ui 的工作原理
|
||||
## Sunmao 的工作原理
|
||||
|
||||
Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增加少量低代码场景中必备的能力,即:
|
||||
Web UI 开发已经相当成熟,所以 Sunmao 只是在此基础之上增加少量低代码场景中必备的能力,即:
|
||||
|
||||
- 响应式
|
||||
- 事件与方法
|
||||
@ -63,7 +63,7 @@ Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增
|
||||
|
||||
### 响应最新的状态
|
||||
|
||||
响应式指的是当应用状态发生变化时,所有依赖该状态的 UI 都自动重写渲染。sunmao-ui 实现了一个高性能的响应式状态中心,所有组件都可以将自己的状态同步至其中以及从中访问其他组件的状态。
|
||||
响应式指的是当应用状态发生变化时,所有依赖该状态的 UI 都自动重写渲染。Sunmao 实现了一个高性能的响应式状态中心,所有组件都可以将自己的状态同步至其中以及从中访问其他组件的状态。
|
||||
|
||||
例如,当我们想让 demo 按钮渲染 demo 输入框中的值,只需要填写 `{{ input.value.length }}`:
|
||||
|
||||
@ -79,7 +79,7 @@ Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增
|
||||
|
||||
例如当你希望实现点击一个主题按钮并切换至暗色主题的功能,最符合直觉的方式就是按钮提供一个 `onClick` 事件并触发主题组件的 `changeTheme` 方法。
|
||||
|
||||
在 sunmao-ui 中,你可以在组件的规范中声明它会对外发送的事件以及可以供外部调用的一组命令式方法。在此基础上,任意组件都可以通过事件与方法和其他组件进行交互。
|
||||
在 Sunmao 中,你可以在组件的规范中声明它会对外发送的事件以及可以供外部调用的一组命令式方法。在此基础上,任意组件都可以通过事件与方法和其他组件进行交互。
|
||||
|
||||
以下是一个监听事件并执行方法的示例:
|
||||
|
||||
@ -89,7 +89,7 @@ Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增
|
||||
|
||||
当我们开发应用时,最终通常都会将组件组合成一个嵌套结构,例如浏览器中的 DOM 树。
|
||||
|
||||
在 sunmao-ui 的应用 schema 中,我们采用的是一个扁平的数组结构记录所有的组件信息,这样使得修改与存储的时候可以更加高效。也因此,我们引入了插槽的概念来表示嵌套结构。
|
||||
在 Sunmao 的应用 schema 中,我们采用的是一个扁平的数组结构记录所有的组件信息,这样使得修改与存储的时候可以更加高效。也因此,我们引入了插槽的概念来表示嵌套结构。
|
||||
|
||||
通过以下方式可以实现并声明一个组件有哪些插槽:
|
||||
|
||||
@ -113,9 +113,9 @@ Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增
|
||||
|
||||
### 类型安全
|
||||
|
||||
在低代码场景中,类型安全可以极大地提升开发体验与应用搭建速度。所以在 sunmao-ui 中我们重度使用 typescript 与 JSON schema 来实现极佳的类型系统。
|
||||
在低代码场景中,类型安全可以极大地提升开发体验与应用搭建速度。所以在 Sunmao 中我们重度使用 typescript 与 JSON schema 来实现极佳的类型系统。
|
||||
|
||||
如果组件开发者使用 typescript 进行开发,sunmao-ui 能够从组件的 JSON schema 定义中推断 typescript 的类型,从而帮助组件开发者编写类型安全的代码。
|
||||
如果组件开发者使用 typescript 进行开发,Sunmao 能够从组件的 JSON schema 定义中推断 typescript 的类型,从而帮助组件开发者编写类型安全的代码。
|
||||
|
||||
![type1-1](../images/00/type1-1.gif)
|
||||
|
||||
@ -131,14 +131,14 @@ Web UI 开发已经相当成熟,所以 sunmao-ui 只是在此基础之上增
|
||||
|
||||
### 可扩展的可视化编辑器
|
||||
|
||||
sunmao-ui 的可视化编辑器读取所有组件的规范并基于其定义的 JSON schema 自动生成表单。
|
||||
Sunmao 的可视化编辑器读取所有组件的规范并基于其定义的 JSON schema 自动生成表单。
|
||||
|
||||
如果一部分表单需要定制,组件开发者可以实现自定义的编辑器 widget。关于扩展可视化编辑器的设计可以进一步阅读这一[设计文档](https://github.com/webzard-io/sunmao-ui/issues/313)。
|
||||
如果一部分表单需要定制,组件开发者可以实现自定义的编辑器 widget。关于扩展可视化编辑器的设计可以进一步阅读这一[设计文档](https://github.com/webzard-io/Sunmao/issues/313)。
|
||||
|
||||
## 保持开放
|
||||
|
||||
从第一天起,sunmao-ui 就以开源的方式进行开发。但当我们说到“开放”时,我们并不仅仅局限于以 MIT 协议开放所有的源代码。
|
||||
从第一天起,Sunmao 就以开源的方式进行开发。但当我们说到“开放”时,我们并不仅仅局限于以 MIT 协议开放所有的源代码。
|
||||
|
||||
尽管 sunmao-ui 是从 SmartX 内部发起的项目,我们在开发过程中还是选择将所有的提案、讨论与设计决策放在公开的 Github 仓库中进行,而不是其他的内部频道。因为我们相信“保持开放”是 sunmao-ui 发展的基石,并且我们也视开发者使用 sunmao-ui 构建自己的低代码方案为最大的荣耀。
|
||||
尽管 Sunmao 是从 SmartX 内部发起的项目,我们在开发过程中还是选择将所有的提案、讨论与设计决策放在公开的 Github 仓库中进行,而不是其他的内部频道。因为我们相信“保持开放”是 Sunmao 发展的基石,并且我们也视开发者使用 Sunmao 构建自己的低代码方案为最大的荣耀。
|
||||
|
||||
如果你对 sunamo-ui 感兴趣,欢迎访问我们的 Github 仓库或者加入我们 slack 频道,与社区保持联系。
|
Loading…
Reference in New Issue
Block a user