mirror of
https://github.com/penpot/penpot.git
synced 2025-05-12 14:56:38 +02:00
Merge branch 'wip/multicanvas' of github.com:uxbox/uxbox into i18n/multicanvas
This commit is contained in:
commit
d8afb97c7a
84 changed files with 3327 additions and 3774 deletions
102
CONTRIBUTING.md
102
CONTRIBUTING.md
|
@ -44,92 +44,40 @@ We will use the `easy fix` mark for tag for indicate issues that are
|
||||||
easy for begginers.
|
easy for begginers.
|
||||||
|
|
||||||
|
|
||||||
## Development environment ##
|
## Commit Message Guidelines ##
|
||||||
|
|
||||||
### Introduction ###
|
We have very precise rules over how our git commit messages can be formatted.
|
||||||
|
|
||||||
The development environment consists in a docker container that mounts your local
|
The commit message format is:
|
||||||
copy of the uxbox souce code directory tree and executes a tmux inside the container
|
|
||||||
in order to facilitate execute multiple processes inside.
|
|
||||||
|
|
||||||
|
```
|
||||||
|
<type> <subject>
|
||||||
|
|
||||||
### System requirements ###
|
[body]
|
||||||
|
|
||||||
You should have `docker` installed in your system in order to set up properly
|
[footer]
|
||||||
the uxbox development enviroment.
|
|
||||||
|
|
||||||
In debian like linux distributions you can install it executing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install docker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Where type is:
|
||||||
|
|
||||||
### Start the docker container ###
|
- `:bug:` a commit that fixes a bug
|
||||||
|
- `:sparkles:` a commit that an improvement
|
||||||
**Requires a minimum knowledge of tmux usage in order to use that development
|
- `:tada:` a commit with new feature
|
||||||
environment.**
|
- `:recycle:` a commit that introduces a refactor
|
||||||
|
- `:lipstick:` a commit with cosmetic changes
|
||||||
For start it, staying in this repository, execute:
|
- `:ambulance:` a commit that fixes critical bug
|
||||||
|
- `:books:` a commit that improves or adds documentation
|
||||||
```bash
|
- `:construction:`: a wip commit
|
||||||
./manage.sh run
|
- `:construction_worker:` a commit with CI related stuff
|
||||||
```
|
- `:boom:` a commit with breaking changes
|
||||||
|
- `:wrench:` a commit for config updates
|
||||||
This will do the following:
|
- `:zap:` a commit with performance improvements
|
||||||
|
- `:whale:` a commit for docker related stuff
|
||||||
- Build the image if it is not done before.
|
- `:rewind:` a commit that reverts changes
|
||||||
- Download all repositories if them are not downloaded previously.
|
- `:paperclip:` a commit with other not relevant changes
|
||||||
- Start a container with predefined tmux layout.
|
- `:arrow_up:` a commit with dependencies updates
|
||||||
- Start all needed processes such as gulp and figwheel.
|
|
||||||
|
|
||||||
|
|
||||||
### First steps with tmux ###
|
|
||||||
|
|
||||||
Now having the the container running and tmux open inside the container, you are
|
|
||||||
free to execute any commands and open many shells as you want.
|
|
||||||
|
|
||||||
You can create a new shell just pressing the **Ctr+b c** shortcut. And **Ctrl+b w**
|
|
||||||
for switch between windows, **Ctrl+b &** for kill the current window.
|
|
||||||
|
|
||||||
### Inside the tmux session ###
|
|
||||||
|
|
||||||
#### UI ####
|
|
||||||
|
|
||||||
The UI related tasks starts automatically so you do not need do anything. The
|
|
||||||
**window 0** and **window 1** are used for the UI related environment.
|
|
||||||
|
|
||||||
|
|
||||||
#### Backend ####
|
|
||||||
|
|
||||||
The backend related environment is located in the **window 2**, and you can go
|
|
||||||
directly to it using `ctrl+b 2` shortcut.
|
|
||||||
|
|
||||||
By default this tasks are performed:
|
|
||||||
|
|
||||||
- Start postgresql.
|
|
||||||
- Load initial fixtures into the database.
|
|
||||||
|
|
||||||
The backend is not started automatically, and frontend code by default does not
|
|
||||||
requires that (because it uses a remote server on default config).
|
|
||||||
|
|
||||||
You can start it just execting the `run.sh` script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/run.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
You also can start an repl and strart the backend inside of them:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
lein repl
|
|
||||||
```
|
|
||||||
|
|
||||||
And use `(start)` to start all the environment, `(stop)` for stoping it and
|
|
||||||
`(reset)` for restart with code reloading. If some exception is raised when
|
|
||||||
code is reloaded, just use `(refresh)` in order to finish correctly the
|
|
||||||
code swaping and later use `(reset)` again.
|
|
||||||
|
|
||||||
|
More info: https://gist.github.com/parmentf/035de27d6ed1dce0b36a
|
||||||
|
|
||||||
|
|
||||||
## Code of conduct ##
|
## Code of conduct ##
|
||||||
|
|
137
README.md
137
README.md
|
@ -10,36 +10,128 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Introduction ##
|
## Introduction ##
|
||||||
|
|
||||||
The open-source solution for design and prototyping. UXBOX is currently at an early development stage but we are working hard to bring you the beta version as soon as possible. Follow the project progress in Twitter or Github and stay tuned!
|
The open-source solution for design and prototyping. UXBOX is
|
||||||
|
currently at an early development stage but we are working hard to
|
||||||
|
bring you the beta version as soon as possible. Follow the project
|
||||||
|
progress in Twitter or Github and stay tuned!
|
||||||
|
|
||||||
[See SVG specification](https://www.w3.org/Graphics/SVG/)
|
[See SVG specification](https://www.w3.org/Graphics/SVG/)
|
||||||
|
|
||||||
## SVG based ##
|
## SVG based ##
|
||||||
|
|
||||||
UXBOX works with SVG, a standard format, for all your designs and prototypes . This means that all your stuff in UXBOX is portable and editable in many other vector tools and easy to use on the web.
|
UXBOX works with SVG, a standard format, for all your designs and
|
||||||
|
prototypes . This means that all your stuff in UXBOX is portable and
|
||||||
|
editable in many other vector tools and easy to use on the web.
|
||||||
|
|
||||||
## Development ##
|
## Development ##
|
||||||
|
|
||||||
Most of the main operations can be done through the helper script `manage.sh`.
|
### Introduction ###
|
||||||
|
|
||||||
The development requires of UXBOX is done through a single docker container. Each main service is opened in a different [tmux](https://github.com/tmux/tmux) sessions.
|
The development environment consists in a docker container that mounts
|
||||||
|
your local copy of the uxbox souce code directory tree and executes a
|
||||||
|
tmux inside the container in order to facilitate execute multiple
|
||||||
|
processes inside.
|
||||||
|
|
||||||
## Docker
|
|
||||||
|
|
||||||
Docker is also used to build release images for backend and frontend. Use the helper script `manage.sh` to build the images.
|
### System requirements ###
|
||||||
You can run locally UXBOX through a docker-compose or by manually running the containers.
|
|
||||||
|
|
||||||
Complementary to the docker images you can build locally from this repository, you can find additionnal flavors for backend and frontend on external repositories:
|
You should have `docker` installed in your system in order to set up
|
||||||
|
properly the uxbox development enviroment.
|
||||||
|
|
||||||
|
In debian like linux distributions you can install it executing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install docker
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Start the docker container ###
|
||||||
|
|
||||||
|
**Requires a minimum knowledge of tmux usage in order to use that
|
||||||
|
development environment.**
|
||||||
|
|
||||||
|
For start it, staying in this repository, execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./manage.sh run-devenv
|
||||||
|
```
|
||||||
|
|
||||||
|
This will do the following:
|
||||||
|
|
||||||
|
- Build the image if it is not done before.
|
||||||
|
- Download all repositories if them are not downloaded previously.
|
||||||
|
- Start a container with predefined tmux layout.
|
||||||
|
- Start all needed processes such as gulp and figwheel.
|
||||||
|
|
||||||
|
|
||||||
|
### First steps with tmux ###
|
||||||
|
|
||||||
|
Now having the the container running and tmux open inside the
|
||||||
|
container, you are free to execute any commands and open many shells
|
||||||
|
as you want.
|
||||||
|
|
||||||
|
You can create a new shell just pressing the **Ctr+b c** shortcut. And
|
||||||
|
**Ctrl+b w** for switch between windows, **Ctrl+b &** for kill the
|
||||||
|
current window.
|
||||||
|
|
||||||
|
|
||||||
|
### Inside the tmux session ###
|
||||||
|
|
||||||
|
#### UI ####
|
||||||
|
|
||||||
|
The UI related tasks starts automatically so you do not need do anything. The
|
||||||
|
**window 0** and **window 1** are used for the UI related environment.
|
||||||
|
|
||||||
|
|
||||||
|
#### Backend ####
|
||||||
|
|
||||||
|
The backend related environment is located in the **window 2**, and you can go
|
||||||
|
directly to it using `ctrl+b 2` shortcut.
|
||||||
|
|
||||||
|
By default this tasks are performed:
|
||||||
|
|
||||||
|
- Start postgresql.
|
||||||
|
- Load initial fixtures into the database.
|
||||||
|
|
||||||
|
The repl should be started automatically, if not, you can execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clojure -Adev:repl
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use `(start)` to start all the environment, `(stop)` for stoping
|
||||||
|
it and `(reset)` for restart with code reloading. If some exception is
|
||||||
|
raised when code is reloaded, just use `(refresh)` in order to finish
|
||||||
|
correctly the code swaping and later use `(reset)` again.
|
||||||
|
|
||||||
|
|
||||||
|
## Production (Docker)
|
||||||
|
|
||||||
|
Docker is also used to build release images for backend and
|
||||||
|
frontend. Use the helper script `manage.sh` to build the images. You
|
||||||
|
can run locally UXBOX through a docker-compose or by manually running
|
||||||
|
the containers.
|
||||||
|
|
||||||
|
Complementary to the docker images you can build locally from this
|
||||||
|
repository, you can find additionnal flavors for backend and frontend
|
||||||
|
on external repositories:
|
||||||
* [Monogramm/docker-uxbox-frontend](https://github.com/Monogramm/docker-uxbox-frontend)
|
* [Monogramm/docker-uxbox-frontend](https://github.com/Monogramm/docker-uxbox-frontend)
|
||||||
* [Monogramm/docker-uxbox-backend](https://github.com/Monogramm/docker-uxbox-backend)
|
* [Monogramm/docker-uxbox-backend](https://github.com/Monogramm/docker-uxbox-backend)
|
||||||
|
|
||||||
|
|
||||||
### Persistent data
|
### Persistent data
|
||||||
The UXBOX installation and all data are stored in the database (file uploads, etc). The docker daemon will store that data within the docker directory `/var/lib/docker/volumes/...`. That means your data is saved even if the container crashes, is stopped or deleted.
|
|
||||||
|
|
||||||
To make your data persistent to upgrading and get access for backups is using named docker volume or mount a host folder. To achieve this you need one volume for your database container.
|
The UXBOX installation and all data are stored in the database (file
|
||||||
|
uploads, etc). The docker daemon will store that data within the
|
||||||
|
docker directory `/var/lib/docker/volumes/...`. That means your data
|
||||||
|
is saved even if the container crashes, is stopped or deleted.
|
||||||
|
|
||||||
|
To make your data persistent to upgrading and get access for backups
|
||||||
|
is using named docker volume or mount a host folder. To achieve this
|
||||||
|
you need one volume for your database container.
|
||||||
|
|
||||||
Database:
|
Database:
|
||||||
- `/var/lib/postgresql/data` PostgreSQL Data
|
- `/var/lib/postgresql/data` PostgreSQL Data
|
||||||
|
@ -49,8 +141,12 @@ $ docker run -d \
|
||||||
postgresql
|
postgresql
|
||||||
```
|
```
|
||||||
|
|
||||||
You also need to persist the UXBOX backend public resources (media and assets) to not lose images uploaded and allow the frontend to expose assets.
|
You also need to persist the UXBOX backend public resources (media and
|
||||||
|
assets) to not lose images uploaded and allow the frontend to expose
|
||||||
|
assets.
|
||||||
|
|
||||||
- `/srv/uxbox/resources/public` UXBOX backend public resources
|
- `/srv/uxbox/resources/public` UXBOX backend public resources
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker run -d \
|
$ docker run -d \
|
||||||
-v db:/srv/uxbox/resources/public \
|
-v db:/srv/uxbox/resources/public \
|
||||||
|
@ -59,7 +155,8 @@ $ docker run -d \
|
||||||
|
|
||||||
### Auto configuration via environment variables
|
### Auto configuration via environment variables
|
||||||
|
|
||||||
The following environment variables are also honored for configuring your UXBOX instance:
|
The following environment variables are also honored for configuring
|
||||||
|
your UXBOX instance:
|
||||||
|
|
||||||
#### Frontend
|
#### Frontend
|
||||||
|
|
||||||
|
@ -104,13 +201,17 @@ Available at runtime:
|
||||||
- `-e UXBOX_REGISTRATION_ENABLED=...` (defaults to `true`)
|
- `-e UXBOX_REGISTRATION_ENABLED=...` (defaults to `true`)
|
||||||
- `-e UXBOX_SECRET="..."` (defaults to `"5qjiAndGY3"`)
|
- `-e UXBOX_SECRET="..."` (defaults to `"5qjiAndGY3"`)
|
||||||
|
|
||||||
**Important note:** make sure to use quotation marks for string variables or the backend might try to interpret the values as symbols and have weird issues.
|
**Important note:** make sure to use quotation marks for string
|
||||||
|
variables or the backend might try to interpret the values as symbols
|
||||||
|
and have weird issues.
|
||||||
|
|
||||||
## Collections import
|
## Collections import
|
||||||
|
|
||||||
You can easily import icons and images as global stores with the backend collection importer:
|
You can easily import icons and images as global stores with the
|
||||||
|
backend collection importer:
|
||||||
|
|
||||||
* Create a `media` folder with the following sample structure:
|
* Create a `media` folder with the following sample structure:
|
||||||
|
|
||||||
```
|
```
|
||||||
media
|
media
|
||||||
icons
|
icons
|
||||||
|
@ -118,8 +219,10 @@ media
|
||||||
images
|
images
|
||||||
my-images-collection
|
my-images-collection
|
||||||
```
|
```
|
||||||
|
|
||||||
* Add some icons (SVG format) and images to your collection
|
* Add some icons (SVG format) and images to your collection
|
||||||
* Create a `config.edn` file with the following content
|
* Create a `config.edn` file with the following content
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
{:icons
|
{:icons
|
||||||
[{:name "Generic Icons 1"
|
[{:name "Generic Icons 1"
|
||||||
|
@ -131,7 +234,9 @@ media
|
||||||
:path "./images/my-images-collection/"
|
:path "./images/my-images-collection/"
|
||||||
:regex #"^.*\.(png|jpg|webp)$"}]}
|
:regex #"^.*\.(png|jpg|webp)$"}]}
|
||||||
```
|
```
|
||||||
|
|
||||||
* Then go to the backend directory and import collections:
|
* Then go to the backend directory and import collections:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
clojure -Adev -m uxbox.cli.collimp ../media/config.edn
|
clojure -Adev -m uxbox.cli.collimp ../media/config.edn
|
||||||
```
|
```
|
||||||
|
@ -142,7 +247,9 @@ Take a look at the `sample_media` directory for a sample configuration.
|
||||||
|
|
||||||
**Open to you!**
|
**Open to you!**
|
||||||
|
|
||||||
We love the open source software community. Contributing is our passion and because of this, we'll be glad if you want to participate and improve UXBOX. All your awesome ideas and code are welcome!
|
We love the open source software community. Contributing is our
|
||||||
|
passion and because of this, we'll be glad if you want to participate
|
||||||
|
and improve UXBOX. All your awesome ideas and code are welcome!
|
||||||
|
|
||||||
Please refer to the [Contributing Guide](./CONTRIBUTING.md)
|
Please refer to the [Contributing Guide](./CONTRIBUTING.md)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
:sha "80ef2dfdb3248f94f987b476a6bc1a6cfbe5f306"}
|
:sha "80ef2dfdb3248f94f987b476a6bc1a6cfbe5f306"}
|
||||||
|
|
||||||
funcool/datoteka {:mvn/version "1.1.0"}
|
funcool/datoteka {:mvn/version "1.1.0"}
|
||||||
funcool/struct {:mvn/version "1.4.0"}
|
funcool/struct {:mvn/version "2.0.0-SNAPSHOT"}
|
||||||
|
|
||||||
ring/ring {:mvn/version "1.7.1"}
|
ring/ring {:mvn/version "1.7.1"}
|
||||||
metosin/reitit-core {:mvn/version "0.3.9"}
|
metosin/reitit-core {:mvn/version "0.3.9"}
|
||||||
|
|
|
@ -22,8 +22,8 @@
|
||||||
[uxbox.util.transit :as t]))
|
[uxbox.util.transit :as t]))
|
||||||
|
|
||||||
(defn- mk-uuid
|
(defn- mk-uuid
|
||||||
[prefix i]
|
[prefix & args]
|
||||||
(uuid/v5 uuid/+namespace-oid+ (str prefix i)))
|
(uuid/v5 uuid/+namespace-oid+ (apply str prefix args)))
|
||||||
|
|
||||||
(defn- data-encode
|
(defn- data-encode
|
||||||
[data]
|
[data]
|
||||||
|
@ -58,7 +58,13 @@
|
||||||
{:id (mk-uuid "page" i)
|
{:id (mk-uuid "page" i)
|
||||||
:user (mk-uuid "user" ui)
|
:user (mk-uuid "user" ui)
|
||||||
:project (mk-uuid "project" pi)
|
:project (mk-uuid "project" pi)
|
||||||
:data nil
|
:data {:shapes [{:id (mk-uuid "canvas" i 1)
|
||||||
|
:name "Canvas 1"
|
||||||
|
:type :canvas
|
||||||
|
:x1 200
|
||||||
|
:y1 200
|
||||||
|
:x2 1224
|
||||||
|
:y2 968}]}
|
||||||
:metadata {:width 1024
|
:metadata {:width 1024
|
||||||
:height 768
|
:height 768
|
||||||
:layout "tablet"}
|
:layout "tablet"}
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
:multipart :multipart-params
|
:multipart :multipart-params
|
||||||
(throw (ex-info "Not supported key on :parameters" {})))]
|
(throw (ex-info "Not supported key on :parameters" {})))]
|
||||||
(assoc acc newkey {:key key
|
(assoc acc newkey {:key key
|
||||||
:fn #(st/validate % spec)})))
|
:fn #(st/validate spec %)})))
|
||||||
{} parameters))
|
{} parameters))
|
||||||
|
|
||||||
(validate [request parameters debug]
|
(validate [request parameters debug]
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
org.clojure/clojure {:mvn/version "1.10.1"}
|
org.clojure/clojure {:mvn/version "1.10.1"}
|
||||||
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
|
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
|
||||||
|
|
||||||
cljsjs/react-dom-server {:mvn/version "16.8.6-0"}
|
cljsjs/react-dom-server {:mvn/version "16.9.0-0"}
|
||||||
|
|
||||||
environ/environ {:mvn/version "1.1.0"}
|
environ/environ {:mvn/version "1.1.0"}
|
||||||
metosin/reitit-core {:mvn/version "0.3.9"}
|
metosin/reitit-core {:mvn/version "0.3.9"}
|
||||||
|
|
||||||
funcool/beicon {:mvn/version "5.1.0-SNAPSHOT"}
|
funcool/beicon {:mvn/version "5.1.0"}
|
||||||
funcool/cuerdas {:mvn/version "2.2.0"}
|
funcool/cuerdas {:mvn/version "2.2.0"}
|
||||||
funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"}
|
funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"}
|
||||||
funcool/potok {:mvn/version "2.3.0"}
|
funcool/potok {:mvn/version "2.5.0"}
|
||||||
funcool/promesa {:mvn/version "2.0.1"}
|
funcool/promesa {:mvn/version "3.0.0-SNAPSHOT"}
|
||||||
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
|
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
|
||||||
}
|
}
|
||||||
:paths ["src" "vendor" "resources"]
|
:paths ["src" "vendor" "resources"]
|
||||||
|
|
210
frontend/package-lock.json
generated
210
frontend/package-lock.json
generated
|
@ -550,9 +550,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30000988",
|
"version": "1.0.30000989",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000988.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz",
|
||||||
"integrity": "sha512-lPj3T8poYrRc/bniW5SQPND3GRtSrQdUM/R4mCYTbZxyi3jQiggLvZH4+BYUuX0t4TXjU+vMM7KFDQg+rSzZUQ==",
|
"integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"caseless": {
|
"caseless": {
|
||||||
|
@ -573,9 +573,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||||
"integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==",
|
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "^2.0.0",
|
"anymatch": "^2.0.0",
|
||||||
|
@ -973,9 +973,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.3.207",
|
"version": "1.3.237",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.207.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.237.tgz",
|
||||||
"integrity": "sha512-RIgAnfqbjZNECBLjslfy4cIYvcPl3GAXmnENrcoo0TZ8fGkyEEAealAbO7MoevW4xYUPe+e68cWAj6eP0DmMHw==",
|
"integrity": "sha512-SPAFjDr/7iiVK2kgTluwxela6eaWjjFkS9rO/iYpB/KGXgccUom5YC7OIf19c8m8GGptWxLU0Em8xM64A/N7Fg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
|
@ -1907,6 +1907,17 @@
|
||||||
"inherits": "~2.0.0",
|
"inherits": "~2.0.0",
|
||||||
"mkdirp": ">=0.5 0",
|
"mkdirp": ">=0.5 0",
|
||||||
"rimraf": "2"
|
"rimraf": "2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
|
@ -2079,9 +2090,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||||
"integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
|
"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"gulp": {
|
"gulp": {
|
||||||
|
@ -2125,15 +2136,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gulp-autoprefixer": {
|
"gulp-autoprefixer": {
|
||||||
"version": "6.1.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-autoprefixer/-/gulp-autoprefixer-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-autoprefixer/-/gulp-autoprefixer-7.0.0.tgz",
|
||||||
"integrity": "sha512-Ti/BUFe+ekhbDJfspZIMiOsOvw51KhI9EncsDfK7NaxjqRm+v4xS9v99kPxEoiDavpWqQWvG8Y6xT1mMlB3aXA==",
|
"integrity": "sha512-ZGMA/9iPF7kfZIVhznd3eylivBcyLCgcV32cWtvM7j2IjEQzoRg1XaKgCeO5ytutq8XW3Rip+iM4EsVbNshoZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"autoprefixer": "^9.5.1",
|
"autoprefixer": "^9.6.1",
|
||||||
"fancy-log": "^1.3.2",
|
"fancy-log": "^1.3.2",
|
||||||
"plugin-error": "^1.0.1",
|
"plugin-error": "^1.0.1",
|
||||||
"postcss": "^7.0.2",
|
"postcss": "^7.0.17",
|
||||||
"through2": "^3.0.1",
|
"through2": "^3.0.1",
|
||||||
"vinyl-sourcemaps-apply": "^0.2.1"
|
"vinyl-sourcemaps-apply": "^0.2.1"
|
||||||
},
|
},
|
||||||
|
@ -2187,14 +2198,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gulp-if": {
|
"gulp-if": {
|
||||||
"version": "2.0.2",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz",
|
||||||
"integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=",
|
"integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"gulp-match": "^1.0.3",
|
"gulp-match": "^1.1.0",
|
||||||
"ternary-stream": "^2.0.1",
|
"ternary-stream": "^3.0.0",
|
||||||
"through2": "^2.0.1"
|
"through2": "^3.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"through2": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"readable-stream": "2 || 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gulp-match": {
|
"gulp-match": {
|
||||||
|
@ -2207,18 +2229,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gulp-mustache": {
|
"gulp-mustache": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-mustache/-/gulp-mustache-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-mustache/-/gulp-mustache-4.1.2.tgz",
|
||||||
"integrity": "sha512-Cdtzf+owfMF7pD9bwxs+afribmWrCY010uB4GpbnAs9/hpLScnNRTe4I7kKhr/Cn9JJyHDDN/hIsvhAhvP2doA==",
|
"integrity": "sha512-4nJKL6akiP77Znbog2my7XbJ94VnS8HmMBwLYvUNoSYmD99I9vCopIok6XXT1nOu0zXztc5HxQ2PndYt06PWAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^2.0.0",
|
||||||
"mustache": "^3.0.1",
|
"mustache": "^3.0.1",
|
||||||
"plugin-error": "^1.0.0",
|
"plugin-error": "^1.0.0",
|
||||||
"replace-ext": "^1.0.0",
|
"replace-ext": "^1.0.0",
|
||||||
"through2": "^3.0.0"
|
"through2": "^3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"through2": {
|
"through2": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
|
||||||
|
@ -2452,9 +2480,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.7.1",
|
"version": "2.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz",
|
||||||
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
|
"integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"http-signature": {
|
"http-signature": {
|
||||||
|
@ -2989,13 +3017,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merge-stream": {
|
"merge-stream": {
|
||||||
"version": "1.0.1",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
"integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
|
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"requires": {
|
|
||||||
"readable-stream": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
|
@ -3093,9 +3118,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mustache": {
|
"mustache": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.2.tgz",
|
||||||
"integrity": "sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA==",
|
"integrity": "sha512-64neoEgmozb8e/ecGBOSE+RfnevLSFzCI0UKPcrWmjv953/8fXhYO9+EQFtfbi6hwoFxcTA+Fp5mRiOiI9eTuA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mute-stdout": {
|
"mute-stdout": {
|
||||||
|
@ -3155,6 +3180,15 @@
|
||||||
"which": "1"
|
"which": "1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||||
|
@ -3164,9 +3198,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "1.1.26",
|
"version": "1.1.28",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.28.tgz",
|
||||||
"integrity": "sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==",
|
"integrity": "sha512-AQw4emh6iSXnCpDiFe0phYcThiccmkNWMZnFZ+lDJjAP8J0m2fVd59duvUUyuTirQOhIAajTFkzG6FHCLBO59g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"semver": "^5.3.0"
|
"semver": "^5.3.0"
|
||||||
|
@ -3612,9 +3646,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss-value-parser": {
|
"postcss-value-parser": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
|
||||||
"integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==",
|
"integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pretty-hrtime": {
|
"pretty-hrtime": {
|
||||||
|
@ -3636,9 +3670,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz",
|
||||||
"integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==",
|
"integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
|
@ -3856,9 +3890,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.11.1",
|
"version": "1.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
|
||||||
"integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
|
"integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"path-parse": "^1.0.6"
|
"path-parse": "^1.0.6"
|
||||||
|
@ -3896,9 +3930,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "2.6.3",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz",
|
||||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
"integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
|
@ -3959,9 +3993,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.7.0",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
|
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"semver-greatest-satisfied-range": {
|
"semver-greatest-satisfied-range": {
|
||||||
|
@ -4353,15 +4387,49 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ternary-stream": {
|
"ternary-stream": {
|
||||||
"version": "2.1.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz",
|
||||||
"integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==",
|
"integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"duplexify": "^3.5.0",
|
"duplexify": "^4.1.1",
|
||||||
"fork-stream": "^0.0.4",
|
"fork-stream": "^0.0.4",
|
||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
"through2": "^2.0.1"
|
"through2": "^3.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"duplexify": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1",
|
||||||
|
"stream-shift": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"through2": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"readable-stream": "2 || 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"through2": {
|
"through2": {
|
||||||
|
@ -4500,9 +4568,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz",
|
||||||
"integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==",
|
"integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typedarray": {
|
"typedarray": {
|
||||||
|
@ -4636,9 +4704,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"v8flags": {
|
"v8flags": {
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-autoprefixer": "^6.1.0",
|
"gulp-autoprefixer": "^7.0.0",
|
||||||
"gulp-clean-css": "^4.2.0",
|
"gulp-clean-css": "^4.2.0",
|
||||||
"gulp-gzip": "^1.4.2",
|
"gulp-gzip": "^1.4.2",
|
||||||
"gulp-if": "^2.0.2",
|
"gulp-if": "^3.0.0",
|
||||||
"gulp-mustache": "^4.1.1",
|
"gulp-mustache": "^4.1.2",
|
||||||
"gulp-plumber": "^1.2.1",
|
"gulp-plumber": "^1.2.1",
|
||||||
"gulp-rename": "^1.4.0",
|
"gulp-rename": "^1.4.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^4.0.2",
|
||||||
"rimraf": "^2.6.3"
|
"rimraf": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"source-map-support": "^0.5.12"
|
"source-map-support": "^0.5.12"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
(ns uxbox.builtins.colors
|
(ns uxbox.builtins.colors
|
||||||
(:require [uxbox.util.uuid :as uuid]
|
(:require [uxbox.util.uuid :as uuid]
|
||||||
[uxbox.util.data :refer (index-by)]))
|
[uxbox.util.data :refer [index-by-id]]))
|
||||||
|
|
||||||
(def collections-list
|
(def collections-list
|
||||||
[{:name "UXBOX"
|
[{:name "UXBOX"
|
||||||
|
@ -672,4 +672,4 @@
|
||||||
"#3D464D"}}])
|
"#3D464D"}}])
|
||||||
|
|
||||||
(def collections
|
(def collections
|
||||||
(index-by collections-list :id))
|
(index-by-id collections-list))
|
||||||
|
|
|
@ -67,12 +67,12 @@
|
||||||
(add-watch html-history/path ::main #(on-navigate router %4))
|
(add-watch html-history/path ::main #(on-navigate router %4))
|
||||||
|
|
||||||
(when (:auth storage)
|
(when (:auth storage)
|
||||||
(st/emit! (udu/fetch-profile)))
|
(st/emit! udu/fetch-profile))
|
||||||
|
|
||||||
(mf/mount (ui/app) (dom/get-element "app"))
|
(mf/mount (mf/element ui/app) (dom/get-element "app"))
|
||||||
(mf/mount (lightbox) (dom/get-element "lightbox"))
|
(mf/mount (lightbox) (dom/get-element "lightbox"))
|
||||||
(mf/mount (mf/element modal) (dom/get-element "modal"))
|
(mf/mount (mf/element modal) (dom/get-element "modal"))
|
||||||
(mf/mount (loader) (dom/get-element "loader"))
|
(mf/mount (mf/element loader) (dom/get-element "loader"))
|
||||||
|
|
||||||
(on-navigate router cpath)))
|
(on-navigate router cpath)))
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,14 @@
|
||||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.data.auth
|
(ns uxbox.main.data.auth
|
||||||
(:require [cljs.spec.alpha :as s]
|
(:require
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.main.store :refer [initial-state]]
|
[uxbox.main.store :refer [initial-state]]
|
||||||
[uxbox.main.data.projects :as udp]
|
[uxbox.main.data.users :as du]
|
||||||
[uxbox.main.data.users :as udu]
|
[uxbox.util.messages :as um]
|
||||||
[uxbox.util.messages :as uum]
|
|
||||||
[uxbox.util.router :as rt]
|
[uxbox.util.router :as rt]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.i18n :as i18n :refer [tr]]
|
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||||
|
@ -22,12 +21,16 @@
|
||||||
(s/def ::username string?)
|
(s/def ::username string?)
|
||||||
(s/def ::password string?)
|
(s/def ::password string?)
|
||||||
(s/def ::fullname string?)
|
(s/def ::fullname string?)
|
||||||
(s/def ::email us/email?)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::token string?)
|
|
||||||
|
|
||||||
;; --- Logged In
|
;; --- Logged In
|
||||||
|
|
||||||
(defrecord LoggedIn [data]
|
(defn logged-in
|
||||||
|
[data]
|
||||||
|
(reify
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::logged-in)
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [this state]
|
(update [this state]
|
||||||
(assoc state :auth data))
|
(assoc state :auth data))
|
||||||
|
@ -35,20 +38,22 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [this state s]
|
(watch [this state s]
|
||||||
(swap! storage assoc :auth data)
|
(swap! storage assoc :auth data)
|
||||||
(rx/of (udu/fetch-profile)
|
(rx/of du/fetch-profile
|
||||||
(rt/navigate :dashboard/projects))))
|
(rt/navigate :dashboard/projects)))))
|
||||||
|
|
||||||
(defn logged-in?
|
(defn logged-in?
|
||||||
[v]
|
[v]
|
||||||
(instance? LoggedIn v))
|
(= (ptk/type v) ::logged-in))
|
||||||
|
|
||||||
(defn logged-in
|
|
||||||
[data]
|
|
||||||
(LoggedIn. data))
|
|
||||||
|
|
||||||
;; --- Login
|
;; --- Login
|
||||||
|
|
||||||
(defrecord Login [username password]
|
(s/def ::login-params
|
||||||
|
(s/keys :req-un [::username ::password]))
|
||||||
|
|
||||||
|
(defn login
|
||||||
|
[{:keys [username password] :as data}]
|
||||||
|
(s/assert ::login-params data)
|
||||||
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(merge state (dissoc initial-state :route :router)))
|
(merge state (dissoc initial-state :route :router)))
|
||||||
|
@ -58,23 +63,16 @@
|
||||||
(let [params {:username username
|
(let [params {:username username
|
||||||
:password password
|
:password password
|
||||||
:scope "webapp"}
|
:scope "webapp"}
|
||||||
on-error #(rx/of (uum/error (tr "errors.auth.unauthorized")))]
|
on-error #(rx/of (um/error (tr "errors.auth.unauthorized")))]
|
||||||
(->> (rp/req :auth/login params)
|
(->> (rp/req :auth/login params)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/map logged-in)
|
(rx/map logged-in)
|
||||||
(rx/catch rp/client-error? on-error)))))
|
(rx/catch rp/client-error? on-error))))))
|
||||||
|
|
||||||
(s/def ::login-event
|
|
||||||
(s/keys :req-un [::username ::password]))
|
|
||||||
|
|
||||||
(defn login
|
|
||||||
[params]
|
|
||||||
{:pre [(us/valid? ::login-event params)]}
|
|
||||||
(map->Login params))
|
|
||||||
|
|
||||||
;; --- Logout
|
;; --- Logout
|
||||||
|
|
||||||
(defrecord ClearUserData []
|
(def clear-user-data
|
||||||
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(merge state (dissoc initial-state :route :router)))
|
(merge state (dissoc initial-state :route :router)))
|
||||||
|
@ -87,23 +85,29 @@
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state s]
|
(effect [_ state s]
|
||||||
(reset! storage {})
|
(reset! storage {})
|
||||||
(i18n/set-default-locale!)))
|
(i18n/set-default-locale!))))
|
||||||
|
|
||||||
(defrecord Logout []
|
(def logout
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(rx/of (rt/nav :auth/login)
|
(rx/of (rt/nav :auth/login)
|
||||||
(->ClearUserData))))
|
clear-user-data))))
|
||||||
|
|
||||||
(defn logout
|
|
||||||
[]
|
|
||||||
(->Logout))
|
|
||||||
|
|
||||||
;; --- Register
|
;; --- Register
|
||||||
|
|
||||||
;; TODO: clean form on success
|
(s/def ::register-params
|
||||||
|
(s/keys :req-un [::fullname
|
||||||
|
::username
|
||||||
|
::password
|
||||||
|
::email]))
|
||||||
|
|
||||||
(defrecord Register [data on-error]
|
(defn register
|
||||||
|
"Create a register event instance."
|
||||||
|
[data on-error]
|
||||||
|
(s/assert ::register-params data)
|
||||||
|
(s/assert ::us/fn on-error)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(letfn [(handle-error [{payload :payload}]
|
(letfn [(handle-error [{payload :payload}]
|
||||||
|
@ -117,21 +121,17 @@
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter #(= % ::registered))
|
(rx/filter #(= % ::registered))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map #(login data)))))))
|
(rx/map #(login data))))))))
|
||||||
|
|
||||||
(s/def ::register-event
|
|
||||||
(s/keys :req-un [::fullname ::username ::email ::password]))
|
|
||||||
|
|
||||||
(defn register
|
|
||||||
"Create a register event instance."
|
|
||||||
[data on-error]
|
|
||||||
{:pre [(us/valid? ::register-event data)
|
|
||||||
(fn? on-error)]}
|
|
||||||
(Register. data on-error))
|
|
||||||
|
|
||||||
;; --- Recovery Request
|
;; --- Recovery Request
|
||||||
|
|
||||||
(defrecord RecoveryRequest [data]
|
(s/def ::recovery-request-params
|
||||||
|
(s/keys :req-un [::username]))
|
||||||
|
|
||||||
|
(defn recovery-request
|
||||||
|
[data]
|
||||||
|
(s/assert ::recovery-request-params data)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(letfn [(on-error [{payload :payload}]
|
(letfn [(on-error [{payload :payload}]
|
||||||
|
@ -144,15 +144,8 @@
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter #(= % ::recovery-requested))
|
(rx/filter #(= % ::recovery-requested))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map #(uum/info (tr "auth.message.recovery-token-sent"))))))))
|
;; TODO: this should be moved to the UI part
|
||||||
|
(rx/map #(um/info (tr "auth.message.recovery-token-sent")))))))))
|
||||||
(s/def ::recovery-request-event
|
|
||||||
(s/keys :req-un [::username]))
|
|
||||||
|
|
||||||
(defn recovery-request
|
|
||||||
[data]
|
|
||||||
{:pre [(us/valid? ::recovery-request-event data)]}
|
|
||||||
(RecoveryRequest. data))
|
|
||||||
|
|
||||||
;; --- Check Recovery Token
|
;; --- Check Recovery Token
|
||||||
|
|
||||||
|
@ -162,7 +155,7 @@
|
||||||
(letfn [(on-error [{payload :payload}]
|
(letfn [(on-error [{payload :payload}]
|
||||||
(rx/of
|
(rx/of
|
||||||
(rt/navigate :auth/login)
|
(rt/navigate :auth/login)
|
||||||
(uum/error (tr "errors.auth.invalid-recovery-token"))))]
|
(um/error (tr "errors.auth.invalid-recovery-token"))))]
|
||||||
(->> (rp/req :auth/validate-recovery-token token)
|
(->> (rp/req :auth/validate-recovery-token token)
|
||||||
(rx/ignore)
|
(rx/ignore)
|
||||||
(rx/catch rp/client-error? on-error)))))
|
(rx/catch rp/client-error? on-error)))))
|
||||||
|
@ -174,23 +167,22 @@
|
||||||
|
|
||||||
;; --- Recovery (Password)
|
;; --- Recovery (Password)
|
||||||
|
|
||||||
(defrecord Recovery [token password]
|
(s/def ::token string?)
|
||||||
ptk/WatchEvent
|
(s/def ::recovery-params
|
||||||
(watch [_ state stream]
|
|
||||||
(letfn [(on-error [{payload :payload}]
|
|
||||||
(rx/of (uum/error (tr "errors.auth.invalid-recovery-token"))))
|
|
||||||
(on-success [{payload :payload}]
|
|
||||||
(rx/of
|
|
||||||
(rt/navigate :auth/login)
|
|
||||||
(uum/info (tr "auth.message.password-recovered"))))]
|
|
||||||
(->> (rp/req :auth/recovery {:token token :password password})
|
|
||||||
(rx/mapcat on-success)
|
|
||||||
(rx/catch rp/client-error? on-error)))))
|
|
||||||
|
|
||||||
(s/def ::recovery-event
|
|
||||||
(s/keys :req-un [::username ::token]))
|
(s/keys :req-un [::username ::token]))
|
||||||
|
|
||||||
(defn recovery
|
(defn recovery
|
||||||
[{:keys [token password] :as data}]
|
[{:keys [token password] :as data}]
|
||||||
{:pre [(us/valid? ::recovery-event data)]}
|
(s/assert ::recovery-params data)
|
||||||
(Recovery. token password))
|
(reify
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(letfn [(on-error [{payload :payload}]
|
||||||
|
(rx/of (um/error (tr "errors.auth.invalid-recovery-token"))))
|
||||||
|
(on-success [{payload :payload}]
|
||||||
|
(rx/of
|
||||||
|
(rt/navigate :auth/login)
|
||||||
|
(um/info (tr "auth.message.password-recovered"))))]
|
||||||
|
(->> (rp/req :auth/recovery {:token token :password password})
|
||||||
|
(rx/mapcat on-success)
|
||||||
|
(rx/catch rp/client-error? on-error))))))
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.util.router :as r]
|
[uxbox.util.router :as r]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.util.forms :as sc]
|
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.main.data.projects :as dp]
|
[uxbox.main.data.projects :as dp]
|
||||||
[uxbox.main.data.colors :as dc]
|
[uxbox.main.data.colors :as dc]
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
[uxbox.util.data :refer [replace-by-id
|
[uxbox.util.data :refer [replace-by-id
|
||||||
index-by]]))
|
index-by]]))
|
||||||
|
|
||||||
|
;; TODO: this need refactor (completely broken)
|
||||||
|
|
||||||
;; --- Initialize History State
|
;; --- Initialize History State
|
||||||
|
|
||||||
(declare fetch-history)
|
(declare fetch-history)
|
||||||
|
@ -52,7 +54,7 @@
|
||||||
(deftype PinnedPageHistoryFetched [items]
|
(deftype PinnedPageHistoryFetched [items]
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [items-map (index-by items :version)
|
(let [items-map (index-by :version items)
|
||||||
items-set (into #{} items)]
|
items-set (into #{} items)]
|
||||||
(update-in state [:workspace :history]
|
(update-in state [:workspace :history]
|
||||||
(fn [history]
|
(fn [history]
|
||||||
|
@ -164,7 +166,7 @@
|
||||||
(assoc :history true
|
(assoc :history true
|
||||||
:data (:data item)))]
|
:data (:data item)))]
|
||||||
(-> state
|
(-> state
|
||||||
(udp/assoc-page page)
|
(udp/unpack-page page)
|
||||||
(assoc-in [:workspace :history :selected] version)))))
|
(assoc-in [:workspace :history :selected] version)))))
|
||||||
|
|
||||||
(defn select-page-history
|
(defn select-page-history
|
||||||
|
@ -203,7 +205,7 @@
|
||||||
(set! noop true)
|
(set! noop true)
|
||||||
state)
|
state)
|
||||||
(let [packed (get-in state [:packed-pages page-id])]
|
(let [packed (get-in state [:packed-pages page-id])]
|
||||||
(-> (udp/assoc-page state packed)
|
(-> (udp/unpack-page state packed)
|
||||||
(assoc-in [:workspace :history :deselecting] true)
|
(assoc-in [:workspace :history :deselecting] true)
|
||||||
(assoc-in [:workspace :history :selected] nil))))))
|
(assoc-in [:workspace :history :selected] nil))))))
|
||||||
|
|
||||||
|
|
|
@ -5,65 +5,78 @@
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.data.pages
|
(ns uxbox.main.data.pages
|
||||||
(:require [cljs.spec.alpha :as s]
|
(:require
|
||||||
[cuerdas.core :as str]
|
[cljs.spec.alpha :as s]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[lentes.core :as l]
|
[cuerdas.core :as str]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.main.lenses :as ul]
|
[uxbox.util.data :refer [index-by-id]]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.router :as r]
|
|
||||||
[uxbox.util.timers :as ts]
|
[uxbox.util.timers :as ts]
|
||||||
[uxbox.util.time :as dt]))
|
[uxbox.util.uuid :as uuid]))
|
||||||
|
|
||||||
;; --- Specs
|
;; --- Struct
|
||||||
|
|
||||||
(s/def ::grid-x-axis number?)
|
(s/def ::id ::us/uuid)
|
||||||
(s/def ::grid-y-axis number?)
|
(s/def ::name ::us/string)
|
||||||
(s/def ::grid-color string?)
|
(s/def ::inst ::us/inst)
|
||||||
(s/def ::background string?)
|
(s/def ::type ::us/keyword)
|
||||||
(s/def ::background-opacity number?)
|
(s/def ::project ::us/uuid)
|
||||||
(s/def ::grid-alignment boolean?)
|
(s/def ::created-at ::us/inst)
|
||||||
(s/def ::width number?)
|
(s/def ::modified-at ::us/inst)
|
||||||
(s/def ::height number?)
|
(s/def ::version ::us/number)
|
||||||
(s/def ::layout string?)
|
(s/def ::width (s/and ::us/number ::us/positive))
|
||||||
|
(s/def ::height (s/and ::us/number ::us/positive))
|
||||||
|
(s/def ::grid-x-axis ::us/number)
|
||||||
|
(s/def ::grid-y-axis ::us/number)
|
||||||
|
(s/def ::grid-color ::us/string)
|
||||||
|
(s/def ::order ::us/number)
|
||||||
|
(s/def ::background ::us/string)
|
||||||
|
(s/def ::background-opacity ::us/number)
|
||||||
|
(s/def ::user ::us/uuid)
|
||||||
|
|
||||||
(s/def ::metadata
|
(s/def ::metadata
|
||||||
(s/keys :req-un [::width ::height]
|
(s/keys :req-un [::width ::height]
|
||||||
:opt-un [::grid-y-axis
|
:opt-un [::grid-y-axis
|
||||||
::grid-x-axis
|
::grid-x-axis
|
||||||
::grid-color
|
::grid-color
|
||||||
::grid-alignment
|
|
||||||
::order
|
::order
|
||||||
::background
|
::background
|
||||||
::background-opacity
|
::background-opacity]))
|
||||||
::layout]))
|
|
||||||
|
|
||||||
(s/def ::id uuid?)
|
|
||||||
(s/def ::name string?)
|
|
||||||
(s/def ::version integer?)
|
|
||||||
(s/def ::project uuid?)
|
|
||||||
(s/def ::user uuid?)
|
|
||||||
(s/def ::created-at inst?)
|
|
||||||
(s/def ::modified-at inst?)
|
|
||||||
(s/def ::shapes
|
(s/def ::shapes
|
||||||
(-> (s/coll-of uuid? :kind vector?)
|
(s/coll-of ::us/uuid :kind vector? :into []))
|
||||||
(s/nilable)))
|
|
||||||
|
|
||||||
(s/def ::page-entity
|
(s/def ::page-entity
|
||||||
(s/keys :req-un [::id
|
(s/keys :req-un [::id
|
||||||
::name
|
::name
|
||||||
::project
|
::project
|
||||||
::version
|
|
||||||
::created-at
|
::created-at
|
||||||
::modified-at
|
::modified-at
|
||||||
::user
|
::user
|
||||||
::metadata
|
::metadata
|
||||||
::shapes]))
|
::shapes]))
|
||||||
|
|
||||||
;; TODO: add interactions to spec
|
(s/def ::minimal-shape
|
||||||
|
(s/keys :req-un [::type ::name]
|
||||||
|
:opt-un [::id]))
|
||||||
|
|
||||||
|
(s/def :uxbox.backend/shapes
|
||||||
|
(s/coll-of ::minimal-shape :kind vector?))
|
||||||
|
|
||||||
|
(s/def :uxbox.backend/data
|
||||||
|
(s/keys :req-un [:uxbox.backend/shapes]))
|
||||||
|
|
||||||
|
(s/def ::server-page
|
||||||
|
(s/keys :req-un [::id ::name
|
||||||
|
::project
|
||||||
|
::version
|
||||||
|
::created-at
|
||||||
|
::modified-at
|
||||||
|
::user
|
||||||
|
::metadata
|
||||||
|
:uxbox.backend/data]))
|
||||||
|
|
||||||
;; --- Protocols
|
;; --- Protocols
|
||||||
|
|
||||||
|
@ -77,45 +90,26 @@
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
;; TODO: make sure remove all :tmp-* related attrs from shape
|
|
||||||
|
|
||||||
(defn pack-page-shapes
|
|
||||||
"Create a hash-map of shapes indexed by their id that belongs
|
|
||||||
to the provided page."
|
|
||||||
[state page]
|
|
||||||
(let [lookup-shape-xf (map #(get-in state [:shapes %]))]
|
|
||||||
(reduce (fn reducer [acc {:keys [id type items] :as shape}]
|
|
||||||
(let [shape (assoc shape :page (:id page))]
|
|
||||||
(cond
|
|
||||||
(= type :group)
|
|
||||||
(reduce reducer
|
|
||||||
(assoc acc id shape)
|
|
||||||
(sequence lookup-shape-xf items))
|
|
||||||
|
|
||||||
(uuid? id)
|
|
||||||
(assoc acc id shape)
|
|
||||||
|
|
||||||
:else acc)))
|
|
||||||
{}
|
|
||||||
(sequence lookup-shape-xf (:shapes page)))))
|
|
||||||
|
|
||||||
(defn pack-page
|
(defn pack-page
|
||||||
"Return a packed version of page object ready
|
"Return a packed version of page object ready
|
||||||
for send to remore storage service."
|
for send to remore storage service."
|
||||||
[state id]
|
[state id]
|
||||||
|
(letfn [(pack-shapes [ids]
|
||||||
|
(mapv #(get-in state [:shapes %]) ids))]
|
||||||
(let [page (get-in state [:pages id])
|
(let [page (get-in state [:pages id])
|
||||||
shapes (pack-page-shapes state page)]
|
data {:shapes (pack-shapes (:shapes page))}]
|
||||||
(-> page
|
(-> page
|
||||||
(assoc-in [:data :shapes] (vec (:shapes page)))
|
(assoc :data data)
|
||||||
(assoc-in [:data :shapes-map] shapes)
|
(dissoc :shapes)))))
|
||||||
(dissoc :shapes))))
|
|
||||||
|
|
||||||
(defn assoc-page
|
(defn unpack-page
|
||||||
"Unpacks packed page object and assocs it to the
|
"Unpacks packed page object and assocs it to the
|
||||||
provided state."
|
provided state."
|
||||||
[state {:keys [id data] :as page}]
|
[state {:keys [id data] :as page}]
|
||||||
(let [shapes (:shapes data)
|
(let [shapes-data (:shapes data [])
|
||||||
shapes-map (:shapes-map data)
|
shapes (mapv :id shapes-data)
|
||||||
|
shapes-map (index-by-id shapes-data)
|
||||||
|
|
||||||
page (-> page
|
page (-> page
|
||||||
(dissoc :data)
|
(dissoc :data)
|
||||||
(assoc :shapes shapes))]
|
(assoc :shapes shapes))]
|
||||||
|
@ -126,16 +120,16 @@
|
||||||
(defn purge-page
|
(defn purge-page
|
||||||
"Remove page and all related stuff from the state."
|
"Remove page and all related stuff from the state."
|
||||||
[state id]
|
[state id]
|
||||||
(let [shapes (get state :shapes)]
|
(let [pid (get-in state [:pages id :project])]
|
||||||
(-> state
|
(-> state
|
||||||
|
(update-in [:projects pid :pages] #(filterv (partial not= id) %))
|
||||||
(update :pages dissoc id)
|
(update :pages dissoc id)
|
||||||
(update :packed-pages dissoc id)
|
(update :packed-pages dissoc id)
|
||||||
(assoc :shapes (reduce-kv (fn [acc k v]
|
(update :shapes (fn [shapes] (->> shapes
|
||||||
(if (= (:page v) id)
|
(map second)
|
||||||
(dissoc acc k)
|
(filter #(= (:page %) id))
|
||||||
acc))
|
(map :id)
|
||||||
shapes
|
(apply dissoc shapes)))))))
|
||||||
shapes)))))
|
|
||||||
|
|
||||||
(defn assoc-packed-page
|
(defn assoc-packed-page
|
||||||
[state {:keys [id] :as page}]
|
[state {:keys [id] :as page}]
|
||||||
|
@ -147,10 +141,17 @@
|
||||||
|
|
||||||
;; --- Pages Fetched
|
;; --- Pages Fetched
|
||||||
|
|
||||||
(deftype PagesFetched [id pages]
|
(defn pages-fetched
|
||||||
|
[id pages]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(s/assert ::us/coll pages)
|
||||||
|
(reify
|
||||||
IDeref
|
IDeref
|
||||||
(-deref [_] (list id pages))
|
(-deref [_] (list id pages))
|
||||||
|
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::page-fetched)
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [get-order #(get-in % [:metadata :order])
|
(let [get-order #(get-in % [:metadata :order])
|
||||||
|
@ -158,115 +159,129 @@
|
||||||
page-ids (into [] (map :id) pages)]
|
page-ids (into [] (map :id) pages)]
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:projects id :pages] page-ids)
|
(assoc-in $ [:projects id :pages] page-ids)
|
||||||
;; TODO: this is a workaround
|
(reduce unpack-page $ pages)
|
||||||
(assoc-in $ [:projects id :page-id] (first page-ids))
|
(reduce assoc-packed-page $ pages))))))
|
||||||
(reduce assoc-page $ pages)
|
|
||||||
(reduce assoc-packed-page $ pages)))))
|
|
||||||
|
|
||||||
(defn pages-fetched
|
|
||||||
[id pages]
|
|
||||||
{:pre [(uuid? id) (coll? pages)]}
|
|
||||||
(PagesFetched. id pages))
|
|
||||||
|
|
||||||
(defn pages-fetched?
|
(defn pages-fetched?
|
||||||
[v]
|
[v]
|
||||||
(instance? PagesFetched v))
|
(= ::page-fetched (ptk/type v)))
|
||||||
|
|
||||||
;; --- Fetch Pages (by project id)
|
;; --- Fetch Pages (by project id)
|
||||||
|
|
||||||
(deftype FetchPages [id]
|
(defn fetch-pages
|
||||||
|
[id]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(->> (rp/req :fetch/pages-by-project {:project id})
|
(->> (rp/req :fetch/pages-by-project {:project id})
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/map #(pages-fetched id %)))))
|
(rx/map #(pages-fetched id %))))))
|
||||||
|
|
||||||
(defn fetch-pages
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(FetchPages. id))
|
|
||||||
|
|
||||||
;; --- Page Created
|
;; --- Page Created
|
||||||
|
|
||||||
(declare reorder-pages)
|
(declare rehash-pages)
|
||||||
|
|
||||||
(deftype PageCreated [data]
|
(s/def ::page-created-params
|
||||||
ptk/UpdateEvent
|
(s/keys :req-un [::id ::name ::project ::metadata]))
|
||||||
(update [_ state]
|
|
||||||
(-> state
|
|
||||||
(assoc-page data)
|
|
||||||
(assoc-packed-page data)))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(rx/of (reorder-pages (:project data)))))
|
|
||||||
|
|
||||||
(s/def ::page-created
|
|
||||||
(s/keys :req-un [::id
|
|
||||||
::name
|
|
||||||
::project
|
|
||||||
::metadata]))
|
|
||||||
|
|
||||||
(defn page-created
|
(defn page-created
|
||||||
[data]
|
[data]
|
||||||
{:pre [(us/valid? ::page-created data)]}
|
(s/assert ::page-created-params data)
|
||||||
(PageCreated. data))
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pid (:project data)]
|
||||||
|
(-> state
|
||||||
|
(update-in [:projects pid :pages] (fnil conj []) (:id data))
|
||||||
|
(unpack-page data)
|
||||||
|
(assoc-packed-page data))))
|
||||||
|
|
||||||
;; --- Create Page
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(rx/of (rehash-pages (:project data))))))
|
||||||
|
|
||||||
(deftype CreatePage [name project width height layout]
|
;; --- Create Page Form
|
||||||
|
|
||||||
|
(s/def ::form-created-page-params
|
||||||
|
(s/keys :req-un [::name ::project ::width ::height]))
|
||||||
|
|
||||||
|
(defn form->create-page
|
||||||
|
[{:keys [name project width height layout] :as data}]
|
||||||
|
(s/assert ::form-created-page-params data)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [this state s]
|
(watch [this state s]
|
||||||
(let [params {:name name
|
(let [canvas {:id (uuid/random)
|
||||||
:project project
|
:name "Canvas 1"
|
||||||
:data {}
|
:type :canvas
|
||||||
:metadata {:width width
|
:x1 200
|
||||||
|
:y1 200
|
||||||
|
:x2 (+ 200 width)
|
||||||
|
:y2 (+ 200 height)}
|
||||||
|
metadata {:width width
|
||||||
:height height
|
:height height
|
||||||
:layout layout
|
:order -100}
|
||||||
:order -100}}]
|
params {:name name
|
||||||
|
:project project
|
||||||
|
:data {:shapes [canvas]}
|
||||||
|
:metadata metadata}]
|
||||||
(->> (rp/req :create/page params)
|
(->> (rp/req :create/page params)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/map page-created)))))
|
(rx/map page-created))))))
|
||||||
|
|
||||||
(s/def ::create-page
|
;; --- Update Page Form
|
||||||
(s/keys :req-un [::name
|
|
||||||
::project
|
|
||||||
::width
|
|
||||||
::height
|
|
||||||
::layout]))
|
|
||||||
|
|
||||||
(defn create-page
|
(s/def ::form-update-page-params
|
||||||
[{:keys [name project width height layout] :as data}]
|
(s/keys :req-un [::id ::name ::width ::height]))
|
||||||
{:pre [(us/valid? ::create-page data)]}
|
|
||||||
(CreatePage. name project width height layout))
|
(defn form->update-page
|
||||||
|
[{:keys [id name width height] :as data}]
|
||||||
|
(s/assert ::form-update-page-params data)
|
||||||
|
(reify
|
||||||
|
IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:pages id]
|
||||||
|
(fn [page]
|
||||||
|
(-> (assoc page :name name)
|
||||||
|
(assoc-in [:name] name)
|
||||||
|
(assoc-in [:metadata :width] width)
|
||||||
|
(assoc-in [:metadata :height] height)))))))
|
||||||
|
|
||||||
;; --- Page Persisted
|
;; --- Page Persisted
|
||||||
|
|
||||||
(deftype PagePersisted [data]
|
(defn page-persisted
|
||||||
IDeref
|
[data]
|
||||||
|
(s/assert ::server-page data)
|
||||||
|
(reify
|
||||||
|
cljs.core/IDeref
|
||||||
(-deref [_] data)
|
(-deref [_] data)
|
||||||
|
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::page-persisted)
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [{:keys [id version]} data]
|
(let [{:keys [id version]} data]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc-in [:pages id :version] version)
|
(assoc-in [:pages id :version] version)
|
||||||
(assoc-packed-page data)))))
|
(assoc-packed-page data))))))
|
||||||
|
|
||||||
(defn- page-persisted?
|
(defn- page-persisted?
|
||||||
[event]
|
[event]
|
||||||
(instance? PagePersisted event))
|
(= (ptk/type event) ::page-persisted))
|
||||||
|
|
||||||
;; TODO: add page spec
|
|
||||||
|
|
||||||
(defn page-persisted
|
|
||||||
[data]
|
|
||||||
{:pre [(map? data)]}
|
|
||||||
(PagePersisted. data))
|
|
||||||
|
|
||||||
;; --- Persist Page
|
;; --- Persist Page
|
||||||
|
|
||||||
(deftype PersistPage [id on-success]
|
(defn persist-page
|
||||||
|
([id] (persist-page id identity))
|
||||||
|
([id on-success]
|
||||||
|
(assert (uuid? id))
|
||||||
|
(reify
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::persist-page)
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [this state s]
|
(watch [this state s]
|
||||||
(let [page (get-in state [:pages id])]
|
(let [page (get-in state [:pages id])]
|
||||||
|
@ -277,38 +292,32 @@
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/do #(when (fn? on-success)
|
(rx/do #(when (fn? on-success)
|
||||||
(ts/schedule-on-idle on-success)))
|
(ts/schedule-on-idle on-success)))
|
||||||
(rx/map page-persisted)))))))
|
(rx/map page-persisted)))))))))
|
||||||
|
|
||||||
(defn persist-page?
|
(defn persist-page?
|
||||||
[v]
|
[v]
|
||||||
(instance? PersistPage v))
|
(= ::persist-page (ptk/type v)))
|
||||||
|
|
||||||
(defn persist-page
|
|
||||||
([id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(PersistPage. id (constantly nil)))
|
|
||||||
([id on-success]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(PersistPage. id on-success)))
|
|
||||||
|
|
||||||
;; --- Page Metadata Persisted
|
;; --- Page Metadata Persisted
|
||||||
|
|
||||||
(deftype MetadataPersisted [id data]
|
(s/def ::metadata-persisted-params
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:pages id :version] (:version data))))
|
|
||||||
|
|
||||||
(s/def ::metadata-persisted-event
|
|
||||||
(s/keys :req-un [::id ::version]))
|
(s/keys :req-un [::id ::version]))
|
||||||
|
|
||||||
(defn metadata-persisted?
|
|
||||||
[v]
|
|
||||||
(instance? MetadataPersisted v))
|
|
||||||
|
|
||||||
(defn metadata-persisted
|
(defn metadata-persisted
|
||||||
[{:keys [id] :as data}]
|
[{:keys [id] :as data}]
|
||||||
{:pre [(us/valid? ::metadata-persisted-event data)]}
|
(s/assert ::metadata-persisted-params data)
|
||||||
(MetadataPersisted. id data))
|
(reify
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::metadata-persisted)
|
||||||
|
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:pages id :version] (:version data)))))
|
||||||
|
|
||||||
|
(defn metadata-persisted?
|
||||||
|
[v]
|
||||||
|
(= ::metadata-persisted (ptk/type v)))
|
||||||
|
|
||||||
|
|
||||||
;; --- Persist Page Metadata
|
;; --- Persist Page Metadata
|
||||||
|
|
||||||
|
@ -329,52 +338,49 @@
|
||||||
|
|
||||||
;; --- Update Page
|
;; --- Update Page
|
||||||
|
|
||||||
(deftype UpdatePage [id data]
|
(defn update-page-attrs
|
||||||
|
[{:keys [id] :as data}]
|
||||||
|
(s/assert ::page-entity data)
|
||||||
|
(reify
|
||||||
IPageUpdate
|
IPageUpdate
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update-in state [:pages id] merge (dissoc data :id :version))))
|
(update-in state [:pages id] merge (dissoc data :id :version)))))
|
||||||
|
|
||||||
(defn update-page
|
|
||||||
[id data]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::page-entity data)]}
|
|
||||||
(UpdatePage. id data))
|
|
||||||
|
|
||||||
;; --- Update Page Metadata
|
;; --- Update Page Metadata
|
||||||
|
|
||||||
(deftype UpdateMetadata [id metadata]
|
(defn update-metadata
|
||||||
|
[id metadata]
|
||||||
|
(s/assert ::id id)
|
||||||
|
(s/assert ::metadata metadata)
|
||||||
|
(reify
|
||||||
IMetadataUpdate
|
IMetadataUpdate
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [this state]
|
(update [this state]
|
||||||
(assoc-in state [:pages id :metadata] metadata)))
|
(assoc-in state [:pages id :metadata] metadata))))
|
||||||
|
|
||||||
(defn update-metadata
|
|
||||||
[id metadata]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::metadata metadata)]}
|
|
||||||
(UpdateMetadata. id metadata))
|
|
||||||
|
|
||||||
;; --- Reorder Pages
|
;; --- Rehash Pages
|
||||||
;;
|
;;
|
||||||
;; A post processing event that normalizes the
|
;; A post processing event that normalizes the
|
||||||
;; page order numbers after a user sorting
|
;; page order numbers after a user sorting
|
||||||
;; operation for a concrete project.
|
;; operation for a concrete project.
|
||||||
|
|
||||||
(defn reorder-pages
|
(defn rehash-pages
|
||||||
[project-id]
|
[id]
|
||||||
{:pre [(uuid? project-id)]}
|
{:pre [(uuid? id)]}
|
||||||
(reify
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [this state]
|
(update [this state]
|
||||||
(let [page-ids (get-in state [:projects project-id :pages])]
|
(let [page-ids (get-in state [:projects id :pages])]
|
||||||
(reduce (fn [state [index id]]
|
(reduce (fn [state [index id]]
|
||||||
(assoc-in state [:pages id :metadata :order] index))
|
(assoc-in state [:pages id :metadata :order] index))
|
||||||
;; TODO: this is workaround
|
state
|
||||||
(assoc-in state [:projects project-id :page-id] (first page-ids))
|
|
||||||
(map-indexed vector page-ids))))
|
(map-indexed vector page-ids))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [page-ids (get-in state [:projects project-id :pages])]
|
(let [page-ids (get-in state [:projects id :pages])]
|
||||||
(->> (rx/from-coll page-ids)
|
(->> (rx/from-coll page-ids)
|
||||||
(rx/map persist-metadata))))))
|
(rx/map persist-metadata))))))
|
||||||
|
|
||||||
|
@ -391,58 +397,34 @@
|
||||||
pages (vec (concat before [page-id] after))]
|
pages (vec (concat before [page-id] after))]
|
||||||
(assoc-in state [:projects project-id :pages] pages)))))
|
(assoc-in state [:projects project-id :pages] pages)))))
|
||||||
|
|
||||||
;; --- Persist Page Form
|
|
||||||
;;
|
|
||||||
;; A specialized event for persist data
|
|
||||||
;; from the update page form.
|
|
||||||
|
|
||||||
(deftype PersistPageUpdateForm [id name width height layout]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [page (-> (get-in state [:pages id])
|
|
||||||
(assoc-in [:name] name)
|
|
||||||
(assoc-in [:metadata :width] width)
|
|
||||||
(assoc-in [:metadata :height] height)
|
|
||||||
(assoc-in [:metadata :layout] layout))]
|
|
||||||
(rx/of (update-page id page)))))
|
|
||||||
|
|
||||||
(s/def ::persist-page-update-form
|
|
||||||
(s/keys :req-un [::name ::width ::height ::layout]))
|
|
||||||
|
|
||||||
(defn persist-page-update-form
|
|
||||||
[id {:keys [name width height layout] :as data}]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::persist-page-update-form data)]}
|
|
||||||
(PersistPageUpdateForm. id name width height layout))
|
|
||||||
|
|
||||||
;; --- Delete Page (by id)
|
;; --- Delete Page (by id)
|
||||||
|
|
||||||
(deftype DeletePage [id callback]
|
(defn delete-page
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(purge-page state id))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(letfn [(on-success [_]
|
|
||||||
#(purge-page % id))]
|
|
||||||
(->> (rp/req :delete/page id)
|
(->> (rp/req :delete/page id)
|
||||||
(rx/map on-success)
|
(rx/map (constantly ::delete-completed))))))
|
||||||
(rx/tap callback)
|
|
||||||
(rx/filter identity)))))
|
|
||||||
|
|
||||||
(defn delete-page
|
|
||||||
([id] (DeletePage. id (constantly nil)))
|
|
||||||
([id callback] (DeletePage. id callback)))
|
|
||||||
|
|
||||||
;; --- Watch Page Changes
|
;; --- Watch Page Changes
|
||||||
|
|
||||||
(deftype WatchPageChanges [id]
|
(defn watch-page-changes
|
||||||
|
[id]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [stopper (->> stream
|
(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
|
||||||
(rx/filter #(= % ::stop-page-watcher))
|
(->> (rx/merge
|
||||||
(rx/take 1))]
|
|
||||||
(rx/merge
|
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter #(or (satisfies? IPageUpdate %)
|
(rx/filter #(or (satisfies? IPageUpdate %)
|
||||||
(= ::page-update %)))
|
(= ::page-update %)))
|
||||||
(rx/take-until stopper)
|
|
||||||
(rx/debounce 1000)
|
(rx/debounce 1000)
|
||||||
(rx/mapcat #(rx/merge (rx/of (persist-page id))
|
(rx/mapcat #(rx/merge (rx/of (persist-page id))
|
||||||
(->> (rx/filter page-persisted? stream)
|
(->> (rx/filter page-persisted? stream)
|
||||||
|
@ -450,13 +432,10 @@
|
||||||
(rx/ignore)))))
|
(rx/ignore)))))
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter #(satisfies? IMetadataUpdate %))
|
(rx/filter #(satisfies? IMetadataUpdate %))
|
||||||
(rx/take-until stopper)
|
|
||||||
(rx/debounce 1000)
|
(rx/debounce 1000)
|
||||||
(rx/mapcat #(rx/merge (rx/of (persist-metadata id))
|
(rx/mapcat #(rx/merge (rx/of (persist-metadata id))
|
||||||
(->> (rx/filter metadata-persisted? stream)
|
(->> (rx/filter metadata-persisted? stream)
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/ignore)))))))))
|
(rx/ignore))))))
|
||||||
|
(rx/take-until stopper))))))
|
||||||
|
|
||||||
(defn watch-page-changes
|
|
||||||
[id]
|
|
||||||
(WatchPageChanges. id))
|
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.data.projects
|
(ns uxbox.main.data.projects
|
||||||
(:require [cljs.spec.alpha :as s]
|
(:require
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.main.data.pages :as udp]
|
[uxbox.main.data.pages :as udp]
|
||||||
|
[uxbox.util.uuid :as uuid]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.time :as dt]
|
[uxbox.util.time :as dt]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
(defn assoc-project
|
(defn assoc-project
|
||||||
"A reduce function for assoc the project to the state map."
|
"A reduce function for assoc the project to the state map."
|
||||||
[state {:keys [id] :as project}]
|
[state {:keys [id] :as project}]
|
||||||
{:pre [(us/valid? ::project-entity project)]}
|
(s/assert ::project-entity project)
|
||||||
(update-in state [:projects id] merge project))
|
(update-in state [:projects id] merge project))
|
||||||
|
|
||||||
(defn dissoc-project
|
(defn dissoc-project
|
||||||
|
@ -159,74 +160,34 @@
|
||||||
|
|
||||||
;; --- Create Project
|
;; --- Create Project
|
||||||
|
|
||||||
(defrecord CreateProject [name width height layout]
|
(s/def ::create-project-params
|
||||||
|
(s/keys :req-un [::name ::width ::height]))
|
||||||
|
|
||||||
|
(defn create-project
|
||||||
|
[{:keys [name] :as params}]
|
||||||
|
(s/assert ::create-project-params params)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [this state s]
|
(watch [this state stream]
|
||||||
(let [project-data {:name name}
|
|
||||||
page-data {:name "Page 0"
|
|
||||||
:data {}
|
|
||||||
:metadata {:width width
|
|
||||||
:height height
|
|
||||||
:layout layout
|
|
||||||
:order 0}}]
|
|
||||||
(->> (rp/req :create/project {:name name})
|
(->> (rp/req :create/project {:name name})
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/mapcat (fn [{:keys [id] :as project}]
|
(rx/mapcat (fn [{:keys [id] :as project}]
|
||||||
(rp/req :create/page (assoc page-data :project id))))
|
(rx/of #(assoc-project % project)
|
||||||
(rx/map #(fetch-projects))))))
|
(udp/form->create-page (assoc params :project id)))))))))
|
||||||
|
|
||||||
(s/def ::create-project-event
|
;; --- Go To Project
|
||||||
(s/keys :req-un [::name
|
|
||||||
::udp/width
|
|
||||||
::udp/height
|
|
||||||
::udp/layout]))
|
|
||||||
|
|
||||||
(defn create-project
|
|
||||||
[data]
|
|
||||||
{:pre [(us/valid? ::create-project-event data)]}
|
|
||||||
(map->CreateProject data))
|
|
||||||
|
|
||||||
;; --- Go To & Go To Page
|
|
||||||
|
|
||||||
(deftype GoToFirstPage [pages]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [[page & rest] (sort-by #(get-in % [:metadata :order]) pages)
|
|
||||||
params {:project (:project page)
|
|
||||||
:page (:id page)}]
|
|
||||||
(rx/of (rt/navigate :workspace/page params)))))
|
|
||||||
|
|
||||||
(defn go-to-first-page
|
|
||||||
[pages]
|
|
||||||
(GoToFirstPage. pages))
|
|
||||||
|
|
||||||
(defrecord GoTo [project-id]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [page-id (get-in state [:projects project-id :page-id])]
|
|
||||||
(if-not page-id
|
|
||||||
(rx/empty)
|
|
||||||
(let [params {:project project-id
|
|
||||||
:page page-id}]
|
|
||||||
(rx/of (rt/navigate :workspace/page params)))))))
|
|
||||||
|
|
||||||
(defrecord GoToPage [project-id page-id]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state s]
|
|
||||||
(let [params {:project project-id
|
|
||||||
:page page-id}]
|
|
||||||
(rx/of (rt/navigate :workspace/page params)))))
|
|
||||||
|
|
||||||
(defn go-to
|
(defn go-to
|
||||||
"A shortcut event that redirects the user to the
|
[id]
|
||||||
first page of the project."
|
{:pre [(uuid? id)]}
|
||||||
([project-id]
|
(reify
|
||||||
{:pre [(uuid? project-id)]}
|
ptk/WatchEvent
|
||||||
(GoTo. project-id))
|
(watch [_ state stream]
|
||||||
([project-id page-id]
|
(let [[page & rest-pages] (get-in state [:projects id :pages])]
|
||||||
{:pre [(uuid? project-id)
|
(when page
|
||||||
(uuid? page-id)]}
|
(let [params {:project id :page page}]
|
||||||
(GoToPage. project-id page-id)))
|
(rx/of (rt/nav :workspace/page params))))))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Update Opts (Filtering & Ordering)
|
;; --- Update Opts (Filtering & Ordering)
|
||||||
|
|
||||||
|
|
|
@ -2,527 +2,541 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.data.shapes
|
(ns uxbox.main.data.shapes
|
||||||
(:require [cljs.spec.alpha :as s]
|
(:require
|
||||||
[lentes.core :as l]
|
[cljs.spec.alpha :as s]
|
||||||
[beicon.core :as rx]
|
|
||||||
[potok.core :as ptk]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.constants :as c]
|
|
||||||
[uxbox.main.refs :as refs]
|
|
||||||
[uxbox.main.lenses :as ul]
|
|
||||||
[uxbox.main.geom :as geom]
|
[uxbox.main.geom :as geom]
|
||||||
[uxbox.main.workers :as uwrk]
|
[uxbox.util.data :refer [index-of]]
|
||||||
[uxbox.main.data.pages :as udp]
|
|
||||||
[uxbox.main.data.shapes-impl :as impl]
|
|
||||||
[uxbox.main.user-events :as uev]
|
|
||||||
[uxbox.util.data :refer [dissoc-in]]
|
|
||||||
[uxbox.util.forms :as sc]
|
|
||||||
[uxbox.util.spec :as us]
|
|
||||||
[uxbox.util.geom.point :as gpt]
|
|
||||||
[uxbox.util.geom.matrix :as gmt]
|
[uxbox.util.geom.matrix :as gmt]
|
||||||
[uxbox.util.router :as r]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.uuid :as uuid]))
|
[uxbox.util.uuid :as uuid]))
|
||||||
|
|
||||||
;; --- Specs
|
;; --- Specs
|
||||||
|
|
||||||
|
(s/def ::id ::us/uuid)
|
||||||
|
(s/def ::blocked boolean?)
|
||||||
|
(s/def ::collapsed boolean?)
|
||||||
|
(s/def ::content string?)
|
||||||
(s/def ::fill-color string?)
|
(s/def ::fill-color string?)
|
||||||
(s/def ::fill-opacity number?)
|
(s/def ::fill-opacity number?)
|
||||||
(s/def ::line-height number?)
|
|
||||||
(s/def ::letter-spacing number?)
|
|
||||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
|
||||||
(s/def ::font-family string?)
|
(s/def ::font-family string?)
|
||||||
|
(s/def ::font-size number?)
|
||||||
(s/def ::font-style string?)
|
(s/def ::font-style string?)
|
||||||
(s/def ::font-weight string?)
|
(s/def ::font-weight string?)
|
||||||
(s/def ::font-size number?)
|
(s/def ::height number?)
|
||||||
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
|
(s/def ::hidden boolean?)
|
||||||
(s/def ::stroke-width number?)
|
(s/def ::id uuid?)
|
||||||
(s/def ::stroke-color string?)
|
(s/def ::letter-spacing number?)
|
||||||
(s/def ::stroke-opacity number?)
|
(s/def ::line-height number?)
|
||||||
(s/def ::rx number?)
|
(s/def ::locked boolean?)
|
||||||
(s/def ::ry number?)
|
(s/def ::name string?)
|
||||||
|
(s/def ::page uuid?)
|
||||||
(s/def ::proportion number?)
|
(s/def ::proportion number?)
|
||||||
(s/def ::proportion-lock boolean?)
|
(s/def ::proportion-lock boolean?)
|
||||||
(s/def ::collapsed boolean?)
|
(s/def ::rx number?)
|
||||||
(s/def ::hidden boolean?)
|
(s/def ::ry number?)
|
||||||
(s/def ::blocked boolean?)
|
(s/def ::stroke-color string?)
|
||||||
(s/def ::locked boolean?)
|
(s/def ::stroke-opacity number?)
|
||||||
|
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
|
||||||
|
(s/def ::stroke-width number?)
|
||||||
|
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||||
|
(s/def ::type #{:rect :path :circle :image :text})
|
||||||
(s/def ::width number?)
|
(s/def ::width number?)
|
||||||
(s/def ::height number?)
|
|
||||||
(s/def ::x1 number?)
|
(s/def ::x1 number?)
|
||||||
(s/def ::y1 number?)
|
|
||||||
(s/def ::x2 number?)
|
(s/def ::x2 number?)
|
||||||
|
(s/def ::y1 number?)
|
||||||
(s/def ::y2 number?)
|
(s/def ::y2 number?)
|
||||||
(s/def ::id uuid?)
|
|
||||||
(s/def ::page uuid?)
|
|
||||||
(s/def ::type #{:rect
|
|
||||||
:group
|
|
||||||
:path
|
|
||||||
:circle
|
|
||||||
:image
|
|
||||||
:text})
|
|
||||||
|
|
||||||
(s/def ::attributes
|
(s/def ::attributes
|
||||||
(s/keys :opt-un [::fill-color
|
(s/keys :opt-un [::blocked
|
||||||
|
::collapsed
|
||||||
|
::content
|
||||||
|
::fill-color
|
||||||
::fill-opacity
|
::fill-opacity
|
||||||
::line-height
|
|
||||||
::letter-spacing
|
|
||||||
::text-align
|
|
||||||
::font-family
|
::font-family
|
||||||
|
::font-size
|
||||||
::font-style
|
::font-style
|
||||||
::font-weight
|
::font-weight
|
||||||
::font-size
|
::hidden
|
||||||
::stroke-style
|
::letter-spacing
|
||||||
::stroke-width
|
::line-height
|
||||||
|
::locked
|
||||||
|
::proportion
|
||||||
|
::proportion-lock
|
||||||
|
::rx ::ry
|
||||||
::stroke-color
|
::stroke-color
|
||||||
::stroke-opacity
|
::stroke-opacity
|
||||||
::rx ::ry
|
::stroke-style
|
||||||
|
::stroke-width
|
||||||
|
::text-align
|
||||||
::x1 ::x2
|
::x1 ::x2
|
||||||
::y1 ::y2
|
::y1 ::y2]))
|
||||||
::proportion-lock
|
|
||||||
::proportion
|
(s/def ::minimal-shape
|
||||||
::collapsed
|
(s/keys :req-un [::id ::page ::type ::name]))
|
||||||
::hidden
|
|
||||||
::blocked
|
|
||||||
::locked]))
|
|
||||||
|
|
||||||
(s/def ::shape
|
(s/def ::shape
|
||||||
(s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes))
|
(s/and ::minimal-shape ::attributes))
|
||||||
|
|
||||||
(s/def ::rect-like-shape
|
(s/def ::rect-like-shape
|
||||||
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
|
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
|
||||||
|
|
||||||
;; --- Shapes CRUD
|
;; --- Shape Creation
|
||||||
|
|
||||||
(deftype AddShape [data]
|
(defn retrieve-used-names
|
||||||
udp/IPageUpdate
|
"Returns a set of already used names by shapes
|
||||||
ptk/UpdateEvent
|
in the current page."
|
||||||
(update [_ state]
|
[{:keys [shapes] :as state}]
|
||||||
(let [shape (geom/setup-proportions data)
|
(let [pid (get-in state [:workspace :current])
|
||||||
page-id (get-in state [:workspace :current])]
|
xf (comp (filter #(= pid (:page %)))
|
||||||
(impl/assoc-shape-to-page state shape page-id))))
|
(map :name))]
|
||||||
|
(into #{} xf (vals shapes))))
|
||||||
|
|
||||||
(defn add-shape
|
(defn generate-unique-name
|
||||||
[data]
|
"A unique name generator based on the previous
|
||||||
{:pre [(us/valid? ::shape data)]}
|
state of the used names."
|
||||||
(AddShape. data))
|
[state basename]
|
||||||
|
(let [used (retrieve-used-names state)]
|
||||||
|
(loop [counter 1]
|
||||||
|
(let [candidate (str basename "-" counter)]
|
||||||
|
(if (contains? used candidate)
|
||||||
|
(recur (inc counter))
|
||||||
|
candidate)))))
|
||||||
|
|
||||||
;; --- Delete Shape
|
(defn assoc-shape-to-page
|
||||||
|
[state shape page]
|
||||||
|
(let [shape-id (uuid/random)
|
||||||
|
shape-name (generate-unique-name state (:name shape))
|
||||||
|
shape (assoc shape
|
||||||
|
:page page
|
||||||
|
:id shape-id
|
||||||
|
:name shape-name)]
|
||||||
|
(-> state
|
||||||
|
(update-in [:pages page :shapes] #(into [] (cons shape-id %)))
|
||||||
|
(assoc-in [:shapes shape-id] shape))))
|
||||||
|
|
||||||
(deftype DeleteShape [id]
|
(defn duplicate-shapes'
|
||||||
udp/IPageUpdate
|
([state shapes page]
|
||||||
ptk/UpdateEvent
|
(duplicate-shapes' state shapes page nil))
|
||||||
(update [_ state]
|
([state shapes page group]
|
||||||
(let [shape (get-in state [:shapes id])]
|
(letfn [(duplicate-shape [state shape page group]
|
||||||
(impl/dissoc-shape state shape))))
|
(if (= (:type shape) :group)
|
||||||
|
(let [id (uuid/random)
|
||||||
|
items (:items shape)
|
||||||
|
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||||
|
shape (assoc shape
|
||||||
|
:id id
|
||||||
|
:page page
|
||||||
|
:items []
|
||||||
|
:name name)
|
||||||
|
state (if (nil? group)
|
||||||
|
(-> state
|
||||||
|
(update-in [:pages page :shapes]
|
||||||
|
#(into [] (cons id %)))
|
||||||
|
(assoc-in [:shapes id] shape))
|
||||||
|
(-> state
|
||||||
|
(update-in [:shapes group :items]
|
||||||
|
#(into [] (cons id %)))
|
||||||
|
(assoc-in [:shapes id] shape)))]
|
||||||
|
(->> (map #(get-in state [:shapes %]) items)
|
||||||
|
(reverse)
|
||||||
|
(reduce #(duplicate-shape %1 %2 page id) state)))
|
||||||
|
(let [id (uuid/random)
|
||||||
|
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||||
|
shape (-> (dissoc shape :group)
|
||||||
|
(assoc :id id :page page :name name)
|
||||||
|
(merge (when group {:group group})))]
|
||||||
|
(if (nil? group)
|
||||||
|
(-> state
|
||||||
|
(update-in [:pages page :shapes] #(into [] (cons id %)))
|
||||||
|
(assoc-in [:shapes id] shape))
|
||||||
|
(-> state
|
||||||
|
(update-in [:shapes group :items] #(into [] (cons id %)))
|
||||||
|
(assoc-in [:shapes id] shape))))))]
|
||||||
|
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
||||||
|
|
||||||
(defn delete-shape
|
(defn duplicate-shapes
|
||||||
"Remove the shape using its id."
|
([state shapes]
|
||||||
[id]
|
(duplicate-shapes state shapes nil))
|
||||||
{:pre [(uuid? id)]}
|
([state shapes page]
|
||||||
(DeleteShape. id))
|
(letfn [(all-toplevel? [coll]
|
||||||
|
(every? #(nil? (:group %)) coll))
|
||||||
|
(all-same-group? [coll]
|
||||||
|
(let [group (:group (first coll))]
|
||||||
|
(every? #(= group (:group %)) coll)))]
|
||||||
|
(let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))]
|
||||||
|
(cond
|
||||||
|
(all-toplevel? shapes)
|
||||||
|
(let [page (or page (:page (first shapes)))]
|
||||||
|
(duplicate-shapes' state shapes page))
|
||||||
|
|
||||||
;; --- Rename Shape
|
(all-same-group? shapes)
|
||||||
|
(let [page (or page (:page (first shapes)))
|
||||||
|
group (:group (first shapes))]
|
||||||
|
(duplicate-shapes' state shapes page group))
|
||||||
|
|
||||||
(deftype RenameShape [id name]
|
:else
|
||||||
udp/IPageUpdate
|
(let [page (or page (:page (first shapes)))]
|
||||||
ptk/UpdateEvent
|
(duplicate-shapes' state shapes page)))))))
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:shapes id :name] name)))
|
|
||||||
|
|
||||||
(defn rename-shape
|
;; --- Delete Shapes
|
||||||
[id name]
|
|
||||||
{:pre [(uuid? id) (string? name)]}
|
|
||||||
(RenameShape. id name))
|
|
||||||
|
|
||||||
;; --- Update Rotation
|
(defn dissoc-from-index
|
||||||
|
"A function that dissoc shape from the indexed
|
||||||
(deftype UpdateShapeRotation [id rotation]
|
data structure of shapes from the state."
|
||||||
udp/IPageUpdate
|
[state {:keys [id type] :as shape}]
|
||||||
ptk/UpdateEvent
|
(if (= :group type)
|
||||||
(update [_ state]
|
(let [items (map #(get-in state [:shapes %]) (:items shape))]
|
||||||
(assoc-in state [:shapes id :rotation] rotation)))
|
|
||||||
|
|
||||||
(defn update-rotation
|
|
||||||
[id rotation]
|
|
||||||
{:pre [(uuid? id)
|
|
||||||
(number? rotation)
|
|
||||||
(>= rotation 0)
|
|
||||||
(>= 360 rotation)]}
|
|
||||||
(UpdateShapeRotation. id rotation))
|
|
||||||
|
|
||||||
;; --- Update Dimensions
|
|
||||||
|
|
||||||
(deftype UpdateDimensions [id dimensions]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes id] geom/resize-dim dimensions)))
|
|
||||||
|
|
||||||
(s/def ::update-dimensions-opts
|
|
||||||
(s/keys :opt-un [::width ::height]))
|
|
||||||
|
|
||||||
(defn update-dimensions
|
|
||||||
"A helper event just for update the position
|
|
||||||
of the shape using the width and height attrs
|
|
||||||
instread final point of coordinates."
|
|
||||||
[id opts]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]}
|
|
||||||
(UpdateDimensions. id opts))
|
|
||||||
|
|
||||||
;; --- Update Shape Position
|
|
||||||
|
|
||||||
(deftype UpdateShapePosition [id point]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes id] geom/absolute-move point)))
|
|
||||||
|
|
||||||
(defn update-position
|
|
||||||
"Update the start position coordenate of the shape."
|
|
||||||
[id point]
|
|
||||||
{:pre [(uuid? id) (gpt/point? point)]}
|
|
||||||
(UpdateShapePosition. id point))
|
|
||||||
|
|
||||||
;; --- Update Shape Text
|
|
||||||
|
|
||||||
(deftype UpdateShapeTextContent [id text]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:shapes id :content] text)))
|
|
||||||
|
|
||||||
(defn update-text
|
|
||||||
[id text]
|
|
||||||
{:pre [(uuid? id) (string? text)]}
|
|
||||||
(UpdateShapeTextContent. id text))
|
|
||||||
|
|
||||||
;; --- Update Shape Attrs
|
|
||||||
|
|
||||||
(declare UpdateAttrs)
|
|
||||||
;; TODO: moved
|
|
||||||
(deftype UpdateAttrs [id attrs]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [{:keys [type] :as shape} (get-in state [:shapes id])]
|
|
||||||
(if (= type :group)
|
|
||||||
(rx/from-coll (map #(UpdateAttrs. % attrs) (:items shape)))
|
|
||||||
(rx/of #(update-in % [:shapes id] merge attrs))))))
|
|
||||||
|
|
||||||
(defn update-attrs
|
|
||||||
[id attrs]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::attributes attrs)]}
|
|
||||||
(let [atts (us/extract attrs ::attributes)]
|
|
||||||
(UpdateAttrs. id attrs)))
|
|
||||||
|
|
||||||
;; --- Shape Proportions
|
|
||||||
|
|
||||||
(deftype LockShapeProportions [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [[width height] (-> (get-in state [:shapes id])
|
|
||||||
(geom/size)
|
|
||||||
(keep [:width :height]))
|
|
||||||
proportion (/ width height)]
|
|
||||||
(update-in state [:shapes id] assoc
|
|
||||||
:proportion proportion
|
|
||||||
:proportion-lock true))))
|
|
||||||
|
|
||||||
(defn lock-proportions
|
|
||||||
"Mark proportions of the shape locked and save the current
|
|
||||||
proportion as additional precalculated property."
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(LockShapeProportions. id))
|
|
||||||
|
|
||||||
(deftype UnlockShapeProportions [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:shapes id :proportion-lock] false)))
|
|
||||||
|
|
||||||
(defn unlock-proportions
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(UnlockShapeProportions. id))
|
|
||||||
|
|
||||||
;; --- Group Collapsing
|
|
||||||
|
|
||||||
(deftype CollapseGroupShape [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes id] assoc :collapsed true)))
|
|
||||||
|
|
||||||
(defn collapse-shape
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(CollapseGroupShape. id))
|
|
||||||
|
|
||||||
(deftype UncollapseGroupShape [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes id] assoc :collapsed false)))
|
|
||||||
|
|
||||||
(defn uncollapse-shape
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(UncollapseGroupShape. id))
|
|
||||||
|
|
||||||
;; --- Shape Visibility
|
|
||||||
|
|
||||||
(deftype HideShape [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(letfn [(mark-hidden [state id]
|
|
||||||
(let [shape (get-in state [:shapes id])]
|
|
||||||
(if (= :group (:type shape))
|
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :hidden] true)
|
(update-in $ [:shapes] dissoc id)
|
||||||
(reduce mark-hidden $ (:items shape)))
|
(reduce dissoc-from-index $ items)))
|
||||||
(assoc-in state [:shapes id :hidden] true))))]
|
(update-in state [:shapes] dissoc id)))
|
||||||
(mark-hidden state id))))
|
|
||||||
|
|
||||||
(defn hide-shape
|
(defn dissoc-from-page
|
||||||
[id]
|
"Given a shape, try to remove its reference from the
|
||||||
{:pre [(uuid? id)]}
|
corresponding page."
|
||||||
(HideShape. id))
|
[state {:keys [id page] :as shape}]
|
||||||
|
(as-> (get-in state [:pages page :shapes]) $
|
||||||
|
(into [] (remove #(= % id) $))
|
||||||
|
(assoc-in state [:pages page :shapes] $)))
|
||||||
|
|
||||||
(deftype ShowShape [id]
|
(defn dissoc-from-group
|
||||||
udp/IPageUpdate
|
"Given a shape, try to remove its reference from the
|
||||||
ptk/UpdateEvent
|
corresponding group (only if it belongs to one group)."
|
||||||
(update [_ state]
|
[state {:keys [id group] :as shape}]
|
||||||
(letfn [(mark-visible [state id]
|
(if-let [group' (get-in state [:shapes group])]
|
||||||
(let [shape (get-in state [:shapes id])]
|
(as-> (:items group') $
|
||||||
(if (= :group (:type shape))
|
(into [] (remove #(= % id) $))
|
||||||
|
(assoc-in state [:shapes group :items] $))
|
||||||
|
state))
|
||||||
|
|
||||||
|
(declare dissoc-shape)
|
||||||
|
|
||||||
|
(defn clear-empty-groups
|
||||||
|
"Given the shape, try to clean all empty groups
|
||||||
|
that this shape belongs to.
|
||||||
|
|
||||||
|
The main purpose of this function is remove the
|
||||||
|
all empty parent groups of recently removed
|
||||||
|
shape."
|
||||||
|
[state {:keys [group] :as shape}]
|
||||||
|
(if-let [group' (get-in state [:shapes group])]
|
||||||
|
(if (empty? (:items group'))
|
||||||
|
(-> (dissoc-shape state group')
|
||||||
|
(update-in [:workspace :selected] disj (:id group')))
|
||||||
|
state)
|
||||||
|
state))
|
||||||
|
|
||||||
|
(defn dissoc-shape
|
||||||
|
"Given a shape, removes it from the state."
|
||||||
|
[state shape]
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :hidden] false)
|
(dissoc-from-page $ shape)
|
||||||
(reduce mark-visible $ (:items shape)))
|
(dissoc-from-group $ shape)
|
||||||
(assoc-in state [:shapes id :hidden] false))))]
|
(dissoc-from-index $ shape)
|
||||||
(mark-visible state id))))
|
(clear-empty-groups $ shape)))
|
||||||
|
|
||||||
(defn show-shape
|
;; --- Shape Movements
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(ShowShape. id))
|
|
||||||
|
|
||||||
;; --- Shape Blocking
|
(defn- drop-at-index
|
||||||
|
[index coll v]
|
||||||
|
(let [[fst snd] (split-at index coll)]
|
||||||
|
(into [] (concat fst [v] snd))))
|
||||||
|
|
||||||
(deftype BlockShape [id]
|
(defn drop-relative
|
||||||
udp/IPageUpdate
|
[state loc sid]
|
||||||
ptk/UpdateEvent
|
{:pre [(not (nil? sid))]}
|
||||||
(update [_ state]
|
(let [shape (get-in state [:shapes (first sid)])
|
||||||
(letfn [(mark-blocked [state id]
|
{:keys [page group]} shape
|
||||||
(let [shape (get-in state [:shapes id])]
|
sid (:id shape)
|
||||||
(if (= :group (:type shape))
|
|
||||||
|
shapes (if group
|
||||||
|
(get-in state [:shapes group :items])
|
||||||
|
(get-in state [:pages page :shapes]))
|
||||||
|
|
||||||
|
index (case loc
|
||||||
|
:first 0
|
||||||
|
:after (min (- (count shapes) 1) (inc (index-of shapes sid)))
|
||||||
|
:before (max 0 (- (index-of shapes sid) 1))
|
||||||
|
:last (- (count shapes) 1))
|
||||||
|
|
||||||
|
state (-> state
|
||||||
|
(dissoc-from-page shape)
|
||||||
|
(dissoc-from-group shape))
|
||||||
|
|
||||||
|
shapes (if group
|
||||||
|
(get-in state [:shapes group :items])
|
||||||
|
(get-in state [:pages page :shapes]))
|
||||||
|
|
||||||
|
shapes (drop-at-index index shapes sid)]
|
||||||
|
|
||||||
|
(if group
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :blocked] true)
|
(assoc-in $ [:shapes group :items] shapes)
|
||||||
(reduce mark-blocked $ (:items shape)))
|
(update-in $ [:shapes sid] assoc :group group)
|
||||||
(assoc-in state [:shapes id :blocked] true))))]
|
(clear-empty-groups $ shape))
|
||||||
(mark-blocked state id))))
|
|
||||||
|
|
||||||
(defn block-shape
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(BlockShape. id))
|
|
||||||
|
|
||||||
(deftype UnblockShape [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(letfn [(mark-unblocked [state id]
|
|
||||||
(let [shape (get-in state [:shapes id])]
|
|
||||||
(if (= :group (:type shape))
|
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :blocked] false)
|
(assoc-in $ [:pages page :shapes] shapes)
|
||||||
(reduce mark-unblocked $ (:items shape)))
|
(update-in $ [:shapes sid] dissoc :group)
|
||||||
(assoc-in state [:shapes id :blocked] false))))]
|
(clear-empty-groups $ shape)))))
|
||||||
(mark-unblocked state id))))
|
|
||||||
|
|
||||||
(defn unblock-shape
|
(defn drop-aside
|
||||||
[id]
|
[state loc tid sid]
|
||||||
{:pre [(uuid? id)]}
|
{:pre [(not= tid sid)
|
||||||
(UnblockShape. id))
|
(not (nil? tid))
|
||||||
|
(not (nil? sid))]}
|
||||||
|
(let [{:keys [page group]} (get-in state [:shapes tid])
|
||||||
|
source (get-in state [:shapes sid])
|
||||||
|
|
||||||
;; --- Shape Locking
|
state (-> state
|
||||||
|
(dissoc-from-page source)
|
||||||
|
(dissoc-from-group source))
|
||||||
|
|
||||||
(deftype LockShape [id]
|
shapes (if group
|
||||||
udp/IPageUpdate
|
(get-in state [:shapes group :items])
|
||||||
ptk/UpdateEvent
|
(get-in state [:pages page :shapes]))
|
||||||
(update [_ state]
|
|
||||||
(letfn [(mark-locked [state id]
|
index (case loc
|
||||||
(let [shape (get-in state [:shapes id])]
|
:after (inc (index-of shapes tid))
|
||||||
(if (= :group (:type shape))
|
:before (index-of shapes tid))
|
||||||
|
|
||||||
|
shapes (drop-at-index index shapes sid)]
|
||||||
|
(if group
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :locked] true)
|
(assoc-in $ [:shapes group :items] shapes)
|
||||||
(reduce mark-locked $ (:items shape)))
|
(update-in $ [:shapes sid] assoc :group group)
|
||||||
(assoc-in state [:shapes id :locked] true))))]
|
(clear-empty-groups $ source))
|
||||||
(mark-locked state id))))
|
|
||||||
|
|
||||||
(defn lock-shape
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(LockShape. id))
|
|
||||||
|
|
||||||
(deftype UnlockShape [id]
|
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(letfn [(mark-unlocked [state id]
|
|
||||||
(let [shape (get-in state [:shapes id])]
|
|
||||||
(if (= :group (:type shape))
|
|
||||||
(as-> state $
|
(as-> state $
|
||||||
(assoc-in $ [:shapes id :locked] false)
|
(assoc-in $ [:pages page :shapes] shapes)
|
||||||
(reduce mark-unlocked $ (:items shape)))
|
(update-in $ [:shapes sid] dissoc :group)
|
||||||
(assoc-in state [:shapes id :locked] false))))]
|
(clear-empty-groups $ source)))))
|
||||||
(mark-unlocked state id))))
|
|
||||||
|
|
||||||
(defn unlock-shape
|
(def drop-after #(drop-aside %1 :after %2 %3))
|
||||||
[id]
|
(def drop-before #(drop-aside %1 :before %2 %3))
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(UnlockShape. id))
|
|
||||||
|
|
||||||
;; --- Drop Shape
|
(defn drop-inside
|
||||||
|
[state tid sid]
|
||||||
(deftype DropShape [sid tid loc]
|
{:pre [(not= tid sid)]}
|
||||||
udp/IPageUpdate
|
(let [source (get-in state [:shapes sid])
|
||||||
ptk/UpdateEvent
|
state (-> state
|
||||||
(update [_ state]
|
(dissoc-from-page source)
|
||||||
(impl/drop-shape state sid tid loc)))
|
(dissoc-from-group source))
|
||||||
|
shapes (get-in state [:shapes tid :items])]
|
||||||
|
(if (seq shapes)
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes tid :items] (conj shapes sid))
|
||||||
|
(update-in $ [:shapes sid] assoc :group tid))
|
||||||
|
state)))
|
||||||
|
|
||||||
(defn drop-shape
|
(defn drop-shape
|
||||||
"Event used in drag and drop for transfer shape
|
[state sid tid loc]
|
||||||
from one position to an other."
|
(if (= tid sid)
|
||||||
[sid tid loc]
|
state
|
||||||
{:pre [(uuid? sid)
|
(case loc
|
||||||
(uuid? tid)
|
:inside (drop-inside state tid sid)
|
||||||
(keyword? loc)]}
|
:before (drop-before state tid sid)
|
||||||
(DropShape. sid tid loc))
|
:after (drop-after state tid sid)
|
||||||
|
(throw (ex-info "Invalid data" {})))))
|
||||||
|
|
||||||
;; --- Update Interaction
|
(defn move-layer
|
||||||
|
[state shape loc]
|
||||||
|
(case loc
|
||||||
|
:up (drop-relative state :before shape)
|
||||||
|
:down (drop-relative state :after shape)
|
||||||
|
:top (drop-relative state :first shape)
|
||||||
|
:bottom (drop-relative state :last shape)
|
||||||
|
(throw (ex-info "Invalid data" {}))))
|
||||||
|
|
||||||
(deftype UpdateInteraction [shape interaction]
|
;; --- Shape Selection
|
||||||
udp/IPageUpdate
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [id (or (:id interaction)
|
|
||||||
(uuid/random))
|
|
||||||
data (assoc interaction :id id)]
|
|
||||||
(assoc-in state [:shapes shape :interactions id] data))))
|
|
||||||
|
|
||||||
(defn update-interaction
|
(defn- try-match-shape
|
||||||
[shape interaction]
|
[xf selrect acc {:keys [type id items] :as shape}]
|
||||||
(UpdateInteraction. shape interaction))
|
(cond
|
||||||
|
(geom/contained-in? shape selrect)
|
||||||
|
(conj acc id)
|
||||||
|
|
||||||
;; --- Delete Interaction
|
(geom/overlaps? shape selrect)
|
||||||
|
(conj acc id)
|
||||||
|
|
||||||
(deftype DeleteInteracton [shape id]
|
(:locked shape)
|
||||||
udp/IPageUpdate
|
acc
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes shape :interactions] dissoc id)))
|
|
||||||
|
|
||||||
(defn delete-interaction
|
(= type :group)
|
||||||
[shape id]
|
(reduce (partial try-match-shape xf selrect)
|
||||||
{:pre [(uuid? id) (uuid? shape)]}
|
acc (sequence xf items))
|
||||||
(DeleteInteracton. shape id))
|
|
||||||
|
|
||||||
;; --- Path Modifications
|
:else
|
||||||
|
acc))
|
||||||
|
|
||||||
(deftype UpdatePath [id index delta]
|
(defn match-by-selrect
|
||||||
ptk/UpdateEvent
|
[state page-id selrect]
|
||||||
(update [_ state]
|
(let [xf (comp (map #(get-in state [:shapes %]))
|
||||||
(update-in state [:shapes id :segments index] gpt/add delta)))
|
(remove :hidden)
|
||||||
|
(remove :blocked)
|
||||||
|
(remove #(= :canvas (:type %)))
|
||||||
|
(map geom/selection-rect))
|
||||||
|
match (partial try-match-shape xf selrect)
|
||||||
|
shapes (get-in state [:pages page-id :shapes])]
|
||||||
|
(reduce match #{} (sequence xf shapes))))
|
||||||
|
|
||||||
(defn update-path
|
(defn group-shapes
|
||||||
"Update a concrete point in the path shape."
|
[state shapes page]
|
||||||
[id index delta]
|
(letfn [(replace-first-item [pred coll replacement]
|
||||||
{:pre [(uuid? id) (number? index) (gpt/point? delta)]}
|
(into []
|
||||||
(UpdatePath. id index delta))
|
(concat
|
||||||
|
(take-while #(not (pred %)) coll)
|
||||||
|
[replacement]
|
||||||
|
(drop 1 (drop-while #(not (pred %)) coll)))))
|
||||||
|
|
||||||
(deftype InitialPathPointAlign [id index]
|
(move-shapes-to-new-group [state page shapes new-group]
|
||||||
ptk/WatchEvent
|
(reduce (fn [state {:keys [id group] :as shape}]
|
||||||
(watch [_ state s]
|
(-> state
|
||||||
(let [shape (get-in state [:shapes id])
|
(update-in [:shapes group :items] #(remove (set [id]) %))
|
||||||
point (get-in shape [:segments index])]
|
(update-in [:pages page :shapes] #(remove (set [id]) %))
|
||||||
(->> (uwrk/align-point point)
|
(clear-empty-groups shape)
|
||||||
(rx/map #(update-path id index %))))))
|
(assoc-in [:shapes id :group] new-group)
|
||||||
|
))
|
||||||
|
state
|
||||||
|
shapes))
|
||||||
|
|
||||||
(defn initial-path-point-align
|
(update-shapes-on-page [state page shapes group]
|
||||||
"Event responsible of align a specified point of the
|
(as-> (get-in state [:pages page :shapes]) $
|
||||||
shape by `index` with the grid."
|
(replace-first-item (set shapes) $ group)
|
||||||
[id index]
|
(remove (set shapes) $)
|
||||||
{:pre [(uuid? id)
|
(into [] $)
|
||||||
(number? index)
|
(assoc-in state [:pages page :shapes] $)))
|
||||||
(not (neg? index))]}
|
|
||||||
(InitialPathPointAlign. id index))
|
|
||||||
|
|
||||||
;; --- Events (implicit) (for selected)
|
(update-shapes-on-group [state parent-group shapes group]
|
||||||
|
(as-> (get-in state [:shapes parent-group :items]) $
|
||||||
|
(replace-first-item (set shapes) $ group)
|
||||||
|
(remove (set shapes) $)
|
||||||
|
(into [] $)
|
||||||
|
(assoc-in state [:shapes parent-group :items] $)))
|
||||||
|
|
||||||
;; NOTE: moved to workspace
|
(update-shapes-on-index [state shapes group]
|
||||||
(deftype DeselectAll []
|
(reduce (fn [state {:keys [id] :as shape}]
|
||||||
ptk/UpdateEvent
|
(as-> shape $
|
||||||
(update [_ state]
|
(assoc $ :group group)
|
||||||
(assoc-in state [:workspace :selected] #{}))
|
(assoc-in state [:shapes id] $)))
|
||||||
|
state
|
||||||
|
shapes))]
|
||||||
|
(let [sid (uuid/random)
|
||||||
|
shapes' (map #(get-in state [:shapes %]) shapes)
|
||||||
|
distinct-groups (distinct (map :group shapes'))
|
||||||
|
parent-group (cond
|
||||||
|
(not= 1 (count distinct-groups)) :multi
|
||||||
|
(nil? (first distinct-groups)) :page
|
||||||
|
:else (first distinct-groups))
|
||||||
|
name (generate-unique-name state "Group")
|
||||||
|
group {:type :group
|
||||||
|
:name name
|
||||||
|
:items (into [] shapes)
|
||||||
|
:id sid
|
||||||
|
:page page}]
|
||||||
|
(as-> state $
|
||||||
|
(update-shapes-on-index $ shapes' sid)
|
||||||
|
(cond
|
||||||
|
(= :multi parent-group)
|
||||||
|
(-> $
|
||||||
|
(move-shapes-to-new-group page shapes' sid)
|
||||||
|
(update-in [:pages page :shapes] #(into [] (cons sid %))))
|
||||||
|
(= :page parent-group)
|
||||||
|
(update-shapes-on-page $ page shapes sid)
|
||||||
|
:else
|
||||||
|
(update-shapes-on-group $ parent-group shapes sid))
|
||||||
|
(update $ :shapes assoc sid group)
|
||||||
|
(cond
|
||||||
|
(= :multi parent-group) $
|
||||||
|
(= :page parent-group) $
|
||||||
|
:else (assoc-in $ [:shapes sid :group] parent-group))
|
||||||
|
(update $ :workspace assoc :selected #{sid})))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
(defn degroup-shapes
|
||||||
(watch [_ state stream]
|
[state shapes page-id]
|
||||||
(rx/just ::uev/interrupt)))
|
(letfn [(get-relocation-position [state {id :id parent-id :group}]
|
||||||
|
(if (nil? parent-id)
|
||||||
|
(index-of (get-in state [:pages page-id :shapes]) id)
|
||||||
|
(index-of (get-in state [:shapes parent-id :items]) id)))
|
||||||
|
|
||||||
(defn deselect-all
|
(relocate-shape [state shape-id parent-id position]
|
||||||
"Clear all possible state of drawing, edition
|
(if (nil? parent-id)
|
||||||
or any similar action taken by the user."
|
(-> state
|
||||||
[]
|
(update-in [:pages page-id :shapes] #(drop-at-index position % shape-id))
|
||||||
(DeselectAll.))
|
(update-in [:shapes shape-id] dissoc :group))
|
||||||
|
(-> state
|
||||||
|
(update-in [:shapes parent-id :items] #(drop-at-index position % shape-id))
|
||||||
|
(assoc-in [:shapes shape-id :group] parent-id))))
|
||||||
|
|
||||||
;; --- Group Selected Shapes
|
(remove-group [state {id :id parent-id :group}]
|
||||||
|
(let [xform (remove #{id})]
|
||||||
|
(as-> state $
|
||||||
|
(update $ :shapes dissoc id)
|
||||||
|
(if (nil? parent-id)
|
||||||
|
(update-in $ [:pages page-id :shapes] #(into [] xform %))
|
||||||
|
(update-in $ [:shapes parent-id :items] #(into [] xform %))))))
|
||||||
|
|
||||||
(deftype GroupSelectedShapes []
|
(relocate-group-items [state {id :id parent-id :group items :items :as group}]
|
||||||
udp/IPageUpdate
|
(let [position (get-relocation-position state group)]
|
||||||
ptk/UpdateEvent
|
(as-> state $
|
||||||
(update [_ state]
|
(reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items))
|
||||||
(let [id (get-in state [:workspace :page])
|
(remove-group $ group))))
|
||||||
selected (get-in state [:workspace :selected])]
|
|
||||||
(assert (not (empty? selected)) "selected set is empty")
|
|
||||||
(assert (uuid? id) "selected page is not an uuid")
|
|
||||||
(impl/group-shapes state selected id))))
|
|
||||||
|
|
||||||
(defn group-selected
|
(select-degrouped [state groups]
|
||||||
[]
|
(let [items (into #{} (mapcat :items groups))]
|
||||||
(GroupSelectedShapes.))
|
(assoc-in state [:workspace :selected] items)))
|
||||||
|
|
||||||
;; --- Ungroup Selected Shapes
|
(remove-from-parent [state id parent-id]
|
||||||
|
(assert (not (nil? parent-id)) "parent-id should never be nil here")
|
||||||
|
(update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %)))
|
||||||
|
|
||||||
(deftype UngroupSelectedShapes []
|
(strip-empty-groups [state parent-id]
|
||||||
udp/IPageUpdate
|
(if (nil? parent-id)
|
||||||
ptk/UpdateEvent
|
state
|
||||||
(update [_ state]
|
(let [group (get-in state [:shapes parent-id])]
|
||||||
(let [id (get-in state [:workspace :page])
|
(if (empty? (:items group))
|
||||||
selected (get-in state [:workspace :selected])]
|
(-> state
|
||||||
(assert (not (empty? selected)) "selected set is empty")
|
(remove-group group)
|
||||||
(assert (uuid? id) "selected page is not an uuid")
|
(strip-empty-groups (:group group)))
|
||||||
(impl/degroup-shapes state selected id))))
|
state))))
|
||||||
|
|
||||||
(defn ungroup-selected
|
(selective-degroup [state [shape & rest :as shapes]]
|
||||||
[]
|
(let [group (get-in state [:shapes (:group shape)])
|
||||||
(UngroupSelectedShapes.))
|
position (get-relocation-position state group)
|
||||||
|
parent-id (:group group)]
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:workspace :selected] (into #{} (map :id shapes)))
|
||||||
|
(reduce (fn [state {shape-id :id}]
|
||||||
|
(-> state
|
||||||
|
(relocate-shape shape-id parent-id position)
|
||||||
|
(remove-from-parent shape-id (:id group))))
|
||||||
|
$ (reverse shapes))
|
||||||
|
(strip-empty-groups $ (:id group)))))]
|
||||||
|
|
||||||
;; --- Duplicate Selected
|
(let [shapes (into #{} (map #(get-in state [:shapes %])) shapes)
|
||||||
|
groups (into #{} (filter #(= (:type %) :group)) shapes)
|
||||||
|
parents (into #{} (map :group) shapes)]
|
||||||
|
(cond
|
||||||
|
(and (= (count shapes) (count groups))
|
||||||
|
(= 1 (count parents))
|
||||||
|
(not (empty? groups)))
|
||||||
|
(as-> state $
|
||||||
|
(reduce relocate-group-items $ groups)
|
||||||
|
(reduce remove-group $ groups)
|
||||||
|
(select-degrouped $ groups))
|
||||||
|
|
||||||
(deftype DuplicateSelected []
|
(and (empty? groups)
|
||||||
udp/IPageUpdate
|
(= 1 (count parents))
|
||||||
ptk/UpdateEvent
|
(not (nil? (first parents))))
|
||||||
(update [_ state]
|
(selective-degroup state shapes)
|
||||||
(let [selected (get-in state [:workspace :selected])]
|
|
||||||
(impl/duplicate-shapes state selected))))
|
|
||||||
|
|
||||||
(defn duplicate-selected
|
|
||||||
[]
|
|
||||||
(DuplicateSelected.))
|
|
||||||
|
|
||||||
|
:else
|
||||||
|
(throw (ex-info "invalid condition for degrouping" {}))))))
|
||||||
|
|
||||||
|
(defn materialize-xfmt
|
||||||
|
[state id xfmt]
|
||||||
|
(let [{:keys [type items] :as shape} (get-in state [:shapes id])]
|
||||||
|
(if (= type :group)
|
||||||
|
(reduce #(materialize-xfmt %1 %2 xfmt) state items)
|
||||||
|
(update-in state [:shapes id] geom/transform xfmt))))
|
||||||
|
|
|
@ -1,470 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
|
|
||||||
(ns uxbox.main.data.shapes-impl
|
|
||||||
(:require [lentes.core :as l]
|
|
||||||
[uxbox.main.geom :as geom]
|
|
||||||
[uxbox.main.lenses :as ul]
|
|
||||||
[uxbox.util.geom.matrix :as gmt]
|
|
||||||
[uxbox.util.uuid :as uuid]
|
|
||||||
[uxbox.util.data :refer (index-of)]))
|
|
||||||
|
|
||||||
;; --- Shape Creation
|
|
||||||
|
|
||||||
(defn retrieve-used-names
|
|
||||||
"Returns a set of already used names by shapes
|
|
||||||
in the current page."
|
|
||||||
[{:keys [shapes] :as state}]
|
|
||||||
(let [page (l/focus ul/selected-page state)
|
|
||||||
xform (comp (map second)
|
|
||||||
(filter #(= page (:page %)))
|
|
||||||
(map :name))]
|
|
||||||
(into #{} xform shapes)))
|
|
||||||
|
|
||||||
(defn generate-unique-name
|
|
||||||
"A unique name generator based on the previous
|
|
||||||
state of the used names."
|
|
||||||
[state basename]
|
|
||||||
(let [used (retrieve-used-names state)]
|
|
||||||
(loop [counter 1]
|
|
||||||
(let [candidate (str basename "-" counter)]
|
|
||||||
(if (contains? used candidate)
|
|
||||||
(recur (inc counter))
|
|
||||||
candidate)))))
|
|
||||||
|
|
||||||
(defn assoc-shape-to-page
|
|
||||||
[state shape page]
|
|
||||||
(let [shape-id (uuid/random)
|
|
||||||
shape-name (generate-unique-name state (:name shape))
|
|
||||||
shape (assoc shape
|
|
||||||
:page page
|
|
||||||
:id shape-id
|
|
||||||
:name shape-name)]
|
|
||||||
(-> state
|
|
||||||
(update-in [:pages page :shapes] #(into [] (cons shape-id %)))
|
|
||||||
(assoc-in [:shapes shape-id] shape))))
|
|
||||||
|
|
||||||
(defn duplicate-shapes'
|
|
||||||
([state shapes page]
|
|
||||||
(duplicate-shapes' state shapes page nil))
|
|
||||||
([state shapes page group]
|
|
||||||
(letfn [(duplicate-shape [state shape page group]
|
|
||||||
(if (= (:type shape) :group)
|
|
||||||
(let [id (uuid/random)
|
|
||||||
items (:items shape)
|
|
||||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
|
||||||
shape (assoc shape
|
|
||||||
:id id
|
|
||||||
:page page
|
|
||||||
:items []
|
|
||||||
:name name)
|
|
||||||
state (if (nil? group)
|
|
||||||
(-> state
|
|
||||||
(update-in [:pages page :shapes]
|
|
||||||
#(into [] (cons id %)))
|
|
||||||
(assoc-in [:shapes id] shape))
|
|
||||||
(-> state
|
|
||||||
(update-in [:shapes group :items]
|
|
||||||
#(into [] (cons id %)))
|
|
||||||
(assoc-in [:shapes id] shape)))]
|
|
||||||
(->> (map #(get-in state [:shapes %]) items)
|
|
||||||
(reverse)
|
|
||||||
(reduce #(duplicate-shape %1 %2 page id) state)))
|
|
||||||
(let [id (uuid/random)
|
|
||||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
|
||||||
shape (-> (dissoc shape :group)
|
|
||||||
(assoc :id id :page page :name name)
|
|
||||||
(merge (when group {:group group})))]
|
|
||||||
(if (nil? group)
|
|
||||||
(-> state
|
|
||||||
(update-in [:pages page :shapes] #(into [] (cons id %)))
|
|
||||||
(assoc-in [:shapes id] shape))
|
|
||||||
(-> state
|
|
||||||
(update-in [:shapes group :items] #(into [] (cons id %)))
|
|
||||||
(assoc-in [:shapes id] shape))))))]
|
|
||||||
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
|
||||||
|
|
||||||
(defn duplicate-shapes
|
|
||||||
([state shapes]
|
|
||||||
(duplicate-shapes state shapes nil))
|
|
||||||
([state shapes page]
|
|
||||||
(letfn [(all-toplevel? [coll]
|
|
||||||
(every? #(nil? (:group %)) coll))
|
|
||||||
(all-same-group? [coll]
|
|
||||||
(let [group (:group (first coll))]
|
|
||||||
(every? #(= group (:group %)) coll)))]
|
|
||||||
(let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))]
|
|
||||||
(cond
|
|
||||||
(all-toplevel? shapes)
|
|
||||||
(let [page (or page (:page (first shapes)))]
|
|
||||||
(duplicate-shapes' state shapes page))
|
|
||||||
|
|
||||||
(all-same-group? shapes)
|
|
||||||
(let [page (or page (:page (first shapes)))
|
|
||||||
group (:group (first shapes))]
|
|
||||||
(duplicate-shapes' state shapes page group))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [page (or page (:page (first shapes)))]
|
|
||||||
(duplicate-shapes' state shapes page)))))))
|
|
||||||
|
|
||||||
;; --- Delete Shapes
|
|
||||||
|
|
||||||
(defn dissoc-from-index
|
|
||||||
"A function that dissoc shape from the indexed
|
|
||||||
data structure of shapes from the state."
|
|
||||||
[state {:keys [id type] :as shape}]
|
|
||||||
(if (= :group type)
|
|
||||||
(let [items (map #(get-in state [:shapes %]) (:items shape))]
|
|
||||||
(as-> state $
|
|
||||||
(update-in $ [:shapes] dissoc id)
|
|
||||||
(reduce dissoc-from-index $ items)))
|
|
||||||
(update-in state [:shapes] dissoc id)))
|
|
||||||
|
|
||||||
(defn dissoc-from-page
|
|
||||||
"Given a shape, try to remove its reference from the
|
|
||||||
corresponding page."
|
|
||||||
[state {:keys [id page] :as shape}]
|
|
||||||
(as-> (get-in state [:pages page :shapes]) $
|
|
||||||
(into [] (remove #(= % id) $))
|
|
||||||
(assoc-in state [:pages page :shapes] $)))
|
|
||||||
|
|
||||||
(defn dissoc-from-group
|
|
||||||
"Given a shape, try to remove its reference from the
|
|
||||||
corresponding group (only if it belongs to one group)."
|
|
||||||
[state {:keys [id group] :as shape}]
|
|
||||||
(if-let [group' (get-in state [:shapes group])]
|
|
||||||
(as-> (:items group') $
|
|
||||||
(into [] (remove #(= % id) $))
|
|
||||||
(assoc-in state [:shapes group :items] $))
|
|
||||||
state))
|
|
||||||
|
|
||||||
(declare dissoc-shape)
|
|
||||||
|
|
||||||
(defn clear-empty-groups
|
|
||||||
"Given the shape, try to clean all empty groups
|
|
||||||
that this shape belongs to.
|
|
||||||
|
|
||||||
The main purpose of this function is remove the
|
|
||||||
all empty parent groups of recently removed
|
|
||||||
shape."
|
|
||||||
[state {:keys [group] :as shape}]
|
|
||||||
(if-let [group' (get-in state [:shapes group])]
|
|
||||||
(if (empty? (:items group'))
|
|
||||||
(-> (dissoc-shape state group')
|
|
||||||
(update-in [:workspace :selected] disj (:id group')))
|
|
||||||
state)
|
|
||||||
state))
|
|
||||||
|
|
||||||
(defn dissoc-shape
|
|
||||||
"Given a shape, removes it from the state."
|
|
||||||
[state shape]
|
|
||||||
(as-> state $
|
|
||||||
(dissoc-from-page $ shape)
|
|
||||||
(dissoc-from-group $ shape)
|
|
||||||
(dissoc-from-index $ shape)
|
|
||||||
(clear-empty-groups $ shape)))
|
|
||||||
|
|
||||||
;; --- Shape Movements
|
|
||||||
|
|
||||||
(defn- drop-at-index
|
|
||||||
[index coll v]
|
|
||||||
(let [[fst snd] (split-at index coll)]
|
|
||||||
(into [] (concat fst [v] snd))))
|
|
||||||
|
|
||||||
(defn drop-relative
|
|
||||||
[state loc sid]
|
|
||||||
{:pre [(not (nil? sid))]}
|
|
||||||
(let [shape (get-in state [:shapes (first sid)])
|
|
||||||
{:keys [page group]} shape
|
|
||||||
sid (:id shape)
|
|
||||||
|
|
||||||
shapes (if group
|
|
||||||
(get-in state [:shapes group :items])
|
|
||||||
(get-in state [:pages page :shapes]))
|
|
||||||
|
|
||||||
index (case loc
|
|
||||||
:first 0
|
|
||||||
:after (min (- (count shapes) 1) (inc (index-of shapes sid)))
|
|
||||||
:before (max 0 (- (index-of shapes sid) 1))
|
|
||||||
:last (- (count shapes) 1))
|
|
||||||
|
|
||||||
state (-> state
|
|
||||||
(dissoc-from-page shape)
|
|
||||||
(dissoc-from-group shape))
|
|
||||||
|
|
||||||
shapes (if group
|
|
||||||
(get-in state [:shapes group :items])
|
|
||||||
(get-in state [:pages page :shapes]))
|
|
||||||
|
|
||||||
shapes (drop-at-index index shapes sid)]
|
|
||||||
|
|
||||||
(if group
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:shapes group :items] shapes)
|
|
||||||
(update-in $ [:shapes sid] assoc :group group)
|
|
||||||
(clear-empty-groups $ shape))
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:pages page :shapes] shapes)
|
|
||||||
(update-in $ [:shapes sid] dissoc :group)
|
|
||||||
(clear-empty-groups $ shape)))))
|
|
||||||
|
|
||||||
(defn drop-aside
|
|
||||||
[state loc tid sid]
|
|
||||||
{:pre [(not= tid sid)
|
|
||||||
(not (nil? tid))
|
|
||||||
(not (nil? sid))]}
|
|
||||||
(let [{:keys [page group]} (get-in state [:shapes tid])
|
|
||||||
source (get-in state [:shapes sid])
|
|
||||||
|
|
||||||
state (-> state
|
|
||||||
(dissoc-from-page source)
|
|
||||||
(dissoc-from-group source))
|
|
||||||
|
|
||||||
shapes (if group
|
|
||||||
(get-in state [:shapes group :items])
|
|
||||||
(get-in state [:pages page :shapes]))
|
|
||||||
|
|
||||||
index (case loc
|
|
||||||
:after (inc (index-of shapes tid))
|
|
||||||
:before (index-of shapes tid))
|
|
||||||
|
|
||||||
shapes (drop-at-index index shapes sid)]
|
|
||||||
(if group
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:shapes group :items] shapes)
|
|
||||||
(update-in $ [:shapes sid] assoc :group group)
|
|
||||||
(clear-empty-groups $ source))
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:pages page :shapes] shapes)
|
|
||||||
(update-in $ [:shapes sid] dissoc :group)
|
|
||||||
(clear-empty-groups $ source)))))
|
|
||||||
|
|
||||||
(def drop-after #(drop-aside %1 :after %2 %3))
|
|
||||||
(def drop-before #(drop-aside %1 :before %2 %3))
|
|
||||||
|
|
||||||
(defn drop-inside
|
|
||||||
[state tid sid]
|
|
||||||
{:pre [(not= tid sid)]}
|
|
||||||
(let [source (get-in state [:shapes sid])
|
|
||||||
state (-> state
|
|
||||||
(dissoc-from-page source)
|
|
||||||
(dissoc-from-group source))
|
|
||||||
shapes (get-in state [:shapes tid :items])]
|
|
||||||
(if (seq shapes)
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:shapes tid :items] (conj shapes sid))
|
|
||||||
(update-in $ [:shapes sid] assoc :group tid))
|
|
||||||
state)))
|
|
||||||
|
|
||||||
(defn drop-shape
|
|
||||||
[state sid tid loc]
|
|
||||||
(if (= tid sid)
|
|
||||||
state
|
|
||||||
(case loc
|
|
||||||
:inside (drop-inside state tid sid)
|
|
||||||
:before (drop-before state tid sid)
|
|
||||||
:after (drop-after state tid sid)
|
|
||||||
(throw (ex-info "Invalid data" {})))))
|
|
||||||
|
|
||||||
(defn move-layer
|
|
||||||
[state shape loc]
|
|
||||||
(case loc
|
|
||||||
:up (drop-relative state :before shape)
|
|
||||||
:down (drop-relative state :after shape)
|
|
||||||
:top (drop-relative state :first shape)
|
|
||||||
:bottom (drop-relative state :last shape)
|
|
||||||
(throw (ex-info "Invalid data" {}))))
|
|
||||||
|
|
||||||
;; --- Shape Selection
|
|
||||||
|
|
||||||
(defn- try-match-shape
|
|
||||||
[xf selrect acc {:keys [type id items] :as shape}]
|
|
||||||
(cond
|
|
||||||
(geom/contained-in? shape selrect)
|
|
||||||
(conj acc id)
|
|
||||||
|
|
||||||
(geom/overlaps? shape selrect)
|
|
||||||
(conj acc id)
|
|
||||||
|
|
||||||
(:locked shape)
|
|
||||||
acc
|
|
||||||
|
|
||||||
(= type :group)
|
|
||||||
(reduce (partial try-match-shape xf selrect)
|
|
||||||
acc (sequence xf items))
|
|
||||||
|
|
||||||
:else
|
|
||||||
acc))
|
|
||||||
|
|
||||||
(defn match-by-selrect
|
|
||||||
[state page-id selrect]
|
|
||||||
(let [xf (comp (map #(get-in state [:shapes %]))
|
|
||||||
(remove :hidden)
|
|
||||||
(remove :blocked)
|
|
||||||
(map geom/selection-rect))
|
|
||||||
match (partial try-match-shape xf selrect)
|
|
||||||
shapes (get-in state [:pages page-id :shapes])]
|
|
||||||
(reduce match #{} (sequence xf shapes))))
|
|
||||||
|
|
||||||
(defn group-shapes
|
|
||||||
[state shapes page]
|
|
||||||
(letfn [(replace-first-item [pred coll replacement]
|
|
||||||
(into []
|
|
||||||
(concat
|
|
||||||
(take-while #(not (pred %)) coll)
|
|
||||||
[replacement]
|
|
||||||
(drop 1 (drop-while #(not (pred %)) coll)))))
|
|
||||||
|
|
||||||
(move-shapes-to-new-group [state page shapes new-group]
|
|
||||||
(reduce (fn [state {:keys [id group] :as shape}]
|
|
||||||
(-> state
|
|
||||||
(update-in [:shapes group :items] #(remove (set [id]) %))
|
|
||||||
(update-in [:pages page :shapes] #(remove (set [id]) %))
|
|
||||||
(clear-empty-groups shape)
|
|
||||||
(assoc-in [:shapes id :group] new-group)
|
|
||||||
))
|
|
||||||
state
|
|
||||||
shapes))
|
|
||||||
|
|
||||||
(update-shapes-on-page [state page shapes group]
|
|
||||||
(as-> (get-in state [:pages page :shapes]) $
|
|
||||||
(replace-first-item (set shapes) $ group)
|
|
||||||
(remove (set shapes) $)
|
|
||||||
(into [] $)
|
|
||||||
(assoc-in state [:pages page :shapes] $)))
|
|
||||||
|
|
||||||
(update-shapes-on-group [state parent-group shapes group]
|
|
||||||
(as-> (get-in state [:shapes parent-group :items]) $
|
|
||||||
(replace-first-item (set shapes) $ group)
|
|
||||||
(remove (set shapes) $)
|
|
||||||
(into [] $)
|
|
||||||
(assoc-in state [:shapes parent-group :items] $)))
|
|
||||||
|
|
||||||
(update-shapes-on-index [state shapes group]
|
|
||||||
(reduce (fn [state {:keys [id] :as shape}]
|
|
||||||
(as-> shape $
|
|
||||||
(assoc $ :group group)
|
|
||||||
(assoc-in state [:shapes id] $)))
|
|
||||||
state
|
|
||||||
shapes))]
|
|
||||||
(let [sid (uuid/random)
|
|
||||||
shapes' (map #(get-in state [:shapes %]) shapes)
|
|
||||||
distinct-groups (distinct (map :group shapes'))
|
|
||||||
parent-group (cond
|
|
||||||
(not= 1 (count distinct-groups)) :multi
|
|
||||||
(nil? (first distinct-groups)) :page
|
|
||||||
:else (first distinct-groups))
|
|
||||||
name (generate-unique-name state "Group")
|
|
||||||
group {:type :group
|
|
||||||
:name name
|
|
||||||
:items (into [] shapes)
|
|
||||||
:id sid
|
|
||||||
:page page}]
|
|
||||||
(as-> state $
|
|
||||||
(update-shapes-on-index $ shapes' sid)
|
|
||||||
(cond
|
|
||||||
(= :multi parent-group)
|
|
||||||
(-> $
|
|
||||||
(move-shapes-to-new-group page shapes' sid)
|
|
||||||
(update-in [:pages page :shapes] #(into [] (cons sid %))))
|
|
||||||
(= :page parent-group)
|
|
||||||
(update-shapes-on-page $ page shapes sid)
|
|
||||||
:else
|
|
||||||
(update-shapes-on-group $ parent-group shapes sid))
|
|
||||||
(update $ :shapes assoc sid group)
|
|
||||||
(cond
|
|
||||||
(= :multi parent-group) $
|
|
||||||
(= :page parent-group) $
|
|
||||||
:else (assoc-in $ [:shapes sid :group] parent-group))
|
|
||||||
(update $ :workspace assoc :selected #{sid})))))
|
|
||||||
|
|
||||||
(defn degroup-shapes
|
|
||||||
[state shapes page-id]
|
|
||||||
(letfn [(get-relocation-position [state {id :id parent-id :group}]
|
|
||||||
(if (nil? parent-id)
|
|
||||||
(index-of (get-in state [:pages page-id :shapes]) id)
|
|
||||||
(index-of (get-in state [:shapes parent-id :items]) id)))
|
|
||||||
|
|
||||||
(relocate-shape [state shape-id parent-id position]
|
|
||||||
(if (nil? parent-id)
|
|
||||||
(-> state
|
|
||||||
(update-in [:pages page-id :shapes] #(drop-at-index position % shape-id))
|
|
||||||
(update-in [:shapes shape-id] dissoc :group))
|
|
||||||
(-> state
|
|
||||||
(update-in [:shapes parent-id :items] #(drop-at-index position % shape-id))
|
|
||||||
(assoc-in [:shapes shape-id :group] parent-id))))
|
|
||||||
|
|
||||||
(remove-group [state {id :id parent-id :group}]
|
|
||||||
(let [xform (remove #{id})]
|
|
||||||
(as-> state $
|
|
||||||
(update $ :shapes dissoc id)
|
|
||||||
(if (nil? parent-id)
|
|
||||||
(update-in $ [:pages page-id :shapes] #(into [] xform %))
|
|
||||||
(update-in $ [:shapes parent-id :items] #(into [] xform %))))))
|
|
||||||
|
|
||||||
(relocate-group-items [state {id :id parent-id :group items :items :as group}]
|
|
||||||
(let [position (get-relocation-position state group)]
|
|
||||||
(as-> state $
|
|
||||||
(reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items))
|
|
||||||
(remove-group $ group))))
|
|
||||||
|
|
||||||
(select-degrouped [state groups]
|
|
||||||
(let [items (into #{} (mapcat :items groups))]
|
|
||||||
(assoc-in state [:workspace :selected] items)))
|
|
||||||
|
|
||||||
(remove-from-parent [state id parent-id]
|
|
||||||
(assert (not (nil? parent-id)) "parent-id should never be nil here")
|
|
||||||
(update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %)))
|
|
||||||
|
|
||||||
(strip-empty-groups [state parent-id]
|
|
||||||
(if (nil? parent-id)
|
|
||||||
state
|
|
||||||
(let [group (get-in state [:shapes parent-id])]
|
|
||||||
(if (empty? (:items group))
|
|
||||||
(-> state
|
|
||||||
(remove-group group)
|
|
||||||
(strip-empty-groups (:group group)))
|
|
||||||
state))))
|
|
||||||
|
|
||||||
(selective-degroup [state [shape & rest :as shapes]]
|
|
||||||
(let [group (get-in state [:shapes (:group shape)])
|
|
||||||
position (get-relocation-position state group)
|
|
||||||
parent-id (:group group)]
|
|
||||||
(as-> state $
|
|
||||||
(assoc-in $ [:workspace :selected] (into #{} (map :id shapes)))
|
|
||||||
(reduce (fn [state {shape-id :id}]
|
|
||||||
(-> state
|
|
||||||
(relocate-shape shape-id parent-id position)
|
|
||||||
(remove-from-parent shape-id (:id group))))
|
|
||||||
$ (reverse shapes))
|
|
||||||
(strip-empty-groups $ (:id group)))))]
|
|
||||||
(let [shapes (into #{} (map #(get-in state [:shapes %])) shapes)
|
|
||||||
groups (into #{} (filter #(= (:type %) :group)) shapes)
|
|
||||||
parents (into #{} (map :group) shapes)]
|
|
||||||
(cond
|
|
||||||
(and (= (count shapes) (count groups))
|
|
||||||
(= 1 (count parents))
|
|
||||||
(not (empty? groups)))
|
|
||||||
(as-> state $
|
|
||||||
(reduce relocate-group-items $ groups)
|
|
||||||
(reduce remove-group $ groups)
|
|
||||||
(select-degrouped $ groups))
|
|
||||||
|
|
||||||
(and (empty? groups)
|
|
||||||
(= 1 (count parents))
|
|
||||||
(not (nil? (first parents))))
|
|
||||||
(selective-degroup state shapes)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(throw (ex-info "invalid condition for degrouping" {}))))))
|
|
||||||
|
|
||||||
(defn materialize-xfmt
|
|
||||||
[state id xfmt]
|
|
||||||
(let [{:keys [type items] :as shape} (get-in state [:shapes id])]
|
|
||||||
(if (= type :group)
|
|
||||||
(reduce #(materialize-xfmt %1 %2 xfmt) state items)
|
|
||||||
(update-in state [:shapes id] geom/transform xfmt))))
|
|
|
@ -21,7 +21,8 @@
|
||||||
(deftype WatchPageChanges [id]
|
(deftype WatchPageChanges [id]
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [stopper (->> stream
|
nil
|
||||||
|
#_(let [stopper (->> stream
|
||||||
(rx/filter #(= % ::udp/stop-page-watcher))
|
(rx/filter #(= % ::udp/stop-page-watcher))
|
||||||
(rx/take 1))]
|
(rx/take 1))]
|
||||||
(->> stream
|
(->> stream
|
||||||
|
@ -41,7 +42,8 @@
|
||||||
(defrecord SaveUndoEntry [id]
|
(defrecord SaveUndoEntry [id]
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page (udp/pack-page state id)
|
state
|
||||||
|
#_(let [page (udp/pack-page state id)
|
||||||
undo {:data (:data page)
|
undo {:data (:data page)
|
||||||
:metadata (:metadata page)}]
|
:metadata (:metadata page)}]
|
||||||
(-> state
|
(-> state
|
||||||
|
@ -88,7 +90,7 @@
|
||||||
udp/IPageUpdate
|
udp/IPageUpdate
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page-id (get-in state [:workspace :page])
|
#_(let [page-id (get-in state [:workspace :page])
|
||||||
undo-state (get-in state [:undo page-id])
|
undo-state (get-in state [:undo page-id])
|
||||||
stack (:stack undo-state)
|
stack (:stack undo-state)
|
||||||
selected (:selected undo-state 0)]
|
selected (:selected undo-state 0)]
|
||||||
|
@ -123,7 +125,7 @@
|
||||||
udp/IPageUpdate
|
udp/IPageUpdate
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page-id (get-in state [:workspace :page])
|
#_(let [page-id (get-in state [:workspace :page])
|
||||||
undo-state (get-in state [:undo page-id])
|
undo-state (get-in state [:undo page-id])
|
||||||
stack (:stack undo-state)
|
stack (:stack undo-state)
|
||||||
selected (:selected undo-state)]
|
selected (:selected undo-state)]
|
||||||
|
|
|
@ -2,65 +2,81 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.data.users
|
(ns uxbox.main.data.users
|
||||||
(:require
|
(:require
|
||||||
[beicon.core :as rx]
|
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.util.i18n :as i18n :refer (tr)]
|
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||||
[uxbox.util.messages :as uum]
|
[uxbox.util.messages :as uum]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.storage :refer [storage]]))
|
[uxbox.util.storage :refer [storage]]))
|
||||||
|
|
||||||
|
;; --- Common Specs
|
||||||
|
|
||||||
|
(s/def ::id uuid?)
|
||||||
|
(s/def ::username string?)
|
||||||
|
(s/def ::fullname string?)
|
||||||
|
(s/def ::email ::us/email)
|
||||||
|
(s/def ::password string?)
|
||||||
|
(s/def ::language string?)
|
||||||
|
(s/def ::photo string?)
|
||||||
|
(s/def ::created-at inst?)
|
||||||
|
(s/def ::password-1 string?)
|
||||||
|
(s/def ::password-2 string?)
|
||||||
|
(s/def ::password-old string?)
|
||||||
|
|
||||||
;; --- Profile Fetched
|
;; --- Profile Fetched
|
||||||
|
|
||||||
(deftype ProfileFetched [data]
|
(s/def ::profile-fetched-params
|
||||||
ptk/UpdateEvent
|
(s/keys :req-un [::id
|
||||||
(update [this state]
|
::username
|
||||||
(assoc state :profile data))
|
::fullname
|
||||||
|
::email
|
||||||
ptk/EffectEvent
|
::created-at
|
||||||
(effect [this state stream]
|
::photo]))
|
||||||
(swap! storage assoc :profile data)
|
|
||||||
;; (prn "profile-fetched" data)
|
|
||||||
(when-let [lang (get-in data [:metadata :language])]
|
|
||||||
(i18n/set-current-locale! lang))))
|
|
||||||
|
|
||||||
(defn profile-fetched
|
(defn profile-fetched
|
||||||
[data]
|
[data]
|
||||||
(ProfileFetched. data))
|
(s/assert ::profile-fetched-params data)
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc state :profile data))
|
||||||
|
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ state stream]
|
||||||
|
(swap! storage assoc :profile data)
|
||||||
|
(when-let [lang (get-in data [:metadata :language])]
|
||||||
|
(i18n/set-current-locale! lang)))))
|
||||||
|
|
||||||
;; --- Fetch Profile
|
;; --- Fetch Profile
|
||||||
|
|
||||||
(deftype FetchProfile []
|
(def fetch-profile
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(->> (rp/req :fetch/profile)
|
(->> (rp/req :fetch/profile)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/map profile-fetched))))
|
(rx/map profile-fetched)))))
|
||||||
|
|
||||||
(defn fetch-profile
|
|
||||||
[]
|
|
||||||
(FetchProfile.))
|
|
||||||
|
|
||||||
;; --- Profile Updated
|
|
||||||
|
|
||||||
(deftype ProfileUpdated [data]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state s]
|
|
||||||
(rx/of (profile-fetched data)
|
|
||||||
(uum/info (tr "settings.profile.profile-saved")))))
|
|
||||||
|
|
||||||
(defn profile-updated
|
|
||||||
[data]
|
|
||||||
(ProfileUpdated. data))
|
|
||||||
|
|
||||||
;; --- Update Profile
|
;; --- Update Profile
|
||||||
|
|
||||||
(deftype UpdateProfile [data on-success on-error]
|
(s/def ::update-profile-params
|
||||||
|
(s/keys :req-un [::fullname
|
||||||
|
::email
|
||||||
|
::username
|
||||||
|
::language]))
|
||||||
|
|
||||||
|
(defn form->update-profile
|
||||||
|
[data on-success on-error]
|
||||||
|
(s/assert ::update-profile-params data)
|
||||||
|
(s/assert ::us/fn on-error)
|
||||||
|
(s/assert ::us/fn on-success)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(letfn [(handle-error [{payload :payload}]
|
(letfn [(handle-error [{payload :payload}]
|
||||||
|
@ -71,34 +87,25 @@
|
||||||
(assoc :email (:email data))
|
(assoc :email (:email data))
|
||||||
(assoc :username (:username data))
|
(assoc :username (:username data))
|
||||||
(assoc-in [:metadata :language] (:language data)))]
|
(assoc-in [:metadata :language] (:language data)))]
|
||||||
(prn "update-profile" data)
|
|
||||||
(->> (rp/req :update/profile data)
|
(->> (rp/req :update/profile data)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/do on-success)
|
(rx/do on-success)
|
||||||
(rx/map profile-updated)
|
(rx/map profile-fetched)
|
||||||
(rx/catch rp/client-error? handle-error))))))
|
(rx/catch rp/client-error? handle-error)))))))
|
||||||
|
|
||||||
(s/def ::fullname string?)
|
|
||||||
(s/def ::email us/email?)
|
|
||||||
(s/def ::username string?)
|
|
||||||
(s/def ::language string?)
|
|
||||||
|
|
||||||
(s/def ::update-profile
|
|
||||||
(s/keys :req-un [::fullname
|
|
||||||
::email
|
|
||||||
::language
|
|
||||||
::username]))
|
|
||||||
|
|
||||||
(defn update-profile
|
|
||||||
[data on-success on-error]
|
|
||||||
{:pre [(us/valid? ::update-profile data)
|
|
||||||
(fn? on-error)
|
|
||||||
(fn? on-success)]}
|
|
||||||
(UpdateProfile. data on-success on-error))
|
|
||||||
|
|
||||||
;; --- Update Password (Form)
|
;; --- Update Password (Form)
|
||||||
|
|
||||||
(deftype UpdatePassword [data on-success on-error]
|
(s/def ::update-password-params
|
||||||
|
(s/keys :req-un [::password-1
|
||||||
|
::password-2
|
||||||
|
::password-old]))
|
||||||
|
|
||||||
|
(defn update-password
|
||||||
|
[data {:keys [on-success on-error]}]
|
||||||
|
(s/assert ::update-password-params data)
|
||||||
|
(s/assert ::us/fn on-success)
|
||||||
|
(s/assert ::us/fn on-error)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(let [params {:old-password (:password-old data)
|
(let [params {:old-password (:password-old data)
|
||||||
|
@ -108,23 +115,8 @@
|
||||||
(on-error (:payload e))
|
(on-error (:payload e))
|
||||||
(rx/empty)))
|
(rx/empty)))
|
||||||
(rx/do on-success)
|
(rx/do on-success)
|
||||||
(rx/ignore)))))
|
(rx/ignore))))))
|
||||||
|
|
||||||
(s/def ::password-1 string?)
|
|
||||||
(s/def ::password-2 string?)
|
|
||||||
(s/def ::password-old string?)
|
|
||||||
|
|
||||||
(s/def ::update-password
|
|
||||||
(s/keys :req-un [::password-1
|
|
||||||
::password-2
|
|
||||||
::password-old]))
|
|
||||||
|
|
||||||
(defn update-password
|
|
||||||
[data & {:keys [on-success on-error]}]
|
|
||||||
{:pre [(us/valid? ::update-password data)
|
|
||||||
(fn? on-success)
|
|
||||||
(fn? on-error)]}
|
|
||||||
(UpdatePassword. data on-success on-error))
|
|
||||||
|
|
||||||
;; --- Update Photo
|
;; --- Update Photo
|
||||||
|
|
||||||
|
@ -133,7 +125,7 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(->> (rp/req :update/profile-photo {:file file})
|
(->> (rp/req :update/profile-photo {:file file})
|
||||||
(rx/do done)
|
(rx/do done)
|
||||||
(rx/map fetch-profile))))
|
(rx/map (constantly fetch-profile)))))
|
||||||
|
|
||||||
(defn update-photo
|
(defn update-photo
|
||||||
([file] (update-photo file (constantly nil)))
|
([file] (update-photo file (constantly nil)))
|
||||||
|
|
|
@ -13,19 +13,14 @@
|
||||||
[uxbox.main.constants :as c]
|
[uxbox.main.constants :as c]
|
||||||
[uxbox.main.data.history :as udh]
|
[uxbox.main.data.history :as udh]
|
||||||
[uxbox.main.data.icons :as udi]
|
[uxbox.main.data.icons :as udi]
|
||||||
[uxbox.main.data.lightbox :as udl]
|
|
||||||
[uxbox.main.data.pages :as udp]
|
[uxbox.main.data.pages :as udp]
|
||||||
[uxbox.main.data.projects :as dp]
|
[uxbox.main.data.projects :as dp]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.shapes :as ds]
|
||||||
[uxbox.main.data.shapes-impl :as simpl]
|
|
||||||
[uxbox.main.data.workspace.ruler :as wruler]
|
|
||||||
[uxbox.main.geom :as geom]
|
[uxbox.main.geom :as geom]
|
||||||
[uxbox.main.lenses :as ul]
|
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.workers :as uwrk]
|
[uxbox.main.workers :as uwrk]
|
||||||
[uxbox.util.data :refer [dissoc-in index-of]]
|
[uxbox.util.data :refer [dissoc-in index-of]]
|
||||||
[uxbox.util.forms :as sc]
|
|
||||||
[uxbox.util.geom.matrix :as gmt]
|
[uxbox.util.geom.matrix :as gmt]
|
||||||
[uxbox.util.geom.point :as gpt]
|
[uxbox.util.geom.point :as gpt]
|
||||||
[uxbox.util.math :as mth]
|
[uxbox.util.math :as mth]
|
||||||
|
@ -35,8 +30,10 @@
|
||||||
|
|
||||||
;; --- Expose inner functions
|
;; --- Expose inner functions
|
||||||
|
|
||||||
(def start-ruler wruler/start-ruler)
|
(def start-ruler nil)
|
||||||
(def clear-ruler wruler/clear-ruler)
|
(def clear-ruler nil)
|
||||||
|
|
||||||
|
(defn interrupt? [e] (= e :interrupt))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; General workspace events
|
;; General workspace events
|
||||||
|
@ -258,7 +255,7 @@
|
||||||
(->> (:clipboard state)
|
(->> (:clipboard state)
|
||||||
(filter #(= id (:id %)))
|
(filter #(= id (:id %)))
|
||||||
(first)))]
|
(first)))]
|
||||||
(simpl/duplicate-shapes state (:items selected) page-id))))
|
(ds/duplicate-shapes state (:items selected) page-id))))
|
||||||
|
|
||||||
(defn paste-from-clipboard
|
(defn paste-from-clipboard
|
||||||
"Copy selected shapes to clipboard."
|
"Copy selected shapes to clipboard."
|
||||||
|
@ -327,18 +324,50 @@
|
||||||
{:pre [(uuid? id)]}
|
{:pre [(uuid? id)]}
|
||||||
(InitializeAlignment. id))
|
(InitializeAlignment. id))
|
||||||
|
|
||||||
|
;; --- Duplicate Selected
|
||||||
|
|
||||||
|
(def duplicate-selected
|
||||||
|
(reify
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [selected (get-in state [:workspace :selected])]
|
||||||
|
(ds/duplicate-shapes state selected)))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Shapes on Workspace events
|
;; Shapes events
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn add-shape
|
||||||
|
[data]
|
||||||
|
(reify
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
;; TODO: revisit the `setup-proportions` seems unnecesary
|
||||||
|
(let [shape (assoc (geom/setup-proportions data)
|
||||||
|
:id (uuid/random))
|
||||||
|
pid (get-in state [:workspace :current])]
|
||||||
|
(ds/assoc-shape-to-page state shape pid)))))
|
||||||
|
|
||||||
|
(defn delete-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(reify
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(ds/dissoc-shape state shape)))))
|
||||||
|
|
||||||
(defrecord SelectShape [id]
|
(defrecord SelectShape [id]
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page-id (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
selected (get-in state [:workspace page-id :selected])]
|
selected (get-in state [:workspace pid :selected])]
|
||||||
(if (contains? selected id)
|
(if (contains? selected id)
|
||||||
(update-in state [:workspace page-id :selected] disj id)
|
(update-in state [:workspace pid :selected] disj id)
|
||||||
(update-in state [:workspace page-id :selected] conj id))))
|
(update-in state [:workspace pid :selected] conj id))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
|
@ -353,9 +382,10 @@
|
||||||
(defrecord DeselectAll []
|
(defrecord DeselectAll []
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page-id (get-in state [:workspace :current])]
|
(let [pid (get-in state [:workspace :current])]
|
||||||
(assoc-in state [:workspace page-id :selected] #{})))
|
(update-in state [:workspace pid] #(-> %
|
||||||
|
(assoc :selected #{})
|
||||||
|
(dissoc :selected-canvas)))))
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(rx/just :interrupt)))
|
(rx/just :interrupt)))
|
||||||
|
@ -388,37 +418,32 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
selrect (get-in state [:workspace pid :selrect])
|
selrect (get-in state [:workspace pid :selrect])
|
||||||
shapes (simpl/match-by-selrect state pid selrect)]
|
shapes (ds/match-by-selrect state pid selrect)]
|
||||||
(assoc-in state [:workspace pid :selected] shapes)))))
|
(assoc-in state [:workspace pid :selected] shapes)))))
|
||||||
|
|
||||||
;; --- Update Shape Attrs
|
;; --- Update Shape Attrs
|
||||||
|
|
||||||
(deftype UpdateShapeAttrs [id attrs]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(update-in state [:shapes id] merge attrs)))
|
|
||||||
|
|
||||||
(defn update-shape-attrs
|
(defn update-shape-attrs
|
||||||
[id attrs]
|
[id attrs]
|
||||||
{:pre [(uuid? id) (us/valid? ::uds/attributes attrs)]}
|
(s/assert ::us/uuid id)
|
||||||
(let [atts (us/extract attrs ::uds/attributes)]
|
(s/assert ::ds/attributes attrs)
|
||||||
(UpdateShapeAttrs. id attrs)))
|
(let [atts (s/conform ::ds/attributes attrs)]
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:shapes id] merge attrs)))))
|
||||||
|
|
||||||
;; --- Update Selected Shapes attrs
|
;; --- Update Selected Shapes attrs
|
||||||
|
|
||||||
|
(defn update-selected-shapes-attrs
|
||||||
(deftype UpdateSelectedShapesAttrs [attrs]
|
[attrs]
|
||||||
|
(s/assert ::ds/attributes attrs)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
selected (get-in state [:workspace pid :selected])]
|
selected (get-in state [:workspace pid :selected])]
|
||||||
(rx/from-coll (map #(update-shape-attrs % attrs) selected)))))
|
(rx/from-coll (map #(update-shape-attrs % attrs) selected))))))
|
||||||
|
|
||||||
(defn update-selected-shapes-attrs
|
|
||||||
[attrs]
|
|
||||||
{:pre [(us/valid? ::uds/attributes attrs)]}
|
|
||||||
(UpdateSelectedShapesAttrs. attrs))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Move Selected
|
;; --- Move Selected
|
||||||
|
|
||||||
|
@ -446,11 +471,20 @@
|
||||||
:fast (gpt/point (if align? (* 3 gx) 10)
|
:fast (gpt/point (if align? (* 3 gx) 10)
|
||||||
(if align? (* 3 gy) 10))}))
|
(if align? (* 3 gy) 10))}))
|
||||||
|
|
||||||
(declare apply-temporal-displacement)
|
|
||||||
(declare initial-shape-align)
|
(declare initial-shape-align)
|
||||||
(declare apply-displacement)
|
(declare apply-displacement)
|
||||||
|
(declare assoc-temporal-modifier)
|
||||||
|
(declare materialize-current-modifier)
|
||||||
|
(declare apply-temporal-displacement)
|
||||||
|
|
||||||
(defrecord MoveSelected [direction speed]
|
(s/def ::direction #{:up :down :right :left})
|
||||||
|
(s/def ::speed #{:std :fast})
|
||||||
|
|
||||||
|
(defn move-selected
|
||||||
|
[direction speed]
|
||||||
|
(s/assert ::direction direction)
|
||||||
|
(s/assert ::speed speed)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [page-id (get-in state [:workspace :current])
|
(let [page-id (get-in state [:workspace :current])
|
||||||
|
@ -467,46 +501,57 @@
|
||||||
(rx/from-coll (map initial-shape-align selected))
|
(rx/from-coll (map initial-shape-align selected))
|
||||||
(rx/from-coll (map apply-displacement selected))))
|
(rx/from-coll (map apply-displacement selected))))
|
||||||
(rx/from-coll (map #(apply-temporal-displacement % displacement) selected))
|
(rx/from-coll (map #(apply-temporal-displacement % displacement) selected))
|
||||||
(rx/from-coll (map apply-displacement selected))))))
|
(rx/from-coll (map materialize-current-modifier selected)))))))
|
||||||
|
|
||||||
(s/def ::direction #{:up :down :right :left})
|
|
||||||
(s/def ::speed #{:std :fast})
|
|
||||||
|
|
||||||
(defn move-selected
|
|
||||||
[direction speed]
|
|
||||||
{:pre [(us/valid? ::direction direction)
|
|
||||||
(us/valid? ::speed speed)]}
|
|
||||||
(MoveSelected. direction speed))
|
|
||||||
|
|
||||||
;; --- Move Selected Layer
|
;; --- Move Selected Layer
|
||||||
|
|
||||||
(defrecord MoveSelectedLayer [loc]
|
(defn move-selected-layer
|
||||||
|
[loc]
|
||||||
|
(assert (s/valid? ::direction loc))
|
||||||
|
(reify
|
||||||
udp/IPageUpdate
|
udp/IPageUpdate
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [id (get-in state [:workspace :current])
|
(let [id (get-in state [:workspace :current])
|
||||||
selected (get-in state [:workspace id :selected])]
|
selected (get-in state [:workspace id :selected])]
|
||||||
(simpl/move-layer state selected loc))))
|
(ds/move-layer state selected loc)))))
|
||||||
|
|
||||||
(defn move-selected-layer
|
;; --- Update Shape Position
|
||||||
[loc]
|
|
||||||
{:pre [(us/valid? ::direction loc)]}
|
(deftype UpdateShapePosition [id point]
|
||||||
(MoveSelectedLayer. loc))
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:shapes id] geom/absolute-move point)))
|
||||||
|
|
||||||
|
(defn update-position
|
||||||
|
"Update the start position coordenate of the shape."
|
||||||
|
[id point]
|
||||||
|
{:pre [(uuid? id) (gpt/point? point)]}
|
||||||
|
(UpdateShapePosition. id point))
|
||||||
|
|
||||||
;; --- Delete Selected
|
;; --- Delete Selected
|
||||||
|
|
||||||
(defrecord DeleteSelected []
|
(def delete-selected
|
||||||
|
"Deselect all and remove all selected shapes."
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [id (get-in state [:workspace :current])
|
(let [id (get-in state [:workspace :current])
|
||||||
selected (get-in state [:workspace id :selected])]
|
selected (get-in state [:workspace id :selected])]
|
||||||
(rx/from-coll
|
(rx/from-coll
|
||||||
(into [(deselect-all)] (map #(uds/delete-shape %) selected))))))
|
(into [(deselect-all)] (map #(delete-shape %) selected)))))))
|
||||||
|
|
||||||
(defn delete-selected
|
;; --- Rename Shape
|
||||||
"Deselect all and remove all selected shapes."
|
|
||||||
[]
|
(defn rename-shape
|
||||||
(DeleteSelected.))
|
[id name]
|
||||||
|
{:pre [(uuid? id) (string? name)]}
|
||||||
|
(reify
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:shapes id :name] name))))
|
||||||
|
|
||||||
;; --- Change Shape Order (Ordering)
|
;; --- Change Shape Order (Ordering)
|
||||||
|
|
||||||
|
@ -525,7 +570,10 @@
|
||||||
|
|
||||||
;; --- Shape Transformations
|
;; --- Shape Transformations
|
||||||
|
|
||||||
(defrecord InitialShapeAlign [id]
|
(defn initial-shape-align
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id])
|
(let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id])
|
||||||
|
@ -533,78 +581,43 @@
|
||||||
point (gpt/point x1 y1)]
|
point (gpt/point x1 y1)]
|
||||||
(->> (uwrk/align-point point)
|
(->> (uwrk/align-point point)
|
||||||
(rx/map (fn [{:keys [x y] :as pt}]
|
(rx/map (fn [{:keys [x y] :as pt}]
|
||||||
(apply-temporal-displacement id (gpt/subtract pt point))))))))
|
(apply-temporal-displacement id (gpt/subtract pt point)))))))))
|
||||||
|
|
||||||
(defn initial-shape-align
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(InitialShapeAlign. id))
|
|
||||||
|
|
||||||
;; --- Apply Temporal Displacement
|
;; --- Apply Temporal Displacement
|
||||||
|
|
||||||
(defrecord ApplyTemporalDisplacement [id delta]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [pid (get-in state [:workspace :current])
|
|
||||||
prev (get-in state [:workspace pid :modifiers id :displacement] (gmt/matrix))
|
|
||||||
curr (gmt/translate prev delta)]
|
|
||||||
(assoc-in state [:workspace pid :modifiers id :displacement] curr))))
|
|
||||||
|
|
||||||
(defn apply-temporal-displacement
|
(defn apply-temporal-displacement
|
||||||
[id pt]
|
[id delta]
|
||||||
{:pre [(uuid? id) (gpt/point? pt)]}
|
{:pre [(uuid? id) (gpt/point? delta)]}
|
||||||
(ApplyTemporalDisplacement. id pt))
|
(reify
|
||||||
|
|
||||||
;; --- Apply Displacement
|
|
||||||
|
|
||||||
(defrecord ApplyDisplacement [id]
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [prev (get-in state [:shapes id :modifier-mtx] (gmt/matrix))
|
||||||
displacement (get-in state [:workspace pid :modifiers id :displacement])]
|
curr (gmt/translate prev delta)]
|
||||||
(if (gmt/matrix? displacement)
|
(rx/of (assoc-temporal-modifier id curr))))))
|
||||||
(rx/of #(simpl/materialize-xfmt % id displacement)
|
|
||||||
#(update-in % [:workspace pid :modifiers id] dissoc :displacement)
|
|
||||||
::udp/page-update)
|
|
||||||
(rx/empty)))))
|
|
||||||
|
|
||||||
(defn apply-displacement
|
;; --- Modifiers
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(ApplyDisplacement. id))
|
|
||||||
|
|
||||||
;; --- Apply Temporal Resize Matrix
|
(defn assoc-temporal-modifier
|
||||||
|
[id xfmt]
|
||||||
(deftype ApplyTemporalResize [sid xfmt]
|
{:pre [(uuid? id)
|
||||||
|
(gmt/matrix? xfmt)]}
|
||||||
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])]
|
(assoc-in state [:shapes id :modifier-mtx] xfmt))))
|
||||||
(assoc-in state [:workspace pid :modifiers sid :resize] xfmt))))
|
|
||||||
|
|
||||||
(defn apply-temporal-resize
|
(defn materialize-current-modifier
|
||||||
"Attach temporal resize transformation to the shape."
|
|
||||||
[id xfmt]
|
|
||||||
{:pre [(gmt/matrix? xfmt) (uuid? id)]}
|
|
||||||
(ApplyTemporalResize. id xfmt))
|
|
||||||
|
|
||||||
;; --- Apply Resize Matrix
|
|
||||||
|
|
||||||
(deftype ApplyResize [id]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [pid (get-in state [:workspace :current])
|
|
||||||
resize (get-in state [:workspace pid :modifiers id :resize])]
|
|
||||||
(if (gmt/matrix? resize)
|
|
||||||
(rx/of #(simpl/materialize-xfmt % id resize)
|
|
||||||
#(update-in % [:workspace pid :modifiers id] dissoc :resize)
|
|
||||||
::udp/page-update)
|
|
||||||
(rx/empty)))))
|
|
||||||
|
|
||||||
(defn apply-resize
|
|
||||||
"Apply definitivelly the resize matrix transformation to the shape."
|
|
||||||
[id]
|
[id]
|
||||||
{:pre [(uuid? id)]}
|
{:pre [(uuid? id)]}
|
||||||
(ApplyResize. id))
|
(reify
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [xfmt (get-in state [:shapes id :modifier-mtx])]
|
||||||
|
(when (gmt/matrix? xfmt)
|
||||||
|
(rx/of #(update-in % [:shapes id] geom/transform xfmt)
|
||||||
|
#(update-in % [:shapes id] dissoc :modifier-mtx)
|
||||||
|
::udp/page-update))))))
|
||||||
|
|
||||||
;; --- Start shape "edition mode"
|
;; --- Start shape "edition mode"
|
||||||
|
|
||||||
|
@ -627,19 +640,273 @@
|
||||||
|
|
||||||
;; --- Select for Drawing
|
;; --- Select for Drawing
|
||||||
|
|
||||||
(defn select-for-drawing
|
(def clear-drawing
|
||||||
[shape]
|
|
||||||
(reify
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])]
|
||||||
current (get-in state [:workspace pid :drawing-tool])]
|
(update-in state [:workspace pid] dissoc :drawing-tool :drawing)))))
|
||||||
(if (or (nil? shape)
|
|
||||||
(= shape current))
|
(defn select-for-drawing?
|
||||||
(update-in state [:workspace pid] dissoc :drawing :drawing-tool)
|
[e]
|
||||||
(update-in state [:workspace pid] assoc
|
(= (::type (meta e)) ::select-for-drawing))
|
||||||
:drawing shape
|
|
||||||
:drawing-tool shape))))))
|
(defn select-for-drawing
|
||||||
|
([tool] (select-for-drawing tool nil))
|
||||||
|
([tool data]
|
||||||
|
(reify
|
||||||
|
IMeta
|
||||||
|
(-meta [_] {::type ::select-for-drawing})
|
||||||
|
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pid (get-in state [:workspace :current])]
|
||||||
|
(update-in state [:workspace pid] assoc :drawing-tool tool :drawing data))))))
|
||||||
|
|
||||||
|
;; --- Shape Proportions
|
||||||
|
|
||||||
|
(deftype LockShapeProportions [id]
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [[width height] (-> (get-in state [:shapes id])
|
||||||
|
(geom/size)
|
||||||
|
(keep [:width :height]))
|
||||||
|
proportion (/ width height)]
|
||||||
|
(update-in state [:shapes id] assoc
|
||||||
|
:proportion proportion
|
||||||
|
:proportion-lock true))))
|
||||||
|
|
||||||
|
(defn lock-proportions
|
||||||
|
"Mark proportions of the shape locked and save the current
|
||||||
|
proportion as additional precalculated property."
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(LockShapeProportions. id))
|
||||||
|
|
||||||
|
(deftype UnlockShapeProportions [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:shapes id :proportion-lock] false)))
|
||||||
|
|
||||||
|
(defn unlock-proportions
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(UnlockShapeProportions. id))
|
||||||
|
|
||||||
|
;; --- Update Dimensions
|
||||||
|
|
||||||
|
(s/def ::width (s/and ::us/number ::us/positive))
|
||||||
|
(s/def ::height (s/and ::us/number ::us/positive))
|
||||||
|
|
||||||
|
(s/def ::update-dimensions
|
||||||
|
(s/keys :opt-un [::width ::height]))
|
||||||
|
|
||||||
|
(defn update-dimensions
|
||||||
|
"A helper event just for update the position
|
||||||
|
of the shape using the width and height attrs
|
||||||
|
instread final point of coordinates."
|
||||||
|
[id dimensions]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(s/assert ::update-dimensions dimensions)
|
||||||
|
(reify
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:shapes id] geom/resize-dim dimensions))))
|
||||||
|
|
||||||
|
;; --- Update Interaction
|
||||||
|
|
||||||
|
(deftype UpdateInteraction [shape interaction]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [id (or (:id interaction)
|
||||||
|
(uuid/random))
|
||||||
|
data (assoc interaction :id id)]
|
||||||
|
(assoc-in state [:shapes shape :interactions id] data))))
|
||||||
|
|
||||||
|
(defn update-interaction
|
||||||
|
[shape interaction]
|
||||||
|
(UpdateInteraction. shape interaction))
|
||||||
|
|
||||||
|
;; --- Delete Interaction
|
||||||
|
|
||||||
|
(deftype DeleteInteracton [shape id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:shapes shape :interactions] dissoc id)))
|
||||||
|
|
||||||
|
(defn delete-interaction
|
||||||
|
[shape id]
|
||||||
|
{:pre [(uuid? id) (uuid? shape)]}
|
||||||
|
(DeleteInteracton. shape id))
|
||||||
|
|
||||||
|
;; --- Path Modifications
|
||||||
|
|
||||||
|
(deftype UpdatePath [id index delta]
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update-in state [:shapes id :segments index] gpt/add delta)))
|
||||||
|
|
||||||
|
(defn update-path
|
||||||
|
"Update a concrete point in the path shape."
|
||||||
|
[id index delta]
|
||||||
|
{:pre [(uuid? id) (number? index) (gpt/point? delta)]}
|
||||||
|
(UpdatePath. id index delta))
|
||||||
|
|
||||||
|
;; --- Initial Path Point Alignment
|
||||||
|
|
||||||
|
(deftype InitialPathPointAlign [id index]
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state s]
|
||||||
|
(let [shape (get-in state [:shapes id])
|
||||||
|
point (get-in shape [:segments index])]
|
||||||
|
(->> (uwrk/align-point point)
|
||||||
|
(rx/map #(update-path id index %))))))
|
||||||
|
|
||||||
|
(defn initial-path-point-align
|
||||||
|
"Event responsible of align a specified point of the
|
||||||
|
shape by `index` with the grid."
|
||||||
|
[id index]
|
||||||
|
{:pre [(uuid? id)
|
||||||
|
(number? index)
|
||||||
|
(not (neg? index))]}
|
||||||
|
(InitialPathPointAlign. id index))
|
||||||
|
|
||||||
|
;; --- Shape Visibility
|
||||||
|
|
||||||
|
(deftype HideShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-hidden [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :hidden] true)
|
||||||
|
(reduce mark-hidden $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :hidden] true))))]
|
||||||
|
(mark-hidden state id))))
|
||||||
|
|
||||||
|
(defn hide-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(HideShape. id))
|
||||||
|
|
||||||
|
(deftype ShowShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-visible [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :hidden] false)
|
||||||
|
(reduce mark-visible $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :hidden] false))))]
|
||||||
|
(mark-visible state id))))
|
||||||
|
|
||||||
|
(defn show-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(ShowShape. id))
|
||||||
|
|
||||||
|
;; --- Shape Blocking
|
||||||
|
|
||||||
|
(deftype BlockShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-blocked [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :blocked] true)
|
||||||
|
(reduce mark-blocked $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :blocked] true))))]
|
||||||
|
(mark-blocked state id))))
|
||||||
|
|
||||||
|
(defn block-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(BlockShape. id))
|
||||||
|
|
||||||
|
(deftype UnblockShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-unblocked [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :blocked] false)
|
||||||
|
(reduce mark-unblocked $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :blocked] false))))]
|
||||||
|
(mark-unblocked state id))))
|
||||||
|
|
||||||
|
(defn unblock-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(UnblockShape. id))
|
||||||
|
|
||||||
|
;; --- Shape Locking
|
||||||
|
|
||||||
|
(deftype LockShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-locked [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :locked] true)
|
||||||
|
(reduce mark-locked $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :locked] true))))]
|
||||||
|
(mark-locked state id))))
|
||||||
|
|
||||||
|
(defn lock-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(LockShape. id))
|
||||||
|
|
||||||
|
(deftype UnlockShape [id]
|
||||||
|
udp/IPageUpdate
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(letfn [(mark-unlocked [state id]
|
||||||
|
(let [shape (get-in state [:shapes id])]
|
||||||
|
(if (= :group (:type shape))
|
||||||
|
(as-> state $
|
||||||
|
(assoc-in $ [:shapes id :locked] false)
|
||||||
|
(reduce mark-unlocked $ (:items shape)))
|
||||||
|
(assoc-in state [:shapes id :locked] false))))]
|
||||||
|
(mark-unlocked state id))))
|
||||||
|
|
||||||
|
(defn unlock-shape
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(UnlockShape. id))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Pages
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn delete-page
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(reify
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [pid (get-in state [:pages id :project])]
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (udp/delete-page id))
|
||||||
|
(->> stream
|
||||||
|
(rx/filter #(= % ::udp/delete-completed))
|
||||||
|
(rx/map #(dp/go-to pid))
|
||||||
|
(rx/take 1)))))))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Selection Rect IMPL
|
;; Selection Rect IMPL
|
||||||
|
@ -662,6 +929,42 @@
|
||||||
:y2 end-y
|
:y2 end-y
|
||||||
:type :rect)))
|
:type :rect)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Canvas Interactions
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;; ;; --- Group Collapsing
|
||||||
|
|
||||||
|
;; (deftype CollapseGroupShape [id]
|
||||||
|
;; udp/IPageUpdate
|
||||||
|
;; ptk/UpdateEvent
|
||||||
|
;; (update [_ state]
|
||||||
|
;; (update-in state [:shapes id] assoc :collapsed true)))
|
||||||
|
|
||||||
|
;; (defn collapse-shape
|
||||||
|
;; [id]
|
||||||
|
;; {:pre [(uuid? id)]}
|
||||||
|
;; (CollapseGroupShape. id))
|
||||||
|
|
||||||
|
;; (deftype UncollapseGroupShape [id]
|
||||||
|
;; udp/IPageUpdate
|
||||||
|
;; ptk/UpdateEvent
|
||||||
|
;; (update [_ state]
|
||||||
|
;; (update-in state [:shapes id] assoc :collapsed false)))
|
||||||
|
|
||||||
|
;; (defn uncollapse-shape
|
||||||
|
;; [id]
|
||||||
|
;; {:pre [(uuid? id)]}
|
||||||
|
;; (UncollapseGroupShape. id))
|
||||||
|
|
||||||
|
(defn select-canvas
|
||||||
|
[id]
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pid (get-in state [:workspace :current])]
|
||||||
|
(update-in state [:workspace pid] assoc :selected-canvas id)))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Server Interactions
|
;; Server Interactions
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -670,16 +973,15 @@
|
||||||
|
|
||||||
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
|
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
|
||||||
|
|
||||||
(defrecord UpdateMetadata [id metadata]
|
(defn update-metadata
|
||||||
|
[id metadata]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(s/assert ::udp/metadata metadata)
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(rx/of (udp/update-metadata id metadata)
|
(rx/of (udp/update-metadata id metadata)
|
||||||
(initialize-alignment id))))
|
(initialize-alignment id)))))
|
||||||
|
|
||||||
(defn update-metadata
|
|
||||||
[id metadata]
|
|
||||||
{:pre [(uuid? id) (us/valid? ::udp/metadata metadata)]}
|
|
||||||
(UpdateMetadata. id metadata))
|
|
||||||
|
|
||||||
(defrecord OpenView [page-id]
|
(defrecord OpenView [page-id]
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
(ns uxbox.main.data.workspace.ruler
|
(ns uxbox.main.data.workspace.ruler
|
||||||
"Workspace ruler related events. Mostly or all events
|
"Workspace ruler related events. Mostly or all events
|
||||||
are related to UI logic."
|
are related to UI logic."
|
||||||
(:require [beicon.core :as rx]
|
#_(:require [beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.streams :as streams]
|
[uxbox.main.streams :as streams]
|
||||||
|
@ -17,79 +17,79 @@
|
||||||
|
|
||||||
;; --- Constants
|
;; --- Constants
|
||||||
|
|
||||||
(declare stop-ruler?)
|
;; (declare stop-ruler?)
|
||||||
(declare clear-ruler)
|
;; (declare clear-ruler)
|
||||||
(declare update-ruler)
|
;; (declare update-ruler)
|
||||||
|
|
||||||
(def ^:private immanted-zones
|
;; (def ^:private immanted-zones
|
||||||
(let [transform #(vector (- % 7) (+ % 7) %)
|
;; (let [transform #(vector (- % 7) (+ % 7) %)
|
||||||
right (map transform (range 0 181 15))
|
;; right (map transform (range 0 181 15))
|
||||||
left (map (comp transform -) (range 0 181 15))]
|
;; left (map (comp transform -) (range 0 181 15))]
|
||||||
(vec (concat right left))))
|
;; (vec (concat right left))))
|
||||||
|
|
||||||
(defn- align-position
|
;; (defn- align-position
|
||||||
[pos]
|
;; [pos]
|
||||||
(let [angle (gpt/angle pos)]
|
;; (let [angle (gpt/angle pos)]
|
||||||
(reduce (fn [pos [a1 a2 v]]
|
;; (reduce (fn [pos [a1 a2 v]]
|
||||||
(if (< a1 angle a2)
|
;; (if (< a1 angle a2)
|
||||||
(reduced (gpt/update-angle pos v))
|
;; (reduced (gpt/update-angle pos v))
|
||||||
pos))
|
;; pos))
|
||||||
pos
|
;; pos
|
||||||
immanted-zones)))
|
;; immanted-zones)))
|
||||||
|
|
||||||
;; --- Start Ruler
|
;; ;; --- Start Ruler
|
||||||
|
|
||||||
(deftype StartRuler []
|
;; (deftype StartRuler []
|
||||||
ptk/UpdateEvent
|
;; ptk/UpdateEvent
|
||||||
(update [_ state]
|
;; (update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])
|
;; (let [pid (get-in state [:workspace :current])
|
||||||
pos (get-in state [:workspace :pointer :viewport])]
|
;; pos (get-in state [:workspace :pointer :viewport])]
|
||||||
(assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
|
;; (assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
|
||||||
|
|
||||||
ptk/WatchEvent
|
;; ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
;; (watch [_ state stream]
|
||||||
(let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream)
|
;; (let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream)
|
||||||
(rx/take 1))]
|
;; (rx/take 1))]
|
||||||
(->> streams/mouse-position
|
;; (->> streams/mouse-position
|
||||||
(rx/take-until stoper)
|
;; (rx/take-until stoper)
|
||||||
(rx/map (juxt :viewport :ctrl))
|
;; (rx/map (juxt :viewport :ctrl))
|
||||||
(rx/map (fn [[pt ctrl?]]
|
;; (rx/map (fn [[pt ctrl?]]
|
||||||
(update-ruler pt ctrl?)))))))
|
;; (update-ruler pt ctrl?)))))))
|
||||||
|
|
||||||
(defn start-ruler
|
;; (defn start-ruler
|
||||||
[]
|
;; []
|
||||||
(StartRuler.))
|
;; (StartRuler.))
|
||||||
|
|
||||||
;; --- Update Ruler
|
;; ;; --- Update Ruler
|
||||||
|
|
||||||
(deftype UpdateRuler [point ctrl?]
|
;; (deftype UpdateRuler [point ctrl?]
|
||||||
ptk/UpdateEvent
|
;; ptk/UpdateEvent
|
||||||
(update [_ state]
|
;; (update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])
|
;; (let [pid (get-in state [:workspace :current])
|
||||||
ruler (get-in state [:workspace pid :ruler])]
|
;; ruler (get-in state [:workspace pid :ruler])]
|
||||||
(if-not ctrl?
|
;; (if-not ctrl?
|
||||||
(assoc-in state [:workspace pid :ruler :end] point)
|
;; (assoc-in state [:workspace pid :ruler :end] point)
|
||||||
(let [start (get-in state [:workspace pid :ruler :start])
|
;; (let [start (get-in state [:workspace pid :ruler :start])
|
||||||
end (-> (gpt/subtract point start)
|
;; end (-> (gpt/subtract point start)
|
||||||
(align-position)
|
;; (align-position)
|
||||||
(gpt/add start))]
|
;; (gpt/add start))]
|
||||||
(assoc-in state [:workspace pid :ruler :end] end))))))
|
;; (assoc-in state [:workspace pid :ruler :end] end))))))
|
||||||
|
|
||||||
(defn update-ruler
|
;; (defn update-ruler
|
||||||
[point ctrl?]
|
;; [point ctrl?]
|
||||||
{:pre [(gpt/point? point)
|
;; {:pre [(gpt/point? point)
|
||||||
(boolean? ctrl?)]}
|
;; (boolean? ctrl?)]}
|
||||||
(UpdateRuler. point ctrl?))
|
;; (UpdateRuler. point ctrl?))
|
||||||
|
|
||||||
;; --- Clear Ruler
|
;; ;; --- Clear Ruler
|
||||||
|
|
||||||
(deftype ClearRuler []
|
;; (deftype ClearRuler []
|
||||||
ptk/UpdateEvent
|
;; ptk/UpdateEvent
|
||||||
(update [_ state]
|
;; (update [_ state]
|
||||||
(let [pid (get-in state [:workspace :current])]
|
;; (let [pid (get-in state [:workspace :current])]
|
||||||
(update-in state [:workspace pid] dissoc :ruler))))
|
;; (update-in state [:workspace pid] dissoc :ruler))))
|
||||||
|
|
||||||
(defn clear-ruler
|
;; (defn clear-ruler
|
||||||
[]
|
;; []
|
||||||
(ClearRuler.))
|
;; (ClearRuler.))
|
||||||
|
|
||||||
|
|
|
@ -6,23 +6,7 @@
|
||||||
|
|
||||||
;; TODO: DEPRECTATED, maintained just for temporal documentation, delete on near future
|
;; TODO: DEPRECTATED, maintained just for temporal documentation, delete on near future
|
||||||
|
|
||||||
(ns uxbox.main.data.workspace-drawing
|
(ns uxbox.main.data.workspace-drawing)
|
||||||
"Workspace drawing data events and impl."
|
|
||||||
(:require [beicon.core :as rx]
|
|
||||||
[potok.core :as ptk]
|
|
||||||
[lentes.core :as l]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.constants :as c]
|
|
||||||
[uxbox.main.refs :as refs]
|
|
||||||
[uxbox.main.streams :as streams]
|
|
||||||
[uxbox.main.data.shapes :as uds]
|
|
||||||
[uxbox.main.data.workspace :as udw]
|
|
||||||
[uxbox.main.geom :as geom]
|
|
||||||
[uxbox.main.workers :as uwrk]
|
|
||||||
[uxbox.main.user-events :as uev]
|
|
||||||
[uxbox.main.lenses :as ul]
|
|
||||||
[uxbox.util.geom.path :as pth]
|
|
||||||
[uxbox.util.geom.point :as gpt]))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Data Events
|
;; Data Events
|
||||||
|
@ -234,7 +218,7 @@
|
||||||
;; (rx/filter uev/mouse-up?)
|
;; (rx/filter uev/mouse-up?)
|
||||||
;; (rx/take 1)))
|
;; (rx/take 1)))
|
||||||
;; start? (volatile! true)
|
;; start? (volatile! true)
|
||||||
;; mouse (->> streams/viewport-mouse-position
|
;; mouse (->> streams/mouse-position
|
||||||
;; (rx/take-until stoper)
|
;; (rx/take-until stoper)
|
||||||
;; (rx/mapcat conditional-align)
|
;; (rx/mapcat conditional-align)
|
||||||
;; (rx/map translate-to-canvas)
|
;; (rx/map translate-to-canvas)
|
||||||
|
@ -325,7 +309,7 @@
|
||||||
;; (defn- on-init-draw-free-path
|
;; (defn- on-init-draw-free-path
|
||||||
;; [shape stoper]
|
;; [shape stoper]
|
||||||
;; (let [stoper (get-path-stoper-stream stoper true)
|
;; (let [stoper (get-path-stoper-stream stoper true)
|
||||||
;; mouse (->> streams/viewport-mouse-position
|
;; mouse (->> streams/mouse-position
|
||||||
;; (rx/mapcat conditional-align)
|
;; (rx/mapcat conditional-align)
|
||||||
;; (rx/map translate-to-canvas))
|
;; (rx/map translate-to-canvas))
|
||||||
|
|
||||||
|
@ -341,7 +325,7 @@
|
||||||
;; [shape stoper]
|
;; [shape stoper]
|
||||||
;; (let [last-point (volatile! @refs/canvas-mouse-position)
|
;; (let [last-point (volatile! @refs/canvas-mouse-position)
|
||||||
;; stoper (get-path-stoper-stream stoper)
|
;; stoper (get-path-stoper-stream stoper)
|
||||||
;; mouse (->> (rx/sample 10 streams/viewport-mouse-position)
|
;; mouse (->> (rx/sample 10 streams/mouse-position)
|
||||||
;; (rx/mapcat conditional-align)
|
;; (rx/mapcat conditional-align)
|
||||||
;; (rx/map translate-to-canvas))
|
;; (rx/map translate-to-canvas))
|
||||||
;; points (->> (get-path-point-stream)
|
;; points (->> (get-path-point-stream)
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
:image (move-rect shape dpoint)
|
:image (move-rect shape dpoint)
|
||||||
:rect (move-rect shape dpoint)
|
:rect (move-rect shape dpoint)
|
||||||
:text (move-rect shape dpoint)
|
:text (move-rect shape dpoint)
|
||||||
|
:curve (move-path shape dpoint)
|
||||||
:path (move-path shape dpoint)
|
:path (move-path shape dpoint)
|
||||||
:circle (move-circle shape dpoint)
|
:circle (move-circle shape dpoint)
|
||||||
:group (move-group shape dpoint)))
|
:group (move-group shape dpoint)))
|
||||||
|
@ -125,13 +126,10 @@
|
||||||
"Calculate the size of the shape."
|
"Calculate the size of the shape."
|
||||||
[shape]
|
[shape]
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:group (assoc shape :width 100 :height 100)
|
|
||||||
:circle (size-circle shape)
|
:circle (size-circle shape)
|
||||||
:text (size-rect shape)
|
:curve (size-path shape)
|
||||||
:rect (size-rect shape)
|
:path (size-path shape)
|
||||||
:icon (size-rect shape)
|
(size-rect shape)))
|
||||||
:image (size-rect shape)
|
|
||||||
:path (size-path shape)))
|
|
||||||
|
|
||||||
(defn- size-path
|
(defn- size-path
|
||||||
[{:keys [segments x1 y1 x2 y2] :as shape}]
|
[{:keys [segments x1 y1 x2 y2] :as shape}]
|
||||||
|
@ -179,11 +177,13 @@
|
||||||
(defn setup-proportions
|
(defn setup-proportions
|
||||||
[shape]
|
[shape]
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
|
:canvas (setup-proportions-rect shape)
|
||||||
:rect (setup-proportions-rect shape)
|
:rect (setup-proportions-rect shape)
|
||||||
:circle (setup-proportions-rect shape)
|
:circle (setup-proportions-rect shape)
|
||||||
:icon (setup-proportions-image shape)
|
:icon (setup-proportions-image shape)
|
||||||
:image (setup-proportions-image shape)
|
:image (setup-proportions-image shape)
|
||||||
:text shape
|
:text shape
|
||||||
|
:curve (setup-proportions-rect shape)
|
||||||
:path (setup-proportions-rect shape)))
|
:path (setup-proportions-rect shape)))
|
||||||
|
|
||||||
(defn setup-proportions-image
|
(defn setup-proportions-image
|
||||||
|
@ -461,6 +461,7 @@
|
||||||
(case type
|
(case type
|
||||||
:circle (circle->rect-shape state shape)
|
:circle (circle->rect-shape state shape)
|
||||||
:path (path->rect-shape state shape)
|
:path (path->rect-shape state shape)
|
||||||
|
:curve (path->rect-shape state shape)
|
||||||
shape)))
|
shape)))
|
||||||
|
|
||||||
(defn shapes->rect-shape
|
(defn shapes->rect-shape
|
||||||
|
@ -512,11 +513,13 @@
|
||||||
"Apply the matrix transformation to shape."
|
"Apply the matrix transformation to shape."
|
||||||
[{:keys [type] :as shape} xfmt]
|
[{:keys [type] :as shape} xfmt]
|
||||||
(case type
|
(case type
|
||||||
|
:canvas (transform-rect shape xfmt)
|
||||||
:rect (transform-rect shape xfmt)
|
:rect (transform-rect shape xfmt)
|
||||||
:icon (transform-rect shape xfmt)
|
:icon (transform-rect shape xfmt)
|
||||||
:text (transform-rect shape xfmt)
|
:text (transform-rect shape xfmt)
|
||||||
:image (transform-rect shape xfmt)
|
:image (transform-rect shape xfmt)
|
||||||
:path (transform-path shape xfmt)
|
:path (transform-path shape xfmt)
|
||||||
|
:curve (transform-path shape xfmt)
|
||||||
:circle (transform-circle shape xfmt)))
|
:circle (transform-circle shape xfmt)))
|
||||||
|
|
||||||
(defn- transform-rect
|
(defn- transform-rect
|
||||||
|
@ -564,7 +567,6 @@
|
||||||
;; --- Outer Rect
|
;; --- Outer Rect
|
||||||
|
|
||||||
(declare selection-rect-generic)
|
(declare selection-rect-generic)
|
||||||
(declare selection-rect-group)
|
|
||||||
|
|
||||||
(defn rotation-matrix
|
(defn rotation-matrix
|
||||||
"Generate a rotation matrix from shape."
|
"Generate a rotation matrix from shape."
|
||||||
|
@ -589,25 +591,15 @@
|
||||||
([shape]
|
([shape]
|
||||||
(selection-rect @st/state shape))
|
(selection-rect @st/state shape))
|
||||||
([state shape]
|
([state shape]
|
||||||
(let [{:keys [displacement resize]} (:modifiers shape)]
|
(let [modifier (:modifier-mtx shape)]
|
||||||
(-> (shape->rect-shape shape)
|
(-> (shape->rect-shape shape)
|
||||||
(assoc :type :rect :id (:id shape))
|
(assoc :type :rect :id (:id shape))
|
||||||
(transform (or resize (gmt/matrix)))
|
(transform (or modifier (gmt/matrix)))
|
||||||
(transform (or displacement (gmt/matrix)))
|
|
||||||
(rotate-shape)
|
(rotate-shape)
|
||||||
(size)))))
|
(size)))))
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
(defn resolve-parent
|
|
||||||
"Recursively resolve the real shape parent."
|
|
||||||
([shape]
|
|
||||||
(resolve-parent @st/state shape))
|
|
||||||
([state {:keys [group] :as shape}]
|
|
||||||
(if group
|
|
||||||
(resolve-parent state (get-in state [:shapes group]))
|
|
||||||
shape)))
|
|
||||||
|
|
||||||
(defn contained-in?
|
(defn contained-in?
|
||||||
"Check if a shape is contained in the
|
"Check if a shape is contained in the
|
||||||
provided selection rect."
|
provided selection rect."
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
(ns uxbox.main.lenses
|
|
||||||
(:require [lentes.core :as l]))
|
|
||||||
|
|
||||||
;; --- Workspace
|
|
||||||
;; --- FIXME: remove this ns
|
|
||||||
|
|
||||||
(def workspace (l/key :workspace))
|
|
||||||
(def workspace-flags (comp workspace (l/key :flags)))
|
|
||||||
|
|
||||||
(def selected-drawing (comp workspace (l/key :drawing)))
|
|
||||||
(def selected-shapes (comp workspace (l/key :selected)))
|
|
||||||
(def selected-page (comp workspace (l/key :page)))
|
|
||||||
(def selected-project (comp workspace (l/key :project)))
|
|
|
@ -52,6 +52,10 @@
|
||||||
(-> (l/key :selected)
|
(-> (l/key :selected)
|
||||||
(l/derive workspace)))
|
(l/derive workspace)))
|
||||||
|
|
||||||
|
(def selected-canvas
|
||||||
|
(-> (l/key :selected-canvas)
|
||||||
|
(l/derive workspace)))
|
||||||
|
|
||||||
(def toolboxes
|
(def toolboxes
|
||||||
(-> (l/key :toolboxes)
|
(-> (l/key :toolboxes)
|
||||||
(l/derive workspace)))
|
(l/derive workspace)))
|
||||||
|
@ -100,28 +104,6 @@
|
||||||
(l/lens alignment-activated?))
|
(l/lens alignment-activated?))
|
||||||
(l/derive workspace)))
|
(l/derive workspace)))
|
||||||
|
|
||||||
;; ...
|
|
||||||
|
|
||||||
(def mouse-position
|
|
||||||
(-> (l/in [:workspace :pointer])
|
|
||||||
(l/derive st/state)))
|
|
||||||
|
|
||||||
(def canvas-mouse-position
|
|
||||||
(-> (l/key :canvas)
|
|
||||||
(l/derive mouse-position)))
|
|
||||||
|
|
||||||
(def viewport-mouse-position
|
|
||||||
(-> (l/key :viewport)
|
|
||||||
(l/derive mouse-position)))
|
|
||||||
|
|
||||||
(def window-mouse-position
|
|
||||||
(-> (l/key :window)
|
|
||||||
(l/derive mouse-position)))
|
|
||||||
|
|
||||||
(def workspace-scroll
|
|
||||||
(-> (l/in [:workspace :scroll])
|
|
||||||
(l/derive st/state)))
|
|
||||||
|
|
||||||
(def shapes-by-id
|
(def shapes-by-id
|
||||||
(-> (l/key :shapes)
|
(-> (l/key :shapes)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.repo.pages
|
(ns uxbox.main.repo.pages
|
||||||
"A main interface for access to remote resources."
|
"A main interface for access to remote resources."
|
||||||
(:require [beicon.core :as rx]
|
(:require
|
||||||
[uxbox.config :refer (url)]
|
[uxbox.config :refer [url]]
|
||||||
[uxbox.main.repo.impl :refer (request send!)]
|
[uxbox.main.repo.impl :refer [request send!]]))
|
||||||
[uxbox.util.transit :as t]))
|
|
||||||
|
|
||||||
(defmethod request :fetch/pages
|
(defmethod request :fetch/pages
|
||||||
[type data]
|
[type data]
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2017 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
|
|
||||||
(ns uxbox.main.streams
|
|
||||||
"A collection of derived streams."
|
|
||||||
(:require [beicon.core :as rx]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.user-events :as uev]
|
|
||||||
[uxbox.main.refs :as refs]
|
|
||||||
[uxbox.main.workers :as uwrk]
|
|
||||||
[uxbox.util.geom.point :as gpt]))
|
|
||||||
|
|
||||||
;; --- Events
|
|
||||||
|
|
||||||
(defn- user-interaction-event?
|
|
||||||
[event]
|
|
||||||
(or (uev/keyboard-event? event)
|
|
||||||
(uev/mouse-event? event)))
|
|
||||||
|
|
||||||
(defonce events
|
|
||||||
(rx/filter user-interaction-event? st/stream))
|
|
||||||
|
|
||||||
;; --- Mouse Position Stream
|
|
||||||
|
|
||||||
(defonce mouse-position
|
|
||||||
(rx/filter uev/pointer-event? st/stream))
|
|
||||||
|
|
||||||
(defonce canvas-mouse-position
|
|
||||||
(->> mouse-position
|
|
||||||
(rx/map :canvas)
|
|
||||||
(rx/share)))
|
|
||||||
|
|
||||||
(defonce viewport-mouse-position
|
|
||||||
(->> mouse-position
|
|
||||||
(rx/map :viewport)
|
|
||||||
(rx/share)))
|
|
||||||
|
|
||||||
(defonce window-mouse-position
|
|
||||||
(->> mouse-position
|
|
||||||
(rx/map :window)
|
|
||||||
(rx/share)))
|
|
||||||
|
|
||||||
(defonce mouse-position-ctrl
|
|
||||||
(->> mouse-position
|
|
||||||
(rx/map :ctrl)
|
|
||||||
(rx/share)))
|
|
||||||
|
|
||||||
(defn- coords-delta
|
|
||||||
[[old new]]
|
|
||||||
(gpt/subtract new old))
|
|
||||||
|
|
||||||
(defonce mouse-position-deltas
|
|
||||||
(->> viewport-mouse-position
|
|
||||||
(rx/sample 10)
|
|
||||||
(rx/map #(gpt/divide % @refs/selected-zoom))
|
|
||||||
(rx/mapcat (fn [point]
|
|
||||||
(if @refs/selected-alignment
|
|
||||||
(uwrk/align-point point)
|
|
||||||
(rx/of point))))
|
|
||||||
(rx/buffer 2 1)
|
|
||||||
(rx/map coords-delta)
|
|
||||||
(rx/share)))
|
|
|
@ -11,7 +11,6 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[rumext.core :as mx]
|
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.auth :refer [logout]]
|
[uxbox.main.data.auth :refer [logout]]
|
||||||
|
@ -55,14 +54,17 @@
|
||||||
(defn- on-error
|
(defn- on-error
|
||||||
"A default error handler."
|
"A default error handler."
|
||||||
[{:keys [status] :as error}]
|
[{:keys [status] :as error}]
|
||||||
(js/console.error "on-error:" (pr-str error))
|
(js/console.error "Unhandled Error:"
|
||||||
(js/console.error (.-stack error))
|
"\n - message:" (ex-message error)
|
||||||
|
"\n - data:" (pr-str (ex-data error))
|
||||||
|
"\n - stack:" (.-stack error))
|
||||||
(reset! st/loader false)
|
(reset! st/loader false)
|
||||||
(cond
|
(cond
|
||||||
;; Unauthorized or Auth timeout
|
;; Unauthorized or Auth timeout
|
||||||
(and (:status error)
|
(and (:status error)
|
||||||
(or (= (:status error) 403)
|
(or (= (:status error) 403)
|
||||||
(= (:status error) 419)))
|
(= (:status error) 419)))
|
||||||
|
|
||||||
(ts/schedule 0 #(st/emit! (rt/nav :auth/login)))
|
(ts/schedule 0 #(st/emit! (rt/nav :auth/login)))
|
||||||
|
|
||||||
;; Conflict
|
;; Conflict
|
||||||
|
@ -81,25 +83,21 @@
|
||||||
|
|
||||||
;; --- Main App (Component)
|
;; --- Main App (Component)
|
||||||
|
|
||||||
(mf/def app
|
(def route-iref
|
||||||
:mixins [mx/reactive]
|
(-> (l/key :route)
|
||||||
|
(l/derive st/state)))
|
||||||
|
|
||||||
:init
|
(mf/defc app
|
||||||
(fn [own props]
|
[props]
|
||||||
(assoc own ::route-ref (l/derive (l/key :route) st/state)))
|
(let [route (mf/deref route-iref)]
|
||||||
|
(case (get-in route [:data :name])
|
||||||
:render
|
|
||||||
(fn [own props]
|
|
||||||
(let [route (mx/react (::route-ref own))
|
|
||||||
route-id (get-in route [:data :name])]
|
|
||||||
(case route-id
|
|
||||||
:auth/login (mf/element auth/login-page)
|
:auth/login (mf/element auth/login-page)
|
||||||
:auth/register (auth/register-page)
|
:auth/register (mf/element auth/register-page)
|
||||||
:auth/recovery-request (auth/recovery-request-page)
|
;; :auth/recovery-request (auth/recovery-request-page)
|
||||||
|
|
||||||
:auth/recovery
|
;; :auth/recovery
|
||||||
(let [token (get-in route [:params :path :token])]
|
;; (let [token (get-in route [:params :path :token])]
|
||||||
(auth/recovery-page token))
|
;; (auth/recovery-page token))
|
||||||
|
|
||||||
(:settings/profile
|
(:settings/profile
|
||||||
:settings/password
|
:settings/password
|
||||||
|
@ -119,5 +117,5 @@
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:key page-id}])
|
:key page-id}])
|
||||||
|
|
||||||
nil
|
nil)))
|
||||||
))))
|
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
(ns uxbox.main.ui.auth
|
(ns uxbox.main.ui.auth
|
||||||
(:require [uxbox.main.ui.auth.login :as login]
|
(:require [uxbox.main.ui.auth.login :as login]
|
||||||
[uxbox.main.ui.auth.register :as register]
|
[uxbox.main.ui.auth.register :as register]
|
||||||
[uxbox.main.ui.auth.recovery-request :as recovery-request]
|
#_[uxbox.main.ui.auth.recovery-request :as recovery-request]
|
||||||
[uxbox.main.ui.auth.recovery :as recovery]))
|
#_[uxbox.main.ui.auth.recovery :as recovery]))
|
||||||
|
|
||||||
(def login-page login/login-page)
|
(def login-page login/login-page)
|
||||||
(def register-page register/register-page)
|
(def register-page register/register-page)
|
||||||
(def recovery-page recovery/recovery-page)
|
;; (def recovery-page recovery/recovery-page)
|
||||||
(def recovery-request-page recovery-request/recovery-request-page)
|
;; (def recovery-request-page recovery-request/recovery-request-page)
|
||||||
|
|
|
@ -8,44 +8,30 @@
|
||||||
(ns uxbox.main.ui.auth.login
|
(ns uxbox.main.ui.auth.login
|
||||||
(:require
|
(:require
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
|
||||||
[lentes.core :as l]
|
|
||||||
[rumext.core :as mx]
|
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.config :as cfg]
|
[uxbox.config :as cfg]
|
||||||
[uxbox.main.data.auth :as da]
|
[uxbox.main.data.auth :as da]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||||
[uxbox.main.ui.navigation :as nav]
|
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.forms :as fm]
|
[uxbox.util.forms :as fm]
|
||||||
[uxbox.util.i18n :refer (tr)]
|
[uxbox.util.i18n :refer [tr]]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]
|
||||||
|
[uxbox.util.spec :as us]))
|
||||||
|
|
||||||
(def form-data (fm/focus-data :login st/state))
|
(s/def ::username ::us/not-empty-string)
|
||||||
(def form-errors (fm/focus-errors :login st/state))
|
(s/def ::password ::us/not-empty-string)
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :login))
|
|
||||||
(def assoc-errors (partial fm/assoc-errors :login))
|
|
||||||
(def clear-form (partial fm/clear-form :login))
|
|
||||||
|
|
||||||
(s/def ::username ::fm/non-empty-string)
|
|
||||||
(s/def ::password ::fm/non-empty-string)
|
|
||||||
|
|
||||||
(s/def ::login-form
|
(s/def ::login-form
|
||||||
(s/keys :req-un [::username ::password]))
|
(s/keys :req-un [::username ::password]))
|
||||||
|
|
||||||
(defn- on-change
|
|
||||||
[event field]
|
|
||||||
(let [value (dom/event->value event)]
|
|
||||||
(st/emit! (assoc-value field value))))
|
|
||||||
|
|
||||||
(defn- on-submit
|
(defn- on-submit
|
||||||
[event data]
|
[event form]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(st/emit! (da/login {:username (:username data)
|
(let [{:keys [username password]} (:clean-data form)]
|
||||||
:password (:password data)})))
|
(st/emit! (da/login {:username username
|
||||||
|
:password password}))))
|
||||||
|
|
||||||
(mf/defc demo-warning
|
(mf/defc demo-warning
|
||||||
[_]
|
[_]
|
||||||
|
@ -56,36 +42,41 @@
|
||||||
[:strong "DO NOT USE"] " for real work, " [:br]
|
[:strong "DO NOT USE"] " for real work, " [:br]
|
||||||
" the projects will be periodicaly wiped."]])
|
" the projects will be periodicaly wiped."]])
|
||||||
|
|
||||||
|
|
||||||
(mf/defc login-form
|
(mf/defc login-form
|
||||||
{:wrap [mf/wrap-reactive]}
|
|
||||||
[]
|
[]
|
||||||
(let [data (mf/react form-data)
|
(let [{:keys [data] :as form} (fm/use-form ::login-form {})]
|
||||||
valid? (fm/valid? ::login-form data)]
|
[:form {:on-submit #(on-submit % form)}
|
||||||
[:form {:on-submit #(on-submit % data)}
|
|
||||||
[:div.login-content
|
[:div.login-content
|
||||||
(when cfg/isdemo
|
(when cfg/isdemo
|
||||||
[:& demo-warning])
|
[:& demo-warning])
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "email"
|
{:name "username"
|
||||||
:tab-index "2"
|
:tab-index "2"
|
||||||
:value (:username data "")
|
:value (:username data "")
|
||||||
:on-change #(on-change % :username)
|
:class (fm/error-class form :username)
|
||||||
|
:on-blur (fm/on-input-blur form :username)
|
||||||
|
:on-change (fm/on-input-change form :username)
|
||||||
:placeholder (tr "auth.email-or-username")
|
:placeholder (tr "auth.email-or-username")
|
||||||
:type "text"}]
|
:type "text"}]
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "password"
|
{:name "password"
|
||||||
:tab-index "3"
|
:tab-index "3"
|
||||||
:value (:password data "")
|
:value (:password data "")
|
||||||
:on-change #(on-change % :password)
|
:class (fm/error-class form :password)
|
||||||
|
:on-blur (fm/on-input-blur form :password)
|
||||||
|
:on-change (fm/on-input-change form :password)
|
||||||
:placeholder (tr "auth.password")
|
:placeholder (tr "auth.password")
|
||||||
:type "password"}]
|
:type "password"}]
|
||||||
[:input.btn-primary
|
[:input.btn-primary
|
||||||
{:name "login"
|
{:name "login"
|
||||||
:tab-index "4"
|
:tab-index "4"
|
||||||
:class (when-not valid? "btn-disabled")
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
:disabled (not valid?)
|
:disabled (not (:valid form))
|
||||||
:value (tr "auth.signin")
|
:value (tr "auth.signin")
|
||||||
:type "submit"}]
|
:type "submit"}]
|
||||||
|
|
||||||
[:div.login-links
|
[:div.login-links
|
||||||
[:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
|
[:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
|
||||||
:tab-index "5"}
|
:tab-index "5"}
|
||||||
|
@ -94,14 +85,10 @@
|
||||||
:tab-index "6"}
|
:tab-index "6"}
|
||||||
(tr "auth.no-account")]]]]))
|
(tr "auth.no-account")]]]]))
|
||||||
|
|
||||||
|
|
||||||
;; {:mixins [mx/static (fm/clear-mixin st/store :login)]}
|
|
||||||
|
|
||||||
(mf/defc login-page
|
(mf/defc login-page
|
||||||
[]
|
[]
|
||||||
(mf/use-effect (constantly #(st/emit! (fm/clear-form :login))))
|
|
||||||
[:div.login
|
[:div.login
|
||||||
[:div.login-body
|
[:div.login-body
|
||||||
(messages-widget)
|
[:& messages-widget]
|
||||||
[:a i/logo]
|
[:a i/logo]
|
||||||
[:& login-form]]])
|
[:& login-form]]])
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
(ns uxbox.main.ui.auth.recovery
|
(ns uxbox.main.ui.auth.recovery
|
||||||
(:require
|
(:require
|
||||||
[cljs.spec.alpha :as s :include-macros true]
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[rumext.core :as mx :include-macros true]
|
[rumext.core :as mx :include-macros true]
|
||||||
|
@ -21,62 +21,62 @@
|
||||||
[uxbox.util.i18n :refer (tr)]
|
[uxbox.util.i18n :refer (tr)]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
|
||||||
(def form-data (fm/focus-data :recovery st/state))
|
;; (def form-data (fm/focus-data :recovery st/state))
|
||||||
(def form-errors (fm/focus-errors :recovery st/state))
|
;; (def form-errors (fm/focus-errors :recovery st/state))
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :recovery))
|
;; (def assoc-value (partial fm/assoc-value :recovery))
|
||||||
(def assoc-errors (partial fm/assoc-errors :recovery))
|
;; (def assoc-errors (partial fm/assoc-errors :recovery))
|
||||||
(def clear-form (partial fm/clear-form :recovery))
|
;; (def clear-form (partial fm/clear-form :recovery))
|
||||||
|
|
||||||
;; --- Recovery Form
|
;; ;; --- Recovery Form
|
||||||
|
|
||||||
(s/def ::password ::fm/non-empty-string)
|
;; (s/def ::password ::fm/non-empty-string)
|
||||||
(s/def ::recovery-form
|
;; (s/def ::recovery-form
|
||||||
(s/keys :req-un [::password]))
|
;; (s/keys :req-un [::password]))
|
||||||
|
|
||||||
(mx/defc recovery-form
|
;; (mx/defc recovery-form
|
||||||
{:mixins [mx/static mx/reactive]}
|
;; {:mixins [mx/static mx/reactive]}
|
||||||
[token]
|
;; [token]
|
||||||
(let [data (merge (mx/react form-data) {:token token})
|
;; (let [data (merge (mx/react form-data) {:token token})
|
||||||
valid? (fm/valid? ::recovery-form data)]
|
;; valid? (fm/valid? ::recovery-form data)]
|
||||||
(letfn [(on-change [field event]
|
;; (letfn [(on-change [field event]
|
||||||
(let [value (dom/event->value event)]
|
;; (let [value (dom/event->value event)]
|
||||||
(st/emit! (assoc-value field value))))
|
;; (st/emit! (assoc-value field value))))
|
||||||
(on-submit [event]
|
;; (on-submit [event]
|
||||||
(dom/prevent-default event)
|
;; (dom/prevent-default event)
|
||||||
(st/emit! (uda/recovery data)
|
;; (st/emit! (uda/recovery data)
|
||||||
(clear-form)))]
|
;; (clear-form)))]
|
||||||
[:form {:on-submit on-submit}
|
;; [:form {:on-submit on-submit}
|
||||||
[:div.login-content
|
;; [:div.login-content
|
||||||
[:input.input-text
|
;; [:input.input-text
|
||||||
{:name "password"
|
;; {:name "password"
|
||||||
:value (:password data "")
|
;; :value (:password data "")
|
||||||
:on-change (partial on-change :password)
|
;; :on-change (partial on-change :password)
|
||||||
:placeholder (tr "recover.password.placeholder")
|
;; :placeholder (tr "recover.password.placeholder")
|
||||||
:type "password"}]
|
;; :type "password"}]
|
||||||
[:input.btn-primary
|
;; [:input.btn-primary
|
||||||
{:name "login"
|
;; {:name "login"
|
||||||
:class (when-not valid? "btn-disabled")
|
;; :class (when-not valid? "btn-disabled")
|
||||||
:disabled (not valid?)
|
;; :disabled (not valid?)
|
||||||
:value (tr "recover.recover-password")
|
;; :value (tr "recover.recover-password")
|
||||||
:type "submit"}]
|
;; :type "submit"}]
|
||||||
[:div.login-links
|
;; [:div.login-links
|
||||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]])))
|
;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]])))
|
||||||
|
|
||||||
;; --- Recovery Page
|
;; ;; --- Recovery Page
|
||||||
|
|
||||||
(defn- recovery-page-init
|
;; (defn- recovery-page-init
|
||||||
[own]
|
;; [own]
|
||||||
(let [[token] (::mx/args own)]
|
;; (let [[token] (::mx/args own)]
|
||||||
(st/emit! (uda/validate-recovery-token token))
|
;; (st/emit! (uda/validate-recovery-token token))
|
||||||
own))
|
;; own))
|
||||||
|
|
||||||
(mx/defc recovery-page
|
;; (mx/defc recovery-page
|
||||||
{:mixins [mx/static (fm/clear-mixin st/store :recovery)]
|
;; {:mixins [mx/static (fm/clear-mixin st/store :recovery)]
|
||||||
:init recovery-page-init}
|
;; :init recovery-page-init}
|
||||||
[token]
|
;; [token]
|
||||||
[:div.login
|
;; [:div.login
|
||||||
[:div.login-body
|
;; [:div.login-body
|
||||||
(messages-widget)
|
;; (messages-widget)
|
||||||
[:a i/logo]
|
;; [:a i/logo]
|
||||||
(recovery-form token)]])
|
;; (recovery-form token)]])
|
||||||
|
|
|
@ -20,52 +20,52 @@
|
||||||
[rumext.core :as mx :include-macros true]
|
[rumext.core :as mx :include-macros true]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
|
||||||
(def form-data (fm/focus-data :recovery-request st/state))
|
;; (def form-data (fm/focus-data :recovery-request st/state))
|
||||||
(def form-errors (fm/focus-errors :recovery-request st/state))
|
;; (def form-errors (fm/focus-errors :recovery-request st/state))
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :profile-password))
|
;; (def assoc-value (partial fm/assoc-value :profile-password))
|
||||||
(def assoc-errors (partial fm/assoc-errors :profile-password))
|
;; (def assoc-errors (partial fm/assoc-errors :profile-password))
|
||||||
(def clear-form (partial fm/clear-form :profile-password))
|
;; (def clear-form (partial fm/clear-form :profile-password))
|
||||||
|
|
||||||
(s/def ::username ::fm/non-empty-string)
|
;; (s/def ::username ::fm/non-empty-string)
|
||||||
(s/def ::recovery-request-form (s/keys :req-un [::username]))
|
;; (s/def ::recovery-request-form (s/keys :req-un [::username]))
|
||||||
|
|
||||||
(mx/defc recovery-request-form
|
;; (mx/defc recovery-request-form
|
||||||
{:mixins [mx/static mx/reactive]}
|
;; {:mixins [mx/static mx/reactive]}
|
||||||
[]
|
;; []
|
||||||
(let [data (mx/react form-data)
|
;; (let [data (mx/react form-data)
|
||||||
valid? (fm/valid? ::recovery-request-form data)]
|
;; valid? (fm/valid? ::recovery-request-form data)]
|
||||||
(letfn [(on-change [event]
|
;; (letfn [(on-change [event]
|
||||||
(let [value (dom/event->value event)]
|
;; (let [value (dom/event->value event)]
|
||||||
(st/emit! (assoc-value :username value))))
|
;; (st/emit! (assoc-value :username value))))
|
||||||
(on-submit [event]
|
;; (on-submit [event]
|
||||||
(dom/prevent-default event)
|
;; (dom/prevent-default event)
|
||||||
(st/emit! (uda/recovery-request data)
|
;; (st/emit! (uda/recovery-request data)
|
||||||
(clear-form)))]
|
;; (clear-form)))]
|
||||||
[:form {:on-submit on-submit}
|
;; [:form {:on-submit on-submit}
|
||||||
[:div.login-content
|
;; [:div.login-content
|
||||||
[:input.input-text
|
;; [:input.input-text
|
||||||
{:name "username"
|
;; {:name "username"
|
||||||
:value (:username data "")
|
;; :value (:username data "")
|
||||||
:on-change on-change
|
;; :on-change on-change
|
||||||
:placeholder (tr "recovery-request.username-or-email.placeholder")
|
;; :placeholder (tr "recovery-request.username-or-email.placeholder")
|
||||||
:type "text"}]
|
;; :type "text"}]
|
||||||
[:input.btn-primary
|
;; [:input.btn-primary
|
||||||
{:name "login"
|
;; {:name "login"
|
||||||
:class (when-not valid? "btn-disabled")
|
;; :class (when-not valid? "btn-disabled")
|
||||||
:disabled (not valid?)
|
;; :disabled (not valid?)
|
||||||
:value (tr "recovery-request.recover-password")
|
;; :value (tr "recovery-request.recover-password")
|
||||||
:type "submit"}]
|
;; :type "submit"}]
|
||||||
[:div.login-links
|
;; [:div.login-links
|
||||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]])))
|
;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]])))
|
||||||
|
|
||||||
;; --- Recovery Request Page
|
;; ;; --- Recovery Request Page
|
||||||
|
|
||||||
(mx/defc recovery-request-page
|
;; (mx/defc recovery-request-page
|
||||||
{:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
|
;; {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
|
||||||
[]
|
;; []
|
||||||
[:div.login
|
;; [:div.login
|
||||||
[:div.login-body
|
;; [:div.login-body
|
||||||
(messages-widget)
|
;; (messages-widget)
|
||||||
[:a i/logo]
|
;; [:a i/logo]
|
||||||
(recovery-request-form)]])
|
;; (recovery-request-form)]])
|
||||||
|
|
|
@ -6,118 +6,134 @@
|
||||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
|
||||||
(ns uxbox.main.ui.auth.register
|
(ns uxbox.main.ui.auth.register
|
||||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
(:require
|
||||||
[lentes.core :as l]
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
[lentes.core :as l]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.data.auth :as uda]
|
[uxbox.main.data.auth :as uda]
|
||||||
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||||
[uxbox.main.ui.navigation :as nav]
|
[uxbox.main.ui.navigation :as nav]
|
||||||
[uxbox.util.i18n :refer (tr)]
|
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.forms :as fm]
|
[uxbox.util.forms :as fm]
|
||||||
[rumext.core :as mx :include-macros true]
|
[uxbox.util.i18n :refer [tr]]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
|
||||||
(def form-data (fm/focus-data :register st/state))
|
(s/def ::username ::fm/not-empty-string)
|
||||||
(def form-errors (fm/focus-errors :register st/state))
|
(s/def ::fullname ::fm/not-empty-string)
|
||||||
|
(s/def ::password ::fm/not-empty-string)
|
||||||
(def assoc-value (partial fm/assoc-value :register))
|
|
||||||
(def assoc-error (partial fm/assoc-error :register))
|
|
||||||
(def clear-form (partial fm/clear-form :register))
|
|
||||||
|
|
||||||
;; TODO: add better password validation
|
|
||||||
|
|
||||||
(s/def ::username ::fm/non-empty-string)
|
|
||||||
(s/def ::fullname ::fm/non-empty-string)
|
|
||||||
(s/def ::password ::fm/non-empty-string)
|
|
||||||
(s/def ::email ::fm/email)
|
(s/def ::email ::fm/email)
|
||||||
|
|
||||||
(s/def ::register-form
|
(s/def ::register-form
|
||||||
(s/keys :req-un [::username
|
(s/keys :req-un [::username
|
||||||
|
::password
|
||||||
::fullname
|
::fullname
|
||||||
::email
|
::email]))
|
||||||
::password]))
|
|
||||||
|
|
||||||
(mx/defc register-form
|
(defn- on-error
|
||||||
{:mixins [mx/static mx/reactive
|
[error form]
|
||||||
(fm/clear-mixin st/store :register)]}
|
(case (:code error)
|
||||||
[]
|
|
||||||
(let [data (mx/react form-data)
|
|
||||||
errors (mx/react form-errors)
|
|
||||||
valid? (fm/valid? ::register-form data)]
|
|
||||||
(letfn [(on-change [field event]
|
|
||||||
(let [value (dom/event->value event)]
|
|
||||||
(st/emit! (assoc-value field value))))
|
|
||||||
(on-error [{:keys [type code] :as payload}]
|
|
||||||
(case code
|
|
||||||
:uxbox.services.users/registration-disabled
|
:uxbox.services.users/registration-disabled
|
||||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||||
|
|
||||||
:uxbox.services.users/email-already-exists
|
:uxbox.services.users/email-already-exists
|
||||||
(st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists")))
|
(swap! form assoc-in [:errors :email]
|
||||||
|
{:type ::api
|
||||||
|
:message "errors.api.form.email-already-exists"})
|
||||||
|
|
||||||
:uxbox.services.users/username-already-exists
|
:uxbox.services.users/username-already-exists
|
||||||
(st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists")))))
|
(swap! form assoc-in [:errors :username]
|
||||||
(on-submit [event]
|
{:type ::api
|
||||||
|
:message "errors.api.form.username-already-exists"})))
|
||||||
|
|
||||||
|
(defn- on-submit
|
||||||
|
[event form]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(st/emit! (uda/register data on-error)))]
|
(let [data (:clean-data form)
|
||||||
[:form {:on-submit on-submit}
|
on-error #(on-error % form)]
|
||||||
|
(st/emit! (uda/register data on-error))))
|
||||||
|
|
||||||
|
(mf/defc register-form
|
||||||
|
[props]
|
||||||
|
(let [{:keys [data] :as form} (fm/use-form ::register-form {})]
|
||||||
|
[:form {:on-submit #(on-submit % form)}
|
||||||
[:div.login-content
|
[:div.login-content
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "fullname"
|
{:name "fullname"
|
||||||
:tab-index "2"
|
:tab-index "1"
|
||||||
:value (:fullname data "")
|
:value (:fullname data "")
|
||||||
:on-change (partial on-change :fullname)
|
:class (fm/error-class form :fullname)
|
||||||
|
:on-blur (fm/on-input-blur form :fullname)
|
||||||
|
:on-change (fm/on-input-change form :fullname)
|
||||||
:placeholder (tr "register.fullname.placeholder")
|
:placeholder (tr "register.fullname.placeholder")
|
||||||
:type "text"}]
|
:type "text"}]
|
||||||
(fm/input-error errors :fullname)
|
|
||||||
|
[:& fm/field-error {:form form
|
||||||
|
:type #{::api}
|
||||||
|
:field :fullname}]
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "username"
|
{:type "text"
|
||||||
:tab-index "3"
|
:name "username"
|
||||||
|
:tab-index "2"
|
||||||
|
:class (fm/error-class form :username)
|
||||||
|
:on-blur (fm/on-input-blur form :username)
|
||||||
|
:on-change (fm/on-input-change form :username)
|
||||||
:value (:username data "")
|
:value (:username data "")
|
||||||
:on-change (partial on-change :username)
|
:placeholder (tr "settings.profile.your-username")}]
|
||||||
:placeholder (tr "register.username.placeholder")
|
|
||||||
:type "text"}]
|
[:& fm/field-error {:form form
|
||||||
(fm/input-error errors :username)
|
:type #{::api}
|
||||||
|
:field :username}]
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "email"
|
{:type "email"
|
||||||
:tab-index "4"
|
:name "email"
|
||||||
:ref "email"
|
:tab-index "3"
|
||||||
|
:class (fm/error-class form :email)
|
||||||
|
:on-blur (fm/on-input-blur form :email)
|
||||||
|
:on-change (fm/on-input-change form :email)
|
||||||
:value (:email data "")
|
:value (:email data "")
|
||||||
:on-change (partial on-change :email)
|
:placeholder (tr "settings.profile.your-email")}]
|
||||||
:placeholder (tr "register.email.placeholder")
|
|
||||||
:type "text"}]
|
[:& fm/field-error {:form form
|
||||||
(fm/input-error errors :email)
|
:type #{::api}
|
||||||
|
:field :email}]
|
||||||
|
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:name "password"
|
{:name "password"
|
||||||
:tab-index "5"
|
:tab-index "4"
|
||||||
:ref "password"
|
|
||||||
:value (:password data "")
|
:value (:password data "")
|
||||||
:on-change (partial on-change :password)
|
:class (fm/error-class form :password)
|
||||||
|
:on-blur (fm/on-input-blur form :password)
|
||||||
|
:on-change (fm/on-input-change form :password)
|
||||||
:placeholder (tr "register.password.placeholder")
|
:placeholder (tr "register.password.placeholder")
|
||||||
:type "password"}]
|
:type "password"}]
|
||||||
(fm/input-error errors :password)
|
|
||||||
|
[:& fm/field-error {:form form
|
||||||
|
:type #{::api}
|
||||||
|
:field :email}]
|
||||||
|
|
||||||
[:input.btn-primary
|
[:input.btn-primary
|
||||||
{:name "login"
|
{:type "submit"
|
||||||
:tab-index "6"
|
:tab-index "5"
|
||||||
:class (when-not valid? "btn-disabled")
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
:disabled (not valid?)
|
:disabled (not (:valid form))
|
||||||
:value (tr "register.get-started")
|
:value (tr "register.get-started")}]
|
||||||
:type "submit"}]
|
|
||||||
[:div.login-links
|
[:div.login-links
|
||||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "register.already-have-account")]]]])))
|
[:a {:on-click #(st/emit! (rt/nav :auth/login))}
|
||||||
|
(tr "register.already-have-account")]]]]))
|
||||||
|
|
||||||
;; --- Register Page
|
;; --- Register Page
|
||||||
|
|
||||||
(mx/defc register-page
|
(mf/defc register-page
|
||||||
{:mixins [mx/static]}
|
[props]
|
||||||
[own]
|
|
||||||
[:div.login
|
[:div.login
|
||||||
[:div.login-body
|
[:div.login-body
|
||||||
(messages-widget)
|
(messages-widget)
|
||||||
[:a i/logo]
|
[:a i/logo]
|
||||||
(register-form)]])
|
[:& register-form]]])
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
[{:keys [route] :as props}]
|
[{:keys [route] :as props}]
|
||||||
(let [[section type id] (parse-route route)]
|
(let [[section type id] (parse-route route)]
|
||||||
[:main.dashboard-main
|
[:main.dashboard-main
|
||||||
(messages-widget)
|
[:& messages-widget]
|
||||||
[:& header {:section section}]
|
[:& header {:section section}]
|
||||||
(case section
|
(case section
|
||||||
:dashboard/icons
|
:dashboard/icons
|
||||||
|
|
|
@ -9,20 +9,18 @@
|
||||||
(:require
|
(:require
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[rumext.core :as mx]
|
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.constants :as c]
|
[uxbox.main.constants :as c]
|
||||||
[uxbox.main.data.lightbox :as udl]
|
|
||||||
[uxbox.main.data.projects :as udp]
|
[uxbox.main.data.projects :as udp]
|
||||||
[uxbox.main.exports :as exports]
|
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.dashboard.projects-createform]
|
[uxbox.main.ui.modal :as modal]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||||
|
[uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]]
|
||||||
[uxbox.util.data :refer [read-string]]
|
[uxbox.util.data :refer [read-string]]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.i18n :as t :refer (tr)]
|
[uxbox.util.i18n :as t :refer [tr]]
|
||||||
[uxbox.util.router :as r]
|
[uxbox.util.router :as r]
|
||||||
[uxbox.util.time :as dt]))
|
[uxbox.util.time :as dt]))
|
||||||
|
|
||||||
|
@ -34,15 +32,14 @@
|
||||||
|
|
||||||
;; --- Refs
|
;; --- Refs
|
||||||
|
|
||||||
(def opts-ref
|
(def opts-iref
|
||||||
(-> (l/in [:dashboard :projects])
|
(-> (l/in [:dashboard :projects])
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
(def projects-ref
|
(def projects-iref
|
||||||
(-> (l/key :projects)
|
(-> (l/key :projects)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
(defn sort-projects-by
|
(defn sort-projects-by
|
||||||
|
@ -117,26 +114,9 @@
|
||||||
|
|
||||||
(mf/defc grid-item-thumbnail
|
(mf/defc grid-item-thumbnail
|
||||||
[{:keys [project] :as props}]
|
[{:keys [project] :as props}]
|
||||||
(let [url (mf/use-state nil)]
|
|
||||||
#_(mf/use-effect
|
|
||||||
{:deps #js [(:page-id project)]
|
|
||||||
:init (fn []
|
|
||||||
(when-let [page-id (:page-id project)]
|
|
||||||
(let [svg (exports/render-page page-id)
|
|
||||||
uri (some-> svg
|
|
||||||
(blob/create "image/svg+xml")
|
|
||||||
(blob/create-uri))]
|
|
||||||
(reset! url uri)
|
|
||||||
uri)))
|
|
||||||
:end #(when % (blob/revoke-uri %))})
|
|
||||||
(if @url
|
|
||||||
[:div.grid-item-th
|
|
||||||
{:style {:background-image (str "url('" @url "')")}}]
|
|
||||||
[:div.grid-item-th
|
[:div.grid-item-th
|
||||||
[:img.img-th {:src "/images/project-placeholder.svg"
|
[:img.img-th {:src "/images/project-placeholder.svg"
|
||||||
:alt (tr "ds.project-thumbnail.alt")}]])))
|
:alt (tr "ds.project-thumbnail.alt")}]])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Grid Item
|
;; --- Grid Item
|
||||||
|
|
||||||
|
@ -148,7 +128,7 @@
|
||||||
delete #(st/emit! (udp/delete-project project))
|
delete #(st/emit! (udp/delete-project project))
|
||||||
on-delete #(do
|
on-delete #(do
|
||||||
(dom/stop-propagation %)
|
(dom/stop-propagation %)
|
||||||
(udl/open! :confirm {:on-accept delete}))
|
(modal/show! confirm-dialog {:on-accept delete}))
|
||||||
on-blur #(let [target (dom/event->target %)
|
on-blur #(let [target (dom/event->target %)
|
||||||
name (dom/get-value target)
|
name (dom/get-value target)
|
||||||
id (:id project)]
|
id (:id project)]
|
||||||
|
@ -193,13 +173,14 @@
|
||||||
[{:keys [opts] :as props}]
|
[{:keys [opts] :as props}]
|
||||||
(let [order (:order opts :created)
|
(let [order (:order opts :created)
|
||||||
filter (:filter opts "")
|
filter (:filter opts "")
|
||||||
projects (mf/deref projects-ref)
|
projects (mf/deref projects-iref)
|
||||||
projects (->> (vals projects)
|
projects (->> (vals projects)
|
||||||
(filter-projects-by filter)
|
(filter-projects-by filter)
|
||||||
(sort-projects-by order))
|
(sort-projects-by order))
|
||||||
on-click #(do
|
on-click #(do
|
||||||
(dom/prevent-default %)
|
(dom/prevent-default %)
|
||||||
(udl/open! :create-project))]
|
(modal/show! create-project-dialog {})
|
||||||
|
#_(udl/open! :create-project))]
|
||||||
[:section.dashboard-grid
|
[:section.dashboard-grid
|
||||||
[:h2 (tr "ds.project-title")]
|
[:h2 (tr "ds.project-title")]
|
||||||
[:div.dashboard-grid-content
|
[:div.dashboard-grid-content
|
||||||
|
@ -214,7 +195,7 @@
|
||||||
(mf/defc projects-page
|
(mf/defc projects-page
|
||||||
[_]
|
[_]
|
||||||
(mf/use-effect #(st/emit! (udp/initialize)))
|
(mf/use-effect #(st/emit! (udp/initialize)))
|
||||||
(let [opts (mf/deref opts-ref)]
|
(let [opts (mf/deref opts-iref)]
|
||||||
[:section.dashboard-content
|
[:section.dashboard-content
|
||||||
[:& menu {:opts opts}]
|
[:& menu {:opts opts}]
|
||||||
[:& grid {:opts opts}]]))
|
[:& grid {:opts opts}]]))
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
|
||||||
|
|
||||||
(ns uxbox.main.ui.dashboard.projects-createform
|
|
||||||
(:require
|
|
||||||
[cljs.spec.alpha :as s]
|
|
||||||
[cuerdas.core :as str]
|
|
||||||
[lentes.core :as l]
|
|
||||||
[rumext.core :as mx]
|
|
||||||
[rumext.alpha :as mf]
|
|
||||||
[uxbox.builtins.icons :as i]
|
|
||||||
[uxbox.main.constants :as c]
|
|
||||||
[uxbox.main.data.lightbox :as udl]
|
|
||||||
[uxbox.main.data.projects :as udp]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.ui.lightbox :as lbx]
|
|
||||||
[uxbox.util.data :refer [read-string parse-int]]
|
|
||||||
[uxbox.util.dom :as dom]
|
|
||||||
[uxbox.util.forms :as fm]
|
|
||||||
[uxbox.util.i18n :as t :refer [tr]]
|
|
||||||
[uxbox.util.router :as r]
|
|
||||||
[uxbox.util.time :as dt]))
|
|
||||||
|
|
||||||
(def form-data (fm/focus-data :create-project st/state))
|
|
||||||
(def form-errors (fm/focus-errors :create-project st/state))
|
|
||||||
(def assoc-value (partial fm/assoc-value :create-project))
|
|
||||||
(def clear-form (partial fm/clear-form :create-project))
|
|
||||||
|
|
||||||
(s/def ::name ::fm/non-empty-string)
|
|
||||||
(s/def ::layout ::fm/non-empty-string)
|
|
||||||
(s/def ::width number?)
|
|
||||||
(s/def ::height number?)
|
|
||||||
|
|
||||||
(s/def ::project-form
|
|
||||||
(s/keys :req-un [::name
|
|
||||||
::width
|
|
||||||
::height
|
|
||||||
::layout]))
|
|
||||||
|
|
||||||
;; --- Create Project Form
|
|
||||||
|
|
||||||
(mx/defc layout-input
|
|
||||||
[{:keys [::layout-id] :as data}]
|
|
||||||
(let [layout (get c/page-layouts layout-id)]
|
|
||||||
[:div
|
|
||||||
[:input {:type "radio"
|
|
||||||
:key layout-id
|
|
||||||
:id layout-id
|
|
||||||
:name "project-layout"
|
|
||||||
:value (:name layout)
|
|
||||||
:checked (= layout-id (:layout data))
|
|
||||||
:on-change #(st/emit! (assoc-value :layout layout-id)
|
|
||||||
(assoc-value :width (:width layout))
|
|
||||||
(assoc-value :height (:height layout)))}]
|
|
||||||
[:label {:value (:name layout)
|
|
||||||
:for layout-id}
|
|
||||||
(:name layout)]]))
|
|
||||||
|
|
||||||
(mx/defc layout-selector
|
|
||||||
[props]
|
|
||||||
[:div.input-radio.radio-primary
|
|
||||||
(layout-input (assoc props ::layout-id "mobile"))
|
|
||||||
(layout-input (assoc props ::layout-id "tablet"))
|
|
||||||
(layout-input (assoc props ::layout-id "notebook"))
|
|
||||||
(layout-input (assoc props ::layout-id "desktop"))])
|
|
||||||
|
|
||||||
(mf/def create-project-form
|
|
||||||
:mixins #{mf/reactive}
|
|
||||||
:render
|
|
||||||
(fn [own props]
|
|
||||||
(let [data (merge c/project-defaults (mf/react form-data))
|
|
||||||
valid? (fm/valid? ::project-form data)]
|
|
||||||
(letfn [(on-submit [event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(when valid?
|
|
||||||
(st/emit! (udp/create-project data)
|
|
||||||
(udl/close-lightbox))))
|
|
||||||
|
|
||||||
(update-size [field e]
|
|
||||||
(let [value (dom/event->value e)
|
|
||||||
value (parse-int value)]
|
|
||||||
(st/emit! (assoc-value field value))))
|
|
||||||
|
|
||||||
(update-name [e]
|
|
||||||
(let [value (dom/event->value e)]
|
|
||||||
(st/emit! (assoc-value :name value))))
|
|
||||||
(swap-size []
|
|
||||||
(st/emit! (assoc-value :width (:height data))
|
|
||||||
(assoc-value :height (:width data))))]
|
|
||||||
[:form {:on-submit on-submit}
|
|
||||||
[:input#project-name.input-text
|
|
||||||
{:placeholder (tr "ds.project.placeholder")
|
|
||||||
:type "text"
|
|
||||||
:value (:name data)
|
|
||||||
:auto-focus true
|
|
||||||
:on-change update-name}]
|
|
||||||
[:div.project-size
|
|
||||||
[:div.input-element.pixels
|
|
||||||
[:span (tr "ds.width")]
|
|
||||||
[:input#project-witdh.input-text
|
|
||||||
{:placeholder (tr "ds.width")
|
|
||||||
:type "number"
|
|
||||||
:min 0 ;;TODO check this value
|
|
||||||
:max 666666 ;;TODO check this value
|
|
||||||
:value (str (:width data))
|
|
||||||
:on-change (partial update-size :width)}]]
|
|
||||||
[:a.toggle-layout {:on-click swap-size} i/toggle]
|
|
||||||
[:div.input-element.pixels
|
|
||||||
[:span (tr "ds.height")]
|
|
||||||
[:input#project-height.input-text
|
|
||||||
{:placeholder (tr "ds.height")
|
|
||||||
:type "number"
|
|
||||||
:min 0 ;;TODO check this value
|
|
||||||
:max 666666 ;;TODO check this value
|
|
||||||
:value (str (:height data))
|
|
||||||
:on-change (partial update-size :height)}]]]
|
|
||||||
|
|
||||||
;; Layout selector
|
|
||||||
(layout-selector data)
|
|
||||||
|
|
||||||
;; Submit
|
|
||||||
[:input#project-btn.btn-primary
|
|
||||||
{:value (tr "ds.go")
|
|
||||||
:class (when-not valid? "btn-disabled")
|
|
||||||
:disabled (not valid?)
|
|
||||||
:type "submit"}]]))))
|
|
||||||
|
|
||||||
;; --- Create Project Lightbox
|
|
||||||
|
|
||||||
(mf/def create-project-lightbox
|
|
||||||
:will-unmount
|
|
||||||
(fn [own]
|
|
||||||
(st/emit! (fm/clear-form :create-project))
|
|
||||||
own)
|
|
||||||
|
|
||||||
:render
|
|
||||||
(fn [own]
|
|
||||||
[:div.lightbox-body
|
|
||||||
[:h3 (tr "ds.project.new")]
|
|
||||||
(create-project-form)
|
|
||||||
[:a.close {:on-click #(st/emit! (udl/close-lightbox))}
|
|
||||||
i/close]]))
|
|
||||||
|
|
||||||
(defmethod lbx/render-lightbox :create-project
|
|
||||||
[_]
|
|
||||||
(create-project-lightbox))
|
|
||||||
|
|
102
frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs
Normal file
102
frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
|
||||||
|
(ns uxbox.main.ui.dashboard.projects-forms
|
||||||
|
(:require
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
|
[uxbox.builtins.icons :as i]
|
||||||
|
[uxbox.main.data.projects :as udp]
|
||||||
|
[uxbox.main.store :as st]
|
||||||
|
[uxbox.main.ui.modal :as modal]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.forms :as fm]
|
||||||
|
[uxbox.util.i18n :as t :refer [tr]]))
|
||||||
|
|
||||||
|
(s/def ::name ::fm/not-empty-string)
|
||||||
|
(s/def ::width ::fm/number-str)
|
||||||
|
(s/def ::height ::fm/number-str)
|
||||||
|
|
||||||
|
(s/def ::project-form
|
||||||
|
(s/keys :req-un [::name ::width ::height]))
|
||||||
|
|
||||||
|
(def defaults
|
||||||
|
{:name ""
|
||||||
|
:width "1366"
|
||||||
|
:height "768"})
|
||||||
|
|
||||||
|
;; --- Create Project Form
|
||||||
|
|
||||||
|
(defn- on-submit
|
||||||
|
[event form]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(let [data (:clean-data form)]
|
||||||
|
(st/emit! (udp/create-project data))
|
||||||
|
(modal/hide!)))
|
||||||
|
|
||||||
|
(defn- swap-size
|
||||||
|
[event {:keys [data] :as form}]
|
||||||
|
(swap! data assoc
|
||||||
|
:width (:height data)
|
||||||
|
:height (:width data)))
|
||||||
|
|
||||||
|
(mf/defc create-project-form
|
||||||
|
[props]
|
||||||
|
(let [{:keys [data] :as form} (fm/use-form ::project-form defaults)]
|
||||||
|
[:form {:on-submit #(on-submit % form)}
|
||||||
|
[:input.input-text
|
||||||
|
{:placeholder "New project name"
|
||||||
|
:type "text"
|
||||||
|
:name "name"
|
||||||
|
:value (:name data)
|
||||||
|
:class (fm/error-class form :name)
|
||||||
|
:on-blur (fm/on-input-blur form :name)
|
||||||
|
:on-change (fm/on-input-change form :name)
|
||||||
|
:auto-focus true}]
|
||||||
|
[:div.project-size
|
||||||
|
[:div.input-element.pixels
|
||||||
|
[:span "Width"]
|
||||||
|
[:input#project-witdh.input-text
|
||||||
|
{:placeholder "Width"
|
||||||
|
:name "width"
|
||||||
|
:type "number"
|
||||||
|
:min 0
|
||||||
|
:max 5000
|
||||||
|
:class (fm/error-class form :width)
|
||||||
|
:on-blur (fm/on-input-blur form :width)
|
||||||
|
:on-change (fm/on-input-change form :width)
|
||||||
|
:value (:width data)}]]
|
||||||
|
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
|
||||||
|
[:div.input-element.pixels
|
||||||
|
[:span "Height"]
|
||||||
|
[:input#project-height.input-text
|
||||||
|
{:placeholder "Height"
|
||||||
|
:type "number"
|
||||||
|
:name "height"
|
||||||
|
:min 0
|
||||||
|
:max 5000
|
||||||
|
:class (fm/error-class form :height)
|
||||||
|
:on-blur (fm/on-input-blur form :height)
|
||||||
|
:on-change (fm/on-input-change form :height)
|
||||||
|
:value (:height data)}]]]
|
||||||
|
|
||||||
|
;; Submit
|
||||||
|
[:input#project-btn.btn-primary
|
||||||
|
{:value "Go go go!"
|
||||||
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
|
:disabled (not (:valid form))
|
||||||
|
:type "submit"}]]))
|
||||||
|
|
||||||
|
;; --- Create Project Lightbox
|
||||||
|
|
||||||
|
(mf/defc create-project-dialog
|
||||||
|
[props]
|
||||||
|
[:div.lightbox-body
|
||||||
|
[:h3 "New project"]
|
||||||
|
[:& create-project-form]
|
||||||
|
[:a.close {:on-click modal/hide!} i/close]])
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.ui.loader
|
(ns uxbox.main.ui.loader
|
||||||
(:require [uxbox.main.store :as st]
|
(:require
|
||||||
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[rumext.core :as mx :include-macros true]))
|
[uxbox.main.store :as st]))
|
||||||
|
|
||||||
;; --- Component
|
;; --- Component
|
||||||
|
|
||||||
(mx/defc loader
|
(mf/defc loader
|
||||||
{:mixins [mx/reactive mx/static]}
|
|
||||||
[]
|
[]
|
||||||
(when (mx/react st/loader)
|
(when (mf/deref st/loader)
|
||||||
[:div.loader-content i/loader]))
|
[:div.loader-content i/loader]))
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
(ns uxbox.main.ui.messages
|
(ns uxbox.main.ui.messages
|
||||||
(:require [lentes.core :as l]
|
(:require
|
||||||
|
[lentes.core :as l]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.util.messages :as uum]
|
[uxbox.util.messages :as um]))
|
||||||
[rumext.core :as mx :include-macros true]))
|
|
||||||
|
|
||||||
(def ^:private message-ref
|
(def ^:private message-iref
|
||||||
(-> (l/key :message)
|
(-> (l/key :message)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
(mx/defc messages-widget
|
(mf/defc messages-widget
|
||||||
{:mixins [mx/static mx/reactive]}
|
|
||||||
[]
|
[]
|
||||||
(let [message (mx/react message-ref)
|
(let [message (mf/deref message-iref)
|
||||||
on-close #(st/emit! (uum/hide))]
|
on-close #(st/emit! (um/hide))]
|
||||||
(uum/messages-widget (assoc message :on-close on-close))))
|
[:& um/messages-widget {:message message
|
||||||
|
:on-close on-close}]))
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
[{:keys [route] :as props}]
|
[{:keys [route] :as props}]
|
||||||
(let [section (get-in route [:data :name])]
|
(let [section (get-in route [:data :name])]
|
||||||
[:main.dashboard-main
|
[:main.dashboard-main
|
||||||
(messages-widget)
|
[:& messages-widget]
|
||||||
[:& header {:section section}]
|
[:& header {:section section}]
|
||||||
(case section
|
(case section
|
||||||
:settings/profile (mf/element profile/profile-page)
|
:settings/profile (mf/element profile/profile-page)
|
||||||
|
|
|
@ -2,99 +2,93 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
;; Copyright (c) 2016-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
|
||||||
(ns uxbox.main.ui.settings.password
|
(ns uxbox.main.ui.settings.password
|
||||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
(:require
|
||||||
[lentes.core :as l]
|
[rumext.alpha :as mf]
|
||||||
[cuerdas.core :as str]
|
[cljs.spec.alpha :as s]
|
||||||
[potok.core :as ptk]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.data.users :as udu]
|
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
[uxbox.main.data.users :as udu]
|
||||||
[uxbox.main.ui.settings.header :refer [header]]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.util.i18n :refer [tr]]
|
|
||||||
[uxbox.util.forms :as fm]
|
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.messages :as um]
|
[uxbox.util.forms :as fm]
|
||||||
[rumext.core :as mx :include-macros true]))
|
[uxbox.util.i18n :refer [tr]]
|
||||||
|
[uxbox.util.messages :as um]))
|
||||||
|
|
||||||
(def form-data (fm/focus-data :profile-password st/state))
|
(defn- on-error
|
||||||
(def form-errors (fm/focus-errors :profile-password st/state))
|
[form error]
|
||||||
|
(case (:code error)
|
||||||
|
:uxbox.services.users/old-password-not-match
|
||||||
|
(swap! form assoc-in [:errors :password-old]
|
||||||
|
{:type ::api :message "settings.password.wrong-old-password"})
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :profile-password))
|
:else (throw (ex-info "unexpected" {:error error}))))
|
||||||
(def assoc-error (partial fm/assoc-error :profile-password))
|
|
||||||
(def clear-form (partial fm/clear-form :profile-password))
|
|
||||||
|
|
||||||
;; TODO: add better password validation
|
(defn- on-submit
|
||||||
|
[event form]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(let [data (:clean-data form)
|
||||||
|
opts {:on-success #(st/emit! (um/info (tr "settings.password.password-saved")))
|
||||||
|
:on-error #(on-error form %)}]
|
||||||
|
(st/emit! (udu/update-password data opts))))
|
||||||
|
|
||||||
(s/def ::password-1 ::fm/non-empty-string)
|
(s/def ::password-1 ::fm/not-empty-string)
|
||||||
(s/def ::password-2 ::fm/non-empty-string)
|
(s/def ::password-2 ::fm/not-empty-string)
|
||||||
(s/def ::password-old ::fm/non-empty-string)
|
(s/def ::password-old ::fm/not-empty-string)
|
||||||
|
|
||||||
(s/def ::password-form
|
(s/def ::password-form
|
||||||
(s/keys :req-un [::password-1
|
(s/keys :req-un [::password-1
|
||||||
::password-2
|
::password-2
|
||||||
::password-old]))
|
::password-old]))
|
||||||
|
|
||||||
(mx/defc password-form
|
(mf/defc password-form
|
||||||
{:mixins [mx/reactive mx/static]}
|
[props]
|
||||||
[]
|
(let [{:keys [data] :as form} (fm/use-form ::password-form {})]
|
||||||
(let [data (mx/react form-data)
|
[:form.password-form {:on-submit #(on-submit % form)}
|
||||||
errors (mx/react form-errors)
|
|
||||||
valid? (fm/valid? ::password-form data)]
|
|
||||||
(letfn [(on-change [field event]
|
|
||||||
(let [value (dom/event->value event)]
|
|
||||||
(st/emit! (assoc-value field value))))
|
|
||||||
(on-success []
|
|
||||||
(st/emit! (um/info (tr "settings.password.password-saved"))))
|
|
||||||
(on-error [{:keys [code] :as payload}]
|
|
||||||
(case code
|
|
||||||
:uxbox.services.users/old-password-not-match
|
|
||||||
(st/emit! (assoc-error :password-old (tr "settings.password.wrong-old-password")))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(throw (ex-info "unexpected" {:error payload}))))
|
|
||||||
(on-submit [event]
|
|
||||||
(st/emit! (udu/update-password data
|
|
||||||
:on-success on-success
|
|
||||||
:on-error on-error)))]
|
|
||||||
[:form.password-form
|
|
||||||
[:span.user-settings-label (tr "settings.password.change-password")]
|
[:span.user-settings-label (tr "settings.password.change-password")]
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "password"
|
{:type "password"
|
||||||
:class (fm/error-class errors :password-old)
|
:name "password-old"
|
||||||
:value (:password-old data "")
|
:value (:password-old data "")
|
||||||
:on-change (partial on-change :password-old)
|
:class (fm/error-class form :password-old)
|
||||||
|
:on-blur (fm/on-input-blur form :password-old)
|
||||||
|
:on-change (fm/on-input-change form :password-old)
|
||||||
:placeholder (tr "settings.password.old-password")}]
|
:placeholder (tr "settings.password.old-password")}]
|
||||||
(fm/input-error errors :password-old)
|
|
||||||
|
[:& fm/field-error {:form form :field :password-old :type ::api}]
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "password"
|
{:type "password"
|
||||||
:class (fm/error-class errors :password-1)
|
:name "password-1"
|
||||||
:value (:password-1 data "")
|
:value (:password-1 data "")
|
||||||
:on-change (partial on-change :password-1)
|
:class (fm/error-class form :password-1)
|
||||||
|
:on-blur (fm/on-input-blur form :password-1)
|
||||||
|
:on-change (fm/on-input-change form :password-1)
|
||||||
:placeholder (tr "settings.password.new-password")}]
|
:placeholder (tr "settings.password.new-password")}]
|
||||||
(fm/input-error errors :password-1)
|
;; [:& fm/field-error {:form form :field :password-1}]
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "password"
|
{:type "password"
|
||||||
:class (fm/error-class errors :password-2)
|
:name "password-2"
|
||||||
:value (:password-2 data "")
|
:value (:password-2 data "")
|
||||||
:on-change (partial on-change :password-2)
|
:class (fm/error-class form :password-2)
|
||||||
|
:on-blur (fm/on-input-blur form :password-2)
|
||||||
|
:on-change (fm/on-input-change form :password-2)
|
||||||
:placeholder (tr "settings.password.confirm-password")}]
|
:placeholder (tr "settings.password.confirm-password")}]
|
||||||
(fm/input-error errors :password-2)
|
;; [:& fm/field-error {:form form :field :password-2}]
|
||||||
|
|
||||||
[:input.btn-primary
|
[:input.btn-primary
|
||||||
{:type "button"
|
{:type "submit"
|
||||||
:class (when-not valid? "btn-disabled")
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
:disabled (not valid?)
|
:disabled (not (:valid form))
|
||||||
:on-click on-submit
|
:value (tr "settings.update-settings")}]]))
|
||||||
:value (tr "settings.update-settings")}]])))
|
|
||||||
|
|
||||||
;; --- Password Page
|
;; --- Password Page
|
||||||
|
|
||||||
(mx/defc password-page
|
(mf/defc password-page
|
||||||
[]
|
[props]
|
||||||
[:section.dashboard-content.user-settings
|
[:section.dashboard-content.user-settings
|
||||||
[:section.user-settings-content
|
[:section.user-settings-content
|
||||||
(password-form)]])
|
[:& password-form]]])
|
||||||
|
|
|
@ -14,35 +14,28 @@
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.users :as udu]
|
[uxbox.main.data.users :as udu]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.util.dom :as dom]
|
|
||||||
[uxbox.util.data :refer [read-string]]
|
[uxbox.util.data :refer [read-string]]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.forms :as fm]
|
[uxbox.util.forms :as fm]
|
||||||
[uxbox.util.i18n :as i18n :refer [tr]]
|
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||||
[uxbox.util.interop :refer [iterable->seq]]))
|
[uxbox.util.interop :refer [iterable->seq]]
|
||||||
|
[uxbox.util.messages :as um]))
|
||||||
|
|
||||||
|
|
||||||
(def form-data (fm/focus-data :profile st/state))
|
(defn- profile->form
|
||||||
(def form-errors (fm/focus-errors :profile st/state))
|
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :profile))
|
|
||||||
(def assoc-error (partial fm/assoc-error :profile))
|
|
||||||
(def clear-form (partial fm/clear-form :profile))
|
|
||||||
|
|
||||||
(defn profile->form
|
|
||||||
[profile]
|
[profile]
|
||||||
(let [language (get-in profile [:metadata :language])]
|
(let [language (get-in profile [:metadata :language])]
|
||||||
(-> (select-keys profile [:fullname :username :email])
|
(-> (select-keys profile [:fullname :username :email])
|
||||||
(cond-> language (assoc :language language)))))
|
(cond-> language (assoc :language language)))))
|
||||||
|
|
||||||
(def profile-ref
|
(def ^:private profile-ref
|
||||||
(-> (comp (l/key :profile)
|
(-> (l/key :profile)
|
||||||
(l/lens profile->form))
|
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
(s/def ::fullname ::fm/non-empty-string)
|
(s/def ::fullname ::fm/not-empty-string)
|
||||||
(s/def ::username ::fm/non-empty-string)
|
(s/def ::username ::fm/not-empty-string)
|
||||||
|
(s/def ::language ::fm/not-empty-string)
|
||||||
(s/def ::email ::fm/email)
|
(s/def ::email ::fm/email)
|
||||||
(s/def ::language #{"en" "fr"})
|
|
||||||
|
|
||||||
(s/def ::profile-form
|
(s/def ::profile-form
|
||||||
(s/keys :req-un [::fullname
|
(s/keys :req-un [::fullname
|
||||||
|
@ -51,69 +44,94 @@
|
||||||
::email]))
|
::email]))
|
||||||
|
|
||||||
(defn- on-error
|
(defn- on-error
|
||||||
[{:keys [code] :as payload}]
|
[error form]
|
||||||
(case code
|
(case (:code error)
|
||||||
:uxbox.services.users/registration-disabled
|
|
||||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
|
||||||
:uxbox.services.users/email-already-exists
|
:uxbox.services.users/email-already-exists
|
||||||
(st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists")))
|
(swap! form assoc-in [:errors :email]
|
||||||
:uxbox.services.users/username-already-exists
|
{:type ::api
|
||||||
(st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists")))))
|
:message "errors.api.form.email-already-exists"})
|
||||||
|
|
||||||
(defn- on-field-change
|
:uxbox.services.users/username-already-exists
|
||||||
[event field]
|
(swap! form assoc-in [:errors :username]
|
||||||
(let [value (dom/event->value event)]
|
{:type ::api
|
||||||
(st/emit! (assoc-value field value))))
|
:message "errors.api.form.username-already-exists"})))
|
||||||
|
|
||||||
|
(defn- initial-data
|
||||||
|
[]
|
||||||
|
(merge {:language @i18n/locale}
|
||||||
|
(profile->form (deref profile-ref))))
|
||||||
|
|
||||||
|
(defn- on-submit
|
||||||
|
[event form]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(let [data (:clean-data form)
|
||||||
|
on-success #(st/emit! (um/info (tr "settings.profile.profile-saved")))
|
||||||
|
on-error #(on-error % form)]
|
||||||
|
(st/emit! (udu/form->update-profile data on-success on-error))))
|
||||||
|
|
||||||
;; --- Profile Form
|
;; --- Profile Form
|
||||||
(mf/def profile-form
|
|
||||||
:mixins [mf/memo mf/reactive mf/sync-render (fm/clear-mixin st/store :profile)]
|
(mf/defc profile-form
|
||||||
:render
|
[props]
|
||||||
(fn [own props]
|
(let [{:keys [data] :as form} (fm/use-form ::profile-form initial-data)]
|
||||||
(let [data (merge {:language @i18n/locale}
|
[:form.profile-form {:on-submit #(on-submit % form)}
|
||||||
(mf/react profile-ref)
|
|
||||||
(mf/react form-data))
|
|
||||||
errors (mf/react form-errors)
|
|
||||||
valid? (fm/valid? ::profile-form data)
|
|
||||||
on-success #(st/emit! (clear-form))
|
|
||||||
on-submit #(st/emit! (udu/update-profile data on-success on-error))]
|
|
||||||
[:form.profile-form
|
|
||||||
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "text"
|
{:type "text"
|
||||||
:on-change #(on-field-change % :fullname)
|
:name "fullname"
|
||||||
|
:class (fm/error-class form :fullname)
|
||||||
|
:on-blur (fm/on-input-blur form :fullname)
|
||||||
|
:on-change (fm/on-input-change form :fullname)
|
||||||
:value (:fullname data "")
|
:value (:fullname data "")
|
||||||
:placeholder (tr "settings.profile.your-name")}]
|
:placeholder (tr "settings.profile.your-name")}]
|
||||||
|
|
||||||
|
[:& fm/field-error {:form form
|
||||||
|
:type #{::api}
|
||||||
|
:field :fullname}]
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "text"
|
{:type "text"
|
||||||
:on-change #(on-field-change % :username)
|
:name "username"
|
||||||
|
:class (fm/error-class form :username)
|
||||||
|
:on-blur (fm/on-input-blur form :username)
|
||||||
|
:on-change (fm/on-input-change form :username)
|
||||||
:value (:username data "")
|
:value (:username data "")
|
||||||
:placeholder (tr "settings.profile.your-username")}]
|
:placeholder (tr "settings.profile.your-username")}]
|
||||||
(fm/input-error errors :username)
|
|
||||||
|
[:& fm/field-error {:form form
|
||||||
|
:type #{::api}
|
||||||
|
:field :username}]
|
||||||
|
|
||||||
[:input.input-text
|
[:input.input-text
|
||||||
{:type "email"
|
{:type "email"
|
||||||
:on-change #(on-field-change % :email)
|
:name "email"
|
||||||
|
:class (fm/error-class form :email)
|
||||||
|
:on-blur (fm/on-input-blur form :email)
|
||||||
|
:on-change (fm/on-input-change form :email)
|
||||||
:value (:email data "")
|
:value (:email data "")
|
||||||
:placeholder (tr "settings.profile.your-email")}]
|
:placeholder (tr "settings.profile.your-email")}]
|
||||||
(fm/input-error errors :email)
|
|
||||||
|
[:& fm/field-error {:form form
|
||||||
|
:type #{::api}
|
||||||
|
:field :email}]
|
||||||
|
|
||||||
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
||||||
[:select.input-select {:value (:language data)
|
[:select.input-select {:value (:language data)
|
||||||
:on-change #(on-field-change % :language)}
|
:name "language"
|
||||||
|
:class (fm/error-class form :language)
|
||||||
|
:on-blur (fm/on-input-blur form :language)
|
||||||
|
:on-change (fm/on-input-change form :language)}
|
||||||
[:option {:value "en"} "English"]
|
[:option {:value "en"} "English"]
|
||||||
[:option {:value "fr"} "Français"]]
|
[:option {:value "fr"} "Français"]]
|
||||||
|
|
||||||
[:input.btn-primary
|
[:input.btn-primary
|
||||||
{:type "button"
|
{:type "submit"
|
||||||
:class (when-not valid? "btn-disabled")
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
:disabled (not valid?)
|
:disabled (not (:valid form))
|
||||||
:on-click on-submit
|
:value (tr "settings.update-settings")}]]))
|
||||||
:value (tr "settings.update-settings")}]])))
|
|
||||||
|
|
||||||
;; --- Profile Photo Form
|
;; --- Profile Photo Form
|
||||||
|
|
||||||
(mf/defc profile-photo-form
|
(mf/defc profile-photo-form
|
||||||
{:wrap [mf/wrap-reactive]}
|
|
||||||
[]
|
[]
|
||||||
(letfn [(on-change [event]
|
(letfn [(on-change [event]
|
||||||
(let [target (dom/get-target event)
|
(let [target (dom/get-target event)
|
||||||
|
@ -122,7 +140,7 @@
|
||||||
(first))]
|
(first))]
|
||||||
(st/emit! (udu/update-photo file))
|
(st/emit! (udu/update-photo file))
|
||||||
(dom/clean-value! target)))]
|
(dom/clean-value! target)))]
|
||||||
(let [{:keys [photo]} (mf/react profile-ref)
|
(let [{:keys [photo] :as profile} (mf/deref profile-ref)
|
||||||
photo (if (or (str/empty? photo) (nil? photo))
|
photo (if (or (str/empty? photo) (nil? photo))
|
||||||
"images/avatar.jpg"
|
"images/avatar.jpg"
|
||||||
photo)]
|
photo)]
|
||||||
|
@ -140,4 +158,4 @@
|
||||||
[:section.user-settings-content
|
[:section.user-settings-content
|
||||||
[:span.user-settings-label (tr "settings.profile.your-avatar")]
|
[:span.user-settings-label (tr "settings.profile.your-avatar")]
|
||||||
[:& profile-photo-form]
|
[:& profile-photo-form]
|
||||||
(profile-form)]])
|
[:& profile-form]]])
|
||||||
|
|
|
@ -14,12 +14,15 @@
|
||||||
[uxbox.main.ui.shapes.image :as image]
|
[uxbox.main.ui.shapes.image :as image]
|
||||||
[uxbox.main.ui.shapes.path :as path]
|
[uxbox.main.ui.shapes.path :as path]
|
||||||
[uxbox.main.ui.shapes.rect :as rect]
|
[uxbox.main.ui.shapes.rect :as rect]
|
||||||
|
[uxbox.main.ui.shapes.canvas :as canvas]
|
||||||
[uxbox.main.ui.shapes.text :as text]))
|
[uxbox.main.ui.shapes.text :as text]))
|
||||||
|
|
||||||
(defn render-shape
|
(defn render-shape
|
||||||
[shape]
|
[shape]
|
||||||
(mf/html
|
(mf/html
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
|
:canvas [:& canvas/canvas-component {:shape shape}]
|
||||||
|
:curve [:& path/path-component {:shape shape}]
|
||||||
:text [:& text/text-component {:shape shape}]
|
:text [:& text/text-component {:shape shape}]
|
||||||
:icon [:& icon/icon-component {:shape shape}]
|
:icon [:& icon/icon-component {:shape shape}]
|
||||||
:rect [:& rect/rect-component {:shape shape}]
|
:rect [:& rect/rect-component {:shape shape}]
|
||||||
|
|
42
frontend/src/uxbox/main/ui/shapes/canvas.cljs
Normal file
42
frontend/src/uxbox/main/ui/shapes/canvas.cljs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
|
||||||
|
(ns uxbox.main.ui.shapes.canvas
|
||||||
|
(:require
|
||||||
|
[lentes.core :as l]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
|
[uxbox.main.constants :as c]
|
||||||
|
[uxbox.main.data.workspace :as dw]
|
||||||
|
[uxbox.main.geom :as geom]
|
||||||
|
[uxbox.main.refs :as refs]
|
||||||
|
[uxbox.main.ui.shapes.common :as common]
|
||||||
|
[uxbox.main.store :as st]
|
||||||
|
[uxbox.main.ui.shapes.rect :refer [rect-shape]]
|
||||||
|
;; [uxbox.main.ui.workspace.streams :as uws]
|
||||||
|
[uxbox.util.data :refer [parse-int]]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.geom.point :as gpt]))
|
||||||
|
|
||||||
|
(def canvas-default-props
|
||||||
|
{:fill-color "#ffffff"})
|
||||||
|
|
||||||
|
(mf/defc canvas-component
|
||||||
|
[{:keys [shape] :as props}]
|
||||||
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
|
selected? (contains? selected (:id shape))
|
||||||
|
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||||
|
shape (merge canvas-default-props shape)]
|
||||||
|
(letfn [(on-double-click [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(st/emit! (dw/deselect-all)
|
||||||
|
(dw/select-shape (:id shape))))]
|
||||||
|
[:g.shape {:class (when selected? "selected")
|
||||||
|
:on-double-click on-double-click
|
||||||
|
:on-mouse-down on-mouse-down}
|
||||||
|
[:& rect-shape {:shape shape}]])))
|
||||||
|
|
||||||
|
|
|
@ -21,37 +21,37 @@
|
||||||
|
|
||||||
(mf/defc circle-component
|
(mf/defc circle-component
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
selected (mf/deref refs/selected-shapes)
|
|
||||||
selected? (contains? selected (:id shape))
|
selected? (contains? selected (:id shape))
|
||||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& circle-shape {:shape shape :modifiers modifiers}]]))
|
[:& circle-shape {:shape shape}]]))
|
||||||
|
|
||||||
;; --- Circle Shape
|
;; --- Circle Shape
|
||||||
|
|
||||||
(mf/defc circle-shape
|
(mf/defc circle-shape
|
||||||
[{:keys [shape modifiers] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [{:keys [id rotation cx cy]} shape
|
(let [{:keys [id rotation cx cy modifier-mtx]} shape
|
||||||
{:keys [resize displacement]} modifiers
|
|
||||||
|
|
||||||
shape (cond-> shape
|
shape (cond
|
||||||
displacement (geom/transform displacement)
|
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||||
resize (geom/transform resize))
|
:else shape)
|
||||||
|
|
||||||
center (gpt/point (:cx shape)
|
center (gpt/point (:cx shape)
|
||||||
(:cy shape))
|
(:cy shape))
|
||||||
|
|
||||||
rotation (or rotation 0)
|
rotation (or rotation 0)
|
||||||
|
|
||||||
moving? (boolean displacement)
|
moving? (boolean modifier-mtx)
|
||||||
|
|
||||||
xfmt (-> (gmt/matrix)
|
transform (when (pos? rotation)
|
||||||
(gmt/rotate* rotation center))
|
(str (-> (gmt/matrix)
|
||||||
|
(gmt/rotate* rotation center))))
|
||||||
|
|
||||||
props {:id (str "shape-" id)
|
props {:id (str "shape-" id)
|
||||||
:class (classnames :move-cursor moving?)
|
:class (classnames :move-cursor moving?)
|
||||||
:transform (str xfmt)}
|
:transform transform}
|
||||||
|
|
||||||
attrs (merge props
|
attrs (merge props
|
||||||
(attrs/extract-style-attrs shape)
|
(attrs/extract-style-attrs shape)
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.main.ui.workspace.streams :as ws]
|
[uxbox.main.ui.workspace.streams :as uws]
|
||||||
|
[uxbox.util.geom.matrix :as gmt]
|
||||||
[uxbox.util.dom :as dom]))
|
[uxbox.util.dom :as dom]))
|
||||||
|
|
||||||
;; --- Shape Movement (by mouse)
|
;; --- Shape Movement (by mouse)
|
||||||
|
@ -24,20 +25,17 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
wst (get-in state [:workspace pid])
|
flags (get-in state [:workspace pid :flags])
|
||||||
stoper (->> ws/interaction-events
|
stoper (rx/filter uws/mouse-up? stream)]
|
||||||
(rx/filter ws/mouse-up?)
|
|
||||||
(rx/take 1))
|
|
||||||
stream (->> ws/mouse-position-deltas
|
|
||||||
(rx/take-until stoper))]
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(when (refs/alignment-activated? (:flags wst))
|
(when (refs/alignment-activated? flags)
|
||||||
(rx/of (dw/initial-shape-align id)))
|
(rx/of (dw/initial-shape-align id)))
|
||||||
(rx/map #(dw/apply-temporal-displacement id %) stream)
|
(->> uws/mouse-position-deltas
|
||||||
(rx/of (dw/apply-displacement id)))))))
|
(rx/map #(dw/apply-temporal-displacement id %))
|
||||||
|
(rx/take-until stoper))
|
||||||
|
(rx/of (dw/materialize-current-modifier id)))))))
|
||||||
|
|
||||||
(defn start-move-selected
|
(def start-move-selected
|
||||||
[]
|
|
||||||
(reify
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
|
@ -45,9 +43,8 @@
|
||||||
selected (get-in state [:workspace pid :selected])]
|
selected (get-in state [:workspace pid :selected])]
|
||||||
(rx/from-coll (map start-move selected))))))
|
(rx/from-coll (map start-move selected))))))
|
||||||
|
|
||||||
|
|
||||||
(defn on-mouse-down
|
(defn on-mouse-down
|
||||||
[event {:keys [id] :as shape} selected]
|
[event {:keys [id type] :as shape} selected]
|
||||||
(let [selected? (contains? selected id)
|
(let [selected? (contains? selected id)
|
||||||
drawing? @refs/selected-drawing-tool]
|
drawing? @refs/selected-drawing-tool]
|
||||||
(when-not (:blocked shape)
|
(when-not (:blocked shape)
|
||||||
|
@ -55,11 +52,17 @@
|
||||||
drawing?
|
drawing?
|
||||||
nil
|
nil
|
||||||
|
|
||||||
|
(= type :canvas)
|
||||||
|
(when selected?
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(st/emit! start-move-selected))
|
||||||
|
|
||||||
(and (not selected?) (empty? selected))
|
(and (not selected?) (empty? selected))
|
||||||
(do
|
(do
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! (dw/select-shape id)
|
(st/emit! (dw/deselect-all)
|
||||||
(start-move-selected)))
|
(dw/select-shape id)
|
||||||
|
start-move-selected))
|
||||||
|
|
||||||
(and (not selected?) (not (empty? selected)))
|
(and (not selected?) (not (empty? selected)))
|
||||||
(do
|
(do
|
||||||
|
@ -68,8 +71,8 @@
|
||||||
(st/emit! (dw/select-shape id))
|
(st/emit! (dw/select-shape id))
|
||||||
(st/emit! (dw/deselect-all)
|
(st/emit! (dw/deselect-all)
|
||||||
(dw/select-shape id)
|
(dw/select-shape id)
|
||||||
(start-move-selected))))
|
start-move-selected)))
|
||||||
:else
|
:else
|
||||||
(do
|
(do
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! (start-move-selected)))))))
|
(st/emit! start-move-selected))))))
|
||||||
|
|
|
@ -23,14 +23,12 @@
|
||||||
|
|
||||||
(mf/defc icon-component
|
(mf/defc icon-component
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [id (:id shape)
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
modifiers (mf/deref (refs/selected-modifiers id))
|
selected? (contains? selected (:id shape))
|
||||||
selected (mf/deref refs/selected-shapes)
|
|
||||||
selected? (contains? selected id)
|
|
||||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& icon-shape {:shape shape :modifiers modifiers}]]))
|
[:& icon-shape {:shape shape}]]))
|
||||||
|
|
||||||
;; --- Icon Shape
|
;; --- Icon Shape
|
||||||
|
|
||||||
|
@ -43,22 +41,20 @@
|
||||||
(gmt/rotate* mt rotation center)))
|
(gmt/rotate* mt rotation center)))
|
||||||
|
|
||||||
(mf/defc icon-shape
|
(mf/defc icon-shape
|
||||||
[{:keys [shape modifiers] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [{:keys [id content metadata rotation x1 y1]} shape
|
(let [{:keys [id content metadata rotation modifier-mtx]} shape
|
||||||
{:keys [resize displacement]} modifiers
|
|
||||||
|
|
||||||
xfmt (cond-> (gmt/matrix)
|
shape (cond
|
||||||
displacement (gmt/multiply displacement)
|
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||||
resize (gmt/multiply resize))
|
:else shape)
|
||||||
|
|
||||||
{:keys [x1 y1 width height] :as shape} (-> (geom/transform shape xfmt)
|
{:keys [x1 y1 width height] :as shape} (geom/size shape)
|
||||||
(geom/size))
|
|
||||||
|
transform (when (pos? rotation)
|
||||||
|
(str (rotate (gmt/matrix) shape)))
|
||||||
|
|
||||||
view-box (apply str (interpose " " (:view-box metadata)))
|
view-box (apply str (interpose " " (:view-box metadata)))
|
||||||
xfmt (cond-> (gmt/matrix)
|
moving? (boolean modifier-mtx)
|
||||||
(pos? rotation) (rotate shape))
|
|
||||||
|
|
||||||
moving? (boolean displacement)
|
|
||||||
props {:id (str id)
|
props {:id (str id)
|
||||||
:x x1
|
:x x1
|
||||||
:y y1
|
:y y1
|
||||||
|
@ -70,7 +66,7 @@
|
||||||
:dangerouslySetInnerHTML #js {:__html content}}
|
:dangerouslySetInnerHTML #js {:__html content}}
|
||||||
|
|
||||||
attrs (merge props (attrs/extract-style-attrs shape))]
|
attrs (merge props (attrs/extract-style-attrs shape))]
|
||||||
[:g {:transform (str xfmt)}
|
[:g {:transform transform}
|
||||||
[:> :svg (normalize-props attrs) ]]))
|
[:> :svg (normalize-props attrs) ]]))
|
||||||
|
|
||||||
;; --- Icon SVG
|
;; --- Icon SVG
|
||||||
|
|
|
@ -30,8 +30,7 @@
|
||||||
|
|
||||||
(mf/defc image-component
|
(mf/defc image-component
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
selected (mf/deref refs/selected-shapes)
|
|
||||||
image (mf/deref (image-ref (:image shape)))
|
image (mf/deref (image-ref (:image shape)))
|
||||||
selected? (contains? selected (:id shape))
|
selected? (contains? selected (:id shape))
|
||||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||||
|
@ -42,27 +41,23 @@
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& image-shape {:shape shape
|
[:& image-shape {:shape shape
|
||||||
:image image
|
:image image}]])))
|
||||||
:modifiers modifiers}]])))
|
|
||||||
|
|
||||||
;; --- Image Shape
|
;; --- Image Shape
|
||||||
|
|
||||||
(mf/defc image-shape
|
(mf/defc image-shape
|
||||||
[{:keys [shape image modifiers] :as props}]
|
[{:keys [shape image] :as props}]
|
||||||
(let [{:keys [id x1 y1 width height]} (geom/size shape)
|
(let [{:keys [id x1 y1 width height modifier-mtx]} (geom/size shape)
|
||||||
{:keys [resize displacement]} modifiers
|
moving? (boolean modifier-mtx)
|
||||||
|
transform (when (gmt/matrix? modifier-mtx)
|
||||||
|
(str modifier-mtx))
|
||||||
|
|
||||||
xfmt (cond-> (gmt/matrix)
|
|
||||||
resize (gmt/multiply resize)
|
|
||||||
displacement (gmt/multiply displacement))
|
|
||||||
|
|
||||||
moving? (boolean displacement)
|
|
||||||
props {:x x1 :y y1
|
props {:x x1 :y y1
|
||||||
:id (str "shape-" id)
|
:id (str "shape-" id)
|
||||||
:preserve-aspect-ratio "none"
|
:preserve-aspect-ratio "none"
|
||||||
:class (classnames :move-cursor moving?)
|
:class (classnames :move-cursor moving?)
|
||||||
:xlink-href (:url image)
|
:xlink-href (:url image)
|
||||||
:transform (str xfmt)
|
:transform transform
|
||||||
:width width
|
:width width
|
||||||
:height height}
|
:height height}
|
||||||
attrs (merge props (attrs/extract-style-attrs shape))]
|
attrs (merge props (attrs/extract-style-attrs shape))]
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
(:require
|
(:require
|
||||||
[cuerdas.core :as str :include-macros true]
|
[cuerdas.core :as str :include-macros true]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.main.data.workspace :as udw]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.geom :as geom]
|
[uxbox.main.geom :as geom]
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||||
[uxbox.main.ui.shapes.common :as common]
|
[uxbox.main.ui.shapes.common :as common]
|
||||||
[uxbox.util.data :refer [classnames normalize-props]]))
|
[uxbox.util.data :refer [classnames normalize-props]]
|
||||||
|
[uxbox.util.geom.matrix :as gmt]))
|
||||||
|
|
||||||
;; --- Path Component
|
;; --- Path Component
|
||||||
|
|
||||||
|
@ -22,20 +23,17 @@
|
||||||
|
|
||||||
(mf/defc path-component
|
(mf/defc path-component
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
selected (mf/deref refs/selected-shapes)
|
|
||||||
selected? (contains? selected (:id shape))]
|
selected? (contains? selected (:id shape))]
|
||||||
(letfn [(on-mouse-down [event]
|
(letfn [(on-mouse-down [event]
|
||||||
(common/on-mouse-down event shape selected))
|
(common/on-mouse-down event shape selected))
|
||||||
(on-double-click [event]
|
(on-double-click [event]
|
||||||
(when selected?
|
(when selected?
|
||||||
(prn "on-double-click")
|
(st/emit! (dw/start-edition-mode (:id shape)))))]
|
||||||
(st/emit! (udw/start-edition-mode (:id shape)))))]
|
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-double-click on-double-click
|
:on-double-click on-double-click
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& path-shape {:shape shape
|
[:& path-shape {:shape shape
|
||||||
:modifiers modifiers
|
|
||||||
:background? true}]])))
|
:background? true}]])))
|
||||||
|
|
||||||
;; --- Path Shape
|
;; --- Path Shape
|
||||||
|
@ -62,12 +60,13 @@
|
||||||
(recur buffer (inc index)))))))
|
(recur buffer (inc index)))))))
|
||||||
|
|
||||||
(mf/defc path-shape
|
(mf/defc path-shape
|
||||||
[{:keys [shape modifiers background?] :as props}]
|
[{:keys [shape background?] :as props}]
|
||||||
(let [{:keys [resize displacement]} modifiers
|
(let [modifier-mtx (:modifier-mtx shape)
|
||||||
shape (cond-> shape
|
shape (cond
|
||||||
displacement (geom/transform displacement)
|
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||||
resize (geom/transform resize))
|
:else shape)
|
||||||
moving? (boolean displacement)
|
|
||||||
|
moving? (boolean modifier-mtx)
|
||||||
|
|
||||||
pdata (render-path shape)
|
pdata (render-path shape)
|
||||||
props {:id (str (:id shape))
|
props {:id (str (:id shape))
|
||||||
|
|
|
@ -22,16 +22,12 @@
|
||||||
|
|
||||||
(mf/defc rect-component
|
(mf/defc rect-component
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [id (:id shape)
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
modifiers (mf/deref (refs/selected-modifiers id))
|
selected? (contains? selected (:id shape))
|
||||||
selected (mf/deref refs/selected-shapes)
|
|
||||||
selected? (contains? selected id)
|
|
||||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||||
;; shape (assoc shape :modifiers modifiers)]
|
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& rect-shape {:shape shape
|
[:& rect-shape {:shape shape}]]))
|
||||||
:modifiers modifiers}]]))
|
|
||||||
|
|
||||||
;; --- Rect Shape
|
;; --- Rect Shape
|
||||||
|
|
||||||
|
@ -43,27 +39,25 @@
|
||||||
(gmt/rotate* mt rotation center)))
|
(gmt/rotate* mt rotation center)))
|
||||||
|
|
||||||
(mf/defc rect-shape
|
(mf/defc rect-shape
|
||||||
[{:keys [shape modifiers] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [{:keys [id rotation]} shape
|
(let [{:keys [id rotation modifier-mtx]} shape
|
||||||
{:keys [displacement resize]} modifiers
|
|
||||||
|
|
||||||
xfmt (cond-> (gmt/matrix)
|
shape (cond
|
||||||
displacement (gmt/multiply displacement)
|
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||||
resize (gmt/multiply resize))
|
:else shape)
|
||||||
|
|
||||||
{:keys [x1 y1 width height] :as shape} (-> (geom/transform shape xfmt)
|
{:keys [x1 y1 width height] :as shape} (geom/size shape)
|
||||||
(geom/size))
|
|
||||||
|
|
||||||
xfmt (cond-> (gmt/matrix)
|
transform (when (pos? rotation)
|
||||||
(pos? rotation) (rotate shape))
|
(str (rotate (gmt/matrix) shape)))
|
||||||
|
|
||||||
moving? (boolean displacement)
|
moving? (boolean modifier-mtx)
|
||||||
|
|
||||||
props {:x x1 :y y1
|
props {:x x1 :y y1
|
||||||
:id (str "shape-" id)
|
:id (str "shape-" id)
|
||||||
:class-name (classnames :move-cursor moving?)
|
:class-name (classnames :move-cursor moving?)
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
:transform (str xfmt)}
|
:transform transform}
|
||||||
attrs (merge (attrs/extract-style-attrs shape) props)]
|
attrs (merge (attrs/extract-style-attrs shape) props)]
|
||||||
[:> :rect (normalize-props attrs)]))
|
[:> :rect (normalize-props attrs)]))
|
||||||
|
|
|
@ -39,22 +39,17 @@
|
||||||
(declare text-shape-wrapper)
|
(declare text-shape-wrapper)
|
||||||
(declare text-shape-edit)
|
(declare text-shape-edit)
|
||||||
|
|
||||||
(mf/def text-component
|
(mf/defc text-component
|
||||||
:mixins [mf/memo mf/reactive]
|
[{:keys [shape] :as props}]
|
||||||
:render
|
|
||||||
(fn [own {:keys [shape] :as props}]
|
|
||||||
(let [{:keys [id x1 y1 content group]} shape
|
(let [{:keys [id x1 y1 content group]} shape
|
||||||
modifiers (mf/react (refs/selected-modifiers id))
|
selected (mf/deref refs/selected-shapes)
|
||||||
selected (mf/react refs/selected-shapes)
|
edition (mf/deref refs/selected-edition)
|
||||||
edition? (= (mf/react refs/selected-edition) id)
|
edition? (= edition id)
|
||||||
selected? (and (contains? selected id)
|
selected? (and (contains? selected id)
|
||||||
(= (count selected) 1))
|
(= (count selected) 1))]
|
||||||
shape (assoc shape :modifiers modifiers)]
|
|
||||||
(letfn [(on-mouse-down [event]
|
(letfn [(on-mouse-down [event]
|
||||||
(handle-mouse-down event shape selected))
|
(handle-mouse-down event shape selected))
|
||||||
(on-double-click [event]
|
(on-double-click [event]
|
||||||
;; TODO: handle grouping event propagation
|
|
||||||
;; TODO: handle actions locking properly
|
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! (udw/start-edition-mode id)))]
|
(st/emit! (udw/start-edition-mode id)))]
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
|
@ -62,7 +57,7 @@
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
(if edition?
|
(if edition?
|
||||||
[:& text-shape-edit {:shape shape}]
|
[:& text-shape-edit {:shape shape}]
|
||||||
[:& text-shape-wrapper {:shape shape}])]))))
|
[:& text-shape-wrapper {:shape shape}])])))
|
||||||
|
|
||||||
;; --- Text Styles Helpers
|
;; --- Text Styles Helpers
|
||||||
|
|
||||||
|
@ -126,7 +121,7 @@
|
||||||
style (make-style shape)
|
style (make-style shape)
|
||||||
on-input (fn [ev]
|
on-input (fn [ev]
|
||||||
(let [content (dom/event->inner-text ev)]
|
(let [content (dom/event->inner-text ev)]
|
||||||
(st/emit! (uds/update-text id content))))]
|
(st/emit! (udw/update-shape-attrs id {:content content}))))]
|
||||||
[:foreignObject {:x x1 :y y1 :width width :height height}
|
[:foreignObject {:x x1 :y y1 :width width :height height}
|
||||||
[:div {:style (normalize-props style)
|
[:div {:style (normalize-props style)
|
||||||
:ref (::container own)
|
:ref (::container own)
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
[:li {:on-click #(on-click % :settings/notifications)}
|
[:li {:on-click #(on-click % :settings/notifications)}
|
||||||
i/mail
|
i/mail
|
||||||
[:span (tr "ds.user.notifications")]]
|
[:span (tr "ds.user.notifications")]]
|
||||||
[:li {:on-click #(on-click % (da/logout))}
|
[:li {:on-click #(on-click % da/logout)}
|
||||||
i/exit
|
i/exit
|
||||||
[:span (tr "ds.user.exit")]]]))
|
[:span (tr "ds.user.exit")]]]))
|
||||||
|
|
||||||
|
@ -49,10 +49,9 @@
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
(mf/defc user
|
(mf/defc user
|
||||||
{:wrap [mf/wrap-reactive]}
|
[props]
|
||||||
[_]
|
|
||||||
(let [open (mf/use-state false)
|
(let [open (mf/use-state false)
|
||||||
profile (mf/react profile-ref)
|
profile (mf/deref profile-ref)
|
||||||
photo (if (str/empty? (:photo profile ""))
|
photo (if (str/empty? (:photo profile ""))
|
||||||
"/images/avatar.jpg"
|
"/images/avatar.jpg"
|
||||||
(:photo profile))]
|
(:photo profile))]
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
(:require
|
(:require
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[rumext.core :as mx]
|
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.main.constants :as c]
|
[uxbox.main.constants :as c]
|
||||||
[uxbox.main.data.history :as udh]
|
[uxbox.main.data.history :as udh]
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
(let [prev-zoom @refs/selected-zoom
|
(let [prev-zoom @refs/selected-zoom
|
||||||
dom (mf/ref-node canvas)
|
dom (mf/ref-node canvas)
|
||||||
scroll-position (scroll/get-current-position-absolute dom)
|
scroll-position (scroll/get-current-position-absolute dom)
|
||||||
mouse-point @uws/viewport-mouse-position]
|
mouse-point @uws/mouse-position]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(if (pos? (.-deltaY event))
|
(if (pos? (.-deltaY event))
|
||||||
|
@ -62,7 +61,7 @@
|
||||||
|
|
||||||
(defn- subscribe
|
(defn- subscribe
|
||||||
[canvas page]
|
[canvas page]
|
||||||
(scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
;; (scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
||||||
(st/emit! (udp/watch-page-changes (:id page))
|
(st/emit! (udp/watch-page-changes (:id page))
|
||||||
(udu/watch-page-changes (:id page)))
|
(udu/watch-page-changes (:id page)))
|
||||||
(let [sub (shortcuts/init)]
|
(let [sub (shortcuts/init)]
|
||||||
|
@ -83,10 +82,10 @@
|
||||||
:no-tool-bar-left (not left-sidebar?)
|
:no-tool-bar-left (not left-sidebar?)
|
||||||
:scrolling (:viewport-positionig workspace))]
|
:scrolling (:viewport-positionig workspace))]
|
||||||
|
|
||||||
(mf/use-effect {:deps #js [canvas page]
|
(mf/use-effect #(subscribe canvas page)
|
||||||
:fn #(subscribe canvas page)})
|
#js [(:id page)])
|
||||||
[:*
|
[:*
|
||||||
(messages-widget)
|
[:& messages-widget]
|
||||||
[:& header {:page page
|
[:& header {:page page
|
||||||
:flags flags
|
:flags flags
|
||||||
:key (:id page)}]
|
:key (:id page)}]
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
|
||||||
|
|
||||||
(ns uxbox.main.ui.workspace.canvas
|
|
||||||
(:require
|
|
||||||
[rumext.alpha :as mf]
|
|
||||||
[uxbox.main.constants :as c]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.ui.shapes :as uus]
|
|
||||||
[uxbox.main.ui.workspace.drawarea :refer [draw-area]]
|
|
||||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
|
||||||
[uxbox.util.geom.point :as gpt]))
|
|
||||||
|
|
||||||
;; --- Background
|
|
||||||
|
|
||||||
(mf/def background
|
|
||||||
:mixins [mf/memo]
|
|
||||||
:render
|
|
||||||
(fn [own {:keys [background] :as metadata}]
|
|
||||||
[:rect
|
|
||||||
{:x 0 :y 0
|
|
||||||
:width "100%"
|
|
||||||
:height "100%"
|
|
||||||
:fill (or background "#ffffff")}]))
|
|
||||||
|
|
||||||
;; --- Canvas
|
|
||||||
|
|
||||||
(mf/defc canvas
|
|
||||||
[{:keys [page wst] :as props}]
|
|
||||||
(let [{:keys [metadata id]} page
|
|
||||||
zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area
|
|
||||||
width (:width metadata)
|
|
||||||
height (:height metadata)]
|
|
||||||
[:svg.page-canvas {:x c/canvas-start-x
|
|
||||||
:y c/canvas-start-y
|
|
||||||
:width width
|
|
||||||
:height height}
|
|
||||||
[:& background metadata]
|
|
||||||
#_[:svg.page-layout
|
|
||||||
[:g.main
|
|
||||||
(for [id (reverse (:shapes page))]
|
|
||||||
[:& uus/shape-component {:id id :key id}])
|
|
||||||
(when (seq (:selected wst))
|
|
||||||
[:& selection-handlers {:wst wst}])
|
|
||||||
(when-let [dshape (:drawing wst)]
|
|
||||||
[:& draw-area {:shape dshape
|
|
||||||
:zoom (:zoom wst)
|
|
||||||
:modifiers (:modifiers wst)}])]]]))
|
|
|
@ -21,19 +21,24 @@
|
||||||
[uxbox.main.workers :as uwrk]
|
[uxbox.main.workers :as uwrk]
|
||||||
[uxbox.util.math :as mth]
|
[uxbox.util.math :as mth]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.data :refer [seek]]
|
||||||
|
[uxbox.util.geom.matrix :as gmt]
|
||||||
[uxbox.util.geom.path :as path]
|
[uxbox.util.geom.path :as path]
|
||||||
[uxbox.util.geom.point :as gpt]))
|
[uxbox.util.geom.point :as gpt]
|
||||||
|
[uxbox.util.uuid :as uuid]))
|
||||||
|
|
||||||
;; --- Events
|
;; --- Events
|
||||||
|
|
||||||
(declare handle-drawing)
|
(declare handle-drawing)
|
||||||
(declare handle-drawing-generic)
|
(declare handle-drawing-generic)
|
||||||
(declare handle-drawing-path)
|
(declare handle-drawing-path)
|
||||||
(declare handle-drawing-free-path)
|
(declare handle-drawing-curve)
|
||||||
(declare handle-finish-drawing)
|
(declare handle-finish-drawing)
|
||||||
|
(declare conditional-align)
|
||||||
|
|
||||||
(defn start-drawing
|
(defn start-drawing
|
||||||
[object]
|
[type]
|
||||||
|
{:pre [(keyword? type)]}
|
||||||
(let [id (gensym "drawing")]
|
(let [id (gensym "drawing")]
|
||||||
(reify
|
(reify
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
|
@ -42,35 +47,69 @@
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [lock (get-in state [:workspace :drawing-lock])]
|
(let [pid (get-in state [:workspace :current])
|
||||||
|
lock (get-in state [:workspace :drawing-lock])]
|
||||||
(if (= lock id)
|
(if (= lock id)
|
||||||
(rx/merge (->> stream
|
(rx/merge
|
||||||
(rx/filter #(= % handle-finish-drawing))
|
(->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||||
(rx/of (handle-drawing object)))
|
(rx/of (handle-drawing type)))
|
||||||
(rx/empty)))))))
|
(rx/empty)))))))
|
||||||
|
|
||||||
(defn- conditional-align [point align?]
|
(def ^:private minimal-shapes
|
||||||
(if align?
|
[{:type :rect
|
||||||
(uwrk/align-point point)
|
:name "Rect"
|
||||||
(rx/of point)))
|
:stroke-color "#000000"}
|
||||||
|
{:type :image}
|
||||||
|
{:type :circle
|
||||||
|
:name "Circle"}
|
||||||
|
{:type :path
|
||||||
|
:name "Path"
|
||||||
|
:stroke-style :solid
|
||||||
|
:stroke-color "#000000"
|
||||||
|
:stroke-width 2
|
||||||
|
:fill-color "#000000"
|
||||||
|
:fill-opacity 0
|
||||||
|
:segments []}
|
||||||
|
{:type :canvas
|
||||||
|
:name "Canvas"
|
||||||
|
:stroke-color "#000000"}
|
||||||
|
{:type :curve
|
||||||
|
:name "Path"
|
||||||
|
:stroke-style :solid
|
||||||
|
:stroke-color "#000000"
|
||||||
|
:stroke-width 2
|
||||||
|
:fill-color "#000000"
|
||||||
|
:fill-opacity 0
|
||||||
|
:segments []}
|
||||||
|
{:type :text
|
||||||
|
:name "Text"
|
||||||
|
:content "Type your text here"}])
|
||||||
|
|
||||||
|
(defn- make-minimal-shape
|
||||||
|
[type]
|
||||||
|
(let [tool (seek #(= type (:type %)) minimal-shapes)]
|
||||||
|
(assert tool "unexpected drawing tool")
|
||||||
|
(assoc tool :id (uuid/random))))
|
||||||
|
|
||||||
;; TODO: maybe this should be a simple function
|
|
||||||
(defn handle-drawing
|
(defn handle-drawing
|
||||||
[shape]
|
[type]
|
||||||
(reify
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pid (get-in state [:workspace :current])
|
||||||
|
data (make-minimal-shape type)]
|
||||||
|
(update-in state [:workspace pid :drawing] merge data)))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(rx/of
|
(case type
|
||||||
(if (= :path (:type shape))
|
:path (rx/of handle-drawing-path)
|
||||||
(if (:free shape)
|
:curve (rx/of handle-drawing-curve)
|
||||||
(handle-drawing-free-path shape)
|
(rx/of handle-drawing-generic)))))
|
||||||
(handle-drawing-path shape))
|
|
||||||
(handle-drawing-generic shape))))))
|
|
||||||
|
|
||||||
(defn- handle-drawing-generic
|
(def handle-drawing-generic
|
||||||
[shape]
|
|
||||||
(letfn [(initialize-drawing [state point]
|
(letfn [(initialize-drawing [state point]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
shape (get-in state [:workspace pid :drawing])
|
shape (get-in state [:workspace pid :drawing])
|
||||||
|
@ -80,24 +119,13 @@
|
||||||
:y2 (+ (:y point) 2)})]
|
:y2 (+ (:y point) 2)})]
|
||||||
(assoc-in state [:workspace pid :drawing] (assoc shape ::initialized? true))))
|
(assoc-in state [:workspace pid :drawing] (assoc shape ::initialized? true))))
|
||||||
|
|
||||||
;; TODO: this is a new approach for resizing, when all the
|
|
||||||
;; subsystem are migrated to the new resize approach, this
|
|
||||||
;; function should be moved into uxbox.main.geom ns.
|
|
||||||
(resize-shape [shape point lock?]
|
(resize-shape [shape point lock?]
|
||||||
(if (= (:type shape) :circle)
|
(let [shape (-> (geom/shape->rect-shape shape)
|
||||||
(let [rx (mth/abs (- (:x point) (:cx shape)))
|
(geom/size))
|
||||||
ry (mth/abs (- (:y point) (:cy shape)))]
|
result (geom/resize-shape :bottom-right shape point lock?)
|
||||||
(if lock?
|
scale (geom/calculate-scale-ratio shape result)
|
||||||
(assoc shape :rx rx :ry ry)
|
mtx (geom/generate-resize-matrix :bottom-right shape scale)]
|
||||||
(assoc shape :rx rx :ry rx)))
|
(assoc shape :modifier-mtx mtx)))
|
||||||
(let [width (- (:x point) (:x1 shape))
|
|
||||||
height (- (:y point) (:y1 shape))
|
|
||||||
proportion (:proportion shape 1)]
|
|
||||||
(assoc shape
|
|
||||||
:x2 (+ (:x1 shape) width)
|
|
||||||
:y2 (if lock?
|
|
||||||
(+ (:y1 shape) (/ width proportion))
|
|
||||||
(+ (:y1 shape) height))))))
|
|
||||||
|
|
||||||
(update-drawing [state point lock?]
|
(update-drawing [state point lock?]
|
||||||
(let [pid (get-in state [:workspace :current])]
|
(let [pid (get-in state [:workspace :current])]
|
||||||
|
@ -107,28 +135,26 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
zoom (get-in state [:workspace pid :zoom])
|
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||||
flags (get-in state [:workspace pid :flags])
|
|
||||||
align? (refs/alignment-activated? flags)
|
align? (refs/alignment-activated? flags)
|
||||||
|
|
||||||
stoper (->> (rx/filter #(or (uws/mouse-up? %) (= % :interrupt)) stream)
|
stoper? #(or (uws/mouse-up? %) (= % :interrupt))
|
||||||
(rx/take 1))
|
stoper (rx/filter stoper? stream)
|
||||||
|
|
||||||
mouse (->> uws/viewport-mouse-position
|
mouse (->> uws/mouse-position
|
||||||
(rx/mapcat #(conditional-align % align?))
|
(rx/mapcat #(conditional-align % align?))
|
||||||
(rx/with-latest vector uws/mouse-position-ctrl))]
|
(rx/map #(gpt/divide % zoom)))]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> uws/viewport-mouse-position
|
(->> mouse
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/mapcat #(conditional-align % align?))
|
|
||||||
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
||||||
(->> mouse
|
(->> mouse
|
||||||
|
(rx/with-latest vector uws/mouse-position-ctrl)
|
||||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % pt ctrl?)))
|
(rx/map (fn [[pt ctrl?]] #(update-drawing % pt ctrl?)))
|
||||||
(rx/take-until stoper))
|
(rx/take-until stoper))
|
||||||
(rx/of handle-finish-drawing)))))))
|
(rx/of handle-finish-drawing)))))))
|
||||||
|
|
||||||
(defn handle-drawing-path
|
(def handle-drawing-path
|
||||||
[shape]
|
|
||||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||||
(or (= event :interrupt)
|
(or (= event :interrupt)
|
||||||
(and (uws/mouse-event? event)
|
(and (uws/mouse-event? event)
|
||||||
|
@ -162,17 +188,17 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
zoom (get-in state [:workspace pid :zoom])
|
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||||
flags (get-in state [:workspace pid :flags])
|
|
||||||
align? (refs/alignment-activated? flags)
|
|
||||||
|
|
||||||
last-point (volatile! @uws/viewport-mouse-position)
|
align? (refs/alignment-activated? flags)
|
||||||
|
last-point (volatile! (gpt/divide @uws/mouse-position zoom))
|
||||||
|
|
||||||
stoper (->> (rx/filter stoper-event? stream)
|
stoper (->> (rx/filter stoper-event? stream)
|
||||||
(rx/take 1))
|
(rx/share))
|
||||||
|
|
||||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||||
(rx/mapcat #(conditional-align % align?)))
|
(rx/mapcat #(conditional-align % align?))
|
||||||
|
(rx/map #(gpt/divide % zoom)))
|
||||||
|
|
||||||
points (->> stream
|
points (->> stream
|
||||||
(rx/filter uws/mouse-click?)
|
(rx/filter uws/mouse-click?)
|
||||||
|
@ -186,7 +212,6 @@
|
||||||
(rx/with-latest vector counter)
|
(rx/with-latest vector counter)
|
||||||
(rx/map flatten))
|
(rx/map flatten))
|
||||||
|
|
||||||
|
|
||||||
imm-transform #(vector (- % 7) (+ % 7) %)
|
imm-transform #(vector (- % 7) (+ % 7) %)
|
||||||
immanted-zones (vec (concat
|
immanted-zones (vec (concat
|
||||||
(map imm-transform (range 0 181 15))
|
(map imm-transform (range 0 181 15))
|
||||||
|
@ -205,8 +230,8 @@
|
||||||
|
|
||||||
(->> points
|
(->> points
|
||||||
(rx/take-until stoper)
|
(rx/take-until stoper)
|
||||||
(rx/map (fn [pt]
|
(rx/map (fn [pt]#(insert-point-segment % pt))))
|
||||||
#(insert-point-segment % pt))))
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> stream'
|
(->> stream'
|
||||||
(rx/map (fn [[point ctrl? index :as xxx]]
|
(rx/map (fn [[point ctrl? index :as xxx]]
|
||||||
|
@ -221,8 +246,7 @@
|
||||||
(rx/of remove-dangling-segmnet
|
(rx/of remove-dangling-segmnet
|
||||||
handle-finish-drawing))))))))
|
handle-finish-drawing))))))))
|
||||||
|
|
||||||
(defn- handle-drawing-free-path
|
(def handle-drawing-curve
|
||||||
[shape]
|
|
||||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||||
(or (= event :interrupt)
|
(or (= event :interrupt)
|
||||||
(and (uws/mouse-event? event) (= type :up))))
|
(and (uws/mouse-event? event) (= type :up))))
|
||||||
|
@ -242,15 +266,14 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
zoom (get-in state [:workspace pid :zoom])
|
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||||
flags (get-in state [:workspace pid :flags])
|
|
||||||
align? (refs/alignment-activated? flags)
|
align? (refs/alignment-activated? flags)
|
||||||
|
stoper (rx/filter stoper-event? stream)
|
||||||
|
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||||
|
(rx/mapcat #(conditional-align % align?))
|
||||||
|
(rx/map #(gpt/divide % zoom)))]
|
||||||
|
|
||||||
stoper (->> (rx/filter stoper-event? stream)
|
|
||||||
(rx/take 1))
|
|
||||||
|
|
||||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
|
||||||
(rx/mapcat #(conditional-align % align?)))]
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of initialize-drawing)
|
(rx/of initialize-drawing)
|
||||||
(->> mouse
|
(->> mouse
|
||||||
|
@ -265,22 +288,17 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (get-in state [:workspace :current])
|
(let [pid (get-in state [:workspace :current])
|
||||||
shape (get-in state [:workspace pid :drawing])]
|
shape (get-in state [:workspace pid :drawing])]
|
||||||
(if (::initialized? shape)
|
(rx/concat
|
||||||
(let [resize-mtx (get-in state [:workspace pid :modifiers (:id shape) :resize])
|
(rx/of dw/clear-drawing)
|
||||||
shape (cond-> shape
|
(when (::initialized? shape)
|
||||||
resize-mtx (geom/transform resize-mtx))]
|
(let [modifier-mtx (:modifier-mtx shape)
|
||||||
(rx/of
|
shape (if (gmt/matrix? modifier-mtx)
|
||||||
;; Remove the stalled modifiers
|
(geom/transform shape modifier-mtx)
|
||||||
;; TODO: maybe a specific event for "clear modifiers"
|
shape)
|
||||||
#(update-in % [:workspace pid :modifiers] dissoc (:id shape))
|
shape (dissoc shape ::initialized? :modifier-mtx)]
|
||||||
|
|
||||||
;; Unselect the drawing tool
|
|
||||||
#(update-in % [:workspace pid] dissoc :drawing :drawing-tool)
|
|
||||||
|
|
||||||
;; Add & select the cred shape to the workspace
|
;; Add & select the cred shape to the workspace
|
||||||
(ds/add-shape shape)
|
(rx/of (dw/add-shape shape)
|
||||||
(dw/select-first-shape)))
|
(dw/select-first-shape)))))))))
|
||||||
(rx/of #(update-in % [:workspace pid] dissoc :drawing :drawing-tool)))))))
|
|
||||||
|
|
||||||
(def close-drawing-path
|
(def close-drawing-path
|
||||||
(reify
|
(reify
|
||||||
|
@ -295,11 +313,12 @@
|
||||||
(declare path-draw-area)
|
(declare path-draw-area)
|
||||||
|
|
||||||
(mf/defc draw-area
|
(mf/defc draw-area
|
||||||
[{:keys [zoom shape modifiers] :as props}]
|
[{:keys [zoom shape] :as props}]
|
||||||
(if (= (:type shape) :path)
|
(when (:id shape)
|
||||||
[:& path-draw-area {:shape shape}]
|
(case (:type shape)
|
||||||
[:& generic-draw-area {:shape (assoc shape :modifiers modifiers)
|
(:path :curve) [:& path-draw-area {:shape shape}]
|
||||||
:zoom zoom}]))
|
[:& generic-draw-area {:shape shape
|
||||||
|
:zoom zoom}])))
|
||||||
|
|
||||||
(mf/defc generic-draw-area
|
(mf/defc generic-draw-area
|
||||||
[{:keys [shape zoom]}]
|
[{:keys [shape zoom]}]
|
||||||
|
@ -328,7 +347,7 @@
|
||||||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
||||||
[:g
|
[:g
|
||||||
(shapes/render-shape shape)
|
(shapes/render-shape shape)
|
||||||
(when-not (:free shape)
|
(when (not= :curve (:type shape))
|
||||||
[:circle.close-bezier
|
[:circle.close-bezier
|
||||||
{:cx x
|
{:cx x
|
||||||
:cy y
|
:cy y
|
||||||
|
@ -336,3 +355,8 @@
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:on-mouse-enter on-mouse-enter
|
:on-mouse-enter on-mouse-enter
|
||||||
:on-mouse-leave on-mouse-leave}])])))
|
:on-mouse-leave on-mouse-leave}])])))
|
||||||
|
|
||||||
|
(defn- conditional-align [point align?]
|
||||||
|
(if align?
|
||||||
|
(uwrk/align-point point)
|
||||||
|
(rx/of point)))
|
||||||
|
|
|
@ -30,9 +30,8 @@
|
||||||
;; --- Zoom Widget
|
;; --- Zoom Widget
|
||||||
|
|
||||||
(mf/defc zoom-widget
|
(mf/defc zoom-widget
|
||||||
{:wrap [mf/wrap-reactive]}
|
|
||||||
[props]
|
[props]
|
||||||
(let [zoom (mf/react refs/selected-zoom)
|
(let [zoom (mf/deref refs/selected-zoom)
|
||||||
increase #(st/emit! (dw/increase-zoom))
|
increase #(st/emit! (dw/increase-zoom))
|
||||||
decrease #(st/emit! (dw/decrease-zoom))]
|
decrease #(st/emit! (dw/decrease-zoom))]
|
||||||
[:ul.options-view
|
[:ul.options-view
|
||||||
|
|
|
@ -48,13 +48,11 @@
|
||||||
|
|
||||||
(on-uploaded [[image]]
|
(on-uploaded [[image]]
|
||||||
(let [{:keys [id name width height]} image
|
(let [{:keys [id name width height]} image
|
||||||
shape {:type :image
|
shape {:name name
|
||||||
:name name
|
|
||||||
:id (uuid/random)
|
|
||||||
:metadata {:width width
|
:metadata {:width width
|
||||||
:height height}
|
:height height}
|
||||||
:image id}]
|
:image id}]
|
||||||
(st/emit! (dw/select-for-drawing shape))
|
(st/emit! (dw/select-for-drawing :image shape))
|
||||||
(modal/hide!)))
|
(modal/hide!)))
|
||||||
|
|
||||||
(on-files-selected [event]
|
(on-files-selected [event]
|
||||||
|
@ -93,13 +91,11 @@
|
||||||
(mf/defc image-item
|
(mf/defc image-item
|
||||||
[{:keys [image] :as props}]
|
[{:keys [image] :as props}]
|
||||||
(letfn [(on-click [event]
|
(letfn [(on-click [event]
|
||||||
(let [shape {:type :image
|
(let [shape {:name (:name image)
|
||||||
:name (:name image)
|
|
||||||
:id (uuid/random)
|
|
||||||
:metadata {:width (:width image)
|
:metadata {:width (:width image)
|
||||||
:height (:height image)}
|
:height (:height image)}
|
||||||
:image (:id image)}]
|
:image (:id image)}]
|
||||||
(st/emit! (dw/select-for-drawing shape))
|
(st/emit! (dw/select-for-drawing :image shape))
|
||||||
(modal/hide!)))]
|
(modal/hide!)))]
|
||||||
[:div.library-item {:on-click on-click}
|
[:div.library-item {:on-click on-click}
|
||||||
[:div.library-item-th
|
[:div.library-item-th
|
||||||
|
|
|
@ -10,10 +10,9 @@
|
||||||
(:require
|
(:require
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
|
[potok.core :as ptk]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.main.constants :as c]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.data.shapes :as uds]
|
|
||||||
[uxbox.main.data.workspace :as udw]
|
|
||||||
[uxbox.main.geom :as geom]
|
[uxbox.main.geom :as geom]
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
|
@ -34,19 +33,13 @@
|
||||||
|
|
||||||
;; --- Resize Implementation
|
;; --- Resize Implementation
|
||||||
|
|
||||||
;; TODO: this function need to be refactored
|
|
||||||
|
|
||||||
(defn- start-resize
|
(defn- start-resize
|
||||||
[vid ids shape]
|
[vid ids shape]
|
||||||
(letfn [(on-resize [shape [point lock?]]
|
(letfn [(resize [shape [point lock?]]
|
||||||
(let [result (geom/resize-shape vid shape point lock?)
|
(let [result (geom/resize-shape vid shape point lock?)
|
||||||
scale (geom/calculate-scale-ratio shape result)
|
scale (geom/calculate-scale-ratio shape result)
|
||||||
mtx (geom/generate-resize-matrix vid shape scale)
|
mtx (geom/generate-resize-matrix vid shape scale)]
|
||||||
xfm (map #(udw/apply-temporal-resize % mtx))]
|
(apply rx/of (map #(dw/assoc-temporal-modifier % mtx) ids))))
|
||||||
(apply st/emit! (sequence xfm ids))))
|
|
||||||
|
|
||||||
(on-end []
|
|
||||||
(apply st/emit! (map udw/apply-resize ids)))
|
|
||||||
|
|
||||||
;; Unifies the instantaneous proportion lock modifier
|
;; Unifies the instantaneous proportion lock modifier
|
||||||
;; activated by Ctrl key and the shapes own proportion
|
;; activated by Ctrl key and the shapes own proportion
|
||||||
|
@ -65,19 +58,23 @@
|
||||||
;; Apply the current zoom factor to the point.
|
;; Apply the current zoom factor to the point.
|
||||||
(apply-zoom [point]
|
(apply-zoom [point]
|
||||||
(gpt/divide point @refs/selected-zoom))]
|
(gpt/divide point @refs/selected-zoom))]
|
||||||
|
(reify
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
(let [shape (->> (geom/shape->rect-shape shape)
|
(let [shape (->> (geom/shape->rect-shape shape)
|
||||||
(geom/size))
|
(geom/size))
|
||||||
stoper (->> ws/interaction-events
|
stoper (rx/filter ws/mouse-up? stream)]
|
||||||
(rx/filter ws/mouse-up?)
|
(rx/concat
|
||||||
(rx/take 1))
|
(->> ws/mouse-position
|
||||||
stream (->> ws/viewport-mouse-position
|
|
||||||
(rx/take-until stoper)
|
|
||||||
(rx/map apply-zoom)
|
(rx/map apply-zoom)
|
||||||
(rx/mapcat apply-grid-alignment)
|
(rx/mapcat apply-grid-alignment)
|
||||||
(rx/with-latest vector ws/mouse-position-ctrl)
|
(rx/with-latest vector ws/mouse-position-ctrl)
|
||||||
(rx/map normalize-proportion-lock))]
|
(rx/map normalize-proportion-lock)
|
||||||
(rx/subscribe stream (partial on-resize shape) nil on-end))))
|
(rx/mapcat (partial resize shape))
|
||||||
|
(rx/take-until stoper))
|
||||||
|
(rx/from-coll (map dw/materialize-current-modifier ids))))))))
|
||||||
|
|
||||||
|
;; (rx/subscribe stream (partial on-resize shape) nil on-end))))
|
||||||
|
|
||||||
;; --- Controls (Component)
|
;; --- Controls (Component)
|
||||||
|
|
||||||
|
@ -160,22 +157,23 @@
|
||||||
(letfn [(on-mouse-down [event index]
|
(letfn [(on-mouse-down [event index]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
|
||||||
(let [stoper (get-edition-stream-stoper ws/interaction-events)
|
;; TODO: this need code ux refactor
|
||||||
|
(let [stoper (get-edition-stream-stoper)
|
||||||
stream (rx/take-until stoper ws/mouse-position-deltas)]
|
stream (rx/take-until stoper ws/mouse-position-deltas)]
|
||||||
(when @refs/selected-alignment
|
(when @refs/selected-alignment
|
||||||
(st/emit! (uds/initial-path-point-align (:id shape) index)))
|
(st/emit! (dw/initial-path-point-align (:id shape) index)))
|
||||||
(rx/subscribe stream #(on-handler-move % index))))
|
(rx/subscribe stream #(on-handler-move % index))))
|
||||||
|
|
||||||
(get-edition-stream-stoper [stream]
|
(get-edition-stream-stoper []
|
||||||
(let [stoper? #(and (ws/mouse-event? %) (= (:type %) :up))]
|
(let [stoper? #(and (ws/mouse-event? %) (= (:type %) :up))]
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(rx/filter stoper? stream)
|
(rx/filter stoper? st/stream)
|
||||||
(->> stream
|
(->> st/stream
|
||||||
(rx/filter #(= % :interrupt))
|
(rx/filter #(= % :interrupt))
|
||||||
(rx/take 1)))))
|
(rx/take 1)))))
|
||||||
|
|
||||||
(on-handler-move [delta index]
|
(on-handler-move [delta index]
|
||||||
(st/emit! (uds/update-path (:id shape) index delta)))]
|
(st/emit! (dw/update-path (:id shape) index delta)))]
|
||||||
|
|
||||||
(let [displacement (:displacement modifiers)
|
(let [displacement (:displacement modifiers)
|
||||||
segments (cond->> (:segments shape)
|
segments (cond->> (:segments shape)
|
||||||
|
@ -191,30 +189,20 @@
|
||||||
:style {:cursor "pointer"}}])])))
|
:style {:cursor "pointer"}}])])))
|
||||||
|
|
||||||
(mf/defc multiple-selection-handlers
|
(mf/defc multiple-selection-handlers
|
||||||
[{:keys [shapes modifiers zoom] :as props}]
|
[{:keys [shapes zoom] :as props}]
|
||||||
(let [shape (->> shapes
|
(let [shape (->> shapes
|
||||||
(map #(assoc % :modifiers (get modifiers (:id %))))
|
|
||||||
(map #(geom/selection-rect %))
|
(map #(geom/selection-rect %))
|
||||||
(geom/shapes->rect-shape)
|
(geom/shapes->rect-shape)
|
||||||
(geom/selection-rect))
|
(geom/selection-rect))
|
||||||
on-click #(do (dom/stop-propagation %2)
|
on-click #(do (dom/stop-propagation %2)
|
||||||
(start-resize %1 (map :id shapes) shape))]
|
(st/emit! (start-resize %1 (mapv :id shapes) shape)))]
|
||||||
[:& controls {:shape shape
|
[:& controls {:shape shape
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:on-click on-click}]))
|
:on-click on-click}]))
|
||||||
|
|
||||||
(mf/defc single-selection-handlers
|
|
||||||
[{:keys [shape zoom modifiers] :as props}]
|
|
||||||
(let [on-click #(do (dom/stop-propagation %2)
|
|
||||||
(start-resize %1 #{(:id shape)} shape))
|
|
||||||
shape (-> (assoc shape :modifiers modifiers)
|
|
||||||
(geom/selection-rect))]
|
|
||||||
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
|
|
||||||
|
|
||||||
(mf/defc text-edition-selection-handlers
|
(mf/defc text-edition-selection-handlers
|
||||||
[{:keys [shape modifiers zoom] :as props}]
|
[{:keys [shape zoom] :as props}]
|
||||||
(let [{:keys [x1 y1 width height] :as shape} (-> (assoc shape :modifiers modifiers)
|
(let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)]
|
||||||
(geom/selection-rect))]
|
|
||||||
[:g.controls
|
[:g.controls
|
||||||
[:rect.main {:x x1 :y y1
|
[:rect.main {:x x1 :y y1
|
||||||
:width width
|
:width width
|
||||||
|
@ -225,6 +213,13 @@
|
||||||
:stroke-opacity "0.5"
|
:stroke-opacity "0.5"
|
||||||
:fill "transparent"}}]]))
|
:fill "transparent"}}]]))
|
||||||
|
|
||||||
|
(mf/defc single-selection-handlers
|
||||||
|
[{:keys [shape zoom] :as props}]
|
||||||
|
(let [on-click #(do (dom/stop-propagation %2)
|
||||||
|
(st/emit! (start-resize %1 #{(:id shape)} shape)))
|
||||||
|
shape (geom/selection-rect shape)]
|
||||||
|
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
|
||||||
|
|
||||||
(def ^:private shapes-map-iref
|
(def ^:private shapes-map-iref
|
||||||
(-> (l/key :shapes)
|
(-> (l/key :shapes)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
@ -233,35 +228,29 @@
|
||||||
[{:keys [wst] :as props}]
|
[{:keys [wst] :as props}]
|
||||||
(let [shapes-map (mf/deref shapes-map-iref)
|
(let [shapes-map (mf/deref shapes-map-iref)
|
||||||
shapes (map #(get shapes-map %) (:selected wst))
|
shapes (map #(get shapes-map %) (:selected wst))
|
||||||
edition? (:edition wst)
|
edition (:edition wst)
|
||||||
modifiers (:modifiers wst)
|
|
||||||
zoom (:zoom wst 1)
|
zoom (:zoom wst 1)
|
||||||
num (count shapes)
|
num (count shapes)
|
||||||
{:keys [id type] :as shape} (first shapes)]
|
{:keys [id type] :as shape} (first shapes)]
|
||||||
|
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
(zero? num)
|
(zero? num)
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(> num 1)
|
(> num 1)
|
||||||
[:& multiple-selection-handlers {:shapes shapes
|
[:& multiple-selection-handlers {:shapes shapes
|
||||||
:modifiers modifiers
|
|
||||||
:zoom zoom}]
|
:zoom zoom}]
|
||||||
|
|
||||||
(and (= type :text)
|
(and (= type :text)
|
||||||
(= edition? (:id shape)))
|
(= edition (:id shape)))
|
||||||
[:& text-edition-selection-handlers {:shape shape
|
[:& text-edition-selection-handlers {:shape shape
|
||||||
:modifiers (get modifiers id)
|
|
||||||
:zoom zoom}]
|
:zoom zoom}]
|
||||||
(and (= type :path)
|
(and (or (= type :path)
|
||||||
(= edition? (:id shape)))
|
(= type :curve))
|
||||||
|
(= edition (:id shape)))
|
||||||
[:& path-edition-selection-handlers {:shape shape
|
[:& path-edition-selection-handlers {:shape shape
|
||||||
:zoom zoom
|
:zoom zoom}]
|
||||||
:modifiers (get modifiers id)}]
|
|
||||||
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:& single-selection-handlers {:shape shape
|
[:& single-selection-handlers {:shape shape
|
||||||
:modifiers (get modifiers id)
|
|
||||||
:zoom zoom}])))
|
:zoom zoom}])))
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.data.lightbox :as dl]
|
[uxbox.main.data.lightbox :as dl]
|
||||||
[uxbox.main.data.workspace :as dw]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.undo :as du])
|
||||||
[uxbox.main.data.undo :as udu]
|
|
||||||
[uxbox.main.data.history :as udh]
|
|
||||||
[uxbox.main.ui.workspace.sidebar.drawtools :as wsd])
|
|
||||||
(:import goog.events.EventType
|
(:import goog.events.EventType
|
||||||
goog.events.KeyCodes
|
goog.events.KeyCodes
|
||||||
goog.ui.KeyboardShortcutHandler
|
goog.ui.KeyboardShortcutHandler
|
||||||
|
@ -27,26 +24,24 @@
|
||||||
|
|
||||||
(defonce +shortcuts+
|
(defonce +shortcuts+
|
||||||
{:shift+g #(st/emit! (dw/toggle-flag :grid))
|
{:shift+g #(st/emit! (dw/toggle-flag :grid))
|
||||||
:ctrl+g #(st/emit! (uds/group-selected))
|
|
||||||
:ctrl+shift+g #(st/emit! (uds/ungroup-selected))
|
|
||||||
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
||||||
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
||||||
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
||||||
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
||||||
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
||||||
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||||
:ctrl+d #(st/emit! (uds/duplicate-selected))
|
:ctrl+d #(st/emit! dw/duplicate-selected)
|
||||||
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||||
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||||
:ctrl+shift+v #(dl/open! :clipboard)
|
:ctrl+shift+v #(dl/open! :clipboard)
|
||||||
:ctrl+z #(st/emit! (udu/undo))
|
:ctrl+z #(st/emit! (du/undo))
|
||||||
:ctrl+shift+z #(st/emit! (udu/redo))
|
:ctrl+shift+z #(st/emit! (du/redo))
|
||||||
:ctrl+y #(st/emit! (udu/redo))
|
:ctrl+y #(st/emit! (du/redo))
|
||||||
:ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
|
:ctrl+b #(st/emit! (dw/select-for-drawing :rect))
|
||||||
:ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
|
:ctrl+e #(st/emit! (dw/select-for-drawing :circle))
|
||||||
:ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
|
:ctrl+t #(st/emit! (dw/select-for-drawing :text))
|
||||||
:esc #(st/emit! (dw/deselect-all))
|
:esc #(st/emit! (dw/deselect-all))
|
||||||
:delete #(st/emit! (dw/delete-selected))
|
:delete #(st/emit! dw/delete-selected)
|
||||||
:ctrl+up #(st/emit! (dw/move-selected-layer :up))
|
:ctrl+up #(st/emit! (dw/move-selected-layer :up))
|
||||||
:ctrl+down #(st/emit! (dw/move-selected-layer :down))
|
:ctrl+down #(st/emit! (dw/move-selected-layer :down))
|
||||||
:ctrl+shift+up #(st/emit! (dw/move-selected-layer :top))
|
:ctrl+shift+up #(st/emit! (dw/move-selected-layer :top))
|
||||||
|
|
|
@ -21,8 +21,7 @@
|
||||||
(mf/defc left-sidebar
|
(mf/defc left-sidebar
|
||||||
{:wrap [mf/wrap-memo]}
|
{:wrap [mf/wrap-memo]}
|
||||||
[{:keys [flags page] :as props}]
|
[{:keys [flags page] :as props}]
|
||||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
[:aside.settings-bar.settings-bar-left
|
||||||
[:> rdnd/provider {:backend rdnd/html5}
|
|
||||||
[:div.settings-bar-inside
|
[:div.settings-bar-inside
|
||||||
(when (contains? flags :sitemap)
|
(when (contains? flags :sitemap)
|
||||||
[:& sitemap-toolbox {:project-id (:project page)
|
[:& sitemap-toolbox {:project-id (:project page)
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
#_(when (contains? flags :document-history)
|
#_(when (contains? flags :document-history)
|
||||||
(history-toolbox page-id))
|
(history-toolbox page-id))
|
||||||
(when (contains? flags :layers)
|
(when (contains? flags :layers)
|
||||||
[:& layers-toolbox {:page page}])]]])
|
[:& layers-toolbox {:page page}])]])
|
||||||
|
|
||||||
;; --- Right Sidebar (Component)
|
;; --- Right Sidebar (Component)
|
||||||
|
|
||||||
|
@ -43,6 +42,5 @@
|
||||||
[:& draw-toolbox {:flags flags}])
|
[:& draw-toolbox {:flags flags}])
|
||||||
(when (contains? flags :element-options)
|
(when (contains? flags :element-options)
|
||||||
[:& options-toolbox {:page page}])
|
[:& options-toolbox {:page page}])
|
||||||
(when (contains? flags :icons)
|
#_(when (contains? flags :icons)
|
||||||
#_(icons-toolbox))]])
|
(icons-toolbox))]])
|
||||||
|
|
||||||
|
|
|
@ -17,96 +17,69 @@
|
||||||
|
|
||||||
;; --- Constants
|
;; --- Constants
|
||||||
|
|
||||||
(def +draw-tool-rect+
|
|
||||||
{:type :rect
|
|
||||||
:id (uuid/random)
|
|
||||||
:name "Rect"
|
|
||||||
:stroke-color "#000000"})
|
|
||||||
|
|
||||||
(def +draw-tool-circle+
|
|
||||||
{:type :circle
|
|
||||||
:id (uuid/random)
|
|
||||||
:name "Circle"})
|
|
||||||
|
|
||||||
(def +draw-tool-path+
|
|
||||||
{:type :path
|
|
||||||
:id (uuid/random)
|
|
||||||
:name "Path"
|
|
||||||
:stroke-style :solid
|
|
||||||
:stroke-color "#000000"
|
|
||||||
:stroke-width 2
|
|
||||||
:fill-color "#000000"
|
|
||||||
:fill-opacity 0
|
|
||||||
;; :close? true
|
|
||||||
:points []})
|
|
||||||
|
|
||||||
(def +draw-tool-curve+
|
|
||||||
(assoc +draw-tool-path+
|
|
||||||
:id (uuid/random)
|
|
||||||
:free true))
|
|
||||||
|
|
||||||
(def +draw-tool-text+
|
|
||||||
{:type :text
|
|
||||||
:id (uuid/random)
|
|
||||||
:name "Text"
|
|
||||||
:content "Hello world"})
|
|
||||||
|
|
||||||
(def +draw-tools+
|
(def +draw-tools+
|
||||||
[{:icon i/box
|
[{:icon i/box
|
||||||
:help "ds.help.rect"
|
:help "ds.help.rect"
|
||||||
:shape +draw-tool-rect+
|
:type :rect
|
||||||
:priority 1}
|
:priority 1}
|
||||||
{:icon i/circle
|
{:icon i/circle
|
||||||
:help "ds.help.circle"
|
:help "ds.help.circle"
|
||||||
:shape +draw-tool-circle+
|
:type :circle
|
||||||
:priority 2}
|
:priority 2}
|
||||||
{:icon i/text
|
{:icon i/text
|
||||||
:help "ds.help.text"
|
:help "ds.help.text"
|
||||||
:shape +draw-tool-text+
|
:type :text
|
||||||
:priority 4}
|
:priority 4}
|
||||||
{:icon i/curve
|
{:icon i/curve
|
||||||
:help "ds.help.path"
|
:help "ds.help.path"
|
||||||
:shape +draw-tool-path+
|
:type :path
|
||||||
:priority 5}
|
:priority 5}
|
||||||
{:icon i/pencil
|
{:icon i/pencil
|
||||||
:help "ds.help.curve"
|
:help "ds.help.curve"
|
||||||
:shape +draw-tool-curve+
|
:type :curve
|
||||||
:priority 6}])
|
:priority 6}
|
||||||
|
;; TODO: we need an icon for canvas creation
|
||||||
|
{:icon i/box
|
||||||
|
:help "ds.help.canvas"
|
||||||
|
:type :canvas
|
||||||
|
:priority 7}])
|
||||||
|
|
||||||
;; --- Draw Toolbox (Component)
|
;; --- Draw Toolbox (Component)
|
||||||
|
|
||||||
(mf/defc draw-toolbox
|
(mf/defc draw-toolbox
|
||||||
{:wrap [mf/wrap-memo]}
|
{:wrap [mf/wrap-memo]}
|
||||||
[{:keys [flags] :as props}]
|
[{:keys [flags] :as props}]
|
||||||
(let [close #(st/emit! (dw/toggle-flag :drawtools))
|
(letfn [(close [event]
|
||||||
dtool (mf/deref refs/selected-drawing-tool)
|
(st/emit! (dw/deactivate-flag :drawtools)))
|
||||||
tools (->> (into [] +draw-tools+)
|
(select [event tool]
|
||||||
(sort-by (comp :priority second)))
|
(st/emit! :interrupt
|
||||||
|
|
||||||
select-drawtool #(st/emit! :interrupt
|
|
||||||
(dw/deactivate-ruler)
|
(dw/deactivate-ruler)
|
||||||
(dw/select-for-drawing %))
|
(dw/select-for-drawing tool)))
|
||||||
toggle-ruler #(st/emit! (dw/select-for-drawing nil)
|
(toggle-ruler [event]
|
||||||
|
(st/emit! (dw/select-for-drawing nil)
|
||||||
(dw/deselect-all)
|
(dw/deselect-all)
|
||||||
(dw/toggle-ruler))]
|
(dw/toggle-ruler)))]
|
||||||
|
|
||||||
[:div#form-tools.tool-window.drawing-tools
|
(let [selected (mf/deref refs/selected-drawing-tool)
|
||||||
|
tools (sort-by (comp :priority second) +draw-tools+)]
|
||||||
|
[:div.tool-window.drawing-tools
|
||||||
[:div.tool-window-bar
|
[:div.tool-window-bar
|
||||||
[:div.tool-window-icon i/window]
|
[:div.tool-window-icon i/window]
|
||||||
[:span (tr "ds.settings.draw-tools")]
|
[:span (tr "ds.settings.draw-tools")]
|
||||||
[:div.tool-window-close {:on-click close} i/close]]
|
[:div.tool-window-close {:on-click close} i/close]]
|
||||||
[:div.tool-window-content
|
[:div.tool-window-content
|
||||||
(for [[i props] (map-indexed vector tools)]
|
(for [item tools]
|
||||||
(let [selected? (= dtool (:shape props))]
|
(let [selected? (= (:type item) selected)]
|
||||||
[:div.tool-btn.tooltip.tooltip-hover
|
[:div.tool-btn.tooltip.tooltip-hover
|
||||||
{:alt (tr (:help props))
|
{:alt (tr (:help item))
|
||||||
:class (when selected? "selected")
|
:class (when selected? "selected")
|
||||||
:key i
|
:key (:type item)
|
||||||
:on-click (partial select-drawtool (:shape props))}
|
:on-click #(select % (:type item))}
|
||||||
(:icon props)]))
|
(:icon item)]))
|
||||||
[:div.tool-btn.tooltip.tooltip-hover
|
|
||||||
|
#_[:div.tool-btn.tooltip.tooltip-hover
|
||||||
{:alt (tr "ds.help.ruler")
|
{:alt (tr "ds.help.ruler")
|
||||||
:on-click toggle-ruler
|
:on-click toggle-ruler
|
||||||
:class (when (contains? flags :ruler) "selected")}
|
:class (when (contains? flags :ruler) "selected")}
|
||||||
i/ruler-tool]]]))
|
i/ruler-tool]]])))
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.icons :as udi]
|
[uxbox.main.data.icons :as udi]
|
||||||
[uxbox.main.data.workspace :as dw]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.lenses :as ul]
|
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.dashboard.icons :as icons]
|
[uxbox.main.ui.dashboard.icons :as icons]
|
||||||
[uxbox.main.ui.shapes.icon :as icon]
|
[uxbox.main.ui.shapes.icon :as icon]
|
||||||
|
@ -23,12 +22,12 @@
|
||||||
|
|
||||||
;; --- Refs
|
;; --- Refs
|
||||||
|
|
||||||
(def ^:private drawing-shape-ref
|
;; (def ^:private drawing-shape-ref
|
||||||
"A focused vision of the drawing property
|
;; "A focused vision of the drawing property
|
||||||
of the workspace status. This avoids
|
;; of the workspace status. This avoids
|
||||||
rerender the whole toolbox on each workspace
|
;; rerender the whole toolbox on each workspace
|
||||||
change."
|
;; change."
|
||||||
(l/derive ul/selected-drawing st/state))
|
;; (l/derive ul/selected-drawing st/state))
|
||||||
|
|
||||||
(def ^:private icons-toolbox-ref
|
(def ^:private icons-toolbox-ref
|
||||||
(-> (l/in [:workspace :icons-toolbox])
|
(-> (l/in [:workspace :icons-toolbox])
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.pages :as udp]
|
[uxbox.main.data.pages :as udp]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.data.workspace :as udw]
|
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
|
@ -45,9 +44,10 @@
|
||||||
on-blur (fn [event]
|
on-blur (fn [event]
|
||||||
(let [target (dom/event->target event)
|
(let [target (dom/event->target event)
|
||||||
parent (.-parentNode target)
|
parent (.-parentNode target)
|
||||||
|
parent (.-parentNode parent)
|
||||||
name (dom/get-value target)]
|
name (dom/get-value target)]
|
||||||
(set! (.-draggable parent) true)
|
(set! (.-draggable parent) true)
|
||||||
(st/emit! (uds/rename-shape (:id shape) name))
|
(st/emit! (dw/rename-shape (:id shape) name))
|
||||||
(swap! local assoc :edition false)))
|
(swap! local assoc :edition false)))
|
||||||
on-key-down (fn [event]
|
on-key-down (fn [event]
|
||||||
(js/console.log event)
|
(js/console.log event)
|
||||||
|
@ -55,7 +55,8 @@
|
||||||
(on-blur event)))
|
(on-blur event)))
|
||||||
on-click (fn [event]
|
on-click (fn [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(let [parent (.-parentNode (.-target event))]
|
(let [parent (.-parentNode (.-target event))
|
||||||
|
parent (.-parentNode parent)]
|
||||||
(set! (.-draggable parent) false))
|
(set! (.-draggable parent) false))
|
||||||
(swap! local assoc :edition true))]
|
(swap! local assoc :edition true))]
|
||||||
(if (:edition @local)
|
(if (:edition @local)
|
||||||
|
@ -78,18 +79,18 @@
|
||||||
(let [id (:id shape)
|
(let [id (:id shape)
|
||||||
blocked? (:blocked shape)]
|
blocked? (:blocked shape)]
|
||||||
(if blocked?
|
(if blocked?
|
||||||
(st/emit! (uds/unblock-shape id))
|
(st/emit! (dw/unblock-shape id))
|
||||||
(st/emit! (uds/block-shape id)))))
|
(st/emit! (dw/block-shape id)))))
|
||||||
|
|
||||||
(toggle-visibility [event]
|
(toggle-visibility [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(let [id (:id shape)
|
(let [id (:id shape)
|
||||||
hidden? (:hidden shape)]
|
hidden? (:hidden shape)]
|
||||||
(if hidden?
|
(if hidden?
|
||||||
(st/emit! (uds/show-shape id))
|
(st/emit! (dw/show-shape id))
|
||||||
(st/emit! (uds/hide-shape id)))
|
(st/emit! (dw/hide-shape id)))
|
||||||
(when (contains? selected id)
|
(when (contains? selected id)
|
||||||
(st/emit! (udw/select-shape id)))))
|
(st/emit! (dw/select-shape id)))))
|
||||||
|
|
||||||
(select-shape [event]
|
(select-shape [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
|
@ -100,24 +101,20 @@
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(.-ctrlKey event)
|
(.-ctrlKey event)
|
||||||
(st/emit! (udw/select-shape id))
|
(st/emit! (dw/select-shape id))
|
||||||
|
|
||||||
(> (count selected) 1)
|
(> (count selected) 1)
|
||||||
(st/emit! (udw/deselect-all)
|
(st/emit! (dw/deselect-all)
|
||||||
(udw/select-shape id))
|
(dw/select-shape id))
|
||||||
|
|
||||||
(contains? selected id)
|
|
||||||
(st/emit! (udw/select-shape id))
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(st/emit! (udw/deselect-all)
|
(st/emit! (dw/deselect-all)
|
||||||
(udw/select-shape id)))))
|
(dw/select-shape id)))))
|
||||||
|
|
||||||
(on-drop [item monitor]
|
(on-drop [item monitor]
|
||||||
(st/emit! (udp/persist-page (:page shape))))
|
(st/emit! (udp/persist-page (:page shape))))
|
||||||
|
|
||||||
(on-hover [item monitor]
|
(on-hover [item monitor]
|
||||||
(st/emit! (udw/change-shape-order {:id (:shape-id item)
|
(st/emit! (dw/change-shape-order {:id (:shape-id item)
|
||||||
:index index})))]
|
:index index})))]
|
||||||
(let [selected? (contains? selected (:id shape))
|
(let [selected? (contains? selected (:id shape))
|
||||||
[dprops dnd-ref] (use-sortable
|
[dprops dnd-ref] (use-sortable
|
||||||
|
@ -133,8 +130,7 @@
|
||||||
:dragging-TODO (:dragging? dprops))}
|
:dragging-TODO (:dragging? dprops))}
|
||||||
[:div.element-list-body {:class (classnames :selected selected?)
|
[:div.element-list-body {:class (classnames :selected selected?)
|
||||||
:on-click select-shape
|
:on-click select-shape
|
||||||
:on-double-click #(dom/stop-propagation %)
|
:on-double-click #(dom/stop-propagation %)}
|
||||||
:draggable true}
|
|
||||||
[:div.element-actions
|
[:div.element-actions
|
||||||
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||||
:on-click toggle-visibility}
|
:on-click toggle-visibility}
|
||||||
|
@ -145,20 +141,52 @@
|
||||||
[:div.element-icon (element-icon shape)]
|
[:div.element-icon (element-icon shape)]
|
||||||
[:& layer-name {:shape shape}]]])))
|
[:& layer-name {:shape shape}]]])))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Layer Canvas
|
||||||
|
|
||||||
|
;; (mf/defc layer-canvas
|
||||||
|
;; [{:keys [canvas selected index] :as props}]
|
||||||
|
;; (letfn [(select-shape [event]
|
||||||
|
;; (dom/prevent-default event)
|
||||||
|
;; (st/emit! (dw/select-canvas (:id canvas))))
|
||||||
|
;; (let [selected? (contains? selected (:id shape))]
|
||||||
|
;; [:li {:class (classnames
|
||||||
|
;; :selected selected?)}
|
||||||
|
;; [:div.element-list-body {:class (classnames :selected selected?)
|
||||||
|
;; :on-click select-shape
|
||||||
|
;; :on-double-click #(dom/stop-propagation %)
|
||||||
|
;; :draggable true}
|
||||||
|
;; [:div.element-actions
|
||||||
|
;; [:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||||
|
;; :on-click toggle-visibility}
|
||||||
|
;; i/eye]
|
||||||
|
;; [:div.block-element {:class (when (:blocked shape) "selected")
|
||||||
|
;; :on-click toggle-blocking}
|
||||||
|
;; i/lock]]
|
||||||
|
;; [:div.element-icon (element-icon shape)]
|
||||||
|
;; [:& layer-name {:shape shape}]]])))
|
||||||
|
|
||||||
;; --- Layers List
|
;; --- Layers List
|
||||||
|
|
||||||
(def ^:private shapes-iref
|
(def ^:private shapes-iref
|
||||||
(-> (l/key :shapes)
|
(-> (l/key :shapes)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
|
(def ^:private canvas-iref
|
||||||
|
(-> (l/key :canvas)
|
||||||
|
(l/derive st/state)))
|
||||||
|
|
||||||
(mf/defc layers-list
|
(mf/defc layers-list
|
||||||
[{:keys [shapes selected] :as props}]
|
[{:keys [shapes selected] :as props}]
|
||||||
(let [shapes-map (mf/deref shapes-iref)]
|
(let [shapes-map (mf/deref shapes-iref)
|
||||||
|
canvas-map (mf/deref canvas-iref)
|
||||||
|
selected-shapes (mf/deref refs/selected-shapes)
|
||||||
|
selected-canvas (mf/deref refs/selected-canvas)]
|
||||||
[:div.tool-window-content
|
[:div.tool-window-content
|
||||||
[:ul.element-list
|
[:ul.element-list
|
||||||
(for [[index id] (map-indexed vector shapes)]
|
(for [[index id] (map-indexed vector shapes)]
|
||||||
[:& layer-item {:shape (get shapes-map id)
|
[:& layer-item {:shape (get shapes-map id)
|
||||||
:selected selected
|
:selected selected-shapes
|
||||||
:index index
|
:index index
|
||||||
:key id}])]]))
|
:key id}])]]))
|
||||||
|
|
||||||
|
@ -166,7 +194,7 @@
|
||||||
|
|
||||||
(mf/defc layers-toolbox
|
(mf/defc layers-toolbox
|
||||||
[{:keys [page selected] :as props}]
|
[{:keys [page selected] :as props}]
|
||||||
(let [on-click #(st/emit! (udw/toggle-flag :layers))
|
(let [on-click #(st/emit! (dw/toggle-flag :layers))
|
||||||
selected (mf/deref refs/selected-shapes)]
|
selected (mf/deref refs/selected-shapes)]
|
||||||
[:div#layers.tool-window
|
[:div#layers.tool-window
|
||||||
[:div.tool-window-bar
|
[:div.tool-window-bar
|
||||||
|
|
|
@ -93,14 +93,14 @@
|
||||||
value (parse-int value 0)
|
value (parse-int value 0)
|
||||||
sid (:id shape)
|
sid (:id shape)
|
||||||
props {attr value}]
|
props {attr value}]
|
||||||
(st/emit! (uds/update-dimensions sid props))))
|
(st/emit! (udw/update-dimensions sid props))))
|
||||||
|
|
||||||
(defn- on-rotation-change
|
(defn- on-rotation-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-int value 0)
|
value (parse-int value 0)
|
||||||
sid (:id shape)]
|
sid (:id shape)]
|
||||||
(st/emit! (uds/update-rotation sid value))))
|
(st/emit! (udw/update-shape-attrs sid {:rotation value}))))
|
||||||
|
|
||||||
(defn- on-position-change
|
(defn- on-position-change
|
||||||
[event shape attr]
|
[event shape attr]
|
||||||
|
@ -108,11 +108,11 @@
|
||||||
value (parse-int value nil)
|
value (parse-int value nil)
|
||||||
sid (:id shape)
|
sid (:id shape)
|
||||||
point (gpt/point {attr value})]
|
point (gpt/point {attr value})]
|
||||||
(st/emit! (uds/update-position sid point))))
|
(st/emit! (udw/update-position sid point))))
|
||||||
|
|
||||||
(defn- on-proportion-lock-change
|
(defn- on-proportion-lock-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(if (:proportion-lock shape)
|
(if (:proportion-lock shape)
|
||||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||||
|
|
||||||
|
|
|
@ -90,14 +90,13 @@
|
||||||
value (parse-int value 0)
|
value (parse-int value 0)
|
||||||
sid (:id shape)
|
sid (:id shape)
|
||||||
props {attr value}]
|
props {attr value}]
|
||||||
(st/emit! (uds/update-dimensions sid props))))
|
(st/emit! (udw/update-dimensions sid props))))
|
||||||
|
|
||||||
(defn- on-rotation-change
|
(defn- on-rotation-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-int value 0)
|
value (parse-int value 0)]
|
||||||
sid (:id shape)]
|
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||||
(st/emit! (uds/update-rotation sid value))))
|
|
||||||
|
|
||||||
(defn- on-position-change
|
(defn- on-position-change
|
||||||
[event shape attr]
|
[event shape attr]
|
||||||
|
@ -105,11 +104,11 @@
|
||||||
value (parse-int value nil)
|
value (parse-int value nil)
|
||||||
sid (:id shape)
|
sid (:id shape)
|
||||||
point (gpt/point {attr value})]
|
point (gpt/point {attr value})]
|
||||||
(st/emit! (uds/update-position sid point))))
|
(st/emit! (udw/update-position sid point))))
|
||||||
|
|
||||||
(defn- on-proportion-lock-change
|
(defn- on-proportion-lock-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(if (:proportion-lock shape)
|
(if (:proportion-lock shape)
|
||||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.data.workspace :as udw]
|
|
||||||
[uxbox.main.geom :as geom]
|
[uxbox.main.geom :as geom]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||||
|
@ -106,30 +105,30 @@
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-int value 0)
|
value (parse-int value 0)
|
||||||
props {attr value}]
|
props {attr value}]
|
||||||
(st/emit! (uds/update-dimensions (:id shape) props))))
|
(st/emit! (dw/update-dimensions (:id shape) props))))
|
||||||
|
|
||||||
(defn- on-rotation-change
|
(defn- on-rotation-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-int value 0)]
|
value (parse-int value 0)]
|
||||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
(st/emit! (dw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||||
|
|
||||||
(defn- on-opacity-change
|
(defn- on-opacity-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-float value 1)
|
value (parse-float value 1)
|
||||||
value (/ value 10000)]
|
value (/ value 10000)]
|
||||||
(st/emit! (uds/update-attrs (:id shape) {:opacity value}))))
|
(st/emit! (dw/update-shape-attrs (:id shape) {:opacity value}))))
|
||||||
|
|
||||||
(defn- on-position-change
|
(defn- on-position-change
|
||||||
[event shape attr]
|
[event shape attr]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
value (parse-int value nil)
|
value (parse-int value nil)
|
||||||
point (gpt/point {attr value})]
|
point (gpt/point {attr value})]
|
||||||
(st/emit! (uds/update-position (:id shape) point))))
|
(st/emit! (dw/update-position (:id shape) point))))
|
||||||
|
|
||||||
(defn- on-proportion-lock-change
|
(defn- on-proportion-lock-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(if (:proportion-lock shape)
|
(if (:proportion-lock shape)
|
||||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
(st/emit! (dw/unlock-proportions (:id shape)))
|
||||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
(st/emit! (dw/lock-proportions (:id shape)))))
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.lightbox :as udl]
|
[uxbox.main.data.lightbox :as udl]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.colorpicker :as cp]
|
[uxbox.main.ui.colorpicker :as cp]
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
(delete [item]
|
(delete [item]
|
||||||
(let [sid (:id shape)
|
(let [sid (:id shape)
|
||||||
id (:id item)]
|
id (:id item)]
|
||||||
(st/emit! (uds/delete-interaction sid id))))
|
(st/emit! (dw/delete-interaction sid id))))
|
||||||
(on-delete [item event]
|
(on-delete [item event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(let [delete (partial delete item)]
|
(let [delete (partial delete item)]
|
||||||
|
@ -455,7 +455,7 @@
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(let [sid (:id shape)
|
(let [sid (:id shape)
|
||||||
data (deref form)]
|
data (deref form)]
|
||||||
(st/emit! (uds/update-interaction sid data))
|
(st/emit! (dw/update-interaction sid data))
|
||||||
(reset! form nil)))
|
(reset! form nil)))
|
||||||
(on-cancel [event]
|
(on-cancel [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
(on-name-change [event]
|
(on-name-change [event]
|
||||||
(let [value (-> (dom/event->value event)
|
(let [value (-> (dom/event->value event)
|
||||||
(str/trim))]
|
(str/trim))]
|
||||||
(st/emit! (->> (assoc page :name value)
|
(st/emit! (-> (assoc page :name value)
|
||||||
(udp/update-page (:id page))))))
|
(udp/update-page-attrs)))))
|
||||||
|
|
||||||
(show-color-picker [event]
|
(show-color-picker [event]
|
||||||
(let [x (.-clientX event)
|
(let [x (.-clientX event)
|
||||||
|
|
|
@ -87,23 +87,23 @@
|
||||||
[event shape attr]
|
[event shape attr]
|
||||||
(let [value (-> (dom/event->value event)
|
(let [value (-> (dom/event->value event)
|
||||||
(parse-int 0))]
|
(parse-int 0))]
|
||||||
(st/emit! (uds/update-dimensions (:id shape) {attr value}))))
|
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
|
||||||
|
|
||||||
(defn- on-rotation-change
|
(defn- on-rotation-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(let [value (-> (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
(parse-int 0))]
|
value (parse-int value 0)]
|
||||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||||
|
|
||||||
(defn- on-position-change
|
(defn- on-position-change
|
||||||
[event shape attr]
|
[event shape attr]
|
||||||
(let [value (-> (dom/event->value event)
|
(let [value (-> (dom/event->value event)
|
||||||
(parse-int nil))
|
(parse-int nil))
|
||||||
point (gpt/point {attr value})]
|
point (gpt/point {attr value})]
|
||||||
(st/emit! (uds/update-position (:id shape) point))))
|
(st/emit! (udw/update-position (:id shape) point))))
|
||||||
|
|
||||||
(defn- on-proportion-lock-change
|
(defn- on-proportion-lock-change
|
||||||
[event shape]
|
[event shape]
|
||||||
(if (:proportion-lock shape)
|
(if (:proportion-lock shape)
|
||||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
{:mixins [mx/static]}
|
{:mixins [mx/static]}
|
||||||
[menu {:keys [id] :as shape}]
|
[menu {:keys [id] :as shape}]
|
||||||
(letfn [(update-attrs [attrs]
|
(letfn [(update-attrs [attrs]
|
||||||
(st/emit! (uds/update-attrs id attrs)))
|
(st/emit! (udw/update-shape-attrs id attrs)))
|
||||||
(on-font-family-change [event]
|
(on-font-family-change [event]
|
||||||
(let [value (dom/event->value event)
|
(let [value (dom/event->value event)
|
||||||
attrs {:font-family (read-string value)
|
attrs {:font-family (read-string value)
|
||||||
|
|
|
@ -10,21 +10,18 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[rumext.util :as mfu]
|
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.lightbox :as udl]
|
|
||||||
[uxbox.main.data.pages :as udp]
|
[uxbox.main.data.pages :as udp]
|
||||||
[uxbox.main.data.projects :as dp]
|
[uxbox.main.data.projects :as dp]
|
||||||
[uxbox.main.data.workspace :as dw]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.refs :as refs]
|
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.lightbox :as lbx]
|
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||||
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
[uxbox.main.ui.modal :as modal]
|
||||||
|
[uxbox.main.ui.workspace.sidebar.sitemap-forms :refer [page-form-dialog]]
|
||||||
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||||
[uxbox.util.data :refer [classnames]]
|
[uxbox.util.data :refer [classnames]]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.dom.dnd :as dnd]
|
[uxbox.util.i18n :refer [tr]]
|
||||||
[uxbox.util.i18n :refer (tr)]
|
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
|
||||||
;; --- Page Item
|
;; --- Page Item
|
||||||
|
@ -32,17 +29,15 @@
|
||||||
(mf/defc page-item
|
(mf/defc page-item
|
||||||
[{:keys [page index deletable? selected?] :as props}]
|
[{:keys [page index deletable? selected?] :as props}]
|
||||||
(letfn [(on-edit [event]
|
(letfn [(on-edit [event]
|
||||||
(udl/open! :page-form {:page page}))
|
(modal/show! page-form-dialog {:page page}))
|
||||||
(delete []
|
(delete []
|
||||||
(let [next #(st/emit! (dp/go-to (:project page)))]
|
(st/emit! (dw/delete-page (:id page))))
|
||||||
(st/emit! (udp/delete-page (:id page) next))))
|
|
||||||
|
|
||||||
(on-delete [event]
|
(on-delete [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(udl/open! :confirm {:on-accept delete}))
|
(modal/show! confirm-dialog {:on-accept delete}))
|
||||||
(on-drop [item monitor]
|
(on-drop [item monitor]
|
||||||
(st/emit! (udp/reorder-pages (:project page))))
|
(st/emit! (udp/rehash-pages (:project page))))
|
||||||
(on-hover [item monitor]
|
(on-hover [item monitor]
|
||||||
(st/emit! (udp/move-page {:project-id (:project-id item)
|
(st/emit! (udp/move-page {:project-id (:project-id item)
|
||||||
:page-id (:page-id item)
|
:page-id (:page-id item)
|
||||||
|
@ -101,7 +96,7 @@
|
||||||
:fn #(-> (l/in [:projects project-id])
|
:fn #(-> (l/in [:projects project-id])
|
||||||
(l/derive st/state))})
|
(l/derive st/state))})
|
||||||
project (mf/deref project-iref)
|
project (mf/deref project-iref)
|
||||||
create #(udl/open! :page-form {:page {:project project-id}})
|
create #(modal/show! page-form-dialog {:page {:project project-id}})
|
||||||
close #(st/emit! (dw/toggle-flag :sitemap))]
|
close #(st/emit! (dw/toggle-flag :sitemap))]
|
||||||
[:div.sitemap.tool-window
|
[:div.sitemap.tool-window
|
||||||
[:div.tool-window-bar
|
[:div.tool-window-bar
|
||||||
|
|
114
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs
Normal file
114
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
|
||||||
|
(ns uxbox.main.ui.workspace.sidebar.sitemap-forms
|
||||||
|
(:require
|
||||||
|
[rumext.alpha :as mf]
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
|
[uxbox.builtins.icons :as i]
|
||||||
|
[uxbox.main.constants :as c]
|
||||||
|
[uxbox.main.data.pages :as udp]
|
||||||
|
[uxbox.main.store :as st]
|
||||||
|
[uxbox.main.ui.modal :as modal]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.spec :as us]
|
||||||
|
[uxbox.util.forms :as fm]
|
||||||
|
[uxbox.util.i18n :refer [tr]]))
|
||||||
|
|
||||||
|
(s/def ::id ::us/uuid)
|
||||||
|
(s/def ::project ::us/uuid)
|
||||||
|
(s/def ::name ::us/not-empty-string)
|
||||||
|
(s/def ::width ::us/number-str)
|
||||||
|
(s/def ::height ::us/number-str)
|
||||||
|
|
||||||
|
(s/def ::page-form
|
||||||
|
(s/keys :req-un [::id
|
||||||
|
::project
|
||||||
|
::name
|
||||||
|
::width
|
||||||
|
::height]))
|
||||||
|
|
||||||
|
(def defaults
|
||||||
|
{:name ""
|
||||||
|
:width "1366"
|
||||||
|
:height "768"})
|
||||||
|
|
||||||
|
(defn- on-submit
|
||||||
|
[event form]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(modal/hide!)
|
||||||
|
(let [data (:clean-data form)]
|
||||||
|
(if (nil? (:id data))
|
||||||
|
(st/emit! (udp/form->create-page data))
|
||||||
|
(st/emit! (udp/form->update-page data)))))
|
||||||
|
|
||||||
|
(defn- swap-size
|
||||||
|
[event {:keys [data] :as form}]
|
||||||
|
(swap! data assoc
|
||||||
|
:width (:height data)
|
||||||
|
:height (:width data)))
|
||||||
|
|
||||||
|
(defn- initial-data
|
||||||
|
[page]
|
||||||
|
(merge {:name "" :width "1366" :height "768"}
|
||||||
|
(select-keys page [:name :id :project])
|
||||||
|
(select-keys (:metadata page) [:width :height])))
|
||||||
|
|
||||||
|
(mf/defc page-form
|
||||||
|
[{:keys [page] :as props}]
|
||||||
|
(let [{:keys [data] :as form} (fm/use-form ::page-form #(initial-data page))]
|
||||||
|
[:form {:on-submit #(on-submit % form)}
|
||||||
|
[:input.input-text
|
||||||
|
{:placeholder "Page name"
|
||||||
|
:type "text"
|
||||||
|
:name "name"
|
||||||
|
:class (fm/error-class form :name)
|
||||||
|
:on-blur (fm/on-input-blur form :name)
|
||||||
|
:on-change (fm/on-input-change form :name)
|
||||||
|
:value (:name data)
|
||||||
|
:auto-focus true}]
|
||||||
|
[:div.project-size
|
||||||
|
[:div.input-element.pixels
|
||||||
|
[:span "Width"]
|
||||||
|
[:input#project-witdh.input-text
|
||||||
|
{:placeholder "Width"
|
||||||
|
:name "width"
|
||||||
|
:type "number"
|
||||||
|
:min 0
|
||||||
|
:max 5000
|
||||||
|
:class (fm/error-class form :width)
|
||||||
|
:on-blur (fm/on-input-blur form :width)
|
||||||
|
:on-change (fm/on-input-change form :width)
|
||||||
|
:value (:width data)}]]
|
||||||
|
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
|
||||||
|
[:div.input-element.pixels
|
||||||
|
[:span "Height"]
|
||||||
|
[:input#project-height.input-text
|
||||||
|
{:placeholder "Height"
|
||||||
|
:name "height"
|
||||||
|
:type "number"
|
||||||
|
:min 0
|
||||||
|
:max 5000
|
||||||
|
:class (fm/error-class form :height)
|
||||||
|
:on-blur (fm/on-input-blur form :height)
|
||||||
|
:on-change (fm/on-input-change form :height)
|
||||||
|
:value (:height data)}]]]
|
||||||
|
[:input.btn-primary
|
||||||
|
{:value "Go go go!"
|
||||||
|
:type "submit"
|
||||||
|
:class (when-not (:valid form) "btn-disabled")
|
||||||
|
:disabled (not (:valid form))}]]))
|
||||||
|
|
||||||
|
(mf/defc page-form-dialog
|
||||||
|
[{:keys [page] :as props}]
|
||||||
|
[:div.lightbox-body
|
||||||
|
(if (nil? (:id page))
|
||||||
|
[:h3 "New page"]
|
||||||
|
[:h3 "Edit page"])
|
||||||
|
[:& page-form {:page page}]
|
||||||
|
[:a.close {:on-click modal/hide!} i/close]])
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
|
||||||
|
|
||||||
(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform
|
|
||||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
|
||||||
[lentes.core :as l]
|
|
||||||
[uxbox.builtins.icons :as i]
|
|
||||||
[uxbox.main.store :as st]
|
|
||||||
[uxbox.main.constants :as c]
|
|
||||||
[uxbox.main.data.pages :as udp]
|
|
||||||
[uxbox.main.data.lightbox :as udl]
|
|
||||||
[uxbox.main.ui.lightbox :as lbx]
|
|
||||||
[uxbox.util.data :refer [parse-int]]
|
|
||||||
[uxbox.util.dom :as dom]
|
|
||||||
[uxbox.util.forms :as fm]
|
|
||||||
[uxbox.util.i18n :refer (tr)]
|
|
||||||
[uxbox.util.router :as r]
|
|
||||||
[rumext.core :as mx :include-macros true]))
|
|
||||||
|
|
||||||
|
|
||||||
(def form-data (fm/focus-data :workspace-page-form st/state))
|
|
||||||
(def form-errors (fm/focus-errors :workspace-page-form st/state))
|
|
||||||
|
|
||||||
(def assoc-value (partial fm/assoc-value :workspace-page-form))
|
|
||||||
(def assoc-error (partial fm/assoc-error :workspace-page-form))
|
|
||||||
(def clear-form (partial fm/clear-form :workspace-page-form))
|
|
||||||
|
|
||||||
;; --- Lightbox
|
|
||||||
|
|
||||||
(s/def ::name ::fm/non-empty-string)
|
|
||||||
(s/def ::layout ::fm/non-empty-string)
|
|
||||||
(s/def ::width number?)
|
|
||||||
(s/def ::height number?)
|
|
||||||
|
|
||||||
(s/def ::page-form
|
|
||||||
(s/keys :req-un [::name
|
|
||||||
::width
|
|
||||||
::height
|
|
||||||
::layout]))
|
|
||||||
|
|
||||||
(mx/defc layout-input
|
|
||||||
[data id]
|
|
||||||
(let [{:keys [id name width height]} (get c/page-layouts id)]
|
|
||||||
(letfn [(on-change [event]
|
|
||||||
(st/emit! (assoc-value :layout id)
|
|
||||||
(assoc-value :width width)
|
|
||||||
(assoc-value :height height)))]
|
|
||||||
[:div
|
|
||||||
[:input {:type "radio"
|
|
||||||
:id id
|
|
||||||
:name "project-layout"
|
|
||||||
:value id
|
|
||||||
:checked (when (= id (:layout data)) "checked")
|
|
||||||
:on-change on-change}]
|
|
||||||
[:label {:value id :for id} name]])))
|
|
||||||
|
|
||||||
(mx/defc page-form
|
|
||||||
{:mixins [mx/static mx/reactive]}
|
|
||||||
[{:keys [metadata id] :as page}]
|
|
||||||
(let [data (merge c/page-defaults
|
|
||||||
(select-keys page [:name :id :project])
|
|
||||||
(select-keys metadata [:width :height :layout])
|
|
||||||
(mx/react form-data))
|
|
||||||
valid? (fm/valid? ::page-form data)]
|
|
||||||
(letfn [(update-size [field e]
|
|
||||||
(let [value (dom/event->value e)
|
|
||||||
value (parse-int value)]
|
|
||||||
(st/emit! (assoc-value field value))))
|
|
||||||
(update-name [e]
|
|
||||||
(let [value (dom/event->value e)]
|
|
||||||
(st/emit! (assoc-value :name value))))
|
|
||||||
(toggle-sizes []
|
|
||||||
(let [{:keys [width height]} data]
|
|
||||||
(st/emit! (assoc-value :width width)
|
|
||||||
(assoc-value :height height))))
|
|
||||||
(on-cancel [e]
|
|
||||||
(dom/prevent-default e)
|
|
||||||
(udl/close!))
|
|
||||||
(on-save [e]
|
|
||||||
(dom/prevent-default e)
|
|
||||||
(udl/close!)
|
|
||||||
(if (nil? id)
|
|
||||||
(st/emit! (udp/create-page data))
|
|
||||||
(st/emit! (udp/persist-page-update-form id data))))]
|
|
||||||
[:form
|
|
||||||
[:input#project-name.input-text
|
|
||||||
{:placeholder (tr "ds.page.placeholder")
|
|
||||||
:type "text"
|
|
||||||
:value (:name data "")
|
|
||||||
:auto-focus true
|
|
||||||
:on-change update-name}]
|
|
||||||
[:div.project-size
|
|
||||||
[:div.input-element.pixels
|
|
||||||
[:span (tr "ds.width")]
|
|
||||||
[:input#project-witdh.input-text
|
|
||||||
{:placeholder (tr "ds.width")
|
|
||||||
:type "number"
|
|
||||||
:min 0
|
|
||||||
:max 4000
|
|
||||||
:value (:width data)
|
|
||||||
:on-change #(update-size :width %)}]]
|
|
||||||
[:a.toggle-layout {:on-click toggle-sizes} i/toggle]
|
|
||||||
[:div.input-element.pixels
|
|
||||||
[:span (tr "ds.height")]
|
|
||||||
[:input#project-height.input-text
|
|
||||||
{:placeholder (tr "ds.height")
|
|
||||||
:type "number"
|
|
||||||
:min 0
|
|
||||||
:max 4000
|
|
||||||
:value (:height data)
|
|
||||||
:on-change #(update-size :height %)}]]]
|
|
||||||
|
|
||||||
[:div.input-radio.radio-primary
|
|
||||||
(layout-input data "mobile")
|
|
||||||
(layout-input data "tablet")
|
|
||||||
(layout-input data "notebook")
|
|
||||||
(layout-input data "desktop")]
|
|
||||||
|
|
||||||
[:input#project-btn.btn-primary
|
|
||||||
{:value (tr "ds.go")
|
|
||||||
:disabled (not valid?)
|
|
||||||
:on-click on-save
|
|
||||||
:type "button"}]])))
|
|
||||||
|
|
||||||
(mx/defc page-form-lightbox
|
|
||||||
{:mixins [mx/static (fm/clear-mixin st/store :workspace-page-form)]}
|
|
||||||
[{:keys [id] :as page}]
|
|
||||||
(letfn [(on-cancel [event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(udl/close!))]
|
|
||||||
(let [creation? (nil? id)]
|
|
||||||
[:div.lightbox-body
|
|
||||||
(if creation?
|
|
||||||
[:h3 (tr "ds.page.new")]
|
|
||||||
[:h3 (tr "ds.page.edit")])
|
|
||||||
(page-form page)
|
|
||||||
[:a.close {:on-click on-cancel} i/close]])))
|
|
||||||
|
|
||||||
(defmethod lbx/render-lightbox :page-form
|
|
||||||
[{:keys [page]}]
|
|
||||||
(page-form-lightbox page))
|
|
|
@ -52,21 +52,7 @@
|
||||||
(and (mouse-event? v)
|
(and (mouse-event? v)
|
||||||
(= :click (:type v))))
|
(= :click (:type v))))
|
||||||
|
|
||||||
(defrecord PointerEvent [window
|
(defrecord PointerEvent [source pt ctrl shift])
|
||||||
viewport
|
|
||||||
ctrl
|
|
||||||
shift])
|
|
||||||
|
|
||||||
(defn pointer-event
|
|
||||||
[window viewport ctrl shift]
|
|
||||||
{:pre [(gpt/point? window)
|
|
||||||
(gpt/point? viewport)
|
|
||||||
(boolean? ctrl)
|
|
||||||
(boolean? shift)]}
|
|
||||||
(PointerEvent. window
|
|
||||||
viewport
|
|
||||||
ctrl
|
|
||||||
shift))
|
|
||||||
|
|
||||||
(defn pointer-event?
|
(defn pointer-event?
|
||||||
[v]
|
[v]
|
||||||
|
@ -90,33 +76,26 @@
|
||||||
|
|
||||||
;; --- Derived streams
|
;; --- Derived streams
|
||||||
|
|
||||||
;; TODO: this shoul be DEPRECATED
|
|
||||||
(defonce interaction-events
|
|
||||||
(rx/filter interaction-event? st/stream))
|
|
||||||
|
|
||||||
(defonce mouse-position
|
(defonce mouse-position
|
||||||
(rx/filter pointer-event? st/stream))
|
(let [sub (rx/behavior-subject nil)
|
||||||
|
ob (->> st/stream
|
||||||
(defonce viewport-mouse-position
|
(rx/filter pointer-event?)
|
||||||
(let [sub (rx/behavior-subject nil)]
|
(rx/filter #(= :viewport (:source %)))
|
||||||
(-> (rx/map :viewport mouse-position)
|
(rx/map :pt))]
|
||||||
(rx/subscribe-with sub))
|
(rx/subscribe-with ob sub)
|
||||||
sub))
|
|
||||||
|
|
||||||
(defonce window-mouse-position
|
|
||||||
(let [sub (rx/behavior-subject nil)]
|
|
||||||
(-> (rx/map :window mouse-position)
|
|
||||||
(rx/subscribe-with sub))
|
|
||||||
sub))
|
sub))
|
||||||
|
|
||||||
(defonce mouse-position-ctrl
|
(defonce mouse-position-ctrl
|
||||||
(let [sub (rx/behavior-subject nil)]
|
(let [sub (rx/behavior-subject nil)
|
||||||
(-> (rx/map :ctrl mouse-position)
|
ob (->> st/stream
|
||||||
(rx/subscribe-with sub))
|
(rx/filter pointer-event?)
|
||||||
|
(rx/map :ctrl)
|
||||||
|
(rx/dedupe))]
|
||||||
|
(rx/subscribe-with ob sub)
|
||||||
sub))
|
sub))
|
||||||
|
|
||||||
(defonce mouse-position-deltas
|
(defonce mouse-position-deltas
|
||||||
(->> viewport-mouse-position
|
(->> mouse-position
|
||||||
(rx/sample 10)
|
(rx/sample 10)
|
||||||
(rx/map #(gpt/divide % @refs/selected-zoom))
|
(rx/map #(gpt/divide % @refs/selected-zoom))
|
||||||
(rx/mapcat (fn [point]
|
(rx/mapcat (fn [point]
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
[uxbox.main.refs :as refs]
|
[uxbox.main.refs :as refs]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.main.ui.workspace.canvas :refer [canvas]]
|
|
||||||
[uxbox.main.ui.workspace.grid :refer [grid]]
|
[uxbox.main.ui.workspace.grid :refer [grid]]
|
||||||
[uxbox.main.ui.workspace.ruler :refer [ruler]]
|
[uxbox.main.ui.workspace.ruler :refer [ruler]]
|
||||||
[uxbox.main.ui.workspace.streams :as uws]
|
[uxbox.main.ui.workspace.streams :as uws]
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
|
|
||||||
(mf/defc coordinates
|
(mf/defc coordinates
|
||||||
[{:keys [zoom] :as props}]
|
[{:keys [zoom] :as props}]
|
||||||
(let [coords (some-> (use-rxsub uws/viewport-mouse-position)
|
(let [coords (some-> (use-rxsub uws/mouse-position)
|
||||||
(gpt/divide zoom)
|
(gpt/divide zoom)
|
||||||
(gpt/round 0))]
|
(gpt/round 0))]
|
||||||
[:ul.coordinates
|
[:ul.coordinates
|
||||||
|
@ -60,16 +59,16 @@
|
||||||
:circle "Drag to draw a Circle"
|
:circle "Drag to draw a Circle"
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(mf/defc cursor-tooltip
|
;; (mf/defc cursor-tooltip
|
||||||
{:wrap [mf/wrap-memo]}
|
;; {:wrap [mf/wrap-memo]}
|
||||||
[{:keys [tooltip]}]
|
;; [{:keys [tooltip]}]
|
||||||
(let [coords (mf/deref refs/window-mouse-position)]
|
;; (let [coords (mf/deref refs/window-mouse-position)]
|
||||||
[:span.cursor-tooltip
|
;; [:span.cursor-tooltip
|
||||||
{:style
|
;; {:style
|
||||||
{:position "fixed"
|
;; {:position "fixed"
|
||||||
:left (str (+ (:x coords) 5) "px")
|
;; :left (str (+ (:x coords) 5) "px")
|
||||||
:top (str (- (:y coords) 25) "px")}}
|
;; :top (str (- (:y coords) 25) "px")}}
|
||||||
tooltip]))
|
;; tooltip]))
|
||||||
|
|
||||||
;; --- Selection Rect
|
;; --- Selection Rect
|
||||||
|
|
||||||
|
@ -89,15 +88,13 @@
|
||||||
(reify
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [stoper (->> (rx/merge (rx/filter #(= % :interrupt) stream)
|
(let [stoper (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream)]
|
||||||
(rx/filter uws/mouse-up? stream))
|
|
||||||
(rx/take 1))]
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> uws/viewport-mouse-position
|
(rx/of (dw/deselect-all))
|
||||||
|
(->> uws/mouse-position
|
||||||
(rx/map (fn [pos] #(update-state % pos)))
|
(rx/map (fn [pos] #(update-state % pos)))
|
||||||
(rx/take-until stoper))
|
(rx/take-until stoper))
|
||||||
(rx/of (dw/deselect-all)
|
(rx/of dw/select-shapes-by-current-selrect
|
||||||
dw/select-shapes-by-current-selrect
|
|
||||||
clear-state)))))))
|
clear-state)))))))
|
||||||
|
|
||||||
(mf/defc selrect
|
(mf/defc selrect
|
||||||
|
@ -115,34 +112,76 @@
|
||||||
;; --- Viewport Positioning
|
;; --- Viewport Positioning
|
||||||
|
|
||||||
(def handle-viewport-positioning
|
(def handle-viewport-positioning
|
||||||
(reify
|
(letfn [(on-point [dom reference point]
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [stoper (->> (rx/filter #(= ::finish-positioning %) stream)
|
|
||||||
(rx/take 1))
|
|
||||||
reference @uws/viewport-mouse-position
|
|
||||||
dom (dom/get-element "workspace-viewport")]
|
|
||||||
(->> uws/viewport-mouse-position
|
|
||||||
(rx/map (fn [point]
|
|
||||||
(let [{:keys [x y]} (gpt/subtract point reference)
|
(let [{:keys [x y]} (gpt/subtract point reference)
|
||||||
cx (.-scrollLeft dom)
|
cx (.-scrollLeft dom)
|
||||||
cy (.-scrollTop dom)]
|
cy (.-scrollTop dom)]
|
||||||
(set! (.-scrollLeft dom) (- cx x))
|
(set! (.-scrollLeft dom) (- cx x))
|
||||||
(set! (.-scrollTop dom) (- cy y)))))
|
(set! (.-scrollTop dom) (- cy y))))]
|
||||||
(rx/take-until stoper)
|
(reify
|
||||||
(rx/ignore))))))
|
ptk/EffectEvent
|
||||||
|
(effect [_ state stream]
|
||||||
|
(let [stoper (rx/filter #(= ::finish-positioning %) stream)
|
||||||
|
reference @uws/mouse-position
|
||||||
|
dom (dom/get-element "workspace-viewport")]
|
||||||
|
(-> (rx/take-until stoper uws/mouse-position)
|
||||||
|
(rx/subscribe #(on-point dom reference %))))))))
|
||||||
|
|
||||||
;; --- Viewport
|
;; --- Viewport
|
||||||
|
|
||||||
(mf/def viewport
|
(mf/defc viewport
|
||||||
:init
|
[{:keys [page] :as props}]
|
||||||
(fn [own props]
|
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/deref refs/workspace)
|
||||||
(assoc own ::viewport (mf/create-ref)))
|
viewport-ref (mf/use-ref nil)
|
||||||
|
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
||||||
|
zoom (or zoom 1)]
|
||||||
|
(letfn [(on-mouse-down [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
|
shift? (kbd/shift? event)
|
||||||
|
opts {:shift? shift?
|
||||||
|
:ctrl? ctrl?}]
|
||||||
|
(st/emit! (uws/mouse-event :down ctrl? shift?)))
|
||||||
|
(when (not edition)
|
||||||
|
(if drawing-tool
|
||||||
|
(st/emit! (start-drawing drawing-tool))
|
||||||
|
(st/emit! :interrupt handle-selrect))))
|
||||||
|
|
||||||
:did-mount
|
(on-context-menu [event]
|
||||||
(fn [own]
|
(dom/prevent-default event)
|
||||||
(letfn [(translate-point-to-viewport [pt]
|
(dom/stop-propagation event)
|
||||||
(let [viewport (mf/ref-node (::viewport own))
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
|
shift? (kbd/shift? event)
|
||||||
|
opts {:shift? shift?
|
||||||
|
:ctrl? ctrl?}]
|
||||||
|
(st/emit! (uws/mouse-event :context-menu ctrl? shift?))))
|
||||||
|
|
||||||
|
(on-mouse-up [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
|
shift? (kbd/shift? event)
|
||||||
|
opts {:shift? shift?
|
||||||
|
:ctrl? ctrl?}]
|
||||||
|
(st/emit! (uws/mouse-event :up ctrl? shift?))))
|
||||||
|
|
||||||
|
(on-click [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
|
shift? (kbd/shift? event)
|
||||||
|
opts {:shift? shift?
|
||||||
|
:ctrl? ctrl?}]
|
||||||
|
(st/emit! (uws/mouse-event :click ctrl? shift?))))
|
||||||
|
|
||||||
|
(on-double-click [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
|
shift? (kbd/shift? event)
|
||||||
|
opts {:shift? shift?
|
||||||
|
:ctrl? ctrl?}]
|
||||||
|
(st/emit! (uws/mouse-event :double-click ctrl? shift?))))
|
||||||
|
|
||||||
|
(translate-point-to-viewport [pt]
|
||||||
|
(let [viewport (mf/ref-node viewport-ref)
|
||||||
brect (.getBoundingClientRect viewport)
|
brect (.getBoundingClientRect viewport)
|
||||||
brect (gpt/point (parse-int (.-left brect))
|
brect (gpt/point (parse-int (.-left brect))
|
||||||
(parse-int (.-top brect)))]
|
(parse-int (.-top brect)))]
|
||||||
|
@ -173,98 +212,38 @@
|
||||||
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
|
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
|
||||||
(st/emit! (uws/keyboard-event :up key ctrl? shift?))))
|
(st/emit! (uws/keyboard-event :up key ctrl? shift?))))
|
||||||
|
|
||||||
(on-mousemove [event]
|
(on-mouse-move [event]
|
||||||
(let [wpt (gpt/point (.-clientX event)
|
(let [pt (gpt/point (.-clientX event)
|
||||||
(.-clientY event))
|
(.-clientY event))
|
||||||
vpt (translate-point-to-viewport wpt)
|
pt (translate-point-to-viewport pt)]
|
||||||
ctrl? (kbd/ctrl? event)
|
(st/emit! (uws/->PointerEvent :viewport pt
|
||||||
shift? (kbd/shift? event)
|
(kbd/ctrl? event)
|
||||||
event {:ctrl ctrl?
|
(kbd/shift? event)))))
|
||||||
:shift shift?
|
|
||||||
:window-coords wpt
|
|
||||||
:viewport-coords vpt}]
|
|
||||||
(st/emit! (uws/pointer-event wpt vpt ctrl? shift?))))]
|
|
||||||
|
|
||||||
(let [key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove)
|
(on-mount []
|
||||||
key2 (events/listen js/document EventType.KEYDOWN on-key-down)
|
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
|
||||||
key3 (events/listen js/document EventType.KEYUP on-key-up)]
|
key2 (events/listen js/document EventType.KEYUP on-key-up)]
|
||||||
(assoc own
|
(fn []
|
||||||
::key1 key1
|
(events/unlistenByKey key1)
|
||||||
::key2 key2
|
(events/unlistenByKey key2))))]
|
||||||
::key3 key3))))
|
|
||||||
|
|
||||||
:will-unmount
|
(mf/use-effect on-mount)
|
||||||
(fn [own]
|
|
||||||
(events/unlistenByKey (::key1 own))
|
|
||||||
(events/unlistenByKey (::key2 own))
|
|
||||||
(events/unlistenByKey (::key3 own))
|
|
||||||
(dissoc own ::key1 ::key2 ::key3))
|
|
||||||
|
|
||||||
:mixins [mf/reactive]
|
|
||||||
|
|
||||||
:render
|
|
||||||
(fn [own {:keys [page] :as props}]
|
|
||||||
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/react refs/workspace)
|
|
||||||
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
|
||||||
zoom (or zoom 1)]
|
|
||||||
(letfn [(on-mouse-down [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
|
||||||
shift? (kbd/shift? event)
|
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (uws/mouse-event :down ctrl? shift?)))
|
|
||||||
(when (not edition)
|
|
||||||
(if drawing-tool
|
|
||||||
(st/emit! (start-drawing drawing-tool))
|
|
||||||
(st/emit! :interrupt handle-selrect))))
|
|
||||||
(on-context-menu [event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
|
||||||
shift? (kbd/shift? event)
|
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (uws/mouse-event :context-menu ctrl? shift?))))
|
|
||||||
(on-mouse-up [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
|
||||||
shift? (kbd/shift? event)
|
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (uws/mouse-event :up ctrl? shift?))))
|
|
||||||
(on-click [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
|
||||||
shift? (kbd/shift? event)
|
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (uws/mouse-event :click ctrl? shift?))))
|
|
||||||
(on-double-click [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
|
||||||
shift? (kbd/shift? event)
|
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (uws/mouse-event :double-click ctrl? shift?))))]
|
|
||||||
[:*
|
[:*
|
||||||
[:& coordinates {:zoom zoom}]
|
[:& coordinates {:zoom zoom}]
|
||||||
[:div.tooltip-container
|
#_[:div.tooltip-container
|
||||||
(when tooltip
|
(when tooltip
|
||||||
[:& cursor-tooltip {:tooltip tooltip}])]
|
[:& cursor-tooltip {:tooltip tooltip}])]
|
||||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||||
:height (* c/viewport-height zoom)
|
:height (* c/viewport-height zoom)
|
||||||
:ref (::viewport own)
|
:ref viewport-ref
|
||||||
:class (when drawing-tool "drawing")
|
:class (when drawing-tool "drawing")
|
||||||
:on-context-menu on-context-menu
|
:on-context-menu on-context-menu
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:on-double-click on-double-click
|
:on-double-click on-double-click
|
||||||
|
:on-mouse-move on-mouse-move
|
||||||
:on-mouse-down on-mouse-down
|
:on-mouse-down on-mouse-down
|
||||||
:on-mouse-up on-mouse-up}
|
:on-mouse-up on-mouse-up}
|
||||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||||
(when page
|
|
||||||
[:& canvas {:page page :wst wst}])
|
|
||||||
|
|
||||||
(when page
|
(when page
|
||||||
[:*
|
[:*
|
||||||
(for [id (reverse (:shapes page))]
|
(for [id (reverse (:shapes page))]
|
||||||
|
@ -284,4 +263,5 @@
|
||||||
[:& grid {:page page}])]
|
[:& grid {:page page}])]
|
||||||
(when (contains? flags :ruler)
|
(when (contains? flags :ruler)
|
||||||
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
||||||
[:& selrect {:data (:selrect wst)}]]]))))
|
[:& selrect {:data (:selrect wst)}]]])))
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
"Return a indexed map of the collection
|
"Return a indexed map of the collection
|
||||||
keyed by the result of executing the getter
|
keyed by the result of executing the getter
|
||||||
over each element of the collection."
|
over each element of the collection."
|
||||||
[coll getter]
|
[getter coll]
|
||||||
(persistent!
|
(persistent!
|
||||||
(reduce #(assoc! %1 (getter %2) %2) (transient {}) coll)))
|
(reduce #(assoc! %1 (getter %2) %2) (transient {}) coll)))
|
||||||
|
|
||||||
(def index-by-id #(index-by % :id))
|
(def index-by-id #(index-by :id %))
|
||||||
|
|
||||||
(defn remove-nil-vals
|
(defn remove-nil-vals
|
||||||
"Given a map, return a map removing key-value
|
"Given a map, return a map removing key-value
|
||||||
|
|
|
@ -5,208 +5,118 @@
|
||||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.util.forms
|
(ns uxbox.util.forms
|
||||||
|
(:refer-clojure :exclude [uuid])
|
||||||
(:require
|
(:require
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s :include-macros true]
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[rumext.core :as mx :include-macros true]
|
[rumext.alpha :as mf]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.util.i18n :refer [tr]]))
|
[uxbox.util.i18n :refer [tr]]))
|
||||||
|
|
||||||
;; --- Form Validation Api
|
;; --- Handlers Helpers
|
||||||
|
|
||||||
|
(defn- impl-mutator
|
||||||
|
[v update-fn]
|
||||||
|
(specify v
|
||||||
|
IReset
|
||||||
|
(-reset! [_ new-value]
|
||||||
|
(update-fn new-value))
|
||||||
|
|
||||||
|
ISwap
|
||||||
|
(-swap!
|
||||||
|
([self f] (update-fn f))
|
||||||
|
([self f x] (update-fn #(f % x)))
|
||||||
|
([self f x y] (update-fn #(f % x y)))
|
||||||
|
([self f x y more] (update-fn #(apply f % x y more))))))
|
||||||
|
|
||||||
|
(defn- translate-error-type
|
||||||
|
[name]
|
||||||
|
"errors.undefined-error")
|
||||||
|
|
||||||
(defn- interpret-problem
|
(defn- interpret-problem
|
||||||
[acc {:keys [path pred val via in] :as problem}]
|
[acc {:keys [path pred val via in] :as problem}]
|
||||||
|
;; (prn "interpret-problem" problem)
|
||||||
(cond
|
(cond
|
||||||
(and (empty? path)
|
(and (empty? path)
|
||||||
(= (first pred) 'contains?))
|
(list? pred)
|
||||||
(let [path (conj path (last pred))]
|
(= (first (last pred)) 'cljs.core/contains?))
|
||||||
(update-in acc path assoc :missing))
|
(let [path (conj path (last (last pred)))]
|
||||||
|
(assoc-in acc path {:name ::missing :type :builtin}))
|
||||||
|
|
||||||
(and (seq path)
|
(and (not (empty? path))
|
||||||
(= 1 (count path)))
|
(not (empty? via)))
|
||||||
(update-in acc path assoc :invalid)
|
(assoc-in acc path {:name (last via) :type :builtin})
|
||||||
|
|
||||||
:else acc))
|
:else acc))
|
||||||
|
|
||||||
(defn validate
|
(defn use-form
|
||||||
[spec data]
|
[spec initial]
|
||||||
(when-not (s/valid? spec data)
|
(let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial)
|
||||||
(let [report (s/explain-data spec data)]
|
:errors {}
|
||||||
(reduce interpret-problem {} (::s/problems report)))))
|
:touched {}})
|
||||||
|
clean-data (s/conform spec (:data state))
|
||||||
|
problems (when (= ::s/invalid clean-data)
|
||||||
|
(::s/problems (s/explain-data spec (:data state))))
|
||||||
|
|
||||||
(defn valid?
|
|
||||||
[spec data]
|
errors (merge (reduce interpret-problem {} problems)
|
||||||
(s/valid? spec data))
|
(:errors state))]
|
||||||
|
(-> (assoc state
|
||||||
|
:errors errors
|
||||||
|
:clean-data (when (not= clean-data ::s/invalid) clean-data)
|
||||||
|
:valid (and (empty? errors)
|
||||||
|
(not= clean-data ::s/invalid)))
|
||||||
|
(impl-mutator update-state))))
|
||||||
|
|
||||||
|
(defn on-input-change
|
||||||
|
[{:keys [data] :as form} field]
|
||||||
|
(fn [event]
|
||||||
|
(let [target (dom/get-target event)
|
||||||
|
value (dom/get-value target)]
|
||||||
|
(swap! form (fn [state]
|
||||||
|
(-> state
|
||||||
|
(assoc-in [:data field] value)
|
||||||
|
(update :errors dissoc field)))))))
|
||||||
|
|
||||||
|
(defn on-input-blur
|
||||||
|
[{:keys [touched] :as form} field]
|
||||||
|
(fn [event]
|
||||||
|
(let [target (dom/get-target event)]
|
||||||
|
(when-not (get touched field)
|
||||||
|
(swap! form assoc-in [:touched field] true)))))
|
||||||
|
|
||||||
|
;; --- Helper Components
|
||||||
|
|
||||||
|
(mf/defc field-error
|
||||||
|
[{:keys [form field type]
|
||||||
|
:or {only (constantly true)}
|
||||||
|
:as props}]
|
||||||
|
(let [touched? (get-in form [:touched field])
|
||||||
|
{:keys [message code] :as error} (get-in form [:errors field])]
|
||||||
|
(when (and touched? error
|
||||||
|
(cond
|
||||||
|
(nil? type) true
|
||||||
|
(keyword? type) (= (:type error) type)
|
||||||
|
(ifn? type) (type (:type error))
|
||||||
|
:else false))
|
||||||
|
(prn "field-error" error)
|
||||||
|
[:ul.form-errors
|
||||||
|
[:li {:key code} (tr message)]])))
|
||||||
|
|
||||||
|
(defn error-class
|
||||||
|
[form field]
|
||||||
|
(when (and (get-in form [:errors field])
|
||||||
|
(get-in form [:touched field]))
|
||||||
|
"invalid"))
|
||||||
|
|
||||||
;; --- Form Specs and Conformers
|
;; --- Form Specs and Conformers
|
||||||
|
|
||||||
(def ^:private email-re
|
;; TODO: migrate to uxbox.util.spec
|
||||||
#"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
|
(s/def ::email ::us/email)
|
||||||
|
(s/def ::not-empty-string ::us/not-empty-string)
|
||||||
(def ^:private number-re
|
(s/def ::color ::us/color)
|
||||||
#"^[-+]?[0-9]*\.?[0-9]+$")
|
(s/def ::number-str ::us/number-str)
|
||||||
|
|
||||||
(def ^:private color-re
|
|
||||||
#"^#[0-9A-Fa-f]{6}$")
|
|
||||||
|
|
||||||
(s/def ::email
|
|
||||||
(s/and string? #(boolean (re-matches email-re %))))
|
|
||||||
|
|
||||||
(s/def ::non-empty-string
|
|
||||||
(s/and string? #(not (str/empty? %))))
|
|
||||||
|
|
||||||
(defn- parse-number
|
|
||||||
[v]
|
|
||||||
(cond
|
|
||||||
(re-matches number-re v) (js/parseFloat v)
|
|
||||||
(number? v) v
|
|
||||||
:else ::s/invalid))
|
|
||||||
|
|
||||||
(s/def ::string-number
|
|
||||||
(s/conformer parse-number str))
|
|
||||||
|
|
||||||
(s/def ::color
|
|
||||||
(s/and string? #(boolean (re-matches color-re %))))
|
|
||||||
|
|
||||||
;; --- Form State Events
|
|
||||||
|
|
||||||
;; --- Assoc Error
|
|
||||||
|
|
||||||
(defrecord AssocError [type field error]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:errors type field] error)))
|
|
||||||
|
|
||||||
(defn assoc-error
|
|
||||||
([type field]
|
|
||||||
(assoc-error type field nil))
|
|
||||||
([type field error]
|
|
||||||
{:pre [(keyword? type)
|
|
||||||
(keyword? field)
|
|
||||||
(any? error)]}
|
|
||||||
(AssocError. type field error)))
|
|
||||||
|
|
||||||
;; --- Assoc Errors
|
|
||||||
|
|
||||||
(defrecord AssocErrors [type errors]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:errors type] errors)))
|
|
||||||
|
|
||||||
(defn assoc-errors
|
|
||||||
([type]
|
|
||||||
(assoc-errors type nil))
|
|
||||||
([type errors]
|
|
||||||
{:pre [(keyword? type)
|
|
||||||
(or (map? errors)
|
|
||||||
(nil? errors))]}
|
|
||||||
(AssocErrors. type errors)))
|
|
||||||
|
|
||||||
;; --- Assoc Value
|
|
||||||
|
|
||||||
(declare clear-error)
|
|
||||||
|
|
||||||
(defrecord AssocValue [type field value]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [form-path (into [:forms type] (if (coll? field) field [field]))]
|
|
||||||
(assoc-in state form-path value)))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(rx/of (clear-error type field))))
|
|
||||||
|
|
||||||
(defn assoc-value
|
|
||||||
[type field value]
|
|
||||||
{:pre [(keyword? type)
|
|
||||||
(keyword? field)
|
|
||||||
(any? value)]}
|
|
||||||
(AssocValue. type field value))
|
|
||||||
|
|
||||||
;; --- Clear Values
|
|
||||||
|
|
||||||
(defrecord ClearValues [type]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:forms type] nil)))
|
|
||||||
|
|
||||||
(defn clear-values
|
|
||||||
[type]
|
|
||||||
{:pre [(keyword? type)]}
|
|
||||||
(ClearValues. type))
|
|
||||||
|
|
||||||
;; --- Clear Error
|
|
||||||
|
|
||||||
(deftype ClearError [type field]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [errors (get-in state [:errors type])]
|
|
||||||
(if (map? errors)
|
|
||||||
(assoc-in state [:errors type] (dissoc errors field))
|
|
||||||
(update state :errors dissoc type)))))
|
|
||||||
|
|
||||||
(defn clear-error
|
|
||||||
[type field]
|
|
||||||
{:pre [(keyword? type)
|
|
||||||
(keyword? field)]}
|
|
||||||
(ClearError. type field))
|
|
||||||
|
|
||||||
;; --- Clear Errors
|
|
||||||
|
|
||||||
(defrecord ClearErrors [type]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:errors type] nil)))
|
|
||||||
|
|
||||||
(defn clear-errors
|
|
||||||
[type]
|
|
||||||
{:pre [(keyword? type)]}
|
|
||||||
(ClearErrors. type))
|
|
||||||
|
|
||||||
;; --- Clear Form
|
|
||||||
|
|
||||||
(deftype ClearForm [type]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(rx/of (clear-values type)
|
|
||||||
(clear-errors type))))
|
|
||||||
|
|
||||||
(defn clear-form
|
|
||||||
[type]
|
|
||||||
{:pre [(keyword? type)]}
|
|
||||||
(ClearForm. type))
|
|
||||||
|
|
||||||
;; --- Helpers
|
|
||||||
|
|
||||||
(defn focus-data
|
|
||||||
[type state]
|
|
||||||
(-> (l/in [:forms type])
|
|
||||||
(l/derive state)))
|
|
||||||
|
|
||||||
(defn focus-errors
|
|
||||||
[type state]
|
|
||||||
(-> (l/in [:errors type])
|
|
||||||
(l/derive state)))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Form UI
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(mx/defc input-error
|
|
||||||
[errors field]
|
|
||||||
(when-let [error (get errors field)]
|
|
||||||
[:ul.form-errors
|
|
||||||
[:li {:key error} (tr error)]]))
|
|
||||||
|
|
||||||
(defn error-class
|
|
||||||
[errors field]
|
|
||||||
(when (get errors field)
|
|
||||||
"invalid"))
|
|
||||||
|
|
||||||
(defn clear-mixin
|
|
||||||
[store type]
|
|
||||||
{:will-unmount (fn [own]
|
|
||||||
(ptk/emit! store (clear-form type))
|
|
||||||
own)})
|
|
||||||
|
|
|
@ -6,15 +6,16 @@
|
||||||
|
|
||||||
(ns uxbox.util.messages
|
(ns uxbox.util.messages
|
||||||
"Messages notifications."
|
"Messages notifications."
|
||||||
(:require [lentes.core :as l]
|
(:require
|
||||||
[cuerdas.core :as str]
|
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[lentes.core :as l]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.util.timers :as ts]
|
|
||||||
[rumext.core :as mx :include-macros true]
|
|
||||||
[uxbox.util.data :refer [classnames]]
|
[uxbox.util.data :refer [classnames]]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.util.timers :as ts]
|
||||||
[uxbox.util.i18n :refer [tr]]))
|
[uxbox.util.i18n :refer [tr]]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -25,33 +26,12 @@
|
||||||
|
|
||||||
(def +animation-timeout+ 600)
|
(def +animation-timeout+ 600)
|
||||||
|
|
||||||
;; --- Message Event
|
;; --- Main API
|
||||||
|
|
||||||
(declare hide)
|
(declare hide)
|
||||||
|
(declare show)
|
||||||
(declare show?)
|
(declare show?)
|
||||||
|
|
||||||
(deftype Show [data]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [message (assoc data :state :visible)]
|
|
||||||
(assoc state :message message)))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state s]
|
|
||||||
(let [stoper (->> (rx/filter show? s)
|
|
||||||
(rx/take 1))]
|
|
||||||
(->> (rx/of (hide))
|
|
||||||
(rx/delay (:timeout data))
|
|
||||||
(rx/take-until stoper)))))
|
|
||||||
|
|
||||||
(defn show
|
|
||||||
[message]
|
|
||||||
(Show. message))
|
|
||||||
|
|
||||||
(defn show?
|
|
||||||
[v]
|
|
||||||
(instance? Show v))
|
|
||||||
|
|
||||||
(defn error
|
(defn error
|
||||||
[message & {:keys [timeout] :or {timeout 3000}}]
|
[message & {:keys [timeout] :or {timeout 3000}}]
|
||||||
(show {:content message
|
(show {:content message
|
||||||
|
@ -72,27 +52,44 @@
|
||||||
:timeout js/Number.MAX_SAFE_INTEGER
|
:timeout js/Number.MAX_SAFE_INTEGER
|
||||||
:type :dialog}))
|
:type :dialog}))
|
||||||
|
|
||||||
;; --- Hide Message
|
;; --- Show Event
|
||||||
|
|
||||||
|
(defn show
|
||||||
|
[data]
|
||||||
|
(reify
|
||||||
|
ptk/EventType
|
||||||
|
(type [_] ::show)
|
||||||
|
|
||||||
(deftype Hide [^:mutable canceled?]
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update state :message
|
(let [message (assoc data :state :visible)]
|
||||||
(fn [v]
|
(assoc state :message message)))
|
||||||
(if (nil? v)
|
|
||||||
(do (set! canceled? true) nil)
|
|
||||||
(assoc v :state :hide)))))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state s]
|
||||||
(if canceled?
|
(let [stoper (->> (rx/filter show? s)
|
||||||
(rx/empty)
|
(rx/take 1))]
|
||||||
(->> (rx/of #(dissoc state :message))
|
(->> (rx/of (hide))
|
||||||
(rx/delay +animation-timeout+)))))
|
(rx/delay (:timeout data))
|
||||||
|
(rx/take-until stoper))))))
|
||||||
|
|
||||||
|
(defn show?
|
||||||
|
[v]
|
||||||
|
(= ::show (ptk/type v)))
|
||||||
|
|
||||||
|
;; --- Hide Event
|
||||||
|
|
||||||
(defn hide
|
(defn hide
|
||||||
[]
|
[]
|
||||||
(Hide. false))
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :message assoc :state :hide))
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(->> (rx/of #(dissoc % :message))
|
||||||
|
(rx/delay +animation-timeout+)))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; UI Components
|
;; UI Components
|
||||||
|
@ -100,10 +97,10 @@
|
||||||
|
|
||||||
;; --- Notification Component
|
;; --- Notification Component
|
||||||
|
|
||||||
(mx/defc notification-box
|
(mf/defc notification-box
|
||||||
{:mixins [mx/static]}
|
[{:keys [message on-close] :as message}]
|
||||||
[{:keys [type on-close] :as message}]
|
(let [type (:type message)
|
||||||
(let [classes (classnames :error (= type :error)
|
classes (classnames :error (= type :error)
|
||||||
:info (= type :info)
|
:info (= type :info)
|
||||||
:hide-message (= (:state message) :hide)
|
:hide-message (= (:state message) :hide)
|
||||||
:quick true)]
|
:quick true)]
|
||||||
|
@ -114,9 +111,8 @@
|
||||||
|
|
||||||
;; --- Dialog Component
|
;; --- Dialog Component
|
||||||
|
|
||||||
(mx/defc dialog-box
|
(mf/defc dialog-box
|
||||||
{:mixins [mx/static mx/reactive]}
|
[{:keys [on-accept on-cancel on-close message] :as props}]
|
||||||
[{:keys [on-accept on-cancel on-close] :as message}]
|
|
||||||
(let [classes (classnames :info true
|
(let [classes (classnames :info true
|
||||||
:hide-message (= (:state message) :hide))]
|
:hide-message (= (:state message) :hide))]
|
||||||
(letfn [(accept [event]
|
(letfn [(accept [event]
|
||||||
|
@ -143,11 +139,10 @@
|
||||||
|
|
||||||
;; --- Main Component (entry point)
|
;; --- Main Component (entry point)
|
||||||
|
|
||||||
(mx/defc messages-widget
|
(mf/defc messages-widget
|
||||||
{:mixins [mx/static mx/reactive]}
|
[{:keys [message] :as props}]
|
||||||
[message]
|
|
||||||
(case (:type message)
|
(case (:type message)
|
||||||
:error (notification-box message)
|
:error (mf/element notification-box props)
|
||||||
:info (notification-box message)
|
:info (mf/element notification-box props)
|
||||||
:dialog (dialog-box message)
|
:dialog (mf/element dialog-box props)
|
||||||
nil))
|
nil))
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.util.spec
|
(ns uxbox.util.spec
|
||||||
(:require [cljs.spec.alpha :as s]))
|
(:require [cljs.spec.alpha :as s]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
|
||||||
;; --- Constants
|
;; --- Constants
|
||||||
|
|
||||||
|
@ -15,11 +17,17 @@
|
||||||
(def uuid-rx
|
(def uuid-rx
|
||||||
#"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
#"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
||||||
|
|
||||||
|
(def number-rx
|
||||||
|
#"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$")
|
||||||
|
|
||||||
|
(def ^:private color-re
|
||||||
|
#"^#[0-9A-Fa-f]{6}$")
|
||||||
|
|
||||||
;; --- Predicates
|
;; --- Predicates
|
||||||
|
|
||||||
(defn email?
|
(defn email?
|
||||||
[v]
|
[v]
|
||||||
(and string?
|
(and (string? v)
|
||||||
(re-matches email-rx v)))
|
(re-matches email-rx v)))
|
||||||
|
|
||||||
(defn color?
|
(defn color?
|
||||||
|
@ -31,8 +39,6 @@
|
||||||
[v]
|
[v]
|
||||||
(instance? js/File v))
|
(instance? js/File v))
|
||||||
|
|
||||||
;; TODO: properly implement
|
|
||||||
|
|
||||||
(defn url-str?
|
(defn url-str?
|
||||||
[v]
|
[v]
|
||||||
(string? v))
|
(string? v))
|
||||||
|
@ -42,6 +48,29 @@
|
||||||
(s/def ::uuid uuid?)
|
(s/def ::uuid uuid?)
|
||||||
(s/def ::email email?)
|
(s/def ::email email?)
|
||||||
(s/def ::color color?)
|
(s/def ::color color?)
|
||||||
|
(s/def ::string string?)
|
||||||
|
(s/def ::number number?)
|
||||||
|
(s/def ::positive pos?)
|
||||||
|
(s/def ::inst inst?)
|
||||||
|
(s/def ::keyword keyword?)
|
||||||
|
(s/def ::fn fn?)
|
||||||
|
(s/def ::coll coll?)
|
||||||
|
|
||||||
|
(s/def ::not-empty-string
|
||||||
|
(s/and string? #(not (str/empty? %))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- conform-number-str
|
||||||
|
[v]
|
||||||
|
(cond
|
||||||
|
(re-matches number-rx v) (js/parseFloat v)
|
||||||
|
(number? v) v
|
||||||
|
:else ::s/invalid))
|
||||||
|
|
||||||
|
(s/def ::number-str
|
||||||
|
(s/conformer conform-number-str str))
|
||||||
|
|
||||||
|
(s/def ::color color?)
|
||||||
|
|
||||||
;; --- Public Api
|
;; --- Public Api
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
(:require [beicon.core :as rx]
|
(:require [beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[uxbox.util.router :as rt]
|
[uxbox.util.router :as rt]
|
||||||
[uxbox.util.forms :as sc]
|
|
||||||
[uxbox.util.data :refer (parse-int)]
|
[uxbox.util.data :refer (parse-int)]
|
||||||
[uxbox.main.repo :as rp]
|
[uxbox.main.repo :as rp]
|
||||||
[uxbox.main.data.pages :as udpg]
|
[uxbox.main.data.pages :as udpg]
|
||||||
|
|
|
@ -56,9 +56,8 @@
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
(mf/defc app
|
(mf/defc app
|
||||||
{:wrap [mf/wrap-reactive]}
|
|
||||||
[]
|
[]
|
||||||
(let [route (mf/react route-ref)]
|
(let [route (mf/deref route-ref)]
|
||||||
(case (get-in route [:data :name])
|
(case (get-in route [:data :name])
|
||||||
:view/notfound (notfound-page)
|
:view/notfound (notfound-page)
|
||||||
:view/viewer (let [{:keys [token id]} (get-in route [:params :path])]
|
:view/viewer (let [{:keys [token id]} (get-in route [:params :path])]
|
||||||
|
|
|
@ -34,9 +34,8 @@
|
||||||
;; --- Component
|
;; --- Component
|
||||||
|
|
||||||
(mf/defc viewer-page
|
(mf/defc viewer-page
|
||||||
{:wrap [mf/wrap-reactive]}
|
|
||||||
[{:keys [token id]}]
|
[{:keys [token id]}]
|
||||||
(let [{:keys [project pages flags]} (mf/react state-ref)]
|
(let [{:keys [project pages flags]} (mf/deref state-ref)]
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
{:fn #(st/emit! (dv/initialize token))})
|
{:fn #(st/emit! (dv/initialize token))})
|
||||||
(when (seq pages)
|
(when (seq pages)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
(:require [cljs.test :as t :include-macros true]
|
(:require [cljs.test :as t :include-macros true]
|
||||||
[cljs.pprint :refer [pprint]]
|
[cljs.pprint :refer [pprint]]
|
||||||
[uxbox.util.uuid :as uuid]
|
[uxbox.util.uuid :as uuid]
|
||||||
[uxbox.main.data.shapes-impl :as impl]))
|
[uxbox.main.data.shapes :as impl]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Duplicate (one shape)
|
;; Duplicate (one shape)
|
||||||
|
|
|
@ -158,7 +158,7 @@
|
||||||
(def figwheel-options
|
(def figwheel-options
|
||||||
{:open-url false
|
{:open-url false
|
||||||
:pprint-config false
|
:pprint-config false
|
||||||
:load-warninged-code true
|
:load-warninged-code false
|
||||||
:auto-testing false
|
:auto-testing false
|
||||||
:css-dirs ["resources/public/css"]
|
:css-dirs ["resources/public/css"]
|
||||||
:ring-server-options {:port 3449 :host "0.0.0.0"}
|
:ring-server-options {:port 3449 :host "0.0.0.0"}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue