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 = ... ; 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` and will dynamically show whatever the `UIEventSource` contains at the moment. For example: ```typescript const src : UIEventSource = ... // 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..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(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.