forked from MapComplete/MapComplete
Chore: remove obsolete documentation, add explanation
This commit is contained in:
parent
58c4dd7cfc
commit
e09af8fcb9
2 changed files with 1 additions and 222 deletions
|
@ -1,221 +0,0 @@
|
|||
Architecture
|
||||
============
|
||||
|
||||
This document aims to give an architectural overview of how MapComplete is built. It should give some feeling on how
|
||||
everything fits together.
|
||||
|
||||
Servers?
|
||||
--------
|
||||
|
||||
There are no servers for MapComplete, all services are configured by third parties.
|
||||
|
||||
Minimal HTML - Minimal CSS
|
||||
--------------------------
|
||||
|
||||
There is quasi no HTML. Most of the components are generated by TypeScript and attached dynamically. The HTML is a
|
||||
barebones skeleton which serves every theme.
|
||||
|
||||
|
||||
The UIEventSource
|
||||
-----------------
|
||||
|
||||
Most (but not all) objects in MapComplete get all the state they need as a parameter in the constructor. However, as is
|
||||
the case with most graphical applications, there are quite some dynamical values.
|
||||
|
||||
All values which change regularly are wrapped into
|
||||
a [`UIEventSource`](../Logic/UIEventSource.ts). A `UIEventSource` is a
|
||||
wrapper containing a value and offers the possibility to add a callback function which is called every time the value is
|
||||
changed (with `setData`)
|
||||
|
||||
Furthermore, there are various helper functions, the most widely used one being `map` - generating a new event source
|
||||
with the new value applied. Note that `map` will also absorb some changes,
|
||||
e.g. `const someEventSource : UIEventSource<string[]> = ... ; someEventSource.map(list = list.length)` will only trigger
|
||||
when the length of the list has changed.
|
||||
|
||||
An object which receives a `UIEventSource` is responsible of responding to changes of this object. This is especially
|
||||
true for UI-components.
|
||||
|
||||
UI
|
||||
--
|
||||
```typescript
|
||||
|
||||
export default class MyComponent {
|
||||
|
||||
constructor(neededParameters, neededUIEventSources) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The Graphical User Interface is composed of various UI-elements. For every UI-element, there is a `BaseUIElement` which creates the actual `HTMLElement` when needed.
|
||||
|
||||
There are some basic elements, such as:
|
||||
|
||||
- `FixedUIElement` which shows a fixed, unchangeable element
|
||||
- `Img` to show an image
|
||||
- `Combine` which wraps everything given (strings and other elements) in a div
|
||||
- `List`
|
||||
|
||||
There is one special component: the `VariableUIElement`
|
||||
The `VariableUIElement` takes a `UIEventSource<string|BaseUIElement>` and will dynamically show whatever the `UIEventSource` contains at the moment.
|
||||
|
||||
For example:
|
||||
|
||||
```typescript
|
||||
|
||||
const src : UIEventSource<string> = ... // E.g. user input, data that will be updated... new VariableUIElement(src)
|
||||
.AttachTo('some-id') // attach it to the html
|
||||
|
||||
```
|
||||
|
||||
Note that every component offers support for `onClick( someCallBack)`
|
||||
|
||||
### Translations
|
||||
|
||||
To add a translation:
|
||||
|
||||
1. Open `langs/en.json`
|
||||
2. Find a correct spot for your translation in the tree
|
||||
3. run `npm run generate:translations`
|
||||
4. `import Translations`
|
||||
5. `Translations.t.<your-translation>.Clone()` is the `UIElement` offering your translation
|
||||
|
||||
### Input elements
|
||||
|
||||
Input elements are a special kind of BaseElement which offer a piece of a form to the user, e.g. a TextField, a Radio button, a dropdown, ...
|
||||
|
||||
The constructor will ask all the parameters to configure them. The actual value can be obtained via `inputElement.GetValue()`, which is a `UIEventSource` that will be triggered every time the user changes the input.
|
||||
|
||||
### Advanced elements
|
||||
|
||||
There are some components which offer useful functionality:
|
||||
|
||||
|
||||
- The `subtleButton` which is a friendly, big button
|
||||
- The Toggle: `const t = new Toggle( componentA, componentB, source)` is a `UIEventSource` which shows `componentA` as long as `source` contains `true` and will show `componentB` otherwise.
|
||||
|
||||
|
||||
### Styling
|
||||
|
||||
Styling is done as much as possible with [TailwindCSS](https://tailwindcss.com/). It contains a ton of utility classes, each of them containing a few rules.
|
||||
|
||||
For example: ` someBaseUIElement.SetClass("flex flex-col border border-black rounded-full")` will set the component to be a flex object, as column, with a black border and pill-shaped.
|
||||
|
||||
If Tailwind is not enough, use `baseUiElement.SetStyle("background: red; someOtherCssRule: abc;")`.
|
||||
|
||||
### An example
|
||||
|
||||
For example: the user should input whether or not a shop is closed during public holidays. There are three options:
|
||||
|
||||
1. closed
|
||||
2. opened as usual
|
||||
3. opened with different hours as usual
|
||||
|
||||
In the case of different hours, input hours should be too.
|
||||
|
||||
This can be constructed as following:
|
||||
|
||||
|
||||
```typescript
|
||||
|
||||
// We construct the dropdown element with values and labelshttps://tailwindcss.com/
|
||||
const isOpened = new Dropdown<string>(Translations.t.is_this_shop_opened_during_holidays,
|
||||
[
|
||||
{ value: "closed", Translation.t.shop_closed_during_holidays.Clone()},
|
||||
{ value: "open", Translations.t.shop_opened_as_usual.Clone()},
|
||||
{ value: "hours", Translations.t.shop_opened_with_other_hours.Clone()}
|
||||
] )
|
||||
|
||||
const startHour = new DateInput(...)drop
|
||||
const endHour = new DateInput( ... )
|
||||
// We construct a toggle which'll only show the extra questions if needed
|
||||
const extraQuestion = new Toggle(
|
||||
new Combine([Translations.t.openFrom, startHour, Translations.t.openTill, endHour]),
|
||||
undefined,
|
||||
isOpened.GetValue().map(isopened => isopened === "hours")
|
||||
)
|
||||
|
||||
return new Combine([isOpened, extraQuestion])
|
||||
|
||||
```
|
||||
|
||||
### Constructing a special class
|
||||
|
||||
If you make a specialized class to offer a certain functionality, you can organize it as following:
|
||||
|
||||
1. Create a new class:
|
||||
|
||||
```typescript
|
||||
|
||||
export default class MyComponent {
|
||||
|
||||
constructor(neededParameters, neededUIEventSources) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2. Construct the needed UI in the constructor
|
||||
|
||||
|
||||
```typescript
|
||||
|
||||
export default class MyComponent {
|
||||
|
||||
constructor(neededParameters, neededUIEventSources) {
|
||||
|
||||
|
||||
const component = ...
|
||||
const toggle = ...
|
||||
... other components ...
|
||||
|
||||
toggle.GetValue.AddCallbackAndRun(isSelected => { .. some actions ... }
|
||||
|
||||
new Combine([everything, ...] )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. You'll notice that you'll end up with one certain component (in this example the combine) to wrap it all together. Change the class to extend this type of component and use `super()` to wrap it all up:
|
||||
|
||||
|
||||
```typescript
|
||||
|
||||
export default class MyComponent extends Combine {
|
||||
|
||||
constructor(...) {
|
||||
|
||||
...
|
||||
super([everything, ...])
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Assets
|
||||
------
|
||||
|
||||
### Themes
|
||||
|
||||
Theme and layer configuration files go into `assets/layers` and `assets/themes`.
|
||||
|
||||
### Images
|
||||
|
||||
Other files (mostly images that are part of the core of MapComplete) go into `assets/svg` and are usable with `Svg.image_file_ui()`. Run `npm run generate:images` if you added a new image.
|
||||
|
||||
|
||||
Logic
|
||||
-----
|
||||
|
||||
The last part is the business logic of the application, found in the directory [Logic](../Logic). Actors are small objects which react to `UIEventSources` to update other eventSources.
|
||||
|
||||
`State.state` is a big singleton object containing a lot of the state of the entire application. That one is a bit of a mess.
|
||||
|
|
@ -392,7 +392,7 @@ export class ChangesetHandler {
|
|||
return [
|
||||
["created_by", `MapComplete ${Constants.vNumber}`],
|
||||
["locale", Locale.language.data],
|
||||
["host", `${window.location.origin}${window.location.pathname}`],
|
||||
["host", `${window.location.origin}${window.location.pathname}`], // Note: deferred changes might give a different hostpath then the theme with which the changes were made
|
||||
["source", setSourceAsSurvey ? "survey" : undefined],
|
||||
["imagery", this.changes.state["backgroundLayer"]?.data?.id],
|
||||
].map(([key, value]) => ({
|
||||
|
|
Loading…
Reference in a new issue