mirror of
https://github.com/penpot/penpot.git
synced 2025-02-13 02:28:18 -05:00
🎉 Add initial version of viewer.
This commit is contained in:
parent
b3e6566bd8
commit
1a3a48e4de
24 changed files with 1109 additions and 92 deletions
3
frontend/resources/images/icons/full-screen.svg
Normal file
3
frontend/resources/images/icons/full-screen.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500.00001" width="500" height="500">
|
||||
<path d="M449.67773 20c-9.59809 3.53351-14.43153 13.472828-22.44921 19.304688-36.68366 35.782049-72.17331 72.786642-109.5879 107.818362-.76612-28.84172-.67096-57.696805-.96874-86.544925h-47.55274V231.42383h170.84375v-47.55664c-28.84878-.29635-57.70268-.19723-86.54492-.97071 41.14084-44.14516 85.49381-85.257142 126.57031-129.404292.3673-8.154531-8.63701-11.847784-12.75195-17.839844-5.42547-5.6539-10.9029-11.427284-17.5586-15.652344zM60.037109 268.57617v47.55664c28.84878.29635 57.702691.19723 86.544921.97071-41.14082 44.14516-85.493811 85.25714-126.570311 129.40429-.3673 8.15453 8.637013 11.84779 12.751953 17.83985 5.42547 5.6539 10.902894 11.42728 17.558594 15.65234 9.59809-3.53351 14.431538-13.47283 22.449218-19.30469 36.683666-35.78205 72.173316-72.78663 109.587896-107.81836.76612 28.84173.67096 57.6968.96874 86.54493h47.55274V268.57617H60.037109z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 966 B |
|
@ -30,6 +30,8 @@
|
|||
@import 'main/layouts/projects-page';
|
||||
@import 'main/layouts/recent-files-page';
|
||||
@import 'main/layouts/library-page';
|
||||
@import "main/layouts/not-found";
|
||||
@import "main/layouts/viewer";
|
||||
|
||||
//#################################################
|
||||
// Commons
|
||||
|
@ -69,6 +71,9 @@
|
|||
@import 'main/partials/debug-icons-preview';
|
||||
@import 'main/partials/editable-label';
|
||||
@import 'main/partials/tab-container';
|
||||
@import "main/partials/viewer-header";
|
||||
@import "main/partials/viewer-thumbnails";
|
||||
@import "main/partials/viewer";
|
||||
|
||||
//#################################################
|
||||
// Resources
|
||||
|
|
43
frontend/resources/styles/main/layouts/not-found.scss
Normal file
43
frontend/resources/styles/main/layouts/not-found.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
.not-found-layout {
|
||||
display: grid;
|
||||
|
||||
grid-template-rows: 120px auto;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.not-found-header {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32px;
|
||||
|
||||
svg {
|
||||
height: 55px;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.not-found-content {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 2;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.main-message {
|
||||
font-size: 18rem;
|
||||
color: $color-black;
|
||||
line-height: 226px;
|
||||
}
|
||||
|
||||
.desc-message {
|
||||
font-size: 3rem;
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
25
frontend/resources/styles/main/layouts/viewer.scss
Normal file
25
frontend/resources/styles/main/layouts/viewer.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.viewer-layout {
|
||||
display: grid;
|
||||
grid-template-rows: 40px auto;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
&.fullscreen {
|
||||
.viewer-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.viewer-content {
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.viewer-header {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
}
|
||||
|
||||
.viewer-content {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
}
|
224
frontend/resources/styles/main/partials/viewer-header.scss
Normal file
224
frontend/resources/styles/main/partials/viewer-header.scss
Normal file
|
@ -0,0 +1,224 @@
|
|||
.viewer-header {
|
||||
align-items: center;
|
||||
background-color: $color-gray-50;
|
||||
border-bottom: 1px solid $color-gray-60;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
padding: $x-small $medium $x-small 55px;
|
||||
position: relative;
|
||||
z-index: 12;
|
||||
justify-content: space-between;
|
||||
|
||||
.main-icon {
|
||||
align-items: center;
|
||||
background-color: $color-gray-60;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
|
||||
a {
|
||||
height: 30px;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-30;
|
||||
height: 30px;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sitemap-zone {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: $x-small;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
height: 20px;
|
||||
margin-right: $small;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color-gray-20;
|
||||
margin-right: $x-small;
|
||||
font-size: $fs14;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.frame-name {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
svg {
|
||||
fill: $color-white;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-name {
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.counters {
|
||||
margin-left: $size-3;
|
||||
}
|
||||
}
|
||||
|
||||
.options-zone {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 250px;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn-primary {
|
||||
padding: 0.4rem 1rem;
|
||||
}
|
||||
|
||||
.btn-fullscreen {
|
||||
align-items: center;
|
||||
background-color: $color-gray-60;
|
||||
border-radius: $br-small;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 25px;
|
||||
justify-content: center;
|
||||
width: 25px;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-widget {
|
||||
cursor: pointer;
|
||||
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color-gray-10;
|
||||
font-size: $fs15;
|
||||
margin-left: $x-small;
|
||||
}
|
||||
|
||||
.dropdown-button svg {
|
||||
fill: $color-gray-10;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.zoom-dropdown {
|
||||
position: absolute;
|
||||
right: -25px;
|
||||
top: 45px;
|
||||
z-index: 12;
|
||||
width: 150px;
|
||||
|
||||
background-color: $color-white;
|
||||
border-radius: $br-small;
|
||||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
|
||||
|
||||
li {
|
||||
color: $color-gray-60;
|
||||
cursor: pointer;
|
||||
font-size: $fs12;
|
||||
display: flex;
|
||||
padding: $small;
|
||||
|
||||
span {
|
||||
color: $color-gray-40;
|
||||
font-size: $fs12;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.add-zoom,
|
||||
.remove-zoom {
|
||||
align-items: center;
|
||||
background-color: $color-gray-60;
|
||||
border-radius: $br-small;
|
||||
cursor: pointer;
|
||||
color: $color-gray-20;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
flex-shrink: 0;
|
||||
font-size: $fs20;
|
||||
font-weight: bold;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.add-zoom,
|
||||
.remove-zoom {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.users-zone {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
margin-left: $small;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
border: 3px solid #f3dd14;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
173
frontend/resources/styles/main/partials/viewer-thumbnails.scss
Normal file
173
frontend/resources/styles/main/partials/viewer-thumbnails.scss
Normal file
|
@ -0,0 +1,173 @@
|
|||
|
||||
.viewer-thumbnails {
|
||||
grid-row: 1 / span 1;
|
||||
grid-column: 1 / span 1;
|
||||
|
||||
background-color: $color-gray-50;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 12;
|
||||
|
||||
&.expanded {
|
||||
grid-row: 1 / span 2;
|
||||
|
||||
.btn-expand svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-summary {
|
||||
padding: 0.5rem 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 50px;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $color-gray-30;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
fill: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.counter {
|
||||
color: $color-gray-10;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-content {
|
||||
display: grid;
|
||||
grid-template-columns: 40px auto 40px;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.left-scroll-handler {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
|
||||
background-color: $color-gray-50;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
z-index: 12;
|
||||
cursor: pointer;
|
||||
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-scroll-handler {
|
||||
grid-column: 3 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
|
||||
background-color: $color-gray-50;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
z-index: 12;
|
||||
cursor: pointer;
|
||||
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-list {
|
||||
grid-column: 1 / span 3;
|
||||
grid-row: 1 / span 1;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
.thumbnails-list-inside {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-list-expanded {
|
||||
grid-column: 1 / span 3;
|
||||
grid-row: 1 / span 1;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thumbnail-preview {
|
||||
background-color: $color-gray-40;
|
||||
width: 120px;
|
||||
min-height: 120px;
|
||||
height: 120px;
|
||||
border: 1px solid $color-gray-20;
|
||||
border-radius: 2px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: $color-primary;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $color-primary;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-info {
|
||||
padding: 0.5rem 0;
|
||||
|
||||
span {
|
||||
font-size: $fs13;
|
||||
}
|
||||
}
|
||||
}
|
20
frontend/resources/styles/main/partials/viewer.scss
Normal file
20
frontend/resources/styles/main/partials/viewer.scss
Normal file
|
@ -0,0 +1,20 @@
|
|||
.viewer-content {
|
||||
background-color: black;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 232px auto;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.viewer-preview {
|
||||
height: 100vh;
|
||||
|
||||
grid-row: 1 / span 2;
|
||||
grid-column: 1 / span 1;
|
||||
|
||||
overflow: scroll;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
|
@ -15,6 +15,31 @@
|
|||
position: relative;
|
||||
z-index: 12;
|
||||
|
||||
.preview {
|
||||
align-items: center;
|
||||
background-color: $color-gray-60;
|
||||
border-radius: $br-small;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 25px;
|
||||
justify-content: center;
|
||||
width: 25px;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-menu {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
window.uxboxConfig = JSON.parse({{& config }});
|
||||
window.uxboxTranslations = JSON.parse({{& translations }});
|
||||
</script>
|
||||
<script src="/js/shared.js?ts={{& ts}}"></script>
|
||||
<script src="/js/main.js?ts={{& ts}}"></script>
|
||||
<script>uxbox.main.init()</script>
|
||||
</body>
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
(def fill (icon-xref :fill))
|
||||
(def folder (icon-xref :folder))
|
||||
(def folder-zip (icon-xref :folder-zip))
|
||||
(def full-screen (icon-xref :full-screen))
|
||||
(def grid (icon-xref :grid))
|
||||
(def grid-snap (icon-xref :grid-snap))
|
||||
(def icon-set (icon-xref :icon-set))
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
(st/emit! (rt/nav :login))
|
||||
|
||||
(nil? match)
|
||||
(prn "TODO 404 main")
|
||||
(st/emit! (rt/nav :not-found))
|
||||
|
||||
:else
|
||||
(st/emit! #(assoc % :route match)))))
|
||||
|
|
131
frontend/src/uxbox/main/data/viewer.cljs
Normal file
131
frontend/src/uxbox/main/data/viewer.cljs
Normal file
|
@ -0,0 +1,131 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.data.viewer
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
|
||||
(s/def ::project (s/keys ::req-un [::id ::name]))
|
||||
(s/def ::file (s/keys :req-un [::id ::name]))
|
||||
(s/def ::page (s/keys :req-un [::id ::name ::cp/data]))
|
||||
|
||||
(s/def ::bundle
|
||||
(s/keys :req-un [::project ::file ::page]))
|
||||
|
||||
|
||||
;; --- Initialization
|
||||
|
||||
(declare fetch-bundle)
|
||||
(declare bundle-fetched)
|
||||
|
||||
(defn initialize
|
||||
[page-id]
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :viewer-local {:zoom 1}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (fetch-bundle page-id)))))
|
||||
|
||||
;; --- Data Fetching
|
||||
|
||||
(defn fetch-bundle
|
||||
[page-id]
|
||||
(ptk/reify ::fetch-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :viewer-bundle-by-page-id {:page-id page-id})
|
||||
(rx/map bundle-fetched)))))
|
||||
|
||||
|
||||
(defn- extract-frames
|
||||
[page]
|
||||
(let [objects (get-in page [:data :objects])
|
||||
root (get objects uuid/zero)]
|
||||
(->> (:shapes root)
|
||||
(map #(get objects %))
|
||||
(filter #(= :frame (:type %)))
|
||||
(vec))))
|
||||
|
||||
(defn bundle-fetched
|
||||
[{:keys [project file page images] :as bundle}]
|
||||
(us/verify ::bundle bundle)
|
||||
(ptk/reify ::file-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [frames (extract-frames page)
|
||||
objects (get-in page [:data :objects])]
|
||||
(assoc state :viewer-data {:project project
|
||||
:objects objects
|
||||
:file file
|
||||
:page page
|
||||
:images images
|
||||
:frames frames})))))
|
||||
|
||||
;; --- Zoom Management
|
||||
|
||||
(def increase-zoom
|
||||
(ptk/reify ::increase-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [increase #(nth c/zoom-levels
|
||||
(+ (d/index-of c/zoom-levels %) 1)
|
||||
(last c/zoom-levels))]
|
||||
(update-in state [:viewer-local :zoom] (fnil increase 1))))))
|
||||
|
||||
(def decrease-zoom
|
||||
(ptk/reify ::decrease-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [decrease #(nth c/zoom-levels
|
||||
(- (d/index-of c/zoom-levels %) 1)
|
||||
(first c/zoom-levels))]
|
||||
(update-in state [:viewer-local :zoom] (fnil decrease 1))))))
|
||||
|
||||
(def reset-zoom
|
||||
(ptk/reify ::reset-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :zoom] 1))))
|
||||
|
||||
(def zoom-to-50
|
||||
(ptk/reify ::zoom-to-50
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :zoom] 0.5))))
|
||||
|
||||
(def zoom-to-200
|
||||
(ptk/reify ::zoom-to-200
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :zoom] 2))))
|
||||
|
||||
;; --- Local State Management
|
||||
|
||||
(def toggle-thumbnails-panel
|
||||
(ptk/reify ::toggle-thumbnails-panel
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:viewer-local :show-thumbnails] not))))
|
|
@ -1955,7 +1955,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-page :file-id])
|
||||
path-params {:project-id project-id :file-id file-id}
|
||||
path-params {:file-id file-id :project-id project-id}
|
||||
query-params {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace path-params query-params))))))
|
||||
|
||||
|
@ -2199,7 +2199,7 @@
|
|||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
parent (get-parent (first selected) (vals objects))
|
||||
parent-id (:id parent)
|
||||
parent-id (:id parent)
|
||||
selected-objects (map (partial get objects) selected)
|
||||
selection-rect (geom/selection-rect selected-objects)
|
||||
frame-id (-> selected-objects first :frame-id)
|
||||
|
@ -2254,7 +2254,7 @@
|
|||
:obj group}
|
||||
{:type :mod-obj
|
||||
:id parent-id
|
||||
:operations [{:type :set :attr :shapes :val (:shapes parent)}]}]]
|
||||
:operations [{:type :set :attr :shapes :val (:shapes parent)}]}]]
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))
|
||||
rx/empty)))))
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.ui.shapes.frame :as frame]
|
||||
[uxbox.main.ui.shapes.circle :as circle]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
|
@ -51,6 +53,9 @@
|
|||
(let [children (mapv #(get objects %) (:shapes shape))]
|
||||
[:& group-shape {:shape shape :children children}]))
|
||||
|
||||
(declare group-shape)
|
||||
(declare frame-shape)
|
||||
|
||||
(mf/defc shape-wrapper
|
||||
[{:keys [shape objects] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
|
@ -63,7 +68,7 @@
|
|||
:path [:& path/path-shape {:shape shape}]
|
||||
:image [:& image/image-shape {:shape shape}]
|
||||
:circle [:& circle/circle-shape {:shape shape}]
|
||||
:group [:& (group/group-shape shape-wrapper) {:shape shape :shape-wrapper shape-wrapper :objects objects}]
|
||||
:group [:& group-shape {:shape shape :objects objects}]
|
||||
nil)))
|
||||
|
||||
(def group-shape (group/group-shape shape-wrapper))
|
||||
|
@ -90,15 +95,3 @@
|
|||
:key (:id item)
|
||||
:objects objects}]))]))
|
||||
|
||||
;; (defn- render-html
|
||||
;; [component]
|
||||
;; (.renderToStaticMarkup js/ReactDOMServer component))
|
||||
|
||||
;; (defn render
|
||||
;; [{:keys [data] :as page}]
|
||||
;; (try
|
||||
;; (-> (mf/element page-svg #js {:data data})
|
||||
;; (render-html))
|
||||
;; (catch :default e
|
||||
;; (js/console.log e)
|
||||
;; nil)))
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
(-> (l/key :workspace-file)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-project
|
||||
(-> (l/key :workspace-project)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-images
|
||||
(-> (l/key :workspace-images)
|
||||
(l/derive st/state)))
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.storage :refer [storage]]))
|
||||
|
||||
;; TODO: move outside uxbox.main
|
||||
|
||||
(enable-console-print!)
|
||||
|
||||
(def ^:dynamic *on-error* identity)
|
||||
|
@ -47,6 +49,7 @@
|
|||
(l/derive state)))
|
||||
|
||||
(defn emit!
|
||||
([] nil)
|
||||
([event]
|
||||
(ptk/emit! store event)
|
||||
nil)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.data.auth :refer [logout]]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -25,12 +26,13 @@
|
|||
[uxbox.main.ui.profile.recovery :refer [profile-recovery-page]]
|
||||
[uxbox.main.ui.profile.recovery-request :refer [profile-recovery-request-page]]
|
||||
[uxbox.main.ui.profile.register :refer [profile-register-page]]
|
||||
[uxbox.main.ui.viewer :refer [viewer-page]]
|
||||
[uxbox.main.ui.settings :as settings]
|
||||
[uxbox.main.ui.not-found :refer [not-found-page]]
|
||||
[uxbox.main.ui.shapes]
|
||||
[uxbox.main.ui.workspace :as workspace]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.messages :as uum]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.timers :as ts]))
|
||||
|
||||
(def route-iref
|
||||
|
@ -49,6 +51,9 @@
|
|||
["/profile" :settings-profile]
|
||||
["/password" :settings-password]]
|
||||
|
||||
["/view/:page-id/:index" :viewer]
|
||||
["/not-found" :not-found]
|
||||
|
||||
(when *assert*
|
||||
["/debug/icons-preview" :debug-icons-preview])
|
||||
|
||||
|
@ -78,54 +83,63 @@
|
|||
[{:keys [error] :as props}]
|
||||
(let [data (ex-data error)]
|
||||
(case (:type data)
|
||||
:not-found [:span "404"]
|
||||
:not-found [:& not-found-page {:error data}]
|
||||
[:span "Internal application errror"])))
|
||||
|
||||
(mf/defc app
|
||||
{:wrap [#(wrap-catch % {:fallback app-error})]}
|
||||
[props]
|
||||
(let [route (mf/deref route-iref)]
|
||||
(case (get-in route [:data :name])
|
||||
:login
|
||||
(mf/element login-page)
|
||||
(when route
|
||||
(case (get-in route [:data :name])
|
||||
:login
|
||||
(mf/element login-page)
|
||||
|
||||
:profile-register
|
||||
(mf/element profile-register-page)
|
||||
:profile-register
|
||||
(mf/element profile-register-page)
|
||||
|
||||
:profile-recovery-request
|
||||
(mf/element profile-recovery-request-page)
|
||||
:profile-recovery-request
|
||||
(mf/element profile-recovery-request-page)
|
||||
|
||||
:profile-recovery
|
||||
(mf/element profile-recovery-page)
|
||||
:profile-recovery
|
||||
(mf/element profile-recovery-page)
|
||||
|
||||
(:settings-profile
|
||||
:settings-password)
|
||||
(mf/element settings/settings #js {:route route})
|
||||
:viewer
|
||||
(let [index (d/parse-integer (get-in route [:params :path :index]))
|
||||
page-id (uuid (get-in route [:params :path :page-id]))]
|
||||
[:& viewer-page {:page-id page-id
|
||||
:index index}])
|
||||
|
||||
:debug-icons-preview
|
||||
(when *assert*
|
||||
(mf/element i/debug-icons-preview))
|
||||
(:settings-profile
|
||||
:settings-password)
|
||||
(mf/element settings/settings #js {:route route})
|
||||
|
||||
(:dashboard-search
|
||||
:dashboard-team
|
||||
:dashboard-project
|
||||
:dashboard-library-icons
|
||||
:dashboard-library-icons-index
|
||||
:dashboard-library-images
|
||||
:dashboard-library-images-index
|
||||
:dashboard-library-palettes
|
||||
:dashboard-library-palettes-index)
|
||||
(mf/element dashboard #js {:route route})
|
||||
:debug-icons-preview
|
||||
(when *assert*
|
||||
(mf/element i/debug-icons-preview))
|
||||
|
||||
:workspace
|
||||
(let [project-id (uuid (get-in route [:params :path :project-id]))
|
||||
file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :query :page-id]))]
|
||||
[:& workspace/workspace {:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:key file-id}])
|
||||
nil)))
|
||||
(:dashboard-search
|
||||
:dashboard-team
|
||||
:dashboard-project
|
||||
:dashboard-library-icons
|
||||
:dashboard-library-icons-index
|
||||
:dashboard-library-images
|
||||
:dashboard-library-images-index
|
||||
:dashboard-library-palettes
|
||||
:dashboard-library-palettes-index)
|
||||
(mf/element dashboard #js {:route route})
|
||||
|
||||
:workspace
|
||||
(let [project-id (uuid (get-in route [:params :path :project-id]))
|
||||
file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :query :page-id]))]
|
||||
[:& workspace/workspace {:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:key file-id}])
|
||||
|
||||
:not-found
|
||||
[:& not-found-page {}]))))
|
||||
|
||||
;; --- Error Handling
|
||||
|
||||
|
|
24
frontend/src/uxbox/main/ui/not_found.cljs
Normal file
24
frontend/src/uxbox/main/ui/not_found.cljs
Normal file
|
@ -0,0 +1,24 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.not-found
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]))
|
||||
|
||||
(mf/defc not-found-page
|
||||
[{:keys [error] :as props}]
|
||||
(js/console.log "not-found" error)
|
||||
[:section.not-found-layout
|
||||
[:div.not-found-header i/logo]
|
||||
[:div.not-found-content
|
||||
[:div.message-container
|
||||
[:div.main-message "404"]
|
||||
[:div.desc-message "Oops! Page not found"]]]])
|
|
@ -11,7 +11,9 @@
|
|||
"A collection of general purpose react hooks."
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]))
|
||||
[goog.events :as events]
|
||||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(defn use-rxsub
|
||||
[ob]
|
||||
|
@ -22,4 +24,3 @@
|
|||
#(rx/cancel! sub)))
|
||||
#js [ob])
|
||||
state))
|
||||
|
||||
|
|
96
frontend/src/uxbox/main/ui/viewer.cljs
Normal file
96
frontend/src/uxbox/main/ui/viewer.cljs
Normal file
|
@ -0,0 +1,96 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.viewer
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[goog.object :as gobj]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.data.viewer :as vd]
|
||||
[uxbox.main.ui.viewer.header :refer [header]]
|
||||
[uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel frame-svg]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.router :as rt])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes))
|
||||
|
||||
(mf/defc main-panel
|
||||
[{:keys [data zoom index]}]
|
||||
(let [frames (:frames data [])
|
||||
objects (:objects data)
|
||||
frame (get frames index)]
|
||||
|
||||
(when-not frame
|
||||
(ex/raise :type :not-found
|
||||
:hint "Frame not found"))
|
||||
|
||||
[:section.viewer-preview
|
||||
[:& frame-svg {:frame frame :zoom zoom :objects objects}]]))
|
||||
|
||||
(mf/defc viewer-content
|
||||
[{:keys [data local index] :as props}]
|
||||
(let [on-mouse-wheel
|
||||
(fn [event]
|
||||
(when (kbd/ctrl? event)
|
||||
;; Disable browser zoom with ctrl+mouse wheel
|
||||
(dom/prevent-default event)))
|
||||
|
||||
on-mount
|
||||
(fn []
|
||||
;; bind with passive=false to allow the event to be cancelled
|
||||
;; https://stackoverflow.com/a/57582286/3219895
|
||||
(let [key1 (events/listen goog/global EventType.WHEEL
|
||||
on-mouse-wheel #js {"passive" false})]
|
||||
(fn []
|
||||
(events/unlistenByKey key1))))]
|
||||
|
||||
(mf/use-effect on-mount)
|
||||
|
||||
[:div.viewer-layout
|
||||
[:& header {:data data
|
||||
:local local
|
||||
:index index}]
|
||||
[:div.viewer-content
|
||||
(when (:show-thumbnails local)
|
||||
[:& thumbnails-panel {:index index
|
||||
:data data}])
|
||||
[:& main-panel {:data data
|
||||
:zoom (:zoom local)
|
||||
:index index}]]]))
|
||||
|
||||
|
||||
;; --- Component: Viewer Page
|
||||
|
||||
(def viewer-data-ref
|
||||
(-> (l/key :viewer-data)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def viewer-local-ref
|
||||
(-> (l/key :viewer-local)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc viewer-page
|
||||
[{:keys [page-id index] :as props}]
|
||||
(mf/use-effect (mf/deps page-id) #(st/emit! (vd/initialize page-id)))
|
||||
(let [data (mf/deref viewer-data-ref)
|
||||
local (mf/deref viewer-local-ref)]
|
||||
(when data
|
||||
[:& viewer-content {:index index
|
||||
:local local
|
||||
:data data}])))
|
85
frontend/src/uxbox/main/ui/viewer/header.cljs
Normal file
85
frontend/src/uxbox/main/ui/viewer/header.cljs
Normal file
|
@ -0,0 +1,85 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.viewer.header
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[goog.object :as gobj]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.data.viewer :as dv]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.router :as rt])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes))
|
||||
|
||||
(mf/defc zoom-widget
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [zoom] :as props}]
|
||||
(let [show-dropdown? (mf/use-state false)
|
||||
increase #(st/emit! dv/increase-zoom)
|
||||
decrease #(st/emit! dv/decrease-zoom)
|
||||
zoom-to-50 #(st/emit! dv/zoom-to-50)
|
||||
zoom-to-100 #(st/emit! dv/reset-zoom)
|
||||
zoom-to-200 #(st/emit! dv/zoom-to-200)]
|
||||
[:div.zoom-widget
|
||||
[:span.add-zoom {:on-click decrease} "-"]
|
||||
[:div.input-container {:on-click #(reset! show-dropdown? true)}
|
||||
[:span {} (str (mth/round (* 100 zoom)) "%")]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show @show-dropdown?
|
||||
:on-close #(reset! show-dropdown? false)}
|
||||
[:ul.zoom-dropdown
|
||||
[:li {:on-click increase}
|
||||
"Zoom in" [:span "+"]]
|
||||
[:li {:on-click decrease}
|
||||
"Zoom out" [:span "-"]]
|
||||
[:li {:on-click zoom-to-50}
|
||||
"Zoom to 50%"]
|
||||
[:li {:on-click zoom-to-100}
|
||||
"Zoom to 100%" [:span "Shift + 0"]]
|
||||
[:li {:on-click zoom-to-200}
|
||||
"Zoom to 200%"]]]]
|
||||
[:span.remove-zoom {:on-click increase} "+"]]))
|
||||
|
||||
(mf/defc header
|
||||
[{:keys [data index local] :as props}]
|
||||
(let [{:keys [project file page frames]} data
|
||||
total (count frames)
|
||||
on-click #(st/emit! dv/toggle-thumbnails-panel)
|
||||
on-edit #(st/emit! (rt/nav :workspace
|
||||
{:project-id (get-in data [:project :id])
|
||||
:file-id (get-in data [:file :id])}
|
||||
{:page-id (get-in data [:page :id])}))]
|
||||
[:header.viewer-header
|
||||
[:div.main-icon
|
||||
[:a i/logo-icon]]
|
||||
|
||||
[:div.sitemap-zone {:alt (tr "header.sitemap")
|
||||
:on-click on-click}
|
||||
[:span.project-name (:name project)]
|
||||
[:span "/"]
|
||||
[:span.file-name (:name file)]
|
||||
[:span "/"]
|
||||
[:span.page-name (:name page)]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:span.counters (str (inc index) " / " total)]]
|
||||
|
||||
[:div.options-zone
|
||||
[:span.btn-primary {:on-click on-edit} "Edit page"]
|
||||
[:& zoom-widget {:zoom (:zoom local)}]
|
||||
[:span.btn-fullscreen.tooltip.tooltip-bottom {:alt "Full screen"} i/full-screen]]]))
|
||||
|
146
frontend/src/uxbox/main/ui/viewer/thumbnails.cljs
Normal file
146
frontend/src/uxbox/main/ui/viewer/thumbnails.cljs
Normal file
|
@ -0,0 +1,146 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.viewer.thumbnails
|
||||
(:require
|
||||
[goog.events :as events]
|
||||
[goog.object :as gobj]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.viewer :as dv]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown']]
|
||||
[uxbox.main.ui.shapes.frame :as frame]
|
||||
[uxbox.main.exports :as exports]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.main.data.viewer :as vd])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes))
|
||||
|
||||
(mf/defc thumbnails-content
|
||||
[{:keys [children expanded? total] :as props}]
|
||||
(let [container (mf/use-ref)
|
||||
width (mf/use-var (.. js/document -documentElement -clientWidth))
|
||||
element-width (mf/use-var 152)
|
||||
|
||||
offset (mf/use-state 0)
|
||||
|
||||
on-left-arrow-click
|
||||
(fn [event]
|
||||
(swap! offset (fn [v]
|
||||
(if (pos? v)
|
||||
(dec v)
|
||||
v))))
|
||||
|
||||
on-right-arrow-click
|
||||
(fn [event]
|
||||
(let [visible (/ @width @element-width)
|
||||
max-val (- total visible)]
|
||||
(swap! offset (fn [v]
|
||||
(if (< v max-val)
|
||||
(inc v)
|
||||
v)))))
|
||||
|
||||
on-scroll
|
||||
(fn [event]
|
||||
(if (pos? (.. event -nativeEvent -deltaY))
|
||||
(on-right-arrow-click event)
|
||||
(on-left-arrow-click event)))
|
||||
|
||||
on-mount
|
||||
(fn []
|
||||
(let [dom (mf/ref-val container)]
|
||||
(reset! width (gobj/get dom "clientWidth"))))]
|
||||
|
||||
(mf/use-effect on-mount)
|
||||
(if expanded?
|
||||
[:div.thumbnails-content
|
||||
[:div.thumbnails-list-expanded children]]
|
||||
[:div.thumbnails-content
|
||||
[:div.left-scroll-handler {:on-click on-left-arrow-click} i/arrow-slide]
|
||||
[:div.right-scroll-handler {:on-click on-right-arrow-click} i/arrow-slide]
|
||||
[:div.thumbnails-list {:ref container :on-wheel on-scroll}
|
||||
[:div.thumbnails-list-inside {:style {:right (str (* @offset 152) "px")}}
|
||||
children]]])))
|
||||
|
||||
(mf/defc frame-svg
|
||||
{::mf/wrap [mf/wrap-memo]}
|
||||
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes frame))
|
||||
modifier (-> (gpt/point (:x frame) (:y frame))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
frame (assoc frame :displacement-modifier modifier)
|
||||
|
||||
transform (str "scale(" zoom ")")]
|
||||
|
||||
|
||||
[:svg {:view-box (str "0 0 " (:width frame 0) " " (:height frame 0))
|
||||
:width (:width frame)
|
||||
:height (:height frame)
|
||||
:transform transform
|
||||
:version "1.1"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"}
|
||||
[:& exports/frame-shape {:shape frame :childs childs}]]))
|
||||
|
||||
(mf/defc thumbnails-summary
|
||||
[{:keys [on-toggle-expand on-close total] :as props}]
|
||||
[:div.thumbnails-summary
|
||||
[:span.counter (str total " frames")]
|
||||
[:span.buttons
|
||||
[:span.btn-expand {:on-click on-toggle-expand} i/arrow-down]
|
||||
[:span.btn-close {:on-click on-close} i/close]]])
|
||||
|
||||
(mf/defc thumbnail-item
|
||||
[{:keys [selected? frame on-click index objects] :as props}]
|
||||
[:div.thumbnail-item {:on-click #(on-click % index)}
|
||||
[:div.thumbnail-preview
|
||||
{:class (classnames :selected selected?)}
|
||||
[:& frame-svg {:frame frame :objects objects}]]
|
||||
[:div.thumbnail-info
|
||||
[:span.name (:name frame)]]])
|
||||
|
||||
(mf/defc thumbnails-panel
|
||||
[{:keys [data index] :as props}]
|
||||
(let [expanded? (mf/use-state false)
|
||||
container (mf/use-ref)
|
||||
page-id (get-in data [:page :id])
|
||||
|
||||
on-close #(st/emit! dv/toggle-thumbnails-panel)
|
||||
|
||||
on-item-click
|
||||
(fn [event index]
|
||||
(st/emit! (rt/nav :viewer {:page-id page-id
|
||||
:index index})))]
|
||||
[:& dropdown' {:on-close on-close
|
||||
:container container
|
||||
:show true}
|
||||
[:section.viewer-thumbnails {:class (classnames :expanded @expanded?)
|
||||
:ref container}
|
||||
[:& thumbnails-summary {:on-toggle-expand #(swap! expanded? not)
|
||||
:on-close on-close
|
||||
:total (count (:frames data))}]
|
||||
[:& thumbnails-content {:expanded? @expanded?
|
||||
:total (count (:frames data))}
|
||||
(for [[i frame] (d/enumerate (:frames data))]
|
||||
[:& thumbnail-item {:key i
|
||||
:index i
|
||||
:frame frame
|
||||
:objects (:objects data)
|
||||
:on-click on-item-click
|
||||
:selected? (= i index)}])]]]))
|
|
@ -121,11 +121,13 @@
|
|||
|
||||
(let [file (mf/deref refs/workspace-file)
|
||||
page (mf/deref refs/workspace-page)
|
||||
project (mf/deref refs/workspace-project)
|
||||
layout (mf/deref refs/workspace-layout)]
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
[:& messages-widget]
|
||||
[:& header {:page page
|
||||
:file file
|
||||
:project project
|
||||
:layout layout}]
|
||||
|
||||
(when page
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[uxbox.main.ui.workspace.images :refer [import-image-modal]]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
|
@ -40,21 +41,21 @@
|
|||
[:div.zoom-input
|
||||
[:span.add-zoom {:on-click decrease} "-"]
|
||||
[:div {:on-click #(reset! show-dropdown? true)}
|
||||
[:span {} (str (mth/round (* 100 zoom)) "%")]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show @show-dropdown?
|
||||
:on-close #(reset! show-dropdown? false)}
|
||||
[:ul.zoom-dropdown
|
||||
[:li {:on-click increase}
|
||||
"Zoom in" [:span "+"]]
|
||||
[:li {:on-click decrease}
|
||||
"Zoom out" [:span "-"]]
|
||||
[:li {:on-click zoom-to-50}
|
||||
"Zoom to 50%" [:span "Shift + 0"]]
|
||||
[:li {:on-click zoom-to-100}
|
||||
"Zoom to 100%" [:span "Shift + 1"]]
|
||||
[:li {:on-click zoom-to-200}
|
||||
"Zoom to 200%" [:span "Shift + 2"]]]]]
|
||||
[:span {} (str (mth/round (* 100 zoom)) "%")]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show @show-dropdown?
|
||||
:on-close #(reset! show-dropdown? false)}
|
||||
[:ul.zoom-dropdown
|
||||
[:li {:on-click increase}
|
||||
"Zoom in" [:span "+"]]
|
||||
[:li {:on-click decrease}
|
||||
"Zoom out" [:span "-"]]
|
||||
[:li {:on-click zoom-to-50}
|
||||
"Zoom to 50%" [:span "Shift + 0"]]
|
||||
[:li {:on-click zoom-to-100}
|
||||
"Zoom to 100%" [:span "Shift + 1"]]
|
||||
[:li {:on-click zoom-to-200}
|
||||
"Zoom to 200%" [:span "Shift + 2"]]]]]
|
||||
[:span.remove-zoom {:on-click increase} "+"]]))
|
||||
|
||||
;; --- Header Users
|
||||
|
@ -132,35 +133,34 @@
|
|||
|
||||
;; --- Header Component
|
||||
|
||||
(def router-ref
|
||||
(-> (l/key :router)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc header
|
||||
[{:keys [page file layout] :as props}]
|
||||
(let [toggle-layout #(st/emit! (dw/toggle-layout-flag %))
|
||||
on-undo (constantly nil)
|
||||
on-redo (constantly nil)
|
||||
[{:keys [page file layout project] :as props}]
|
||||
(let [go-to-dashboard #(st/emit! (rt/nav :dashboard-team {:team-id "self"}))
|
||||
toggle-sitemap #(st/emit! (dw/toggle-layout-flag :sitemap))
|
||||
locale (i18n/use-locale)
|
||||
|
||||
on-image #(modal/show! import-image-modal {})
|
||||
;;on-download #(udl/open! :download)
|
||||
selected-drawtool (mf/deref refs/selected-drawing-tool)
|
||||
select-drawtool #(st/emit! :interrupt
|
||||
#_(dw/deactivate-ruler)
|
||||
(dw/select-for-drawing %))]
|
||||
|
||||
router (mf/deref router-ref)
|
||||
view-url (rt/resolve router :viewer {:page-id (:id page) :index 0})]
|
||||
[:header.workspace-bar
|
||||
[:div.main-icon
|
||||
[:a {:on-click #(st/emit! (rt/nav :dashboard-team {:team-id "self"}))}
|
||||
i/logo-icon]]
|
||||
|
||||
[:a {:on-click go-to-dashboard} i/logo-icon]]
|
||||
|
||||
[:& menu {:layout layout}]
|
||||
|
||||
[:div.project-tree-btn
|
||||
{:alt (tr "header.sitemap")
|
||||
:class (when (contains? layout :sitemap) "selected")
|
||||
:on-click #(st/emit! (dw/toggle-layout-flag :sitemap))}
|
||||
[:span.project-name "Project name /"]
|
||||
[:div.project-tree-btn {:alt (tr "header.sitemap")
|
||||
:class (classnames :selected (contains? layout :sitemap))
|
||||
:on-click toggle-sitemap}
|
||||
[:span.project-name (:name project) " /"]
|
||||
[:span (:name file)]]
|
||||
|
||||
[:div.workspace-options
|
||||
[:& active-users]]
|
||||
[:& zoom-widget]]))
|
||||
|
||||
[:& zoom-widget]
|
||||
|
||||
[:a.preview {
|
||||
;; :target "__blank"
|
||||
:href (str "#" view-url)} i/play]]))
|
||||
|
|
Loading…
Add table
Reference in a new issue