mirror of
https://github.com/penpot/penpot.git
synced 2025-05-26 00:56:09 +02:00
231 lines
8.5 KiB
Markdown
231 lines
8.5 KiB
Markdown
---
|
|
title: 3.08. Data Guide
|
|
desc: Learn about data structures, code organization, file operations, migrations, shape editing, and component syncing. See Penpot's technical guide. Try it free!
|
|
---
|
|
|
|
# Data Guide
|
|
|
|
The data structures are one of the most complex and important parts of Penpot.
|
|
It's critical that the data integrity is always maintained throughout the whole
|
|
usage, and also file exports & imports and data model evolution.
|
|
|
|
To modify the data structure (the most typical case will be to add a new attribute
|
|
to the shapes), this list must be checked. This is not an exhaustive list, but
|
|
all of this is important in general.
|
|
|
|
## General considerations
|
|
|
|
* We prefer that the page and shape attributes are optional. I.E. there is a
|
|
default object behavior, that occurs when the attribute is not present, and
|
|
its presence activates some feature (example: if there is no <code class="language-clojure">fill-color</code>,
|
|
the shape is not filled). When you revert to the default state, it's better
|
|
to remove the attribute than leaving it with <code class="language-clojure">null</code> value. There are some
|
|
process (for example import & export) that filter out and remove all
|
|
attributes that are <code class="language-clojure">null</code>.
|
|
|
|
* So never expect that attribute with <code class="language-clojure">null</code> value is a different state that
|
|
without the attribute.
|
|
|
|
* In objects attribute names we don't use special symbols that are allowed by
|
|
Clojure (for example ending it with ? for boolean values), because this may
|
|
cause problems when exporting.
|
|
|
|
## Data migrations
|
|
|
|
```text
|
|
▾ common/
|
|
▾ src/app/common/
|
|
▾ files/
|
|
migrations.cljc
|
|
```
|
|
|
|
When changing the model it's essential to take into account that the existing
|
|
Penpot files must keep working without changes. If you follow the general
|
|
considerations stated above, usually this is automatic, since the objects
|
|
already in the database just have the default behavior, that should be the same
|
|
as before the change. And the new features apply to new or edited objects.
|
|
|
|
But if this is not possible, and we are talking of a breaking change, you can
|
|
write a data migration. Just define a new data version and a migration script
|
|
in <code class="language-text">migrations.cljc</code> and increment <code class="language-text">file-version</code> in <code class="language-text">common.cljc</code>.
|
|
|
|
From then on, every time a file is loaded from the database, if its version
|
|
number is lower than the current version in the app, the file data will be
|
|
handled to all the needed migration functions. If you later modify and save
|
|
the file, it will be now updated in database.
|
|
|
|
## Shape edit forms
|
|
|
|

|
|
|
|
```text
|
|
▾ frontend/
|
|
▾ src/
|
|
▾ app/
|
|
▾ main/
|
|
▾ ui/
|
|
▾ workspace/
|
|
▾ sidebar/
|
|
▾ options/
|
|
▸ menus/
|
|
▸ rows/
|
|
▾ shapes/
|
|
bool.cljs
|
|
circle.cljs
|
|
frame.cljs
|
|
group.cljs
|
|
image.cljs
|
|
multiple.cljs
|
|
path.cljs
|
|
rect.cljs
|
|
svg_raw.cljs
|
|
text.cljs
|
|
```
|
|
|
|
* In <code class="language-text">shapes/*.cljs</code> there are the components that show the edit menu of each
|
|
shape type.
|
|
|
|
* In <code class="language-text">menus/*.cljs</code> there are the building blocks of these menus.
|
|
|
|
* And in <code class="language-text">rows/*.cljs</code> there are some pieces, for example color input and
|
|
picker.
|
|
|
|
## Multiple edit
|
|
|
|
When modifying the shape edit forms, you must take into account that these
|
|
forms may edit several shapes at the same time, even of different types.
|
|
|
|
When more than one shape is selected, the form inside <code class="language-text">multiple.cljs</code> is used.
|
|
At the top of this module, a couple of maps define what attributes may be edited
|
|
and how, for each type of shape.
|
|
|
|
Then, the blocks in <code class="language-text">menus/*.cljs</code> are used, but they are not given a shape, but
|
|
a values map. For each attribute, if all shapes have the same value, it is taken;
|
|
if not, the attribute will have the value <code class="language-clojure">:multiple</code>.
|
|
|
|
The form blocks must be prepared for this value, display something useful to
|
|
the user in this case, and do a meaningful action when changing the value.
|
|
Usually this will be to set the attribute to a fixed value in all selected
|
|
shapes, but **only** those that may have the attribute (for example, only text
|
|
shapes have font attributes, or only rects has border radius).
|
|
|
|
## Component synchronization
|
|
|
|
```text
|
|
▾ common/
|
|
▾ src/app/common/
|
|
▾ types/
|
|
component.cljc
|
|
```
|
|
|
|
For all shape attributes, you must take into account what happens when the
|
|
attribute in a main component is changed and then the copies are synchronized.
|
|
|
|
In <code class="language-text">component.cljc</code> there is a structure <code class="language-clojure">sync-attrs</code> that maps shape
|
|
attributes to sync groups. When an attribute is changed in a main component,
|
|
the change will be propagated to its copies. If the change occurs in a copy,
|
|
the group will be marked as *touched* in the copy shape, and from then on,
|
|
further changes in the main to this attribute, or others in the same group,
|
|
will not be propagated.
|
|
|
|
Any attribute that is not in this map will be ignored in synchronizations.
|
|
|
|
## Render shapes, export & import
|
|
|
|
```text
|
|
▾ frontend/
|
|
▾ src/
|
|
▾ app/
|
|
▾ main/
|
|
▾ ui/
|
|
▾ shapes/
|
|
▸ text/
|
|
attrs.cljs
|
|
bool.cljs
|
|
circle.cljs
|
|
custom_stroke.cljs
|
|
embed.cljs
|
|
export.cljs
|
|
fill_image.cljs
|
|
filters.cljs
|
|
frame.cljs
|
|
gradients.cljs
|
|
group.cljs
|
|
image.cljs
|
|
mask.cljs
|
|
path.cljs
|
|
rect.cljs
|
|
shape.cljs
|
|
svg_defs.cljs
|
|
svg_raw.cljs
|
|
text.cljs
|
|
▾ worker/
|
|
▾ import/
|
|
parser.cljs
|
|
```
|
|
|
|
To export a penpot file, basically we use the same system that is used to
|
|
display shapes in the workspace or viewer. In <code class="language-text">shapes/*.cljs</code> there are
|
|
components that render one shape of each type into a SVG node.
|
|
|
|
But to be able to import the file later, some attributes that not match
|
|
directly to SVG properties need to be added as metadata (for example,
|
|
proportion locks, constraints, stroke alignment...). This is done in the
|
|
<code class="language-text">export.cljs</code> module.
|
|
|
|
Finally, to import a file, we make use of <code class="language-text">parser.cljs</code>, a module that
|
|
contains the <code class="language-clojure">parse-data</code> function. It receives a SVG node (possibly with
|
|
children) and converts it into a Penpot shape object. There are auxiliary
|
|
functions to read and convert each group of attributes, from the node
|
|
properties or the metadata (with the <code class="language-clojure">get-meta</code> function).
|
|
|
|
Any attribute that is not included in the export and import functions
|
|
will not be exported and will be lost if reimporting the file again.
|
|
|
|
## Code generation
|
|
|
|
```text
|
|
▾ frontend/
|
|
▾ src/
|
|
▾ app/
|
|
▾ main/
|
|
▾ ui/
|
|
▾ viewer/
|
|
▾ inspect/
|
|
▾ attributes/
|
|
blur.cljs
|
|
common.cljs
|
|
fill.cljs
|
|
image.cljs
|
|
layout.cljs
|
|
shadow.cljs
|
|
stroke.cljs
|
|
svg.cljs
|
|
text.cljs
|
|
attributes.cljs
|
|
code.cljs
|
|
▾ util/
|
|
code_gen.cljs
|
|
markup_html.cljs
|
|
markup_svg.cljs
|
|
style_css.cljs
|
|
style_css_formats.cljs
|
|
style_css_values.cljs
|
|
```
|
|
|
|
In the inspect panel we have two modes:
|
|
|
|

|
|
|
|
For the Info tab, the <code class="language-text">attributes.cljs</code> module and all modules under
|
|
<code class="language-text">attributes/*.cljs</code> have the components that extract the attributes to inspect
|
|
each type of shape.
|
|
|
|

|
|
|
|
For the Code tab, the <code class="language-text">util/code_gen.cljs</code> module is in charge. It calls the
|
|
other modules in <code class="language-text">util/</code> depending on the format.
|
|
|
|
For HTML and CSS, there are functions that generate the code as needed from the
|
|
shapes. For SVG, it simply takes the nodes from the viewer main viewport and
|
|
prettily formats it.
|