diff --git a/backend/resources/migrations/0003.projects.sql b/backend/resources/migrations/0003.projects.sql
index 29bfa24e5a..3b3516b1fe 100644
--- a/backend/resources/migrations/0003.projects.sql
+++ b/backend/resources/migrations/0003.projects.sql
@@ -14,8 +14,6 @@ CREATE TABLE project (
CREATE INDEX project__team_id__idx
ON project(team_id);
-
-
CREATE TABLE project_profile_rel (
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE,
diff --git a/backend/src/uxbox/services/init.clj b/backend/src/uxbox/services/init.clj
index 02e60dd56a..3a46deed72 100644
--- a/backend/src/uxbox/services/init.clj
+++ b/backend/src/uxbox/services/init.clj
@@ -22,7 +22,7 @@
(require 'uxbox.services.queries.pages)
(require 'uxbox.services.queries.profile)
(require 'uxbox.services.queries.recent-files)
- ;; (require 'uxbox.services.queries.user-attrs)
+ (require 'uxbox.services.queries.view)
)
(defn- load-mutation-services
diff --git a/backend/src/uxbox/services/queries/files.clj b/backend/src/uxbox/services/queries/files.clj
index 3b50e94290..6e28c4dcaa 100644
--- a/backend/src/uxbox/services/queries/files.clj
+++ b/backend/src/uxbox/services/queries/files.clj
@@ -192,6 +192,16 @@
inner join file as f on (p.id = f.project_id)
where f.id = $1")
+(defn retrieve-file
+ [conn id]
+ (-> (db/query-one conn [sql:file id])
+ (p/then' su/raise-not-found-if-nil)
+ (p/then' decode-row)))
+
+(defn retrieve-file-users
+ [conn id]
+ (db/query conn [sql:file-users id]))
+
(s/def ::file-with-users
(s/keys :req-un [::profile-id ::id]))
@@ -199,10 +209,8 @@
[{:keys [profile-id id] :as params}]
(db/with-atomic [conn db/pool]
(check-edition-permissions! conn profile-id id)
- (p/let [file (-> (db/query-one conn [sql:file id])
- (p/then' su/raise-not-found-if-nil)
- (p/then' decode-row))
- users (db/query conn [sql:file-users id])]
+ (p/let [file (retrieve-file conn id)
+ users (retrieve-file-users conn id)]
(assoc file :users users))))
(s/def ::file
@@ -212,9 +220,7 @@
[{:keys [profile-id id] :as params}]
(db/with-atomic [conn db/pool]
(check-edition-permissions! conn profile-id id)
- (-> (db/query-one conn [sql:file id])
- (p/then' su/raise-not-found-if-nil)
- (p/then' decode-row))))
+ (retrieve-file conn id)))
;; --- Query: Project Files
diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj
index 45d495b4cc..cfe5eb7d9e 100644
--- a/backend/src/uxbox/services/queries/projects.clj
+++ b/backend/src/uxbox/services/queries/projects.clj
@@ -16,13 +16,20 @@
(declare decode-row)
+;; TODO: this module should be refactored for to separate the
+;; permissions checks from the main queries in the same way as pages
+;; and files. This refactor will make this functions more "reusable"
+;; and will prevent duplicating queries on `queries.view` ns as
+;; example.
+
;; --- Query: Projects
(def ^:private sql:projects
"with projects as (
select p.*,
- (select count(*) from file as f
- where f.project_id = p.id and deleted_at is null) as file_count
+ (select count(*) from file as f
+ where f.project_id = p.id
+ and deleted_at is null) as file_count
from project as p
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = $1
@@ -32,8 +39,9 @@
tpr.can_edit = true)
union
select p.*,
- (select count(*) from file as f
- where f.project_id = p.id and deleted_at is null)
+ (select count(*) from file as f
+ where f.project_id = p.id
+ and deleted_at is null)
from project as p
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = $1
@@ -49,11 +57,11 @@
(def ^:private sql:project-by-id
"select p.*
- from project as p
- inner join project_profile_rel as ppr on (ppr.project_id = p.id)
- where ppr.profile_id = $1
- and p.id = $2
- and p.deleted_at is null
+ from project as p
+ inner join project_profile_rel as ppr on (ppr.project_id = p.id)
+ where ppr.profile_id = $1
+ and p.id = $2
+ and p.deleted_at is null
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)")
@@ -68,16 +76,18 @@
(s/def ::project-by-id
(s/keys :req-un [::profile-id ::project-id]))
-(defn projects-by-team [profile-id team-id]
- (db/query db/pool [sql:projects profile-id team-id]))
+(defn retrieve-projects
+ [conn profile-id team-id]
+ (db/query conn [sql:projects profile-id team-id]))
-(defn project-by-id [profile-id project-id]
- (db/query-one db/pool [sql:project-by-id profile-id project-id]))
+(defn retrieve-project
+ [conn profile-id id]
+ (db/query-one conn [sql:project-by-id profile-id id]))
(sq/defquery ::projects-by-team
[{:keys [profile-id team-id]}]
- (projects-by-team profile-id team-id))
+ (retrieve-projects db/pool profile-id team-id))
(sq/defquery ::project-by-id
[{:keys [profile-id project-id]}]
- (project-by-id profile-id project-id))
+ (retrieve-project db/pool profile-id project-id))
diff --git a/backend/src/uxbox/services/queries/recent_files.clj b/backend/src/uxbox/services/queries/recent_files.clj
index 9e3338cf5f..b99b831285 100644
--- a/backend/src/uxbox/services/queries/recent_files.clj
+++ b/backend/src/uxbox/services/queries/recent_files.clj
@@ -14,8 +14,8 @@
[uxbox.db :as db]
[uxbox.common.spec :as us]
[uxbox.services.queries :as sq]
- [uxbox.services.queries.projects :refer [ projects-by-team ]]
- [uxbox.services.queries.files :refer [ decode-row ]]))
+ [uxbox.services.queries.projects :refer [retrieve-projects]]
+ [uxbox.services.queries.files :refer [decode-row]]))
(def ^:private sql:project-files-recent
"select distinct
@@ -51,7 +51,7 @@
(sq/defquery ::recent-files
[{:keys [profile-id team-id]}]
- (-> (projects-by-team profile-id team-id)
+ (-> (retrieve-projects db/pool profile-id team-id)
;; Retrieve for each proyect the 5 more recent files
(p/then #(p/all (map (partial recent-by-project profile-id) %)))
;; Change the structure so it's a map with project-id as keys
diff --git a/backend/src/uxbox/services/queries/view.clj b/backend/src/uxbox/services/queries/view.clj
new file mode 100644
index 0000000000..ee6c0f6087
--- /dev/null
+++ b/backend/src/uxbox/services/queries/view.clj
@@ -0,0 +1,67 @@
+;; 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.services.queries.view
+ (:require
+ [clojure.spec.alpha :as s]
+ [promesa.core :as p]
+ [promesa.exec :as px]
+ [uxbox.common.exceptions :as ex]
+ [uxbox.common.spec :as us]
+ [uxbox.db :as db]
+ [uxbox.media :as media]
+ [uxbox.images :as images]
+ [uxbox.services.queries.pages :as pages]
+ [uxbox.services.queries.files :as files]
+ [uxbox.services.queries :as sq]
+ [uxbox.services.util :as su]
+ [uxbox.util.blob :as blob]
+ [uxbox.util.data :as data]
+ [uxbox.util.uuid :as uuid]
+ [vertx.core :as vc]))
+
+;; --- Helpers & Specs
+
+(s/def ::id ::us/uuid)
+(s/def ::page-id ::us/uuid)
+
+;; --- Query: Viewer Bundle
+
+(def ^:private
+ sql:project
+ "select p.id, p.name
+ from project as p
+ where p.id = $1
+ and p.deleted_at is null")
+
+(defn- retrieve-project
+ [conn id]
+ (db/query-one conn [sql:project id]))
+
+(s/def ::viewer-bundle-by-page-id
+ (s/keys :req-un [::profile-id ::page-id]))
+
+(sq/defquery ::viewer-bundle-by-page-id
+ [{:keys [profile-id page-id]}]
+ (db/with-atomic [conn db/pool]
+ (p/let [page (pages/retrieve-page conn page-id)
+ file (files/retrieve-file conn (:file-id page))
+ users (files/retrieve-file-users conn (:file-id page))
+ images (files/retrieve-file-images conn page)
+ project (retrieve-project conn (:project-id file))]
+ (files/check-edition-permissions! conn profile-id (:file-id page))
+ {:page page
+ :file file
+ :users users
+ :images images
+ :project project})))
+
+;; TODO: bundle by share link
+
+
diff --git a/frontend/deps.edn b/frontend/deps.edn
index 9169e295da..9842471ef0 100644
--- a/frontend/deps.edn
+++ b/frontend/deps.edn
@@ -13,9 +13,7 @@
funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"}
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
funcool/promesa {:mvn/version "5.1.0"}
- funcool/rumext {:mvn/version "2020.04.01-3"
- :exclusions [cljsjs/react
- cljsjs/react-dom]}
+ funcool/rumext {:mvn/version "2020.04.02-3"}
}
:aliases
{:dev
diff --git a/frontend/externs/main.txt b/frontend/externs/main.txt
new file mode 100644
index 0000000000..2c23b76be8
--- /dev/null
+++ b/frontend/externs/main.txt
@@ -0,0 +1,5 @@
+getBrowserEvent
+viewBox
+baseVal
+width
+height
diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js
index 11d0d69556..8fad08fdf9 100644
--- a/frontend/gulpfile.js
+++ b/frontend/gulpfile.js
@@ -77,12 +77,6 @@ function scssPipeline(options) {
// Templates
-function readSvgSprite() {
- const path = paths.build + "/icons-sprite/symbol/svg/sprite.symbol.svg";
- const content = fs.readFileSync(path, {encoding: "utf8"});
- return content;
-}
-
function readLocales() {
const path = __dirname + "/resources/locales.json";
const content = JSON.parse(fs.readFileSync(path, {encoding: "utf8"}));
@@ -132,12 +126,10 @@ function templatePipeline(options) {
const ts = Math.floor(new Date());
const locales = readLocales();
- const icons = readSvgSprite();
const config = readConfig();
const tmpl = mustache({
ts: ts,
- ic: icons,
config: JSON.stringify(config),
translations: JSON.stringify(locales),
});
@@ -155,7 +147,7 @@ function templatePipeline(options) {
gulp.task("scss:main", scssPipeline({
input: paths.resources + "styles/main.scss",
- output: paths.build + "css/main.css"
+ output: paths.output + "css/main.css"
}));
gulp.task("scss", gulp.parallel("scss:main"));
@@ -163,25 +155,23 @@ gulp.task("scss", gulp.parallel("scss:main"));
gulp.task("svg:sprite", function() {
return gulp.src(paths.resources + "images/icons/*.svg")
.pipe(rename({prefix: 'icon-'}))
- .pipe(svgSprite({mode:{symbol: {inline: true}}}))
- .pipe(gulp.dest(paths.build + "icons-sprite/"));
+ .pipe(svgSprite({mode:{symbol: {inline: false}}}))
+ .pipe(gulp.dest(paths.output + "images/svg-sprite/"));
});
gulp.task("template:main", templatePipeline({
input: paths.resources + "templates/index.mustache",
- output: paths.build
+ output: paths.output
}));
-gulp.task("templates", gulp.series("svg:sprite", "template:main"));
+gulp.task("templates", gulp.series("template:main"));
/***********************************************
* Development
***********************************************/
gulp.task("dev:clean", function(next) {
- rimraf(paths.output, function() {
- rimraf(paths.build, next);
- });
+ rimraf(paths.output, next);
});
gulp.task("dev:copy:images", function() {
@@ -189,52 +179,32 @@ gulp.task("dev:copy:images", function() {
.pipe(gulp.dest(paths.output + "images/"));
});
-gulp.task("dev:copy:css", function() {
- return gulp.src(paths.build + "css/**/*")
- .pipe(gulp.dest(paths.output + "css/"));
-});
-
-gulp.task("dev:copy:icons-sprite", function() {
- return gulp.src(paths.build + "icons-sprite/**/*")
- .pipe(gulp.dest(paths.output + "icons-sprite/"));
-});
-
-gulp.task("dev:copy:templates", function() {
- return gulp.src(paths.build + "index.html")
- .pipe(gulp.dest(paths.output));
-});
-
gulp.task("dev:copy:fonts", function() {
return gulp.src(paths.resources + "fonts/**/*")
.pipe(gulp.dest(paths.output + "fonts/"));
});
gulp.task("dev:copy", gulp.parallel("dev:copy:images",
- "dev:copy:css",
- "dev:copy:fonts",
- "dev:copy:icons-sprite",
- "dev:copy:templates"));
+ "dev:copy:fonts"));
gulp.task("dev:dirs", function(next) {
- mkdirp("./resources/public/css/")
- .then(() => next())
+ mkdirp("./resources/public/css/").then(() => next())
});
gulp.task("watch:main", function() {
- gulp.watch(paths.scss, gulp.series("scss", "dev:copy:css"));
+ gulp.watch(paths.scss, gulp.series("scss"));
+ gulp.watch(paths.resources + "images/**/*",
+ gulp.series("svg:sprite",
+ "dev:copy:images"));
gulp.watch([paths.resources + "templates/*.mustache",
- paths.resources + "locales.json",
- paths.resources + "images/**/*"],
- gulp.series("templates",
- "dev:copy:images",
- "dev:copy:templates",
- "dev:copy:icons-sprite"));
+ paths.resources + "locales.json"],
+ gulp.series("templates"));
});
gulp.task("watch", gulp.series(
"dev:dirs",
- gulp.parallel("scss", "templates"),
+ gulp.parallel("scss", "templates", "svg:sprite"),
"dev:copy",
"watch:main"
));
@@ -244,42 +214,14 @@ gulp.task("watch", gulp.series(
***********************************************/
gulp.task("dist:clean", function(next) {
- rimraf(paths.dist, function() {
- rimraf(paths.build, next);
- });
+ rimraf(paths.dist, next);
});
-gulp.task("dist:copy:templates", function() {
- return gulp.src(paths.build + "index.html")
+gulp.task("dist:copy", function() {
+ return gulp.src(paths.output + "**/*")
.pipe(gulp.dest(paths.dist));
});
-gulp.task("dist:copy:images", function() {
- return gulp.src(paths.resources + "images/**/*")
- .pipe(gulp.dest(paths.dist + "images/"));
-});
-
-gulp.task("dist:copy:styles", function() {
- return gulp.src(paths.build + "css/**/*")
- .pipe(gulp.dest(paths.dist + "css/"));
-});
-
-gulp.task("dist:copy:icons-sprite", function() {
- return gulp.src(paths.build + "icons-sprite/**/*")
- .pipe(gulp.dest(paths.dist + "icons-sprite/"));
-});
-
-gulp.task("dist:copy:fonts", function() {
- return gulp.src(paths.resources + "/fonts/**/*")
- .pipe(gulp.dest(paths.dist + "fonts/"));
-});
-
-gulp.task("dist:copy", gulp.parallel("dist:copy:fonts",
- "dist:copy:icons-sprite",
- "dist:copy:styles",
- "dist:copy:templates",
- "dist:copy:images"));
-
gulp.task("dist:gzip", function() {
return gulp.src(`${paths.dist}**/!(*.gz|*.br|*.jpg|*.png)`)
.pipe(gzip({gzipOptions: {level: 9}}))
@@ -287,8 +229,8 @@ gulp.task("dist:gzip", function() {
});
gulp.task("dist", gulp.series(
+ "dev:clean",
"dist:clean",
- "scss",
- "templates",
+ gulp.parallel("scss", "templates", "svg:sprite", "dev:copy"),
"dist:copy"
));
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 31d6d1b843..559a0d696d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -3945,6 +3945,11 @@
}
}
},
+ "mousetrap": {
+ "version": "1.6.5",
+ "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
+ "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -4689,6 +4694,11 @@
"safe-buffer": "^5.1.0"
}
},
+ "randomcolor": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.5.4.tgz",
+ "integrity": "sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA=="
+ },
"randomfill": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 77b9797ca3..fb278caac9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -29,6 +29,8 @@
},
"dependencies": {
"date-fns": "^2.11.1",
+ "mousetrap": "^1.6.5",
+ "randomcolor": "^0.5.4",
"react": "^16.13.1",
"react-color": "^2.18.0",
"react-dnd": "^10.0.2",
diff --git a/frontend/resources/images/icons/full-screen.svg b/frontend/resources/images/icons/full-screen.svg
new file mode 100644
index 0000000000..47e7db42ca
--- /dev/null
+++ b/frontend/resources/images/icons/full-screen.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/frontend/resources/styles/main.scss b/frontend/resources/styles/main.scss
index cb43312a55..545e757798 100644
--- a/frontend/resources/styles/main.scss
+++ b/frontend/resources/styles/main.scss
@@ -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
diff --git a/frontend/resources/styles/main/layouts/not-found.scss b/frontend/resources/styles/main/layouts/not-found.scss
new file mode 100644
index 0000000000..359d7ab532
--- /dev/null
+++ b/frontend/resources/styles/main/layouts/not-found.scss
@@ -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;
+ }
+}
+
diff --git a/frontend/resources/styles/main/layouts/viewer.scss b/frontend/resources/styles/main/layouts/viewer.scss
new file mode 100644
index 0000000000..1b7c73abfb
--- /dev/null
+++ b/frontend/resources/styles/main/layouts/viewer.scss
@@ -0,0 +1,28 @@
+.viewer-layout {
+ display: grid;
+ grid-template-rows: 40px auto;
+ grid-template-columns: 1fr;
+
+ &.fullscreen {
+ .viewer-header {
+ opacity: 0;
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ .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;
+ }
+}
diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss
new file mode 100644
index 0000000000..210d244d78
--- /dev/null
+++ b/frontend/resources/styles/main/partials/viewer-header.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/frontend/resources/styles/main/partials/viewer-thumbnails.scss b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
new file mode 100644
index 0000000000..bd92f3de9b
--- /dev/null
+++ b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
@@ -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;
+ }
+ }
+}
diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss
new file mode 100644
index 0000000000..eeac272b24
--- /dev/null
+++ b/frontend/resources/styles/main/partials/viewer.scss
@@ -0,0 +1,25 @@
+.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;
+ flex-flow: wrap;
+
+ svg {
+ transform-origin: center;
+ }
+}
diff --git a/frontend/resources/styles/main/partials/workspace-bar.scss b/frontend/resources/styles/main/partials/workspace-bar.scss
index 6a075a47f1..52c129cd56 100644
--- a/frontend/resources/styles/main/partials/workspace-bar.scss
+++ b/frontend/resources/styles/main/partials/workspace-bar.scss
@@ -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;
diff --git a/frontend/resources/styles/view.scss b/frontend/resources/styles/view.scss
deleted file mode 100644
index 5b3c64ec6e..0000000000
--- a/frontend/resources/styles/view.scss
+++ /dev/null
@@ -1,45 +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) 2016 Andrey Antukh
-// Copyright (c) 2016 Juan de la Cruz
-
-// UXBOX VIEW STYLES
-//#################################################
-//
-//#################################################
-
-@import 'common/dependencies/colors';
-@import 'common/dependencies/uxbox-light';
-//@import 'common/dependencies/uxbox-dark';
-@import 'common/dependencies/helpers';
-@import 'common/dependencies/mixin';
-@import 'common/dependencies/fonts';
-@import 'common/dependencies/reset';
-@import 'common/dependencies/animations';
-@import 'common/dependencies/z-index';
-
-//#################################################
-// Layouts
-//#################################################
-
-@import 'common/base';
-@import 'view/layouts/main-layout';
-
-//#################################################
-// Commons
-//#################################################
-
-@import 'common/framework';
-
-//#################################################
-// Partials
-//#################################################
-
-
-//#################################################
-// Resources
-//#################################################
-
-@import 'collection/font-collection';
diff --git a/frontend/resources/styles/view/layouts/main-layout.scss b/frontend/resources/styles/view/layouts/main-layout.scss
deleted file mode 100644
index 67f4815104..0000000000
--- a/frontend/resources/styles/view/layouts/main-layout.scss
+++ /dev/null
@@ -1,250 +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) 2016 Andrey Antukh
-// Copyright (c) 2016 Juan de la Cruz
-
-.view-content {
- display: flex;
- flex-direction: column-reverse;
- height: 100vh;
- width: 100%;
- @include bp(tablet) {
- flex-direction: row;
- }
-}
-
-.view-nav {
- background-color: $color-gray-50;
- border-top: 1px solid $color-gray-60;
- border-right: 0;
- display: flex;
- flex-shrink: 0;
- height: 55px;
- width: 100%;
-
- @include bp(tablet) {
- border-right: 1px solid $color-gray-60;
- border-top: 0;
- height: 100%;
- width: 70px;
- }
-
-}
-
-.view-options-btn {
- align-items: center;
- display: flex;
- margin: auto;
-
- @include bp(tablet) {
- flex-direction: column;
- }
-
- li {
- align-items: center;
- background-color: $color-gray-60;
- border: 1px solid transparent;
- border-radius: $br-small;
- cursor: pointer;
- display: flex;
- flex-shrink: 0;
- height: 40px;
- justify-content: center;
- margin: $small;
- position: relative;
- width: 40px;
-
- a {
- padding-top: 6px;
- }
-
- svg {
- fill: $color-gray-20;
- height: 24px;
- width: 24px;
- }
-
- &:hover {
- background-color: $color-gray-10;
- border-color: $color-gray-60;
- }
-
- &.selected {
- background-color: $color-primary;
-
- svg {
- fill: $color-white;
- }
-
- }
-
- }
-
-}
-
-.view-sitemap {
- background-color: $color-gray-50;
- border-top: 1px solid $color-gray-60;
- border-right: 0;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- height: 155px;
- width: 100%;
- overflow: scroll;
-
- .sitemap-title {
- border-bottom: 1px solid $color-gray-60;
- padding: $small;
- font-weight: bold;
- }
-
- @include bp(tablet) {
- border-right: 1px solid $color-gray-60;
- border-top: 0;
- height: 100%;
- width: 220px;
- }
-
-}
-
-
-.sitemap-list {
- width: 100%;
-
- li {
- align-items: center;
- border-bottom: 1px solid $color-gray-60;
- cursor: pointer;
- display: flex;
- flex-direction: row;
- padding: $small;
- width: 100%;
-
- .page-icon {
-
- svg {
- fill: $color-gray-30;
- height: 15px;
- margin-right: $x-small;
- width: 15px;
- }
-
- }
-
- span {
- color: $color-gray-20;
- font-size: $fs14;
- max-width: 75%;
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .page-actions {
- align-items: center;
- display: none;
- margin-left: auto;
-
- a {
-
- svg {
- fill: $color-gray-60;
- height: 15px;
- margin-left: $x-small;
- width: 15px;
-
- &:hover {
- fill: $color-gray-20;
- }
-
- }
-
- }
-
- }
-
- &:hover {
-
- .page-icon {
-
- svg {
- fill: $color-primary;
- }
-
- }
-
- span {
- color: $color-primary;
- }
-
- }
-
- &.selected {
-
- .page-icon {
-
- svg {
- fill: $color-primary;
- }
-
- }
-
- span {
- color: $color-primary;
- font-weight: bold;
- }
-
- }
-
- }
-
- &:hover {
-
- .page-actions {
- display: flex;
- @include animation(0s,.3s,fadeIn);
- }
-
- }
-
-}
-
-.view-canvas {
- background-color: $color-gray-60;
- width: 100%;
- overflow: scroll;
- display: flex;
- .page-layout {
- flex-shrink: 0;
- margin: auto;
- }
-}
-
-.interaction-mark {
- align-items: center;
- background-color: $color-primary;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- height: 20px;
- width: 20px;
- svg {
- fill: $color-white;
- height: 15px;
- width: 15px;
- }
-}
-
-.interaction-bullet {
- fill: $color-primary;
- fill-opacity: 1;
-}
-
-.interaction-hightlight {
- fill: $color-primary;
- fill-opacity: 0.3;
- stroke: $color-primary;
-}
diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache
index 4d9ae3b00e..8fabc07c47 100644
--- a/frontend/resources/templates/index.mustache
+++ b/frontend/resources/templates/index.mustache
@@ -4,11 +4,12 @@
UXBOX - The Open-Source prototyping tool
-
+
+
+
- {{& ic }}
@@ -16,7 +17,7 @@
window.uxboxConfig = JSON.parse({{& config }});
window.uxboxTranslations = JSON.parse({{& translations }});
-
+