0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-10 08:50:57 -05:00
penpot/docs/technical-guide/developer/architecture/frontend.md
Elena Mateos López 4f7659fbf8 📚 Update frontend.md
Fix a typo from `comonents` to `components` in the UI namespaces section, at settings.  :)
2024-11-13 12:34:58 +01:00

258 lines
9.3 KiB
Markdown

---
title: Frontend app
---
### Frontend app
The main application, with the user interface and the presentation logic.
To talk with backend, it uses a custom RPC-style API: some functions in the
backend are exposed through an HTTP server. When the front wants to execute a
query or data mutation, it sends a HTTP request, containing the name of the
function to execute, and the ascii-encoded arguments. The resulting data is
also encoded and returned. This way we don't need any data type conversion,
besides the transport encoding, as there is Clojure at both ends.
When the user opens any file, a persistent websocket is opened with the backend
and associated to the file id. It is used to send presence events, such as
connection, disconnection and mouse movements. And also to receive changes made
by other users that are editing the same file, so it may be updated in real
time.
## Frontend structure
Penpot frontend app code resides under <code class="language-text">frontend/src/app</code> path in the main repository.
@startuml FrontendGeneral
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!include DEVICONS/react.puml
HIDE_STEREOTYPE()
Person(user, "User")
System_Boundary(frontend, "Frontend") {
Container(frontend_app, "Frontend app", "React / ClojureScript", "", "react")
Container(worker, "Worker", "Web worker")
}
Rel(user, frontend_app, "Uses", "HTTPS")
BiRel_L(frontend_app, worker, "Works with")
@enduml
```text
▾ frontend/src/app/
▸ main/
▸ util/
▸ worker/
main.cljs
worker.cljs
```
* <code class="language-text">main.cljs</code> and <code class="language-text">main/</code> contain the main frontend app, written in
ClojureScript language and using React framework, wrapped in [rumext
library](https://github.com/funcool/rumext).
* <code class="language-text">worker.cljs</code> and <code class="language-text">worker/</code> contain the web worker, to make expensive
calculations in background.
* <code class="language-text">util/</code> contains many generic utilities, non dependant on the user
interface.
@startuml FrontendMain
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
HIDE_STEREOTYPE()
Component(ui, "ui", "main web component")
Component(store, "store", "module")
Component(refs, "refs", "module")
Component(repo, "repo", "module")
Component(streams, "streams", "module")
Component(errors, "errors", "module")
Boundary(ui_namespaces, "ui namespaces") {
Component(ui_auth, "auth", "web component")
Component(ui_settings, "settings", "web component")
Component(ui_dashboard, "dashboard", "web component")
Component(ui_workspace, "workspace", "web component")
Component(ui_viewer, "viewer", "web component")
Component(ui_render, "render", "web component")
Component(ui_exports, "exports", "web component")
Component(ui_shapes, "shapes", "component library")
Component(ui_components, "components", "component library")
}
Boundary(data_namespaces, "data namespaces") {
Component(data_common, "common", "events")
Component(data_users, "users", "events")
Component(data_dashboard, "dashboard", "events")
Component(data_workspace, "workspace", "events")
Component(data_viewer, "viewer", "events")
Component(data_comments, "comments", "events")
Component(data_fonts, "fonts", "events")
Component(data_messages, "messages", "events")
Component(data_modal, "modal", "events")
Component(data_shortcuts, "shortcuts", "utilities")
}
Lay_D(ui_exports, data_viewer)
Lay_D(ui_settings, ui_components)
Lay_D(data_viewer, data_common)
Lay_D(data_fonts, data_messages)
Lay_D(data_dashboard, data_modal)
Lay_D(data_workspace, data_shortcuts)
Lay_L(data_dashboard, data_fonts)
Lay_L(data_workspace, data_comments)
Rel_Up(refs, store, "Watches")
Rel_Up(streams, store, "Watches")
Rel(ui, ui_auth, "Routes")
Rel(ui, ui_settings, "Routes")
Rel(ui, ui_dashboard, "Routes")
Rel(ui, ui_workspace, "Routes")
Rel(ui, ui_viewer, "Routes")
Rel(ui, ui_render, "Routes")
Rel(ui_render, ui_exports, "Uses")
Rel(ui_workspace, ui_shapes, "Uses")
Rel(ui_viewer, ui_shapes, "Uses")
Rel_Right(ui_exports, ui_shapes, "Uses")
Rel(ui_auth, data_users, "Uses")
Rel(ui_settings, data_users, "Uses")
Rel(ui_dashboard, data_dashboard, "Uses")
Rel(ui_dashboard, data_fonts, "Uses")
Rel(ui_workspace, data_workspace, "Uses")
Rel(ui_workspace, data_comments, "Uses")
Rel(ui_viewer, data_viewer, "Uses")
@enduml
### General namespaces
* **store** contains the global state of the application. Uses an event loop
paradigm, similar to Redux, with a global state object and a stream of events
that modify it. Made with [potok library](https://funcool.github.io/potok/latest/).
* **refs** has the collection of references or lenses: RX streams that you can
use to subscribe to parts of the global state, and be notified when they
change.
* **streams** has some streams, derived from the main event stream, for keyboard
and mouse events. Used mainly from the workspace viewport.
* **repo** contains the functions to make calls to backend.
* **errors** has functions with global error handlers, to manage exceptions or other
kinds of errors in the ui or the data events, notify the user in a useful way,
and allow to recover and continue working.
### UI namespaces
* **ui** is the root web component. It reads the current url and mounts the needed
subcomponent depending on the route.
* **auth** has the web components for the login, register, password recover,
etc. screens.
* **settings** has the web components for the user profile and settings screens.
* **dashboard** has the web components for the dashboard and its subsections.
* **workspace** has the web components for the file workspace and its subsections.
* **viewer** has the web components for the viewer and its subsections.
* **render** contain special web components to render one page or one specific
shape, to be used in exports.
* **export** contain basic web components that display one shape or frame, to
be used from exports render or else from dashboard and viewer thumbnails and
other places.
* **shapes** is the basic collection of web components that convert all types of
shapes in the corresponding svg elements, without adding any extra function.
* **components** a library of generic UI widgets, to be used as building blocks
of penpot screens (text or numeric inputs, selects, forms, buttons...).
### Data namespaces
* **users** has events to login and register, fetch the user profile and update it.
* **dashboard** has events to fetch and modify teams, projects and files.
* **fonts** has some extra events to manage uploaded fonts from dashboard.
* **workspace** has a lot of events to manage the current file and do all kinds of
edits and updates.
* **comments** has some extra events to manage design comments.
* **viewer** has events to fetch a file contents to display, and manage the
interactive behavior and hand-off.
* **common** has some events used from several places.
* **modal** has some events to show modal popup windows.
* **messages** has some events to show non-modal informative messages.
* **shortcuts** has some utility functions, used in other modules to setup the
keyboard shortcuts.
## Worker app
Some operations are costly to make in real time, so we leave them to be
executed asynchronously in a web worker. This way they don't impact the user
experience. Some of these operations are generating file thumbnails for the
dashboard and maintaining some geometric indexes to speed up snap points while
drawing.
@startuml FrontendWorker
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
HIDE_STEREOTYPE()
Component(worker, "worker", "worker entry point")
Boundary(worker_namespaces, "worker namespaces") {
Component(thumbnails, "thumbnails", "worker methods")
Component(snaps, "snaps", "worker methods")
Component(selection, "selection", "worker methods")
Component(impl, "impl", "worker methods")
Component(import, "import", "worker methods")
Component(export, "export", "worker methods")
}
Rel(worker, thumbnails, "Uses")
Rel(worker, impl, "Uses")
Rel(worker, import, "Uses")
Rel(worker, export, "Uses")
Rel(impl, snaps, "Uses")
Rel(impl, selection, "Uses")
@enduml
* **worker** contains the worker setup code and the global handler that receives
requests from the main app, and process them.
* **thumbnails** has a method to generate the file thumbnails used in dashboard.
* **snaps** manages a distance index of shapes, and has a method to get
other shapes near a given one, to be used in snaps while drawing.
* **selection** manages a geometric index of shapes, with methods to get what
shapes are under the cursor at a given moment, for select.
* **impl** has a simple method to update all indexes in a page at once.
* **import** has a method to import a whole file from an external <code class="language-text">.penpot</code> archive.
* **export** has a method to export a whole file to an external <code class="language-text">.penpot</code> archive.