Merge pull request #3853 from penpot/alotor-dashboard-ui

Refactor Dashboard UI
This commit is contained in:
Eva Marco 2023-11-28 10:39:17 +01:00 committed by GitHub
commit ffd36df0e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 8430 additions and 3150 deletions

View File

@ -29,7 +29,7 @@ paths.resources = "./resources/";
paths.output = "./resources/public/"; paths.output = "./resources/public/";
paths.dist = "./target/dist/"; paths.dist = "./target/dist/";
const touchSourceOnStyleChange = true; const touchSourceOnStyleChange = false;
/*********************************************** /***********************************************
* Marked Extensions * Marked Extensions
@ -234,7 +234,11 @@ gulp.task("scss:modules", function () {
gulp.task("scss:main", function () { gulp.task("scss:main", function () {
return gulp return gulp
.src(paths.resources + "styles/main-default.scss") .src(paths.resources + "styles/main-default.scss")
.pipe(gulpSass.sync().on("error", gulpSass.logError)) .pipe(gulpSass.sync({
includePaths: [
"./node_modules/animate.css"
]
}))
.pipe(gulpPostcss([autoprefixer])) .pipe(gulpPostcss([autoprefixer]))
.pipe(gulp.dest(paths.output + "css/")); .pipe(gulp.dest(paths.output + "css/"));
}); });

View File

@ -15,6 +15,7 @@
"test:compile": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'", "test:compile": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'",
"lint:clj": "clj-kondo --parallel --lint src/", "lint:clj": "clj-kondo --parallel --lint src/",
"lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss", "lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss",
"lint:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w",
"test:run": "node target/tests.js", "test:run": "node target/tests.js",
"test:watch": "clojure -M:dev:shadow-cljs watch test", "test:watch": "clojure -M:dev:shadow-cljs watch test",
"test": "yarn run test:compile && yarn run test:run", "test": "yarn run test:compile && yarn run test:run",
@ -41,6 +42,7 @@
"@storybook/react": "^7.5.3", "@storybook/react": "^7.5.3",
"@storybook/react-vite": "^7.5.3", "@storybook/react-vite": "^7.5.3",
"@storybook/testing-library": "^0.2.2", "@storybook/testing-library": "^0.2.2",
"animate.css": "^4.1.1",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"cypress": "^10.3.0", "cypress": "^10.3.0",

View File

@ -0,0 +1,3 @@
<svg width="79" xmlns="http://www.w3.org/2000/svg" height="1537" viewBox="2462.648 7069.559 79 1537">
<path d="m2483.255 7069.559-10.973 15.432v1441.521l-9.547 4.535-.087-.039v56.915l37.226 17.564 2.274 1.072 2.276-1.072 37.224-17.564v-56.915l-.07.032-9.55-4.536V7084.991l-.334-.468-10.639-14.964-10.973 15.432v.016l-7.983-11.231-7.923 11.144-.282-.397-10.639-14.964Zm1.941 8.964 4.244 5.968h-12.37l4.195-5.899 3.931-.069Zm37.8 0 4.244 5.968h-12.37l4.193-5.899 3.933-.069Zm-18.956 4.219 4.244 5.968h-12.37l4.194-5.898 3.932-.07Zm-28.143 4.758h5.866v1452.525l-5.866-2.768V7087.5Zm8.878 0h5.838v1456.7l-5.838-2.754V7087.5Zm28.923 0h5.866v1453.925l-5.866 2.768V7087.5Zm8.877 0h5.838v1449.75l-5.838 2.754V7087.5Zm-27.834 4.219h5.866v1457.196l-5.866-2.768V7091.719Zm8.879 0h5.837v1454.476l-5.837 2.754v-1457.23Zm28.407 1439.28 5.165 2.11-5.165 2.437v-4.547Zm-59.745.009v4.544l-5.164-2.436 5.164-2.108Zm-5.084 7.17 32.676 15.416v46.867l-32.676-15.415v-46.868Zm69.901 0v46.868l-32.675 15.415v-46.867l32.675-15.416Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1020 B

View File

@ -0,0 +1,22 @@
// 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) KALEIDOS INC
@mixin animation($delay, $duration, $animation) {
-webkit-animation-delay: $delay;
-webkit-animation-duration: $duration;
-webkit-animation-name: $animation;
-webkit-animation-fill-mode: both;
-moz-animation-delay: $delay;
-moz-animation-duration: $duration;
-moz-animation-name: $animation;
-moz-animation-fill-mode: both;
animation-delay: $delay;
animation-duration: $duration;
animation-name: $animation;
animation-fill-mode: both;
}

View File

@ -854,5 +854,4 @@
align-items: center; align-items: center;
justify-content: normal; justify-content: normal;
width: 100%; width: 100%;
overflow: hidden;
} }

View File

@ -0,0 +1,168 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor" as *;
.dashboard-header {
align-items: center;
display: flex;
height: $s-64;
justify-content: space-between;
padding: $s-4 $s-16 $s-4 $s-8;
position: relative;
user-select: none;
&.team {
display: grid;
grid-template-columns: 20% 1fr 20%;
}
.element-name {
margin-right: $s-8;
}
.btn-secondary {
flex-shrink: 0;
height: $s-32;
svg {
height: $s-16;
width: $s-16;
}
}
svg {
fill: $db-secondary;
height: $s-12;
margin-right: $s-4;
width: $s-12;
}
nav {
display: flex;
align-items: flex-end;
justify-content: center;
ul {
display: flex;
font-size: $fs-14;
justify-content: center;
margin: 0;
}
li {
a {
display: flex;
align-items: center;
flex-basis: $s-140;
border-bottom: $s-3 solid transparent;
color: $df-secondary;
height: $s-40;
padding: $s-4 $s-24;
font-weight: $fw400;
&:hover {
color: $db-secondary;
text-decoration: none;
}
}
}
}
.dashboard-title {
display: flex;
align-items: center;
margin-left: $s-12;
h1 {
color: $df-primary;
display: block;
flex-shrink: 0;
font-size: $fs-24;
font-weight: $fw400;
max-width: $s-712;
overflow: hidden;
text-overflow: ellipsis;
user-select: all;
white-space: nowrap;
width: 100%;
}
.context-menu.is-open {
margin-top: $s-8;
}
}
.icon {
display: flex;
align-items: center;
cursor: pointer;
margin-left: $s-8;
svg {
fill: $df-secondary;
width: $s-16;
height: $s-16;
&:hover {
fill: $da-tertiary;
}
}
}
.dashboard-buttons {
display: flex;
justify-content: flex-end;
align-items: center;
}
.dashboard-header-actions {
display: flex;
}
.pin-icon {
margin: 0 $s-8 0 $s-24;
background-color: transparent;
border: none;
svg {
fill: $df-secondary;
}
&.active {
svg {
fill: $db-cuaternary;
}
}
}
.dashboard-header-options {
li {
a {
font-size: $s-16;
color: $df-secondary;
border-color: transparent;
&:hover {
color: $df-primary;
}
}
&.active {
a {
color: $df-primary;
}
}
}
}
}
.btn-primary {
@extend .button-primary;
text-transform: uppercase;
}
.btn-secondary {
@extend .button-secondary;
color: $df-primary;
font-size: $fs-12;
text-transform: uppercase;
padding: 0 $s-16;
}

View File

@ -16,4 +16,21 @@
@import "common/refactor/z-index.scss"; @import "common/refactor/z-index.scss";
@import "common/refactor/mixins.scss"; @import "common/refactor/mixins.scss";
@import "common/refactor/focus.scss"; @import "common/refactor/focus.scss";
@import "common/refactor/animations.scss";
@import "common/refactor/basic-rules.scss"; @import "common/refactor/basic-rules.scss";
// Variables to use the library colors
$db-primary: var(--color-background-primary);
$db-secondary: var(--color-background-secondary);
$db-tertiary: var(--color-background-tertiary);
$db-cuaternary: var(--color-background-quaternary);
$db-subtle: var(--color-background-subtle);
$db-disabled: var(--color-background-disabled);
$df-primary: var(--color-foreground-primary);
$df-secondary: var(--color-foreground-secondary);
$df-tertiary: var(--color-foreground-tertiary);
$da-primary: var(--color-accent-primary);
$da-primary-muted: var(--color-accent-primary-muted);
$da-secondary: var(--color-accent-secondary);
$da-tertiary: var(--color-accent-tertiary);

View File

@ -16,6 +16,7 @@ $fs-12: math.div(12, $fs-base) + rem;
$fs-14: math.div(14, $fs-base) + rem; $fs-14: math.div(14, $fs-base) + rem;
$fs-16: math.div(16, $fs-base) + rem; $fs-16: math.div(16, $fs-base) + rem;
$fs-24: math.div(24, $fs-base) + rem; $fs-24: math.div(24, $fs-base) + rem;
$fs-36: math.div(36, $fs-base) + rem;
// Font weight // Font weight
$fw400: 400; // Regular (CSS value: 'normal') $fw400: 400; // Regular (CSS value: 'normal')

View File

@ -10,77 +10,142 @@ $s-0: 0px;
$s-1: 1px; $s-1: 1px;
$s-2: math.div(0.25rem, 2); $s-2: math.div(0.25rem, 2);
$s-3: calc($s-1 + $s-2); $s-3: calc($s-1 + $s-2);
$s-4: var(--s-4); $s-4: 0.25rem;
$s-6: calc($s-2 + $s-4); $s-6: #{0.25 + math.div(0.25, 2)}rem;
$s-8: calc(var(--s-4) * 2);
$s-12: calc(var(--s-4) * 3); // To calculate:
$s-16: calc(var(--s-4) * 4); // https://docs.google.com/spreadsheets/d/1YVEfudB3bdO2ZOo5azZLJzZgA0Fkt1C85-1jVjuSYXg/edit?usp=sharing
$s-20: calc(var(--s-4) * 5); $s-8: #{0.25 * 2}rem;
$s-24: calc(var(--s-4) * 6); $s-12: #{0.25 * 3}rem;
$s-28: calc(var(--s-4) * 7); $s-16: #{0.25 * 4}rem;
$s-32: calc(var(--s-4) * 8); $s-20: #{0.25 * 5}rem;
$s-36: calc(var(--s-4) * 9); $s-24: #{0.25 * 6}rem;
$s-40: calc(var(--s-4) * 10); $s-28: #{0.25 * 7}rem;
$s-44: calc(var(--s-4) * 11); $s-32: #{0.25 * 8}rem;
$s-48: calc(var(--s-4) * 12); $s-36: #{0.25 * 9}rem;
$s-52: calc(var(--s-4) * 13); $s-40: #{0.25 * 10}rem;
$s-56: calc(var(--s-4) * 14); $s-44: #{0.25 * 11}rem;
$s-60: calc(var(--s-4) * 15); $s-48: #{0.25 * 12}rem;
$s-64: calc(var(--s-4) * 16); $s-52: #{0.25 * 13}rem;
$s-68: calc(var(--s-4) * 17); $s-56: #{0.25 * 14}rem;
$s-72: calc(var(--s-4) * 18); $s-60: #{0.25 * 15}rem;
$s-76: calc(var(--s-4) * 19); $s-64: #{0.25 * 16}rem;
$s-80: calc(var(--s-4) * 20); $s-68: #{0.25 * 17}rem;
$s-84: calc(var(--s-4) * 21); $s-72: #{0.25 * 18}rem;
$s-92: calc(var(--s-4) * 23); $s-76: #{0.25 * 19}rem;
$s-96: calc(var(--s-4) * 24); $s-80: #{0.25 * 20}rem;
$s-100: calc(var(--s-4) * 25); $s-84: #{0.25 * 21}rem;
$s-104: calc(var(--s-4) * 26); $s-88: #{0.25 * 22}rem;
$s-108: calc(var(--s-4) * 27); $s-92: #{0.25 * 23}rem;
$s-116: calc(var(--s-4) * 29); $s-96: #{0.25 * 24}rem;
$s-120: calc(var(--s-4) * 30); $s-100: #{0.25 * 25}rem;
$s-124: calc(var(--s-4) * 31); $s-104: #{0.25 * 26}rem;
$s-128: calc(var(--s-4) * 32); $s-108: #{0.25 * 27}rem;
$s-136: calc(var(--s-4) * 34); $s-112: #{0.25 * 28}rem;
$s-140: calc(var(--s-4) * 35); $s-116: #{0.25 * 29}rem;
$s-148: calc(var(--s-4) * 37); $s-120: #{0.25 * 30}rem;
$s-156: calc(var(--s-4) * 39); $s-124: #{0.25 * 31}rem;
$s-152: calc(var(--s-4) * 38); $s-128: #{0.25 * 32}rem;
$s-160: calc(var(--s-4) * 40); $s-132: #{0.25 * 33}rem;
$s-168: calc(var(--s-4) * 42); $s-136: #{0.25 * 34}rem;
$s-172: calc(var(--s-4) * 43); $s-140: #{0.25 * 35}rem;
$s-180: calc(var(--s-4) * 45); $s-144: #{0.25 * 36}rem;
$s-184: calc(var(--s-4) * 46); $s-148: #{0.25 * 37}rem;
$s-188: calc(var(--s-4) * 47); $s-152: #{0.25 * 38}rem;
$s-192: calc(var(--s-4) * 48); $s-156: #{0.25 * 39}rem;
$s-196: calc(var(--s-4) * 49); $s-160: #{0.25 * 40}rem;
$s-200: calc(var(--s-4) * 50); $s-164: #{0.25 * 41}rem;
$s-216: calc(var(--s-4) * 54); $s-168: #{0.25 * 42}rem;
$s-220: calc(var(--s-4) * 55); $s-172: #{0.25 * 43}rem;
$s-228: calc(var(--s-4) * 57); $s-176: #{0.25 * 44}rem;
$s-240: calc(var(--s-4) * 60); $s-180: #{0.25 * 45}rem;
$s-248: calc(var(--s-4) * 62); $s-184: #{0.25 * 46}rem;
$s-252: calc(var(--s-4) * 63); $s-188: #{0.25 * 47}rem;
$s-256: calc(var(--s-4) * 64); $s-192: #{0.25 * 48}rem;
$s-260: calc(var(--s-4) * 65); $s-196: #{0.25 * 49}rem;
$s-272: calc(var(--s-4) * 68); $s-200: #{0.25 * 50}rem;
$s-276: calc(var(--s-4) * 69); $s-204: #{0.25 * 51}rem;
$s-280: calc(var(--s-4) * 70); $s-208: #{0.25 * 52}rem;
$s-284: calc(var(--s-4) * 71); $s-212: #{0.25 * 53}rem;
$s-300: calc(var(--s-4) * 75); $s-216: #{0.25 * 54}rem;
$s-320: calc(var(--s-4) * 80); $s-220: #{0.25 * 55}rem;
$s-348: calc(var(--s-4) * 87); $s-224: #{0.25 * 56}rem;
$s-356: calc(var(--s-4) * 89); $s-228: #{0.25 * 57}rem;
$s-364: calc(var(--s-4) * 91); $s-232: #{0.25 * 58}rem;
$s-380: calc(var(--s-4) * 95); $s-236: #{0.25 * 59}rem;
$s-400: calc(var(--s-4) * 100); $s-240: #{0.25 * 60}rem;
$s-408: calc(var(--s-4) * 102); $s-244: #{0.25 * 61}rem;
$s-440: calc(var(--s-4) * 110); $s-248: #{0.25 * 62}rem;
$s-480: calc(var(--s-4) * 120); $s-252: #{0.25 * 63}rem;
$s-500: calc(var(--s-4) * 125); $s-256: #{0.25 * 64}rem;
$s-512: calc(var(--s-4) * 128); $s-260: #{0.25 * 65}rem;
$s-520: calc(var(--s-4) * 130); $s-264: #{0.25 * 66}rem;
$s-640: calc(var(--s-4) * 160); $s-268: #{0.25 * 67}rem;
$s-664: calc(var(--s-4) * 166); $s-272: #{0.25 * 68}rem;
$s-712: calc(var(--s-4) * 178); $s-276: #{0.25 * 69}rem;
$s-736: calc(var(--s-4) * 184); $s-280: #{0.25 * 70}rem;
$s-284: #{0.25 * 71}rem;
$s-288: #{0.25 * 72}rem;
$s-292: #{0.25 * 73}rem;
$s-296: #{0.25 * 74}rem;
$s-300: #{0.25 * 75}rem;
$s-304: #{0.25 * 76}rem;
$s-308: #{0.25 * 77}rem;
$s-312: #{0.25 * 78}rem;
$s-316: #{0.25 * 79}rem;
$s-320: #{0.25 * 80}rem;
$s-324: #{0.25 * 81}rem;
$s-328: #{0.25 * 82}rem;
$s-332: #{0.25 * 83}rem;
$s-336: #{0.25 * 84}rem;
$s-340: #{0.25 * 85}rem;
$s-344: #{0.25 * 86}rem;
$s-348: #{0.25 * 87}rem;
$s-352: #{0.25 * 88}rem;
$s-356: #{0.25 * 89}rem;
$s-360: #{0.25 * 90}rem;
$s-364: #{0.25 * 91}rem;
$s-368: #{0.25 * 92}rem;
$s-372: #{0.25 * 93}rem;
$s-376: #{0.25 * 94}rem;
$s-380: #{0.25 * 95}rem;
$s-384: #{0.25 * 96}rem;
$s-388: #{0.25 * 97}rem;
$s-392: #{0.25 * 98}rem;
$s-396: #{0.25 * 99}rem;
$s-400: #{0.25 * 100}rem;
$s-404: #{0.25 * 101}rem;
$s-408: #{0.25 * 102}rem;
$s-412: #{0.25 * 103}rem;
$s-416: #{0.25 * 104}rem;
$s-420: #{0.25 * 105}rem;
$s-424: #{0.25 * 106}rem;
$s-428: #{0.25 * 107}rem;
$s-432: #{0.25 * 108}rem;
$s-436: #{0.25 * 109}rem;
$s-440: #{0.25 * 110}rem;
$s-444: #{0.25 * 111}rem;
$s-448: #{0.25 * 112}rem;
$s-452: #{0.25 * 113}rem;
$s-456: #{0.25 * 114}rem;
$s-460: #{0.25 * 115}rem;
$s-464: #{0.25 * 116}rem;
$s-468: #{0.25 * 117}rem;
$s-472: #{0.25 * 118}rem;
$s-476: #{0.25 * 119}rem;
$s-480: #{0.25 * 120}rem;
$s-484: #{0.25 * 121}rem;
$s-488: #{0.25 * 122}rem;
$s-492: #{0.25 * 123}rem;
$s-496: #{0.25 * 124}rem;
$s-500: #{0.25 * 125}rem;
$s-512: #{0.25 * 128}rem;
$s-520: #{0.25 * 130}rem;
$s-580: #{0.25 * 145}rem;
$s-612: #{0.25 * 153}rem;
$s-640: #{0.25 * 160}rem;
$s-664: #{0.25 * 166}rem;
$s-712: #{0.25 * 178}rem;
$s-736: #{0.25 * 184}rem;
$s-800: #{0.25 * 200}rem;
$s-1000: #{0.25 * 250}rem;

View File

@ -23,6 +23,7 @@
@import "common/dependencies/z-index"; @import "common/dependencies/z-index";
@import "common/dependencies/highlightjs-theme"; @import "common/dependencies/highlightjs-theme";
@import "animate";
@import "common/refactor/color-defs.scss"; @import "common/refactor/color-defs.scss";
@import "common/refactor/themes.scss"; @import "common/refactor/themes.scss";
@import "common/refactor/design-tokens.scss"; @import "common/refactor/design-tokens.scss";

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
(case kns (case kns
"global" knm "global" knm
"old-css" (if (nil? *css-data*) knm "") "old-css" (if (nil? *css-data*) knm "")
(or (get *css-data* (keyword knm)) knm))) (or (get *css-data* (keyword knm)) (str "_not_found_" knm))))
(string? k) (string? k)
k)))) k))))

View File

@ -9,6 +9,10 @@
// Comment-thread-group // Comment-thread-group
.thread-group { .thread-group {
padding: 0 $s-12; padding: 0 $s-12;
cursor: pointer;
border-radius: $br-8;
padding: $s-8 $s-16;
.section-title { .section-title {
@include titleTipography; @include titleTipography;
height: $s-32; height: $s-32;
@ -39,6 +43,10 @@
flex-direction: column; flex-direction: column;
gap: $s-24; gap: $s-24;
} }
&:hover {
background: $db-primary;
}
} }
// Comment-thread // Comment-thread

View File

@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.components.context-menu-a11y (ns app.main.ui.components.context-menu-a11y
(:require-macros [app.main.style :refer [css]]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
@ -66,7 +66,6 @@
left (gobj/get props "left" 0) left (gobj/get props "left" 0)
fixed? (gobj/get props "fixed?" false) fixed? (gobj/get props "fixed?" false)
min-width? (gobj/get props "min-width?" false) min-width? (gobj/get props "min-width?" false)
workspace? (gobj/get props "workspace?" false)
origin (gobj/get props "origin") origin (gobj/get props "origin")
route (mf/deref refs/route) route (mf/deref refs/route)
new-css-system (mf/use-ctx ctx/new-css-system) new-css-system (mf/use-ctx ctx/new-css-system)
@ -192,107 +191,149 @@
(tm/schedule-on-idle (tm/schedule-on-idle
#(dom/focus! (dom/get-element (first ids))))) #(dom/focus! (dom/get-element (first ids)))))
(when (and open? (some? (:levels @local))) (if new-css-system
[:> dropdown' props (when (and open? (some? (:levels @local)))
[:> dropdown' props
(let [level (-> @local :levels peek)
original-options (:options level)
parent-original (:parent-option level)]
[:div {:class (stl/css-case :is-selectable is-selectable
:context-menu true
:is-open open?
:fixed fixed?)
:style {:top (+ top (:offset-y @local))
:left (+ left (:offset-x @local))}
:on-key-down (on-key-down original-options parent-original)}
(let [level (-> @local :levels peek)]
[:ul {:class (stl/css-case :min-width min-width?
:context-menu-items true)
:role "menu"
:ref check-menu-offscreen}
(when-let [parent-option (:parent-option level)]
[:*
[:& context-menu-a11y-item
{:id "go-back-sub-option"
:class (stl/css :context-menu-item)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
[:button {:class (stl/css :context-menu-action :submenu-back)
:data-no-close true
:on-click exit-submenu}
[:span {:class (stl/css :submenu-icon-back)} i/arrow-refactor]
parent-option]]
(let [level (-> @local :levels peek) [:li {:class (stl/css :separator)}]])
original-options (:options level)
parent-original (:parent-option level)] (for [[index option] (d/enumerate (:options level))]
[:div {:class (if (and new-css-system workspace?) (let [option-name (:option-name option)
(dom/classnames (css :is-selectable) is-selectable id (:id option)
(css :context-menu) true sub-options (:sub-options option)
(css :is-open) open? option-handler (:option-handler option)
(css :fixed) fixed?) data-test (:data-test option)]
(dom/classnames :is-selectable is-selectable (when option-name
(if (= option-name :separator)
[:li {:key (dm/str "context-item-" index)
:class (stl/css :separator)}]
[:& context-menu-a11y-item
{:id id
:key id
:class (stl/css-case
:is-selected (and selected (= option-name selected))
:selected (and selected (= data-test selected))
:context-menu-item true)
:key-index (dm/str "context-item-" index)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
(if-not sub-options
[:a {:class (stl/css :context-menu-action)
:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))
:data-test data-test}
(if (and in-dashboard? (= option-name "Default"))
(tr "dashboard.default-team-name")
option-name)
(when (and selected (= data-test selected))
[:span {:class (stl/css :selected-icon)} i/tick-refactor])]
[:a {:class (stl/css :context-menu-action :submenu)
:data-no-close true
:on-click (enter-submenu option-name sub-options)
:data-test data-test}
option-name
[:span {:class (stl/css :submenu-icon)} i/arrow-refactor]])]))))])])])
;; OLD
(when (and open? (some? (:levels @local)))
[:> dropdown' props
(let [level (-> @local :levels peek)
original-options (:options level)
parent-original (:parent-option level)]
[:div {:class (dom/classnames :is-selectable is-selectable
:context-menu true :context-menu true
:is-open open? :is-open open?
:fixed fixed?)) :fixed fixed?)
:style {:top (+ top (:offset-y @local)) :style {:top (+ top (:offset-y @local))
:left (+ left (:offset-x @local))} :left (+ left (:offset-x @local))}
:on-key-down (on-key-down original-options parent-original)} :on-key-down (on-key-down original-options parent-original)}
(let [level (-> @local :levels peek)] (let [level (-> @local :levels peek)]
[:ul {:class (if (and new-css-system workspace?) [:ul {:class (dom/classnames :min-width min-width?
(dom/classnames (css :min-width) min-width? :context-menu-items true)
(css :context-menu-items) true) :role "menu"
(dom/classnames :min-width min-width? :ref check-menu-offscreen}
:context-menu-items true)) (when-let [parent-option (:parent-option level)]
:role "menu" [:*
:ref check-menu-offscreen} [:& context-menu-a11y-item
(when-let [parent-option (:parent-option level)] {:id "go-back-sub-option"
[:* :tab-index "0"
[:& context-menu-a11y-item :on-key-down (fn [event]
{:id "go-back-sub-option" (dom/prevent-default event))}
:class (dom/classnames (css :context-menu-item) (and new-css-system workspace?)) [:div {:class (dom/classnames :context-menu-action true
:tab-index "0" :submenu-back true)
:on-key-down (fn [event] :data-no-close true
(dom/prevent-default event))} :on-click exit-submenu}
[:div {:class (if (and new-css-system workspace?) [:span i/arrow-slide]
(dom/classnames (css :context-menu-action) true
(css :submenu-back) true)
(dom/classnames :context-menu-action true
:submenu-back true))
:data-no-close true
:on-click exit-submenu}
[:span {:class (dom/classnames (css :submenu-icon-back) (and new-css-system workspace?))}
(if (and new-css-system workspace?)
i/arrow-refactor
i/arrow-slide)]
parent-option]]
[:li {:class (if (and new-css-system workspace?)
(dom/classnames (css :separator) true)
(dom/classnames :separator true))}]])
(for [[index option] (d/enumerate (:options level))]
(let [option-name (:option-name option)
id (:id option)
sub-options (:sub-options option)
option-handler (:option-handler option)
data-test (:data-test option)]
(when option-name
(if (= option-name :separator)
[:li {:key (dm/str "context-item-" index)
:class (if (and new-css-system workspace?)
(dom/classnames (css :separator) true)
(dom/classnames :separator true))}]
[:& context-menu-a11y-item
{:id id
:key id
:class (if (and new-css-system workspace?)
(dom/classnames (css :is-selected) (and selected (= option-name selected))
(css :selected) (and selected (= data-test selected))
(css :context-menu-item) true)
(dom/classnames :is-selected (and selected (= option-name selected))))
:key-index (dm/str "context-item-" index)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
(if-not sub-options
[:a {:class (if (and new-css-system workspace?)
(dom/classnames (css :context-menu-action) true)
(dom/classnames :context-menu-action true))
:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))
:data-test data-test}
(if (and in-dashboard? (= option-name "Default"))
(tr "dashboard.default-team-name")
option-name)
(when (and new-css-system selected (= data-test selected)) parent-option]]
[:span {:class (dom/classnames (css :selected-icon) true)}
i/tick-refactor])] [:li.separator]])
[:a {:class (if (and new-css-system workspace?)
(dom/classnames (css :context-menu-action) true (for [[index option] (d/enumerate (:options level))]
(css :submenu) true) (let [option-name (:option-name option)
(dom/classnames :context-menu-action true id (:id option)
:submenu true)) sub-options (:sub-options option)
:data-no-close true option-handler (:option-handler option)
:on-click (enter-submenu option-name sub-options) data-test (:data-test option)]
:data-test data-test} (when option-name
option-name (if (= option-name :separator)
[:span {:class (dom/classnames (css :submenu-icon) (and new-css-system workspace?))} [:li.separator {:key (dm/str "context-item-" index)}]
(if (and new-css-system workspace?) [:& context-menu-a11y-item
i/arrow-refactor {:id id
i/arrow-slide)]])]))))])])]))) :key id
:class (dom/classnames :is-selected (and selected (= option-name selected)))
:key-index (dm/str "context-item-" index)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
(if-not sub-options
[:a {:class (dom/classnames :context-menu-action true)
:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))
:data-test data-test}
(if (and in-dashboard? (= option-name "Default"))
(tr "dashboard.default-team-name")
option-name)]
[:a.context-menu-action.submenu
{:data-no-close true
:on-click (enter-submenu option-name sub-options)
:data-test data-test}
option-name
[:span i/arrow-slide]])]))))])])]))))
(mf/defc context-menu-a11y (mf/defc context-menu-a11y
{::mf/wrap-props false} {::mf/wrap-props false}

View File

@ -72,6 +72,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: $fw700; font-weight: $fw700;
background: none;
border: none;
cursor: pointer;
.submenu-icon-back svg { .submenu-icon-back svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--menu-foreground-color); stroke: var(--menu-foreground-color);

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.ui.components.select :as cs]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -252,10 +253,10 @@
value (or (get-in @form [:data input-name]) default) value (or (get-in @form [:data input-name]) default)
cvalue (d/seek #(= value (:value %)) options) cvalue (d/seek #(= value (:value %)) options)
focus? (mf/use-state false) focus? (mf/use-state false)
on-change
handle-change
(fn [event] (fn [event]
(let [target (dom/get-target event) (let [value (if (string? event) event (dom/get-target-val event))]
value (dom/get-value target)]
(fm/on-input-change form input-name value))) (fm/on-input-change form input-name value)))
on-focus on-focus
@ -267,33 +268,16 @@
(reset! focus? false))] (reset! focus? false))]
(if new-css-system (if new-css-system
[:div {:class (stl/css :custom-select)} [:div {:class (stl/css :select-wrapper)}
[:select {:value value [:& cs/select
:on-change on-change {:default-value value
:on-focus on-focus :options options
:on-blur on-blur :on-change handle-change}]]
:disabled disabled
:data-test data-test}
(for [item options]
[:> :option (clj->js (cond-> {:key (:value item) :value (:value item)}
(:disabled item) (assoc :disabled "disabled")
(:hidden item) (assoc :style {:display "none"})))
(:label item)])]
[:div {:class (stl/css-case :input-container true
:disabled disabled
:focus @focus?)}
[:div {:class (stl/css :main-content)}
[:label {:class (stl/css :label)} label]
[:span {:class (stl/css :value)} (:label cvalue "")]]
[:div {:class (stl/css :icon)}
i/arrow-refactor]]]
[:div.custom-select [:div.custom-select
[:select {:value value [:select {:value value
:on-change on-change :on-change handle-change
:on-focus on-focus :on-focus on-focus
:on-blur on-blur :on-blur on-blur
:disabled disabled :disabled disabled

View File

@ -23,12 +23,18 @@
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
text-transform: none; text-transform: uppercase;
input { input {
@extend .input-element; @extend .input-element;
color: var(--input-foreground-color-active); color: var(--input-foreground-color-active);
width: calc(100% - $s-1); width: calc(100% - $s-1);
margin-top: 0; margin-top: 0;
&:focus {
outline: none;
border: $s-1 solid var(--input-border-color-focus);
border-radius: $br-8;
}
} }
// Input autofill // Input autofill
input:-webkit-autofill, input:-webkit-autofill,
@ -39,11 +45,6 @@
-webkit-box-shadow: 0 0 0 28px var(--input-background-color) inset !important; -webkit-box-shadow: 0 0 0 28px var(--input-background-color) inset !important;
border: $s-1 solid var(--input-background-color); border: $s-1 solid var(--input-background-color);
} }
&:focus {
outline: none;
border: $s-1 solid var(--input-border-color-focus);
border-radius: $br-8;
}
} }
&:global(.invalid) { &:global(.invalid) {
input { input {

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard (ns app.main.ui.dashboard
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.spec :as us] [app.common.spec :as us]
@ -52,7 +53,8 @@
(mf/defc dashboard-content (mf/defc dashboard-content
[{:keys [team projects project section search-term profile] :as props}] [{:keys [team projects project section search-term profile] :as props}]
(let [container (mf/use-ref) (let [new-css-system (mf/use-ctx ctx/new-css-system)
container (mf/use-ref)
content-width (mf/use-state 0) content-width (mf/use-state 0)
project-id (:id project) project-id (:id project)
team-id (:id team) team-id (:id team)
@ -81,60 +83,118 @@
(mf/use-effect on-resize) (mf/use-effect on-resize)
[:div.dashboard-content {:on-click clear-selected-fn :ref container} (if new-css-system
(case section [:div {:class (stl/css :dashboard-content)
:dashboard-projects :on-click clear-selected-fn :ref container}
[:* (case section
[:& projects-section :dashboard-projects
{:team team
:projects projects
:profile profile
:default-project-id default-project-id}]
(when (contains? cf/flags :dashboard-templates-section)
[:& templates-section {:profile profile
:project-id project-id
:team-id team-id
:default-project-id default-project-id
:content-width @content-width}])]
:dashboard-fonts
[:& fonts-page {:team team}]
:dashboard-font-providers
[:& font-providers-page {:team team}]
:dashboard-files
(when project
[:* [:*
[:& files-section {:team team :project project}] [:& projects-section
{:team team
:projects projects
:profile profile
:default-project-id default-project-id}]
(when (contains? cf/flags :dashboard-templates-section) (when (contains? cf/flags :dashboard-templates-section)
[:& templates-section {:profile profile [:& templates-section {:profile profile
:team-id team-id
:project-id project-id :project-id project-id
:team-id team-id
:default-project-id default-project-id :default-project-id default-project-id
:content-width @content-width}])]) :content-width @content-width}])]
:dashboard-search :dashboard-fonts
[:& search-page {:team team [:& fonts-page {:team team}]
:search-term search-term}]
:dashboard-libraries :dashboard-font-providers
[:& libraries-page {:team team}] [:& font-providers-page {:team team}]
:dashboard-team-members :dashboard-files
[:& team-members-page {:team team :profile profile}] (when project
[:*
[:& files-section {:team team :project project}]
(when (contains? cf/flags :dashboard-templates-section)
[:& templates-section {:profile profile
:team-id team-id
:project-id project-id
:default-project-id default-project-id
:content-width @content-width}])])
:dashboard-team-invitations :dashboard-search
[:& team-invitations-page {:team team}] [:& search-page {:team team
:search-term search-term}]
:dashboard-team-webhooks :dashboard-libraries
[:& team-webhooks-page {:team team}] [:& libraries-page {:team team}]
:dashboard-team-settings :dashboard-team-members
[:& team-settings-page {:team team :profile profile}] [:& team-members-page {:team team :profile profile}]
nil)])) :dashboard-team-invitations
[:& team-invitations-page {:team team}]
:dashboard-team-webhooks
[:& team-webhooks-page {:team team}]
:dashboard-team-settings
[:& team-settings-page {:team team :profile profile}]
nil)]
;; OLD
[:div.dashboard-content {:on-click clear-selected-fn :ref container}
(case section
:dashboard-projects
[:*
[:& projects-section
{:team team
:projects projects
:profile profile
:default-project-id default-project-id}]
(when (contains? cf/flags :dashboard-templates-section)
[:& templates-section {:profile profile
:project-id project-id
:team-id team-id
:default-project-id default-project-id
:content-width @content-width}])]
:dashboard-fonts
[:& fonts-page {:team team}]
:dashboard-font-providers
[:& font-providers-page {:team team}]
:dashboard-files
(when project
[:*
[:& files-section {:team team :project project}]
(when (contains? cf/flags :dashboard-templates-section)
[:& templates-section {:profile profile
:team-id team-id
:project-id project-id
:default-project-id default-project-id
:content-width @content-width}])])
:dashboard-search
[:& search-page {:team team
:search-term search-term}]
:dashboard-libraries
[:& libraries-page {:team team}]
:dashboard-team-members
[:& team-members-page {:team team :profile profile}]
:dashboard-team-invitations
[:& team-invitations-page {:team team}]
:dashboard-team-webhooks
[:& team-webhooks-page {:team team}]
:dashboard-team-settings
[:& team-settings-page {:team team :profile profile}]
nil)])))
(mf/defc dashboard (mf/defc dashboard
[{:keys [route profile] :as props}] [{:keys [route profile] :as props}]
@ -168,31 +228,59 @@
(fn [] (fn []
(events/unlistenByKey key)))) (events/unlistenByKey key))))
[:& (mf/provider ctx/current-team-id) {:value team-id} (if new-css-system
[:& (mf/provider ctx/current-project-id) {:value project-id} [:& (mf/provider ctx/current-team-id) {:value team-id}
;; NOTE: dashboard events and other related functions assumes [:& (mf/provider ctx/current-project-id) {:value project-id}
;; that the team is a implicit context variable that is ;; NOTE: dashboard events and other related functions assumes
;; available using react context or accessing ;; that the team is a implicit context variable that is
;; the :current-team-id on the state. We set the key to the ;; available using react context or accessing
;; team-id because we want to completely refresh all the ;; the :current-team-id on the state. We set the key to the
;; components on team change. Many components assumes that the ;; team-id because we want to completely refresh all the
;; team is already set so don't put the team into mf/deps. ;; components on team change. Many components assumes that the
(when team ;; team is already set so don't put the team into mf/deps.
[:main {:class (dom/classnames :dashboard-layout (not new-css-system) (when team
:dashboard-layout-refactor new-css-system) [:main {:class (stl/css :dashboard)
:key (:id team)} :key (:id team)}
[:& sidebar [:& sidebar
{:team team {:team team
:projects projects :projects projects
:project project
:profile profile
:section section
:search-term search-term}]
(when (and team profile (seq projects))
[:& dashboard-content
{:projects projects
:profile profile
:project project :project project
:profile profile
:section section :section section
:search-term search-term :search-term search-term}]
:team team}])])]])) (when (and team profile (seq projects))
[:& dashboard-content
{:projects projects
:profile profile
:project project
:section section
:search-term search-term
:team team}])])]]
[:& (mf/provider ctx/current-team-id) {:value team-id}
[:& (mf/provider ctx/current-project-id) {:value project-id}
;; NOTE: dashboard events and other related functions assumes
;; that the team is a implicit context variable that is
;; available using react context or accessing
;; the :current-team-id on the state. We set the key to the
;; team-id because we want to completely refresh all the
;; components on team change. Many components assumes that the
;; team is already set so don't put the team into mf/deps.
(when team
[:main {:class (dom/classnames :dashboard-layout true) :key (:id team)}
[:& sidebar
{:team team
:projects projects
:project project
:profile profile
:section section
:search-term search-term}]
(when (and team profile (seq projects))
[:& dashboard-content
{:projects projects
:profile profile
:project project
:section section
:search-term search-term
:team team}])])]])))

View File

@ -0,0 +1,28 @@
// 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) KALEIDOS INC
@use "refactor/common-refactor.scss" as *;
.dashboard {
background-color: $db-primary;
display: grid;
grid-template-columns: $s-40 $s-256 1fr;
grid-template-rows: $s-52 1fr;
height: 100vh;
:global(svg#loader-pencil) {
fill: $df-secondary;
width: $s-32;
}
}
.dashboard-content {
display: flex;
flex-direction: column;
position: relative;
grid-row: 1 / span 2;
padding: $s-16 $s-16 0 0;
}

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.comments (ns app.main.ui.dashboard.comments
(:require-macros [app.main.style :as stl])
(:require (:require
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
[app.main.data.events :as ev] [app.main.data.events :as ev]
@ -13,6 +14,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.comments :as cmt] [app.main.ui.comments :as cmt]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -20,11 +22,40 @@
[potok.core :as ptk] [potok.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc comments-icon
[{:keys [profile show? on-show-comments]}]
(let [threads-map (mf/deref refs/comment-threads)
tgroups
(->> (vals threads-map)
(sort-by :modified-at)
(reverse)
(dcm/apply-filters {} profile)
(dcm/group-threads-by-file-and-page))
handle-keydown
(mf/use-callback
(mf/deps on-show-comments)
(fn [event]
(when (kbd/enter? event)
(on-show-comments event))))]
[:div {:class (stl/css :dashboard-comments-section)}
[:button
{:tab-index "0"
:on-click on-show-comments
:on-key-down handle-keydown
:data-test "open-comments"
:class (stl/css-case :button true
:open show?
:unread (boolean (seq tgroups)))}
i/chat]]))
(mf/defc comments-section (mf/defc comments-section
[{:keys [profile team]}] [{:keys [profile team show? on-show-comments on-hide-comments]}]
(let [show-dropdown? (mf/use-state false) (let [new-css-system (mf/use-ctx ctx/new-css-system)
show-dropdown (mf/use-fn #(reset! show-dropdown? true))
hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
threads-map (mf/deref refs/comment-threads) threads-map (mf/deref refs/comment-threads)
users (mf/deref refs/current-team-comments-users) users (mf/deref refs/current-team-comments-users)
team-id (:id team) team-id (:id team)
@ -35,6 +66,13 @@
(dcm/apply-filters {} profile) (dcm/apply-filters {} profile)
(dcm/group-threads-by-file-and-page)) (dcm/group-threads-by-file-and-page))
handle-keydown
(mf/use-callback
(mf/deps on-hide-comments)
(fn [event]
(when (kbd/enter? event)
(on-hide-comments event))))
on-navigate on-navigate
(mf/use-callback (mf/use-callback
(fn [thread] (fn [thread]
@ -45,58 +83,87 @@
(mf/deps team-id) (mf/deps team-id)
(fn [] (fn []
(st/emit! (dcm/retrieve-unread-comment-threads team-id)))) (st/emit! (dcm/retrieve-unread-comment-threads team-id))))
(mf/use-effect (mf/use-effect
(mf/deps @show-dropdown?) (mf/deps show?)
(fn [] (fn []
(when @show-dropdown? (when show?
(st/emit! (ptk/event ::ev/event {::ev/name "open-comment-notifications" (st/emit! (ptk/event ::ev/event {::ev/name "open-comment-notifications"
::ev/origin "dashboard"}))))) ::ev/origin "dashboard"})))))
[:div.dashboard-comments-section (if new-css-system
[:div.button [:div {:class (stl/css :dashboard-comments-section)}
{:tab-index "0" [:& dropdown {:show show? :on-close on-hide-comments}
:on-click show-dropdown [:div {:class (stl/css :dropdown :comments-section :comment-threads-section)}
:on-key-down (fn [event] [:div {:class (stl/css :header)}
(when (kbd/enter? event) [:h3 (tr "labels.comments")]
(show-dropdown event))) [:button
:data-test "open-comments" {:class (stl/css :close)
:class (dom/classnames :open @show-dropdown? :tab-index (if show? "0" "-1")
:unread (boolean (seq tgroups)))} :on-click on-hide-comments
i/chat] :on-key-down handle-keydown} i/close]]
[:& dropdown {:show @show-dropdown? :on-close hide-dropdown} (if (seq tgroups)
[:div.dropdown.comments-section.comment-threads-section. [:div {:class (stl/css :thread-groups)}
[:div.header [:& cmt/comment-thread-group
[:h3 (tr "labels.comments")] {:group (first tgroups)
[:span.close {:tab-index (if @show-dropdown? :on-thread-click on-navigate
"0" :show-file-name true
"-1") :users users}]
:on-click hide-dropdown (for [tgroup (rest tgroups)]
:on-key-down (fn [event] [:& cmt/comment-thread-group
(when (kbd/enter? event) {:group tgroup
(hide-dropdown event)))} i/close]] :on-thread-click on-navigate
:show-file-name true
:users users
:key (:page-id tgroup)}])]
[:hr] [:div {:class (stl/css :thread-groups-placeholder)}
i/chat
(tr "labels.no-comments-available")])]]]
(if (seq tgroups) ;; OLD
[:div.thread-groups [:div.dashboard-comments-section
[:& cmt/comment-thread-group [:div.button
{:group (first tgroups) {:tab-index "0"
:on-thread-click on-navigate :on-click on-show-comments
:show-file-name true :on-key-down (fn [event]
:users users}] (when (kbd/enter? event)
(for [tgroup (rest tgroups)] (on-show-comments event)))
[:* :data-test "open-comments"
[:hr] :class (dom/classnames :open show?
:unread (boolean (seq tgroups)))}
i/chat]
[:& cmt/comment-thread-group [:& dropdown {:show show? :on-close on-hide-comments}
{:group tgroup [:div.dropdown.comments-section.comment-threads-section.
:on-thread-click on-navigate [:div.header
:show-file-name true [:h3 (tr "labels.comments")]
:users users [:span.close {:tab-index (if show? "0" "-1")
:key (:page-id tgroup)}]])] :on-click on-hide-comments
:on-key-down handle-keydown}
i/close]]
[:div.thread-groups-placeholder [:hr]
i/chat
(tr "labels.no-comments-available")])]]])) (if (seq tgroups)
[:div.thread-groups
[:& cmt/comment-thread-group
{:group (first tgroups)
:on-thread-click on-navigate
:show-file-name true
:users users}]
(for [tgroup (rest tgroups)]
[:*
[:hr]
[:& cmt/comment-thread-group
{:group tgroup
:on-thread-click on-navigate
:show-file-name true
:users users
:key (:page-id tgroup)}]])]
[:div.thread-groups-placeholder
i/chat
(tr "labels.no-comments-available")])]]])))

View 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/.
//
// Copyright (c) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
.dashboard-comments-section {
display: flex;
align-items: center;
justify-content: center;
position: relative;
border-radius: $br-8;
button {
cursor: pointer;
background: none;
border: none;
}
}
.thread-groups {
height: calc(100% - $s-32);
overflow-y: scroll;
max-height: $s-440;
overflow: auto;
hr {
background-color: $df-primary;
border: 0;
height: $s-1;
margin: 0;
}
}
.thread-group {
display: flex;
flex-direction: column;
font-size: $fs-12;
}
.thread-groups-placeholder {
align-items: center;
display: flex;
flex-direction: column;
font-size: $fs-12;
padding: $s-24;
text-align: center;
svg {
fill: $df-secondary;
height: $s-24;
margin-bottom: $s-24;
width: $s-24;
}
}
.button {
display: flex;
align-items: center;
justify-content: center;
border-radius: $br-8;
height: $s-32;
width: $s-32;
svg {
width: $s-16;
height: $s-16;
fill: $df-secondary;
}
&.unread svg,
&.open svg {
fill: $da-tertiary;
}
&:hover {
background-color: $db-cuaternary;
svg {
fill: $da-primary;
}
}
}
.dropdown {
@include menuShadow;
background-color: $db-tertiary;
border-radius: $br-8;
border: $s-1 solid transparent;
bottom: $s-4;
height: 40vh;
max-height: $s-480;
min-height: $s-200;
position: absolute;
width: 100%;
z-index: $z-index-3;
hr {
margin: 0;
border-color: $df-secondary;
}
}
.header {
display: flex;
height: $s-40;
align-items: center;
padding: 0 $s-12;
h3 {
color: $df-secondary;
font-size: $fs-11;
line-height: 1.28;
flex-grow: 1;
text-transform: uppercase;
}
.close {
display: flex;
align-items: center;
cursor: pointer;
svg {
width: $s-16;
height: $s-16;
transform: rotate(45deg);
fill: $df-secondary;
}
}
}

View File

@ -5,28 +5,29 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.files (ns app.main.ui.dashboard.files
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.math :as mth]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.grid :refer [grid]] [app.main.ui.dashboard.grid :refer [grid]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]] [app.main.ui.dashboard.project-menu :refer [project-menu]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc header (mf/defc header
[{:keys [project create-fn] :as props}] [{:keys [project create-fn] :as props}]
(let [local (mf/use-state (let [new-css-system (mf/use-ctx ctx/new-css-system)
local (mf/use-state
{:menu-open false {:menu-open false
:edition false}) :edition false})
@ -63,79 +64,140 @@
(dd/clear-selected-files))))] (dd/clear-selected-files))))]
[:header.dashboard-header (if new-css-system
(if (:is-default project) [:header {:class (stl/css :dashboard-header)}
[:div.dashboard-title#dashboard-drafts-title (if (:is-default project)
[:h1 (tr "labels.drafts")]] [:div#dashboard-drafts-title {:class (stl/css :dashboard-title)}
[:h1 (tr "labels.drafts")]]
(if (:edition @local) (if (:edition @local)
[:& inline-edition {:content (:name project) [:& inline-edition
:on-end (fn [name] {:content (:name project)
(let [name (str/trim name)] :on-end (fn [name]
(when-not (str/empty? name) (let [name (str/trim name)]
(st/emit! (-> (dd/rename-project (assoc project :name name)) (when-not (str/empty? name)
(with-meta {::ev/origin "project"})))) (st/emit! (-> (dd/rename-project (assoc project :name name))
(swap! local assoc :edition false)))}] (with-meta {::ev/origin "project"}))))
[:div.dashboard-title (swap! local assoc :edition false)))}]
[:h1 {:on-double-click on-edit [:div {:class (stl/css :dashboard-title)}
:data-test "project-title" [:h1 {:on-double-click on-edit
:id (:id project)} :data-test "project-title"
(:name project)]])) :id (:id project)}
(:name project)]]))
[:& project-menu {:project project [:& project-menu {:project project
:show? (:menu-open @local) :show? (:menu-open @local)
:left (- (:x (:menu-pos @local)) 180) :left (- (:x (:menu-pos @local)) 180)
:top (:y (:menu-pos @local)) :top (:y (:menu-pos @local))
:on-edit on-edit :on-edit on-edit
:on-menu-close on-menu-close :on-menu-close on-menu-close
:on-import on-import}] :on-import on-import}]
[:div.dashboard-header-actions [:div {:class (stl/css :dashboard-header-actions)}
[:a.btn-secondary.btn-small [:a
{:tab-index "0" {:class (stl/css :btn-secondary :btn-small)
:on-click on-create-click :tab-index "0"
:data-test "new-file" :on-click on-create-click
:on-key-down (fn [event] :data-test "new-file"
(when (kbd/enter? event)
(on-create-click event)))}
(tr "dashboard.new-file")]
(when-not (:is-default project)
[:button.icon.pin-icon.tooltip.tooltip-bottom
{:tab-index "0"
:class (when (:is-pinned project) "active")
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:on-key-down (fn [event] :on-key-down (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(toggle-pin event)))} (on-create-click event)))}
(if (:is-pinned project) (tr "dashboard.new-file")]
i/pin-fill
i/pin)])
[:div.icon.tooltip.tooltip-bottom-left (when-not (:is-default project)
{:tab-index "0" [:button
:on-click on-menu-click {:class (stl/css-case :icon true
:alt (tr "dashboard.options") :pin-icon true
:on-key-down (fn [event] :tooltip true
(when (kbd/enter? event) :tooltip-bottom true
(on-menu-click event)))} :active (:is-pinned project))
i/actions]]])) :tab-index "0"
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-pin event)))}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:div
{:class (stl/css :icon :tooltip :tooltip-bottom-left)
:tab-index "0"
:on-click on-menu-click
:alt (tr "dashboard.options")
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-menu-click event)))}
i/actions]]]
;; OLD
[:header.dashboard-header
(if (:is-default project)
[:div.dashboard-title#dashboard-drafts-title
[:h1 (tr "labels.drafts")]]
(if (:edition @local)
[:& inline-edition {:content (:name project)
:on-end (fn [name]
(let [name (str/trim name)]
(when-not (str/empty? name)
(st/emit! (-> (dd/rename-project (assoc project :name name))
(with-meta {::ev/origin "project"}))))
(swap! local assoc :edition false)))}]
[:div.dashboard-title
[:h1 {:on-double-click on-edit
:data-test "project-title"
:id (:id project)}
(:name project)]]))
[:& project-menu {:project project
:show? (:menu-open @local)
:left (- (:x (:menu-pos @local)) 180)
:top (:y (:menu-pos @local))
:on-edit on-edit
:on-menu-close on-menu-close
:on-import on-import}]
[:div.dashboard-header-actions
[:a.btn-secondary.btn-small
{:tab-index "0"
:on-click on-create-click
:data-test "new-file"
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-create-click event)))}
(tr "dashboard.new-file")]
(when-not (:is-default project)
[:button.icon.pin-icon.tooltip.tooltip-bottom
{:tab-index "0"
:class (when (:is-pinned project) "active")
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-pin event)))}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:div.icon.tooltip.tooltip-bottom-left
{:tab-index "0"
:on-click on-menu-click
:alt (tr "dashboard.options")
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-menu-click event)))}
i/actions]]])))
(mf/defc files-section (mf/defc files-section
[{:keys [project team] :as props}] [{:keys [project team] :as props}]
(let [files-map (mf/deref refs/dashboard-files) (let [new-css-system (mf/use-ctx ctx/new-css-system)
files-map (mf/deref refs/dashboard-files)
project-id (:id project) project-id (:id project)
width (mf/use-state nil)
rowref (mf/use-ref)
itemsize (if (>= @width 1030)
280
230)
ratio (if (some? @width) (/ @width itemsize) 0) [rowref limit] (hooks/use-dynamic-grid-item-width)
nitems (mth/floor ratio)
limit (min 10 nitems)
limit (max 1 limit)
files (mf/with-memo [project-id files-map] files (mf/with-memo [project-id files-map]
(->> (vals files-map) (->> (vals files-map)
@ -160,21 +222,6 @@
(st/emit! (-> (dd/create-file (with-meta params mdata)) (st/emit! (-> (dd/create-file (with-meta params mdata))
(with-meta {::ev/origin origin}))))))] (with-meta {::ev/origin origin}))))))]
(mf/with-effect []
(let [node (mf/ref-val rowref)
mnt? (volatile! true)
sub (->> (wapi/observe-resize node)
(rx/observe-on :af)
(rx/subs (fn [entries]
(let [row (first entries)
row-rect (.-contentRect ^js row)
row-width (.-width ^js row-rect)]
(when @mnt?
(reset! width row-width))))))]
(fn []
(vreset! mnt? false)
(rx/dispose! sub))))
(mf/with-effect [project] (mf/with-effect [project]
(when project (when project
(let [pname (if (:is-default project) (let [pname (if (:is-default project)
@ -186,14 +233,28 @@
(st/emit! (dd/fetch-files {:project-id project-id}) (st/emit! (dd/fetch-files {:project-id project-id})
(dd/clear-selected-files))) (dd/clear-selected-files)))
[:* (if new-css-system
[:& header {:team team [:*
:project project [:& header {:team team
:create-fn create-file}] :project project
[:section.dashboard-container.no-bg {:ref rowref} :create-fn create-file}]
[:& grid {:project project [:section {:class (stl/css :dashboard-container :no-bg)
:files files :ref rowref}
:origin :files [:& grid {:project project
:create-fn create-file :files files
:limit limit}]]])) :origin :files
:create-fn create-file
:limit limit}]]]
;; OLD
[:*
[:& header {:team team
:project project
:create-fn create-file}]
[:section.dashboard-container.no-bg {:ref rowref}
[:& grid {:project project
:files files
:origin :files
:create-fn create-file
:limit limit}]]])))

View File

@ -0,0 +1,28 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.fonts (ns app.main.ui.dashboard.fonts
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.media :as cm] [app.common.media :as cm]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
@ -14,6 +15,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -38,29 +40,17 @@
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [section team] :as props}] [{:keys [section team] :as props}]
;; (let [go-fonts (let [new-css-system (mf/use-ctx ctx/new-css-system)]
;; (mf/use-callback (use-set-page-title team section)
;; (mf/deps team) (if new-css-system
;; #(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)}))) [:header {:class (stl/css :dashboard-header)}
[:div#dashboard-fonts-title {:class (stl/css :dashboard-title)}
[:h1 (tr "labels.fonts")]]]
;; go-providers ;; OLD
;; (mf/use-callback [:header.dashboard-header
;; (mf/deps team) [:div.dashboard-title#dashboard-fonts-title
;; #(st/emit! (rt/nav :dashboard-font-providers {:team-id (:id team)})))] [:h1 (tr "labels.fonts")]]])))
(use-set-page-title team section)
[:header.dashboard-header
[:div.dashboard-title#dashboard-fonts-title
[:h1 (tr "labels.fonts")]]
[:nav
#_[:ul
[:li {:class (when (= section :fonts) "active")}
[:a {:on-click go-fonts} (tr "labels.custom-fonts")]]
[:li {:class (when (= section :providers) "active")}
[:a {:on-click go-providers} (tr "labels.font-providers")]]]]
[:div]])
(mf/defc font-variant-display-name (mf/defc font-variant-display-name
[{:keys [variant]}] [{:keys [variant]}]
@ -71,7 +61,8 @@
(mf/defc fonts-upload (mf/defc fonts-upload
[{:keys [team installed-fonts] :as props}] [{:keys [team installed-fonts] :as props}]
(let [fonts (mf/use-state {}) (let [new-css-system (mf/use-ctx ctx/new-css-system)
fonts (mf/use-state {})
input-ref (mf/use-ref) input-ref (mf/use-ref)
uploading (mf/use-state #{}) uploading (mf/use-state #{})
@ -122,78 +113,159 @@
(fn [items] (fn [items]
(run! on-delete items)) (run! on-delete items))
problematic-fonts? (some :height-warning? (vals @fonts))] problematic-fonts? (some :height-warning? (vals @fonts))
[:div.dashboard-fonts-upload handle-upload-all
[:div.dashboard-fonts-hero (mf/use-callback (mf/deps @fonts) #(on-upload-all (vals @fonts)))
[:div.desc
[:h2 (tr "labels.upload-custom-fonts")]
[:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}]
[:div.banner handle-dismiss-all
[:div.icon i/msg-info] (mf/use-callback (mf/deps @fonts) #(on-dismiss-all (vals @fonts)))]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts? (if new-css-system
[:div.banner.warning [:div {:class (stl/css :dashboard-fonts-upload)}
[:div.icon i/msg-warning] [:div {:class (stl/css :dashboard-fonts-hero)}
[:div {:class (stl/css :desc)}
[:h2 (tr "labels.upload-custom-fonts")]
[:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}]
[:button
{:class (stl/css :btn-primary)
:on-click on-click
:tab-index "0"}
[:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload"
:accept cm/str-font-types
:multi true
:ref input-ref
:on-selected on-selected}]]
[:div {:class (stl/css :banner)}
[:div {:class (stl/css :icon)} i/msg-info]
[:div {:class (stl/css :content)}
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts?
[:div {:class (stl/css :banner :warning)}
[:div {:class (stl/css :icon)} i/msg-warning]
[:div {:class (stl/css :content)}
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])]]
[:*
(when (some? (vals @fonts))
[:div {:class (stl/css :font-item :table-row)}
[:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))]
[:div {:class (stl/css :table-field :options)}
[:button {:class (stl/css :btn-primary)
:on-click handle-upload-all :data-test "upload-all"}
[:span (tr "dashboard.fonts.upload-all")]]
[:button {:class (stl/css :btn-secondary)
:on-click handle-dismiss-all :data-test "dismiss-all"}
[:span (tr "dashboard.fonts.dismiss-all")]]]])
(for [item (sort-by :font-family (vals @fonts))]
(let [uploading? (contains? @uploading (:id item))]
[:div {:class (stl/css :font-item :table-row) :key (:id item)}
[:div {:class (stl/css :table-field :family)}
[:input {:type "text"
:on-blur #(on-blur-name (:id item) %)
:default-value (:font-family item)}]]
[:div {:class (stl/css :table-field :variants)}
[:span {:class (stl/css :label)}
[:& font-variant-display-name {:variant item}]]]
[:div {:class (stl/css :table-field :filenames)}
(for [item (:names item)]
[:span item])]
[:div {:class (stl/css :table-field :options)}
(when (:height-warning? item)
[:span {:class (stl/css :icon :failure)} i/msg-warning])
[:button
{:on-click #(on-upload item)
:class (stl/css-case :btn-primary true
:upload-button true
:disabled uploading?)
:disabled uploading?}
(if uploading?
(tr "labels.uploading")
(tr "labels.upload"))]
[:span {:class (stl/css :icon :close)
:on-click #(on-delete item)} i/close]]]))]]
;; OLD
[:div.dashboard-fonts-upload
[:div.dashboard-fonts-hero
[:div.desc
[:h2 (tr "labels.upload-custom-fonts")]
[:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}]
[:div.banner
[:div.icon i/msg-info]
[:div.content [:div.content
[:& i18n/tr-html {:tag-name "span" [:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])] :label "dashboard.fonts.hero-text2"}]]]
[:button.btn-primary (when problematic-fonts?
{:on-click on-click [:div.banner.warning
:tab-index "0"} [:div.icon i/msg-warning]
[:span (tr "labels.add-custom-font")] [:div.content
[:& file-uploader {:input-id "font-upload" [:& i18n/tr-html {:tag-name "span"
:accept cm/str-font-types :label "dashboard.fonts.warning-text"}]]])]
:multi true
:ref input-ref
:on-selected on-selected}]]]
[:* [:button.btn-primary
(when (some? (vals @fonts)) {:on-click on-click
[:div.font-item.table-row :tab-index "0"}
[:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))] [:span (tr "labels.add-custom-font")]
[:div.table-field.options [:& file-uploader {:input-id "font-upload"
[:div.btn-primary :accept cm/str-font-types
{:on-click #(on-upload-all (vals @fonts)) :data-test "upload-all"} :multi true
[:span (tr "dashboard.fonts.upload-all")]] :ref input-ref
[:div.btn-secondary :on-selected on-selected}]]]
{:on-click #(on-dismiss-all (vals @fonts)) :data-test "dismiss-all"}
[:span (tr "dashboard.fonts.dismiss-all")]]]])
(for [item (sort-by :font-family (vals @fonts))]
(let [uploading? (contains? @uploading (:id item))]
[:div.font-item.table-row {:key (:id item)}
[:div.table-field.family
[:input {:type "text"
:on-blur #(on-blur-name (:id item) %)
:default-value (:font-family item)}]]
[:div.table-field.variants
[:span.label
[:& font-variant-display-name {:variant item}]]]
[:div.table-field.filenames
(for [item (:names item)]
[:span item])]
[:*
(when (some? (vals @fonts))
[:div.font-item.table-row
[:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))]
[:div.table-field.options [:div.table-field.options
(when (:height-warning? item) [:div.btn-primary
[:span.icon.failure i/msg-warning]) {:on-click #(on-upload-all (vals @fonts)) :data-test "upload-all"}
[:button.btn-primary.upload-button [:span (tr "dashboard.fonts.upload-all")]]
{:on-click #(on-upload item) [:div.btn-secondary
:class (dom/classnames :disabled uploading?) {:on-click #(on-dismiss-all (vals @fonts)) :data-test "dismiss-all"}
:disabled uploading?} [:span (tr "dashboard.fonts.dismiss-all")]]]])
(if uploading?
(tr "labels.uploading") (for [item (sort-by :font-family (vals @fonts))]
(tr "labels.upload"))] (let [uploading? (contains? @uploading (:id item))]
[:span.icon.close {:on-click #(on-delete item)} i/close]]]))]])) [:div.font-item.table-row {:key (:id item)}
[:div.table-field.family
[:input {:type "text"
:on-blur #(on-blur-name (:id item) %)
:default-value (:font-family item)}]]
[:div.table-field.variants
[:span.label
[:& font-variant-display-name {:variant item}]]]
[:div.table-field.filenames
(for [item (:names item)]
[:span item])]
[:div.table-field.options
(when (:height-warning? item)
[:span.icon.failure i/msg-warning])
[:button.btn-primary.upload-button
{:on-click #(on-upload item)
:class (dom/classnames :disabled uploading?)
:disabled uploading?}
(if uploading?
(tr "labels.uploading")
(tr "labels.upload"))]
[:span.icon.close {:on-click #(on-delete item)} i/close]]]))]])))
(mf/defc installed-font (mf/defc installed-font
[{:keys [font-id variants] :as props}] [{:keys [font-id variants] :as props}]
(let [font (first variants) (let [new-css-system (mf/use-ctx ctx/new-css-system)
font (first variants)
variants (sort-by (fn [item] variants (sort-by (fn [item]
[(:font-weight item) [(:font-weight item)
@ -252,50 +324,94 @@
:on-accept (fn [_props] :on-accept (fn [_props]
(delete-variant-fn id))})))] (delete-variant-fn id))})))]
[:div.font-item.table-row (if new-css-system
[:div.table-field.family [:div {:class (stl/css :font-item :table-row)}
(if @edit? [:div {:class (stl/css :table-field :family)}
[:input {:type "text" (if @edit?
:default-value @state [:input {:type "text"
:on-key-down on-key-down :default-value @state
:on-change on-change}] :on-key-down on-key-down
[:span (:font-family font)])] :on-change on-change}]
[:span (:font-family font)])]
[:div.table-field.variants [:div {:class (stl/css :table-field :variants)}
(for [item variants] (for [item variants]
[:div.variant [:div {:class (stl/css :variant)}
[:span.label [:span {:class (stl/css :label)}
[:& font-variant-display-name {:variant item}]] [:& font-variant-display-name {:variant item}]]
[:span.icon.close [:span
{:on-click #(on-delete-variant (:id item))} {:class (stl/css :icon :close)
i/plus]])] :on-click #(on-delete-variant (:id item))}
i/plus]])]
[:div] (if @edit?
[:div {:class (stl/css :table-field :options)}
[:button
{:disabled (str/blank? @state)
:on-click on-save
:class (stl/css-case :btn-primary true
:btn-disabled (str/blank? @state))}
(tr "labels.save")]
[:button {:class (stl/css :icon :close)
:on-click on-cancel} i/close]]
(if @edit? [:div {:class (stl/css :table-field :options)}
[:div.table-field.options [:span {:class (stl/css :icon)
[:button.btn-primary :on-click #(reset! open-menu? true)} i/actions]
{:disabled (str/blank? @state) [:& context-menu
:on-click on-save {:on-close #(reset! open-menu? false)
:class (dom/classnames :btn-disabled (str/blank? @state))} :show @open-menu?
(tr "labels.save")] :fixed? false
[:span.icon.close {:on-click on-cancel} i/close]] :top -15
:left -115
:options [[(tr "labels.edit") #(reset! edit? true) nil "font-edit"]
[(tr "labels.delete") on-delete nil "font-delete"]]}]])]
;;OLD
[:div.font-item.table-row
[:div.table-field.family
(if @edit?
[:input {:type "text"
:default-value @state
:on-key-down on-key-down
:on-change on-change}]
[:span (:font-family font)])]
[:div.table-field.options [:div.table-field.variants
[:span.icon {:on-click #(reset! open-menu? true)} i/actions] (for [item variants]
[:& context-menu [:div.variant
{:on-close #(reset! open-menu? false) [:span.label
:show @open-menu? [:& font-variant-display-name {:variant item}]]
:fixed? false [:span.icon.close
:top -15 {:on-click #(on-delete-variant (:id item))}
:left -115 i/plus]])]
:options [[(tr "labels.edit") #(reset! edit? true) nil "font-edit"]
[(tr "labels.delete") on-delete nil "font-delete"]]}]])])) [:div]
(if @edit?
[:div.table-field.options
[:button.btn-primary
{:disabled (str/blank? @state)
:on-click on-save
:class (dom/classnames :btn-disabled (str/blank? @state))}
(tr "labels.save")]
[:span.icon.close {:on-click on-cancel} i/close]]
[:div.table-field.options
[:span.icon {:on-click #(reset! open-menu? true)} i/actions]
[:& context-menu
{:on-close #(reset! open-menu? false)
:show @open-menu?
:fixed? false
:top -15
:left -115
:options [[(tr "labels.edit") #(reset! edit? true) nil "font-edit"]
[(tr "labels.delete") on-delete nil "font-delete"]]}]])])))
(mf/defc installed-fonts (mf/defc installed-fonts
[{:keys [fonts] :as props}] [{:keys [fonts] :as props}]
(let [sterm (mf/use-state "") (let [new-css-system (mf/use-ctx ctx/new-css-system)
sterm (mf/use-state "")
matches? matches?
#(str/includes? (str/lower (:font-family %)) @sterm) #(str/includes? (str/lower (:font-family %)) @sterm)
@ -306,48 +422,98 @@
(let [val (dom/get-target-val event)] (let [val (dom/get-target-val event)]
(reset! sterm (str/lower val)))))] (reset! sterm (str/lower val)))))]
[:div.dashboard-installed-fonts (if new-css-system
[:h3 (tr "labels.installed-fonts")] [:div {:class (stl/css :dashboard-installed-fonts)}
[:div.installed-fonts-header [:h3 (tr "labels.installed-fonts")]
[:div.table-field.family (tr "labels.font-family")] [:div {:class (stl/css :installed-fonts-header)}
[:div.table-field.variants (tr "labels.font-variants")] [:div {:class (stl/css :table-field :family)} (tr "labels.font-family")]
[:div] [:div {:class (stl/css :table-field :variants)} (tr "labels.font-variants")]
[:div.table-field.search-input [:div {:class (stl/css :table-field :search-input)}
[:input {:placeholder (tr "labels.search-font") [:input {:placeholder (tr "labels.search-font")
:default-value "" :default-value ""
:on-change on-change}]]] :on-change on-change}]]]
(cond (cond
(seq fonts) (seq fonts)
(for [[font-id variants] (->> (vals fonts) (for [[font-id variants] (->> (vals fonts)
(filter matches?) (filter matches?)
(group-by :font-id))] (group-by :font-id))]
[:& installed-font {:key (str font-id) [:& installed-font {:key (str font-id)
:font-id font-id :font-id font-id
:variants variants}]) :variants variants}])
(nil? fonts) (nil? fonts)
[:div.fonts-placeholder [:div {:class (stl/css :fonts-placeholder)}
[:div.icon i/loader] [:div {:class (stl/css :icon)} i/loader]
[:div.label (tr "dashboard.loading-fonts")]] [:div {:class (stl/css :label)} (tr "dashboard.loading-fonts")]]
:else :else
[:div.fonts-placeholder [:div {:class (stl/css :fonts-placeholder)}
[:div.icon i/text] [:div {:class (stl/css :icon)} i/text]
[:div.label (tr "dashboard.fonts.empty-placeholder")]])])) [:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]])]
;; OLD
[:div.dashboard-installed-fonts
[:h3 (tr "labels.installed-fonts")]
[:div.installed-fonts-header
[:div.table-field.family (tr "labels.font-family")]
[:div.table-field.variants (tr "labels.font-variants")]
[:div]
[:div.table-field.search-input
[:input {:placeholder (tr "labels.search-font")
:default-value ""
:on-change on-change}]]]
(cond
(seq fonts)
(for [[font-id variants] (->> (vals fonts)
(filter matches?)
(group-by :font-id))]
[:& installed-font {:key (str font-id)
:font-id font-id
:variants variants}])
(nil? fonts)
[:div.fonts-placeholder
[:div.icon i/loader]
[:div.label (tr "dashboard.loading-fonts")]]
:else
[:div.fonts-placeholder
[:div.icon i/text]
[:div.label (tr "dashboard.fonts.empty-placeholder")]])])))
(mf/defc fonts-page (mf/defc fonts-page
[{:keys [team] :as props}] [{:keys [team] :as props}]
(let [fonts (mf/deref refs/dashboard-fonts)] (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:* fonts (mf/deref refs/dashboard-fonts)]
[:& header {:team team :section :fonts}] (if new-css-system
[:section.dashboard-container.dashboard-fonts [:*
[:& fonts-upload {:team team :installed-fonts fonts}] [:& header {:team team :section :fonts}]
[:& installed-fonts {:team team :fonts fonts}]]])) [:section {:class (stl/css :dashboard-container :dashboard-fonts)}
[:& fonts-upload {:team team :installed-fonts fonts}]
[:& installed-fonts {:team team :fonts fonts}]]]
;; OLD
[:*
[:& header {:team team :section :fonts}]
[:section.dashboard-container.dashboard-fonts
[:& fonts-upload {:team team :installed-fonts fonts}]
[:& installed-fonts {:team team :fonts fonts}]]])))
(mf/defc font-providers-page (mf/defc font-providers-page
[{:keys [team] :as props}] [{:keys [team] :as props}]
[:* (let [new-css-system (mf/use-ctx ctx/new-css-system)]
[:& header {:team team :section :providers}] (if new-css-system
[:section.dashboard-container [:*
[:span "font providers"]]]) [:& header {:team team :section :providers}]
[:section {:class (stl/css :dashboard-container)}
[:span "font providers"]]]
;; OLD
[:*
[:& header {:team team :section :providers}]
[:section.dashboard-container
[:span "font providers"]]])))

View File

@ -0,0 +1,322 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-fonts {
border-top: $s-1 solid $db-cuaternary;
display: flex;
flex-direction: column;
padding-left: $s-120;
overflow-y: auto;
padding-bottom: $s-120;
.btn-primary {
font-size: $fs-11;
}
}
.dashboard-installed-fonts {
max-width: $s-1000;
width: 100%;
display: flex;
margin-top: $s-24;
flex-direction: column;
h3 {
font-size: $fs-14;
color: $df-secondary;
margin: $s-4;
}
.font-item {
color: $db-secondary;
}
}
.installed-fonts-header {
align-items: center;
color: $df-secondary;
display: flex;
font-size: $fs-12;
height: $s-40;
padding-left: $s-24;
text-transform: uppercase;
> .family {
min-width: $s-200;
width: $s-200;
}
> .variants {
padding-left: $s-12;
}
}
.search-input {
display: flex;
flex-grow: 1;
justify-content: flex-end;
input {
background-color: $db-tertiary;
border-color: transparent;
border-radius: $br-8;
border: $s-1 solid transparent;
color: $df-primary;
font-size: $fs-14;
height: $s-32;
margin: 0;
padding: 0 $s-8;
width: $s-152;
&:focus {
outline: $s-1 solid $da-primary;
}
&::placeholder {
color: $df-secondary;
}
}
}
.font-item {
align-items: center;
background-color: $db-tertiary;
border-radius: $br-4;
color: $df-secondary;
display: flex;
font-size: $fs-14;
justify-content: space-between;
margin-top: $s-4;
max-width: $s-1000;
padding: $s-12 $s-24;
width: 100%;
input {
border: $s-1 solid transparent;
margin: 0;
padding: $s-8;
background-color: $db-tertiary;
border-radius: $br-8;
color: $df-primary;
font-size: $fs-14;
&:focus {
outline: $s-1 solid $da-primary;
}
}
> .family {
min-width: $s-200;
width: $s-200;
}
> .filenames {
min-width: $s-200;
}
> .variants {
font-size: $fs-14;
display: flex;
flex-wrap: wrap;
flex-grow: 1;
padding-left: $s-16;
.variant {
display: flex;
justify-content: space-between;
align-items: center;
padding: $s-8 $s-12;
cursor: pointer;
.icon {
display: flex;
height: $s-16;
width: $s-16;
margin-left: $s-6;
align-items: center;
svg {
fill: transparent;
width: $s-12;
height: $s-12;
transform: rotate(45deg);
}
}
&:hover {
.icon svg {
fill: $df-secondary;
}
}
}
}
.table-field {
color: $df-primary;
.variant {
background-color: $db-cuaternary;
border-radius: $br-8;
margin-right: $s-4;
padding-right: $s-4;
}
}
.filenames {
display: flex;
flex-direction: column;
font-size: $fs-12;
}
.options {
display: flex;
justify-content: flex-end;
min-width: $s-180;
.icon {
width: $s-24;
cursor: pointer;
display: flex;
margin-left: $s-12;
justify-content: center;
align-items: center;
&.failure {
margin-right: $s-12;
svg {
fill: var(--warning-color);
}
}
svg {
width: $s-16;
height: $s-16;
fill: $df-secondary;
}
&.close {
background: none;
border: none;
svg {
transform: rotate(45deg);
fill: $df-secondary;
}
}
}
}
}
.dashboard-fonts-upload {
max-width: $s-1000;
width: 100%;
display: flex;
flex-direction: column;
.upload-button {
width: $s-100;
}
.btn-secondary {
margin-left: $s-12;
}
}
.dashboard-fonts-hero {
font-size: $fs-14;
padding: $s-32 0;
margin-top: $s-80;
display: flex;
justify-content: space-between;
.btn-primary {
height: $s-40;
width: 100%;
}
.desc {
display: flex;
flex-direction: column;
gap: $s-24;
color: $db-secondary;
width: $s-500;
h2 {
color: $df-primary;
font-weight: 400;
}
p {
color: $df-secondary;
font-size: $fs-16;
}
}
.banner {
overflow: hidden;
display: grid;
grid-template-columns: $s-40 1fr;
background-color: $db-primary;
border-radius: $br-12;
border: $s-1 solid $db-cuaternary;
color: $df-primary;
font-size: $fs-12;
&:not(:last-child) {
margin-bottom: $s-12;
}
.icon {
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: $s-12;
svg {
fill: $df-secondary;
height: $s-20;
width: $s-20;
}
}
.content {
margin: $s-12;
a {
color: $da-primary;
}
}
&.warning {
background-color: $db-cuaternary;
.icon svg {
fill: var(--warning-color);
}
}
}
.btn-primary {
flex-shrink: 0;
}
}
.fonts-placeholder {
align-items: center;
border-radius: $br-8;
border: $s-1 solid $db-cuaternary;
display: flex;
flex-direction: column;
height: $s-160;
justify-content: center;
margin-top: $s-16;
max-width: $s-1000;
width: 100%;
.icon svg {
fill: $df-secondary;
width: $s-32;
height: $s-32;
}
.label {
color: $df-secondary;
font-size: $fs-14;
}
}

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.grid (ns app.main.ui.dashboard.grid
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
@ -19,6 +20,7 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :as bc] [app.main.ui.components.color-bullet :as bc]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.file-menu :refer [file-menu]] [app.main.ui.dashboard.file-menu :refer [file-menu]]
[app.main.ui.dashboard.import :refer [use-import-file]] [app.main.ui.dashboard.import :refer [use-import-file]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
@ -66,7 +68,8 @@
(mf/defc grid-item-thumbnail (mf/defc grid-item-thumbnail
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [file-id revn thumbnail-uri background-color]}] [{:keys [file-id revn thumbnail-uri background-color]}]
(let [container (mf/use-ref) (let [new-css-system (mf/use-ctx ctx/new-css-system)
container (mf/use-ref)
visible? (h/use-visible container :once? true)] visible? (h/use-visible container :once? true)]
(mf/with-effect [file-id revn visible? thumbnail-uri] (mf/with-effect [file-id revn visible? thumbnail-uri]
@ -80,13 +83,24 @@
:revn revn :revn revn
:message (ex-message cause))))))) :message (ex-message cause)))))))
[:div.grid-item-th (if new-css-system
{:style {:background-color background-color} [:div {:class (stl/css :grid-item-th)
:ref container} :style {:background-color background-color}
(when visible? :ref container}
(if thumbnail-uri (when visible?
[:img.grid-item-thumbnail-image {:src thumbnail-uri}] (if thumbnail-uri
i/loader-pencil))])) [:img {:class (stl/css :grid-item-thumbnail-image)
:src thumbnail-uri}]
i/loader-pencil))]
;; OLD
[:div.grid-item-th
{:style {:background-color background-color}
:ref container}
(when visible?
(if thumbnail-uri
[:img.grid-item-thumbnail-image {:src thumbnail-uri}]
i/loader-pencil))])))
;; --- Grid Item Library ;; --- Grid Item Library
@ -94,120 +108,228 @@
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [file] :as props}] [{:keys [file] :as props}]
(mf/with-effect [file] (let [new-css-system (mf/use-ctx ctx/new-css-system)]
(when file (mf/with-effect [file]
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))] (when file
(run! fonts/ensure-loaded! font-ids)))) (let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
(run! fonts/ensure-loaded! font-ids))))
[:div.grid-item-th.library (if new-css-system
(if (nil? file) [:div {:class (stl/css :grid-item-th :library)}
i/loader-pencil (if (nil? file)
(let [summary (:library-summary file) i/loader-pencil
components (:components summary) (let [summary (:library-summary file)
colors (:colors summary) components (:components summary)
typographies (:typographies summary)] colors (:colors summary)
[:* typographies (:typographies summary)]
[:*
(when (and (zero? (:count components)) (zero? (:count colors)) (zero? (:count typographies))) (when (and (zero? (:count components)) (zero? (:count colors)) (zero? (:count typographies)))
[:* [:*
[:div.asset-section [:div {:class (stl/css :asset-section)}
[:div.asset-title [:div {:class (stl/css :asset-title)}
[:span (tr "workspace.assets.components")] [:span (tr "workspace.assets.components")]
[:span.num-assets (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space [:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div.asset-section [:div {:class (stl/css :asset-section)}
[:div.asset-title [:div {:class (stl/css :asset-title)}
[:span (tr "workspace.assets.colors")] [:span (tr "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space [:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div.asset-section [:div {:class (stl/css :asset-section)}
[:div.asset-title [:div {:class (stl/css :asset-title)}
[:span (tr "workspace.assets.typography")] [:span (tr "workspace.assets.typography")]
[:span.num-assets (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space [:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space
(when (pos? (:count components)) (when (pos? (:count components))
[:div.asset-section [:div {:class (stl/css :asset-section)}
[:div.asset-title [:div {:class (stl/css :asset-title)}
[:span (tr "workspace.assets.components")] [:span (tr "workspace.assets.components")]
[:span.num-assets (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space [:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list [:div {:class (stl/css :asset-list)}
(for [component (:sample components)] (for [component (:sample components)]
(let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library (let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library
[:div.asset-list-item {:key (str "assets-component-" (:id component))} [:div {:class (stl/css :asset-list-item)
[:& component-svg {:root-shape (get-in component [:objects root-id]) :key (str "assets-component-" (:id component))}
:objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2 [:& component-svg {:root-shape (get-in component [:objects root-id])
[:div.name-block :objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2
[:span.item-name {:title (:name component)} [:div {:class (stl/css :name-block)}
(:name component)]]])) [:span {:class (stl/css :item-name)
(when (> (:count components) (count (:sample components))) :title (:name component)}
[:div.asset-list-item (:name component)]]]))
[:div.name-block (when (> (:count components) (count (:sample components)))
[:span.item-name "(...)"]]])]]) [:div {:class (stl/css :asset-list-item)}
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)} "(...)"]]])]])
(when (pos? (:count colors)) (when (pos? (:count colors))
[:div.asset-section [:div {:class (stl/css :asset-section)}
[:div.asset-title [:div {:class (stl/css :asset-title)}
[:span (tr "workspace.assets.colors")] [:span (tr "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space [:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list [:div {:class (stl/css :asset-list)}
(for [color (:sample colors)] (for [color (:sample colors)]
(let [default-name (cond (let [default-name (cond
(:gradient color) (uc/gradient-type->string (get-in color [:gradient :type])) (:gradient color) (uc/gradient-type->string (get-in color [:gradient :type]))
(:color color) (:color color) (:color color) (:color color)
:else (:value color))] :else (:value color))]
[:div.asset-list-item {:key (str "assets-color-" (:id color))} [:div {:class (stl/css :asset-list-item)
[:& bc/color-bullet {:color {:color (:color color) :key (str "assets-color-" (:id color))}
:opacity (:opacity color)}}] [:& bc/color-bullet {:color {:color (:color color)
[:div.name-block :opacity (:opacity color)}}]
[:span.color-name (:name color)] [:div {:class (stl/css :name-block)}
(when-not (= (:name color) default-name) [:span {:class (stl/css :color-name)} (:name color)]
[:span.color-value (:color color)])]])) (when-not (= (:name color) default-name)
(when (> (:count colors) (count (:sample colors))) [:span {:class (stl/css :color-value)} (:color color)])]]))
[:div.asset-list-item
[:div.name-block
[:span.item-name "(...)"]]])]])
(when (pos? (:count typographies)) (when (> (:count colors) (count (:sample colors)))
[:div.asset-section [:div {:class (stl/css :asset-list-item)}
[:div.asset-title [:div {:class (stl/css :name-block)}
[:span (tr "workspace.assets.typography")] [:span {:class (stl/css :item-name)} "(...)"]]])]])
[:span.num-assets (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list (when (pos? (:count typographies))
(for [typography (:sample typographies)] [:div {:class (stl/css :asset-section)}
[:div.asset-list-item {:key (str "assets-typography-" (:id typography))} [:div {:class (stl/css :asset-title)}
[:div.typography-sample [:span (tr "workspace.assets.typography")]
{:style {:font-family (:font-family typography) [:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
:font-weight (:font-weight typography) [:div {:class (stl/css :asset-list)}
:font-style (:font-style typography)}} (for [typography (:sample typographies)]
(tr "workspace.assets.typography.sample")] [:div {:class (stl/css :asset-list-item)
[:div.name-block :key (str "assets-typography-" (:id typography))}
[:span.item-name {:title (:name typography)} [:div {:class (stl/css :typography-sample)
(:name typography)]]]) :style {:font-family (:font-family typography)
(when (> (:count typographies) (count (:sample typographies))) :font-weight (:font-weight typography)
[:div.asset-list-item :font-style (:font-style typography)}}
[:div.name-block (tr "workspace.assets.typography.sample")]
[:span.item-name "(...)"]]])]])]))]) [:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)
:title (:name typography)}
(:name typography)]]])
(when (> (:count typographies) (count (:sample typographies)))
[:div {:class (stl/css :asset-list-item)}
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)} "(...)"]]])]])]))]
;; OLD
[:div.grid-item-th.library
(if (nil? file)
i/loader-pencil
(let [summary (:library-summary file)
components (:components summary)
colors (:colors summary)
typographies (:typographies summary)]
[:*
(when (and (zero? (:count components)) (zero? (:count colors)) (zero? (:count typographies)))
[:*
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.components")]
[:span.num-assets (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.typography")]
[:span.num-assets (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space
(when (pos? (:count components))
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.components")]
[:span.num-assets (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list
(for [component (:sample components)]
(let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library
[:div.asset-list-item {:key (str "assets-component-" (:id component))}
[:& component-svg {:root-shape (get-in component [:objects root-id])
:objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2
[:div.name-block
[:span.item-name {:title (:name component)}
(:name component)]]]))
(when (> (:count components) (count (:sample components)))
[:div.asset-list-item
[:div.name-block
[:span.item-name "(...)"]]])]])
(when (pos? (:count colors))
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list
(for [color (:sample colors)]
(let [default-name (cond
(:gradient color) (uc/gradient-type->string (get-in color [:gradient :type]))
(:color color) (:color color)
:else (:value color))]
[:div.asset-list-item {:key (str "assets-color-" (:id color))}
[:& bc/color-bullet {:color {:color (:color color)
:opacity (:opacity color)}}]
[:div.name-block
[:span.color-name (:name color)]
(when-not (= (:name color) default-name)
[:span.color-value (:color color)])]]))
(when (> (:count colors) (count (:sample colors)))
[:div.asset-list-item
[:div.name-block
[:span.item-name "(...)"]]])]])
(when (pos? (:count typographies))
[:div.asset-section
[:div.asset-title
[:span (tr "workspace.assets.typography")]
[:span.num-assets (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list
(for [typography (:sample typographies)]
[:div.asset-list-item {:key (str "assets-typography-" (:id typography))}
[:div.typography-sample
{:style {:font-family (:font-family typography)
:font-weight (:font-weight typography)
:font-style (:font-style typography)}}
(tr "workspace.assets.typography.sample")]
[:div.name-block
[:span.item-name {:title (:name typography)}
(:name typography)]]])
(when (> (:count typographies) (count (:sample typographies)))
[:div.asset-list-item
[:div.name-block
[:span.item-name "(...)"]]])]])]))])))
;; --- Grid Item ;; --- Grid Item
(mf/defc grid-item-metadata (mf/defc grid-item-metadata
[{:keys [modified-at]}] [{:keys [modified-at]}]
(let [locale (mf/deref i18n/locale) (let [new-css-system (mf/use-ctx ctx/new-css-system)
locale (mf/deref i18n/locale)
time (dt/timeago modified-at {:locale locale})] time (dt/timeago modified-at {:locale locale})]
[:span.date (if new-css-system
time])) [:span {:class (stl/css :date)} time]
;; OLD
[:span.date time])))
(defn create-counter-element (defn create-counter-element
[_element file-count] [_element file-count new-css-system]
(let [counter-el (dom/create-element "div")] (if new-css-system
(dom/set-property! counter-el "class" "drag-counter") (let [counter-el (dom/create-element "div")]
(dom/set-text! counter-el (str file-count)) (dom/set-property! counter-el "class" (stl/css :drag-counter))
counter-el)) (dom/set-text! counter-el (str file-count))
counter-el)
(let [counter-el (dom/create-element "div")]
(dom/set-property! counter-el "class" "drag-counter")
(dom/set-text! counter-el (str file-count))
counter-el)))
(mf/defc grid-item (mf/defc grid-item
{:wrap [mf/memo]} {:wrap [mf/memo]}
[{:keys [file navigate? origin library-view?] :as props}] [{:keys [file navigate? origin library-view?] :as props}]
(let [file-id (:id file) (let [new-css-system (mf/use-ctx ctx/new-css-system)
file-id (:id file)
local (mf/use-state {:menu-open false local (mf/use-state {:menu-open false
:menu-pos nil :menu-pos nil
:edition false}) :edition false})
@ -250,10 +372,12 @@
select-current? (not (contains? selected-files (:id file))) select-current? (not (contains? selected-files (:id file)))
item-el (mf/ref-val node-ref) item-el (mf/ref-val node-ref)
counter-el (create-counter-element item-el counter-el (create-counter-element
(if select-current? item-el
1 (if select-current?
(count selected-files)))] 1
(count selected-files))
new-css-system)]
(when select-current? (when select-current?
(st/emit! (dd/clear-selected-files)) (st/emit! (dd/clear-selected-files))
(st/emit! (dd/toggle-file-select file))) (st/emit! (dd/toggle-file-select file)))
@ -307,77 +431,149 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(swap! local assoc (swap! local assoc
:edition true :edition true
:menu-open false)))] :menu-open false)))
handle-key-down
(mf/use-callback
(mf/deps on-navigate on-select)
(fn [event]
(dom/stop-propagation event)
(when (kbd/enter? event)
(on-navigate event))
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
(on-select event)) ;; TODO Fix this
)))]
(mf/with-effect [selected? local] (mf/with-effect [selected? local]
(when (and (not selected?) (:menu-open @local)) (when (and (not selected?) (:menu-open @local))
(swap! local assoc :menu-open false))) (swap! local assoc :menu-open false)))
[:li.grid-item.project-th {:class (dom/classnames :library library-view?)} (if new-css-system
[:button [:li
{:tab-index "0" {:class (stl/css-case :grid-item true :project-th true :library library-view?)}
:class (dom/classnames :selected selected? [:button
:library library-view?) {:class (stl/css-case :selected selected? :library library-view?)
:ref node-ref :ref node-ref
:draggable true :draggable true
:on-click on-select :on-click on-select
:on-key-down (fn [event] :on-key-down handle-key-down
(dom/stop-propagation event) :on-double-click on-navigate
(when (kbd/enter? event) :on-drag-start on-drag-start
(on-navigate event)) :on-context-menu on-menu-click}
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
(on-select event)) ;; TODO Fix this
))
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-menu-click}
[:div.overlay] [:div {:class (stl/css :overlay)}]
(if library-view?
[:& grid-item-library {:file file}]
[:& grid-item-thumbnail
{:file-id (:id file)
:revn (:revn file)
:thumbnail-uri (:thumbnail-uri file)
:background-color (dm/get-in file [:data :options :background])}])
(when (and (:is-shared file) (not library-view?)) (if library-view?
[:div.item-badge i/library]) [:& grid-item-library {:file file}]
[:div.info-wrapper [:& grid-item-thumbnail
[:div.item-info {:file-id (:id file)
(if (:edition @local) :revn (:revn file)
[:& inline-edition {:content (:name file) :thumbnail-uri (:thumbnail-uri file)
:on-end edit}] :background-color (dm/get-in file [:data :options :background])}])
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]] (when (and (:is-shared file) (not library-view?))
[:div.project-th-actions {:class (dom/classnames [:div {:class (stl/css :item-badge)} i/library])
:force-display (:menu-open @local))}
[:div.project-th-icon.menu [:div {:class (stl/css :info-wrapper)}
{:tab-index "0" [:div {:class (stl/css :item-info)}
:ref menu-ref (if (:edition @local)
:id (str file-id "-action-menu") [:& inline-edition {:content (:name file)
:on-click on-menu-click :on-end edit}]
:on-key-down (fn [event] [:h3 (:name file)])
(when (kbd/enter? event) [:& grid-item-metadata {:modified-at (:modified-at file)}]]
(dom/stop-propagation event)
(on-menu-click event)))} [:div {:class (stl/css-case :project-th-actions true :force-display (:menu-open @local))}
i/actions [:div
(when selected? {:class (stl/css :project-th-icon :menu)
[:& file-menu {:files (vals selected-files) :tab-index "0"
:show? (:menu-open @local) :ref menu-ref
:left (+ 24 (:x (:menu-pos @local))) :id (str file-id "-action-menu")
:top (:y (:menu-pos @local)) :on-click on-menu-click
:navigate? navigate? :on-key-down (fn [event]
:on-edit on-edit (when (kbd/enter? event)
:on-menu-close on-menu-close (dom/stop-propagation event)
:origin origin (on-menu-click event)))}
:dashboard-local dashboard-local i/actions
:parent-id (str file-id "-action-menu")}])]]]]])) (when selected?
[:& file-menu {:files (vals selected-files)
:show? (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:navigate? navigate?
:on-edit on-edit
:on-menu-close on-menu-close
:origin origin
:dashboard-local dashboard-local
:parent-id (str file-id "-action-menu")}])]]]]]
;; OLD
[:li.grid-item.project-th {:class (dom/classnames :library library-view?)}
[:button
{:tab-index "0"
:class (dom/classnames :selected selected?
:library library-view?)
:ref node-ref
:draggable true
:on-click on-select
:on-key-down (fn [event]
(dom/stop-propagation event)
(when (kbd/enter? event)
(on-navigate event))
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
(on-select event)) ;; TODO Fix this
))
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-menu-click}
[:div.overlay]
(if library-view?
[:& grid-item-library {:file file}]
[:& grid-item-thumbnail
{:file-id (:id file)
:revn (:revn file)
:thumbnail-uri (:thumbnail-uri file)
:background-color (dm/get-in file [:data :options :background])}])
(when (and (:is-shared file) (not library-view?))
[:div.item-badge i/library])
[:div.info-wrapper
[:div.item-info
(if (:edition @local)
[:& inline-edition {:content (:name file)
:on-end edit}]
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
[:div.project-th-actions {:class (dom/classnames
:force-display (:menu-open @local))}
[:div.project-th-icon.menu
{:tab-index "0"
:ref menu-ref
:id (str file-id "-action-menu")
:on-click on-menu-click
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(on-menu-click event)))}
i/actions
(when selected?
[:& file-menu {:files (vals selected-files)
:show? (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:navigate? navigate?
:on-edit on-edit
:on-menu-close on-menu-close
:origin origin
:dashboard-local dashboard-local
:parent-id (str file-id "-action-menu")}])]]]]])))
(mf/defc grid (mf/defc grid
[{:keys [files project origin limit library-view? create-fn] :as props}] [{:keys [files project origin limit library-view? create-fn] :as props}]
(let [dragging? (mf/use-state false) (let [new-css-system (mf/use-ctx ctx/new-css-system)
dragging? (mf/use-state false)
project-id (:id project) project-id (:id project)
node-ref (mf/use-var nil) node-ref (mf/use-var nil)
@ -420,57 +616,112 @@
(reset! dragging? false) (reset! dragging? false)
(import-files (.-files (.-dataTransfer e))))))] (import-files (.-files (.-dataTransfer e))))))]
[:div.dashboard-grid (if new-css-system
{:on-drag-enter on-drag-enter [:div
:on-drag-over on-drag-over {:class (stl/css :dashboard-grid)
:on-drag-leave on-drag-leave :on-drag-enter on-drag-enter
:on-drop on-drop :on-drag-over on-drag-over
:ref node-ref} :on-drag-leave on-drag-leave
(cond :on-drop on-drop
(nil? files) :ref node-ref}
[:& loading-placeholder]
(seq files) (cond
[:ul.grid-row (nil? files)
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}} [:& loading-placeholder]
(when @dragging? (seq files)
[:li.grid-item]) [:ul
{:class (stl/css :grid-row)
:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
(for [item files] (when @dragging?
[:& grid-item [:li {:class (stl/css :grid-item)}])
{:file item
:key (:id item)
:navigate? true
:origin origin
:library-view? library-view?}])]
:else (for [item files]
[:& empty-placeholder [:& grid-item
{:limit limit {:file item
:create-fn create-fn :key (:id item)
:origin origin}])])) :navigate? true
:origin origin
:library-view? library-view?}])]
:else
[:& empty-placeholder
{:limit limit
:create-fn create-fn
:origin origin}])]
;; OLD
[:div.dashboard-grid
{:on-drag-enter on-drag-enter
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
:on-drop on-drop
:ref node-ref}
(cond
(nil? files)
[:& loading-placeholder]
(seq files)
[:ul.grid-row
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
(when @dragging?
[:li.grid-item])
(for [item files]
[:& grid-item
{:file item
:key (:id item)
:navigate? true
:origin origin
:library-view? library-view?}])]
:else
[:& empty-placeholder
{:limit limit
:create-fn create-fn
:origin origin}])])))
(mf/defc line-grid-row (mf/defc line-grid-row
[{:keys [files selected-files dragging? limit] :as props}] [{:keys [files selected-files dragging? limit] :as props}]
(let [elements limit (let [new-css-system (mf/use-ctx ctx/new-css-system)
elements limit
limit (if dragging? (dec limit) limit)] limit (if dragging? (dec limit) limit)]
[:ul.grid-row.no-wrap (if new-css-system
{:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}} [:ul
{:class (stl/css :grid-row :no-wrap)
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
(when dragging? (when dragging?
[:li.grid-item.dragged]) [:li {:class (stl/css :grid-item :dragged)}])
(for [item (take limit files)]
[:& grid-item (for [item (take limit files)]
{:id (:id item) [:& grid-item
:file item {:id (:id item)
:selected-files selected-files :file item
:key (:id item) :selected-files selected-files
:navigate? false}])])) :key (:id item)
:navigate? false}])]
;; OLD
[:ul.grid-row.no-wrap
{:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
(when dragging?
[:li.grid-item.dragged])
(for [item (take limit files)]
[:& grid-item
{:id (:id item)
:file item
:selected-files selected-files
:key (:id item)
:navigate? false}])])))
(mf/defc line-grid (mf/defc line-grid
[{:keys [project team files limit create-fn] :as props}] [{:keys [project team files limit create-fn] :as props}]
(let [dragging? (mf/use-state false) (let [new-css-system (mf/use-ctx ctx/new-css-system)
dragging? (mf/use-state false)
project-id (:id project) project-id (:id project)
team-id (:id team) team-id (:id team)
@ -546,24 +797,47 @@
(reset! dragging? false) (reset! dragging? false)
(import-files (.-files (.-dataTransfer e)))))))] (import-files (.-files (.-dataTransfer e)))))))]
[:div.dashboard-grid {:on-drag-enter on-drag-enter (if new-css-system
:on-drag-over on-drag-over [:div {:class (stl/css :dashboard-grid)
:on-drag-leave on-drag-leave :on-drag-enter on-drag-enter
:on-drop on-drop} :on-drag-over on-drag-over
(cond :on-drag-leave on-drag-leave
(nil? files) :on-drop on-drop}
[:& loading-placeholder] (cond
(nil? files)
[:& loading-placeholder]
(seq files) (seq files)
[:& line-grid-row {:files files [:& line-grid-row {:files files
:team-id team-id :team-id team-id
:selected-files selected-files :selected-files selected-files
:dragging? @dragging? :dragging? @dragging?
:limit limit}] :limit limit}]
:else :else
[:& empty-placeholder [:& empty-placeholder
{:dragging? @dragging? {:dragging? @dragging?
:limit limit :limit limit
:create-fn create-fn}])])) :create-fn create-fn}])]
;; OLD
[:div.dashboard-grid {:on-drag-enter on-drag-enter
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
:on-drop on-drop}
(cond
(nil? files)
[:& loading-placeholder]
(seq files)
[:& line-grid-row {:files files
:team-id team-id
:selected-files selected-files
:dragging? @dragging?
:limit limit}]
:else
[:& empty-placeholder
{:dragging? @dragging?
:limit limit
:create-fn create-fn}])])))

View File

@ -0,0 +1,375 @@
// 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) KALEIDOS INC
@import "refactor/common-refactor.scss";
.dashboard-grid {
font-size: $fs-14;
height: 100%;
overflow-y: auto;
overflow: hidden;
}
.grid-row {
display: grid;
width: 100%;
padding: 0 $s-12;
}
.grid-item {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: column;
flex: 1 0 $s-260;
height: $s-232;
margin: $s-12 $s-16 $s-16 $s-8;
position: relative;
text-align: center;
a,
button {
width: 100%;
font-weight: $fw400;
}
button {
background-color: transparent;
border: none;
}
@media #{$bp-max-1366} {
height: $s-200;
flex: 1 0 $s-232;
}
.grid-item-th {
border-radius: $br-4;
text-align: initial;
img {
object-fit: contain;
}
}
&.dragged {
border-radius: $br-4;
border: $br-2 solid $da-primary;
text-align: initial;
max-height: $s-160;
}
&.overlay {
border-radius: $br-4;
border: $s-2 solid $da-tertiary;
height: 100%;
opacity: 0;
pointer-events: none;
position: absolute;
width: 100%;
z-index: $z-index-1;
}
&:hover .overlay {
display: block;
opacity: 1;
}
.info-wrapper {
display: grid;
grid-template-columns: 1fr auto;
cursor: pointer;
}
.item-info {
display: grid;
padding: $s-8;
text-align: left;
width: 100%;
font-size: $fs-12;
h3 {
border: $s-1 solid transparent;
color: $df-primary;
font-size: $fs-16;
font-weight: $fw400;
height: $s-28;
line-height: 1.92;
max-width: $s-260;
overflow: hidden;
padding-right: $s-8;
padding: 0;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
@media #{$bp-max-1366} {
max-width: $s-232;
}
}
.date {
color: $df-secondary;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
white-space: nowrap;
max-width: $s-260;
&::first-letter {
text-transform: capitalize;
}
@media #{$bp-max-1366} {
max-width: $s-232;
}
}
}
.item-badge {
background-color: $da-primary;
border: none;
border-radius: $br-4;
position: absolute;
top: $s-8;
right: $s-8;
height: $s-32;
width: $s-32;
display: flex;
align-items: center;
justify-content: center;
svg {
fill: $db-secondary;
height: $s-16;
width: $s-16;
}
}
&.add-file {
border: $s-1 dashed $df-secondary;
justify-content: center;
box-shadow: none;
span {
color: $db-primary;
font-size: $fs-14;
}
&:hover {
background-color: $df-primary;
border: $s-2 solid $da-tertiary;
}
}
}
.drag-counter {
position: absolute;
top: $s-4;
left: $s-4;
width: $s-32;
height: $s-32;
background-color: $da-tertiary;
border-radius: $br-circle;
color: $db-secondary;
font-size: $fs-16;
display: flex;
justify-content: center;
align-items: center;
}
// PROJECTS, ELEMENTS & ICONS GRID
.project-th {
background-color: transparent;
border-radius: $br-8;
padding-top: $s-6;
&:hover,
&:focus,
&:focus-within {
background-color: $db-tertiary;
.project-th-actions {
opacity: 1;
}
a {
text-decoration: none;
}
}
.selected {
.grid-item-th {
border: 2px solid $da-tertiary;
}
}
}
.project-th-actions {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
opacity: 0;
right: $s-6;
width: $s-32;
span {
color: $db-secondary;
}
.project-th-icon {
align-items: center;
display: flex;
margin-right: $s-8;
margin-top: 0;
&.menu {
align-items: flex-end;
display: flex;
flex-direction: column;
height: $s-32;
justify-content: center;
margin-right: 0;
margin-top: $s-20;
width: 100%;
> svg {
fill: $df-secondary;
margin-right: 0;
height: $s-16;
width: $s-16;
}
&:hover,
&:focus {
> svg {
fill: $da-tertiary;
}
}
}
}
}
.project-th-actions.force-display {
opacity: 1;
}
.grid-item-th {
border-radius: $br-4;
cursor: pointer;
background-position: center;
background-size: auto 80%;
background-repeat: no-repeat;
height: $s-232;
max-height: $s-160;
overflow: hidden;
position: relative;
width: 100%;
background-color: var(--canvas-color);
display: flex;
justify-content: center;
flex-direction: row;
.img-th {
height: auto;
width: 100%;
}
svg {
height: 100%;
width: 100%;
}
svg#loader-pencil {
fill: $db-cuaternary;
}
}
// LIBRARY VIEW
.library {
height: $s-580;
}
.grid-item.project-th.library {
height: $s-612;
width: $s-300;
}
.grid-item-th.library {
background-color: $db-tertiary;
flex-direction: column;
height: 90%;
justify-content: flex-start;
max-height: 550px;
padding: $s-32;
.asset-section {
font-size: $fs-12;
color: $df-secondary;
&:not(:first-child) {
margin-top: $s-16;
}
}
.asset-title {
display: flex;
font-size: $fs-12;
text-transform: uppercase;
.num-assets {
color: $df-secondary;
}
}
.asset-list-item {
align-items: center;
border-radius: $br-4;
border: $s-1 solid transparent;
color: $df-primary;
display: flex;
font-size: $fs-12;
margin-top: $s-4;
padding: $s-2;
position: relative;
.name-block {
color: $df-secondary;
width: calc(100% - $s-24 - $s-8);
}
.item-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
svg {
background-color: var(--canvas-color);
border-radius: $br-4;
border: $s-2 solid transparent;
height: $s-24;
margin-right: $s-8;
width: $s-24;
}
.color-name {
color: $df-primary;
}
.color-value {
color: $df-secondary;
margin-left: $s-4;
text-transform: uppercase;
}
.typography-sample {
height: $s-20;
margin-right: $s-4;
width: $s-20;
}
}
}

View File

@ -5,7 +5,9 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.inline-edition (ns app.main.ui.dashboard.inline-edition
(:require-macros [app.main.style :as stl])
(:require (:require
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
@ -13,7 +15,8 @@
(mf/defc inline-edition (mf/defc inline-edition
[{:keys [content on-end] :as props}] [{:keys [content on-end] :as props}]
(let [name (mf/use-state content) (let [new-css-system (mf/use-ctx ctx/new-css-system)
name (mf/use-state content)
input-ref (mf/use-ref) input-ref (mf/use-ref)
on-input on-input
@ -59,13 +62,25 @@
(dom/focus! node) (dom/focus! node)
(dom/select-text! node)))) (dom/select-text! node))))
[:div.edit-wrapper (if new-css-system
[:input.element-title {:value @name [:div {:class (stl/css :edit-wrapper)}
:ref input-ref [:input {:class (stl/css :element-title)
:on-click on-click :value @name
:on-change on-input :ref input-ref
:on-key-down on-keyup :on-click on-click
:on-blur on-blur}] :on-change on-input
[:span.close {:on-click on-cancel} i/close]])) :on-key-down on-keyup
:on-blur on-blur}]
[:span {:class (stl/css :close)
:on-click on-cancel} i/close]]
;; OLD
[:div.edit-wrapper
[:input.element-title {:value @name
:ref input-ref
:on-click on-click
:on-change on-input
:on-key-down on-keyup
:on-blur on-blur}]
[:span.close {:on-click on-cancel} i/close]])))

View File

@ -0,0 +1,53 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
.edit-wrapper {
border-radius: $br-4;
display: flex;
padding-right: $s-24;
position: relative;
margin-right: $s-24;
input.element-title {
background-color: $db-primary;
border-radius: $br-8;
color: $df-primary;
font-size: $fs-16;
height: $s-32;
margin: 0;
border: none;
padding: $s-6;
width: 100%;
&:focus-visible {
border: $s-1 solid $da-primary;
outline: none;
}
}
.close {
cursor: pointer;
position: absolute;
top: $s-1;
right: calc(-1 * $s-8);
svg {
fill: $df-secondary;
height: $s-16;
transform: rotate(45deg) translateY(7px);
width: $s-16;
margin: 0;
}
&:hover {
svg {
fill: var(--warning-color);
}
}
}
}

View File

@ -5,23 +5,24 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.libraries (ns app.main.ui.dashboard.libraries
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.grid :refer [grid]] [app.main.ui.dashboard.grid :refer [grid]]
[app.main.ui.hooks :as hooks]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc libraries-page (mf/defc libraries-page
[{:keys [team] :as props}] [{:keys [team] :as props}]
(let [files-map (mf/deref refs/dashboard-shared-files) (let [new-css-system (mf/use-ctx ctx/new-css-system)
files-map (mf/deref refs/dashboard-shared-files)
projects (mf/deref refs/dashboard-projects) projects (mf/deref refs/dashboard-projects)
default-project (->> projects vals (d/seek :is-default)) default-project (->> projects vals (d/seek :is-default))
@ -35,16 +36,7 @@
components-v2 (features/use-feature "components/v2") components-v2 (features/use-feature "components/v2")
width (mf/use-state nil) [rowref limit] (hooks/use-dynamic-grid-item-width 350)]
rowref (mf/use-ref)
itemsize (if components-v2
350
(if (>= @width 1030) 280 230))
ratio (if (some? @width) (/ @width itemsize) 0)
nitems (mth/floor ratio)
limit (min 10 nitems)
limit (max 1 limit)]
(mf/with-effect [team] (mf/with-effect [team]
(when team (when team
@ -57,29 +49,27 @@
(st/emit! (dd/fetch-shared-files) (st/emit! (dd/fetch-shared-files)
(dd/clear-selected-files))) (dd/clear-selected-files)))
(mf/with-effect [] (if new-css-system
(let [node (mf/ref-val rowref) [:*
mnt? (volatile! true) [:header {:class (stl/css :dashboard-header) :ref rowref}
sub (->> (wapi/observe-resize node) [:div#dashboard-libraries-title {:class (stl/css :dashboard-title)}
(rx/observe-on :af) [:h1 (tr "dashboard.libraries-title")]]]
(rx/subs (fn [entries] [:section {:class (stl/css :dashboard-container :no-bg :dashboard-shared)}
(let [row (first entries) [:& grid {:files files
row-rect (.-contentRect ^js row) :project default-project
row-width (.-width ^js row-rect)] :origin :libraries
(when @mnt? :limit limit
(reset! width row-width))))))] :library-view? components-v2}]]]
(fn []
(vreset! mnt? false)
(rx/dispose! sub))))
[:* ;; OLD
[:header.dashboard-header {:ref rowref} [:*
[:div.dashboard-title#dashboard-libraries-title [:header.dashboard-header {:ref rowref}
[:h1 (tr "dashboard.libraries-title")]]] [:div.dashboard-title#dashboard-libraries-title
[:section.dashboard-container.no-bg.dashboard-shared [:h1 (tr "dashboard.libraries-title")]]]
[:& grid {:files files [:section.dashboard-container.no-bg.dashboard-shared
:project default-project [:& grid {:files files
:origin :libraries :project default-project
:limit limit :origin :libraries
:library-view? components-v2}]]])) :limit limit
:library-view? components-v2}]]])))

View File

@ -0,0 +1,28 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}

View File

@ -5,37 +5,68 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.placeholder (ns app.main.ui.dashboard.placeholder
(:require-macros [app.main.style :as stl])
(:require (:require
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc empty-placeholder (mf/defc empty-placeholder
[{:keys [dragging? limit origin create-fn] :as props}] [{:keys [dragging? limit origin create-fn] :as props}]
(let [on-click (let [new-css-system (mf/use-ctx ctx/new-css-system)
on-click
(mf/use-fn (mf/use-fn
(mf/deps create-fn) (mf/deps create-fn)
(fn [_] (fn [_]
(create-fn "dashboard:empty-folder-placeholder")))] (create-fn "dashboard:empty-folder-placeholder")))]
(cond (if new-css-system
(true? dragging?) (cond
[:ul.grid-row.no-wrap (true? dragging?)
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}} [:ul
[:li.grid-item]] {:class (stl/css :grid-row :no-wrap)
:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:li {:class (stl/css :grid-item :grid-empty-placeholder :dragged)}]]
(= :libraries origin) (= :libraries origin)
[:div.grid-empty-placeholder.libs {:data-test "empty-placeholder"} [:div {:class (stl/css :grid-empty-placeholder :libs)
[:div.text :data-test "empty-placeholder"}
[:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]] [:div {:class (stl/css :text)}
[:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]]
:else :else
[:div.grid-empty-placeholder [:div
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}} {:class (stl/css :grid-empty-placeholder)
[:button.create-new {:on-click on-click} (tr "dashboard.new-file")]]))) :style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:button {:class (stl/css :create-new)
:on-click on-click}
i/add-refactor]])
;; OLD
(cond
(true? dragging?)
[:ul.grid-row.no-wrap
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:li.grid-item]]
(= :libraries origin)
[:div.grid-empty-placeholder.libs {:data-test "empty-placeholder"}
[:div.text
[:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]]
:else
[:div.grid-empty-placeholder
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:button.create-new {:on-click on-click} (tr "dashboard.new-file")]]))))
(mf/defc loading-placeholder (mf/defc loading-placeholder
[] []
[:div.grid-empty-placeholder.loader (let [new-css-system (mf/use-ctx ctx/new-css-system)]
[:div.icon i/loader] (if new-css-system
[:div.text (tr "dashboard.loading-files")]]) [:div {:class (stl/css :grid-empty-placeholder :loader)}
[:div {:class (stl/css :icon)} i/loader]
[:div {:class (stl/css :text)} (tr "dashboard.loading-files")]]
[:div.grid-empty-placeholder.loader
[:div.icon i/loader]
[:div.text (tr "dashboard.loading-files")]])))

View File

@ -0,0 +1,92 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "./grid.scss";
.grid-empty-placeholder {
border-radius: $br-12;
display: grid;
padding: $s-12;
margin-right: $s-12;
height: $s-232;
&.loader {
justify-items: center;
}
.icon {
display: flex;
align-items: center;
justify-content: center;
svg {
width: $s-64;
height: $s-64;
fill: $df-secondary;
}
}
&.libs {
background-image: url(/images/ph-left.svg), url(/images/ph-right.svg);
background-position:
15% bottom,
85% top;
background-repeat: no-repeat;
align-items: center;
border: $s-1 solid $db-cuaternary;
border-radius: $br-4;
display: flex;
flex-direction: column;
height: $s-200;
margin: $s-16;
padding: $s-48;
justify-content: center;
.text {
a {
color: $df-primary;
}
p {
max-width: $s-360;
text-align: center;
font-size: $fs-16;
}
}
}
.create-new {
background-color: $db-tertiary;
border-radius: $br-8;
color: $df-primary;
cursor: pointer;
height: $s-160;
margin: $s-8;
text-transform: uppercase;
border: $s-2 solid transparent;
svg {
width: $s-32;
height: $s-32;
stroke: $df-secondary;
}
&:hover {
border: $s-2 solid $da-tertiary;
background-color: $db-cuaternary;
color: $da-primary;
svg {
stroke: $da-tertiary;
}
}
}
.text {
margin-top: $s-12;
color: $df-secondary;
font-size: $fs-16;
}
}

View File

@ -5,10 +5,10 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.projects (ns app.main.ui.dashboard.projects
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.config :as cf] [app.config :as cf]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.events :as ev] [app.main.data.events :as ev]
@ -17,17 +17,17 @@
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.grid :refer [line-grid]] [app.main.ui.dashboard.grid :refer [line-grid]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]] [app.main.ui.dashboard.project-menu :refer [project-menu]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.time :as dt] [app.util.time :as dt]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
[potok.core :as ptk] [potok.core :as ptk]
@ -36,19 +36,32 @@
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[] []
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))] (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:header.dashboard-header on-click (mf/use-fn #(st/emit! (dd/create-project)))]
[:div.dashboard-title#dashboard-projects-title (if new-css-system
[:h1 (tr "dashboard.projects-title")]] [:header {:class (stl/css :dashboard-header)}
[:button.btn-secondary.btn-small [:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
{:on-click on-click [:h1 (tr "dashboard.projects-title")]]
:data-test "new-project-button"} [:button
(tr "dashboard.new-project")]])) {:class (stl/css :btn-secondary :btn-small)
:on-click on-click
:data-test "new-project-button"}
(tr "dashboard.new-project")]]
;; OLD
[:header.dashboard-header
[:div.dashboard-title#dashboard-projects-title
[:h1 (tr "dashboard.projects-title")]]
[:button.btn-secondary.btn-small
{:on-click on-click
:data-test "new-project-button"}
(tr "dashboard.new-project")]])))
(mf/defc team-hero (mf/defc team-hero
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [team close-fn] :as props}] [{:keys [team close-fn] :as props}]
(let [on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members))) (let [new-css-system (mf/use-ctx ctx/new-css-system)
on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members)))
on-invite-click on-invite-click
(mf/use-fn (mf/use-fn
@ -64,28 +77,52 @@
(dom/prevent-default event) (dom/prevent-default event)
(close-fn)))] (close-fn)))]
[:div.team-hero (if new-css-system
[:img {:src "images/deco-team-banner.png" :border "0" [:div {:class (stl/css :team-hero)}
:role "presentation"}] [:div {:class (stl/css :img-wrapper)}
[:div.text [:img {:src "images/deco-team-banner.png"
[:div.title (tr "dasboard.team-hero.title")] :border "0"
[:div.info :role "presentation"}]]
[:span (tr "dasboard.team-hero.text")] [:div {:class (stl/css :text)}
[:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]]] [:div {:class (stl/css :title)} (tr "dasboard.team-hero.title")]
[:button.btn-primary.invite [:div {:class (stl/css :info)}
{:on-click on-invite-click} [:span (tr "dasboard.team-hero.text")]
(tr "onboarding.choice.team-up.invite-members")] [:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]]
[:button.close [:button
{:on-click on-close-click {:class (stl/css :btn-primary :invite)
:aria-label (tr "labels.close")} :on-click on-invite-click}
[:span i/close]]])) (tr "onboarding.choice.team-up.invite-members")]]
[:button
{:class (stl/css :close)
:on-click on-close-click
:aria-label (tr "labels.close")}
[:span i/close]]]
;; OLD
[:div.team-hero
[:img {:src "images/deco-team-banner.png" :border "0"
:role "presentation"}]
[:div.text
[:div.title (tr "dasboard.team-hero.title")]
[:div.info
[:span (tr "dasboard.team-hero.text")]
[:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]]]
[:button.btn-primary.invite
{:on-click on-invite-click}
(tr "onboarding.choice.team-up.invite-members")]
[:button.close
{:on-click on-close-click
:aria-label (tr "labels.close")}
[:span i/close]]])))
(def builtin-templates (def builtin-templates
(l/derived :builtin-templates st/state)) (l/derived :builtin-templates st/state))
(mf/defc tutorial-project (mf/defc tutorial-project
[{:keys [close-tutorial default-project-id] :as props}] [{:keys [close-tutorial default-project-id] :as props}]
(let [state (mf/use-state {:status :waiting (let [new-css-system (mf/use-ctx ctx/new-css-system)
state (mf/use-state {:status :waiting
:file nil}) :file nil})
templates (mf/deref builtin-templates) templates (mf/deref builtin-templates)
@ -115,48 +152,88 @@
(swap! state #(assoc % :status :importing)) (swap! state #(assoc % :status :importing))
(st/emit! (with-meta (dd/clone-template (with-meta params mdata)) (st/emit! (with-meta (dd/clone-template (with-meta params mdata))
{::ev/origin "get-started-hero-block"})))))] {::ev/origin "get-started-hero-block"})))))]
[:article.tutorial (if new-css-system
[:div.thumbnail] [:article {:class (stl/css :tutorial)}
[:div.text [:div {:class (stl/css :thumbnail)}]
[:h2.title (tr "dasboard.tutorial-hero.title")] [:div {:class (stl/css :text)}
[:p.info (tr "dasboard.tutorial-hero.info")] [:h2 {:class (stl/css :title)} (tr "dasboard.tutorial-hero.title")]
[:button.btn-primary.action {:on-click download-tutorial} [:p {:class (stl/css :info)} (tr "dasboard.tutorial-hero.info")]
(case (:status @state) [:button {:class (stl/css :btn-primary :action)
:waiting (tr "dasboard.tutorial-hero.start") :on-click download-tutorial}
:importing [:span.loader i/loader-pencil] (case (:status @state)
:success "")]] :waiting (tr "dasboard.tutorial-hero.start")
:importing [:span.loader i/loader-pencil]
:success "")]]
[:button.close [:button
{:on-click close-tutorial {:class (stl/css :close)
:aria-label (tr "labels.close")} :on-click close-tutorial
[:span.icon i/close]]])) :aria-label (tr "labels.close")}
[:span {:class (stl/css :icon)} i/close]]]
;; OLD
[:article.tutorial
[:div.thumbnail]
[:div.text
[:h2.title (tr "dasboard.tutorial-hero.title")]
[:p.info (tr "dasboard.tutorial-hero.info")]
[:button.btn-primary.action {:on-click download-tutorial}
(case (:status @state)
:waiting (tr "dasboard.tutorial-hero.start")
:importing [:span.loader i/loader-pencil]
:success "")]]
[:button.close
{:on-click close-tutorial
:aria-label (tr "labels.close")}
[:span.icon i/close]]])))
(mf/defc interface-walkthrough (mf/defc interface-walkthrough
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [close-walkthrough] :as props}] [{:keys [close-walkthrough] :as props}]
(let [handle-walkthrough-link (let [new-css-system (mf/use-ctx ctx/new-css-system)
handle-walkthrough-link
(fn [] (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough" (st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough"
::ev/origin "get-started-hero-block" ::ev/origin "get-started-hero-block"
:section "dashboard"})))] :section "dashboard"})))]
[:article.walkthrough (if new-css-system
[:div.thumbnail] [:article {:class (stl/css :walkthrough)}
[:div.text [:div {:class (stl/css :thumbnail)}]
[:h2.title (tr "dasboard.walkthrough-hero.title")] [:div {:class (stl/css :text)}
[:p.info (tr "dasboard.walkthrough-hero.info")] [:h2 {:class (stl/css :title)} (tr "dasboard.walkthrough-hero.title")]
[:a.btn-primary.action [:p {:class (stl/css :info)} (tr "dasboard.walkthrough-hero.info")]
{:href " https://design.penpot.app/walkthrough" [:a {:class (stl/css :btn-primary :action)
:target "_blank" :href " https://design.penpot.app/walkthrough"
:on-click handle-walkthrough-link} :target "_blank"
(tr "dasboard.walkthrough-hero.start")]] :on-click handle-walkthrough-link}
[:button.close (tr "dasboard.walkthrough-hero.start")]]
{:on-click close-walkthrough [:button
:aria-label (tr "labels.close")} {:class (stl/css :close)
[:span.icon i/close]]])) :on-click close-walkthrough
:aria-label (tr "labels.close")}
[:span {:class (stl/css :icon)} i/close]]]
;; OLD
[:article.walkthrough
[:div.thumbnail]
[:div.text
[:h2.title (tr "dasboard.walkthrough-hero.title")]
[:p.info (tr "dasboard.walkthrough-hero.info")]
[:a.btn-primary.action
{:href " https://design.penpot.app/walkthrough"
:target "_blank"
:on-click handle-walkthrough-link}
(tr "dasboard.walkthrough-hero.start")]]
[:button.close
{:on-click close-walkthrough
:aria-label (tr "labels.close")}
[:span.icon i/close]]])))
(mf/defc project-item (mf/defc project-item
[{:keys [project first? team files] :as props}] [{:keys [project first? team files] :as props}]
(let [locale (mf/deref i18n/locale) (let [new-css-system (mf/use-ctx ctx/new-css-system)
locale (mf/deref i18n/locale)
file-count (or (:count project) 0) file-count (or (:count project) 0)
project-id (:id project) project-id (:id project)
team-id (:id team) team-id (:id team)
@ -168,16 +245,7 @@
:menu-pos nil :menu-pos nil
:edition? (= (:id project) edit-id)}) :edition? (= (:id project) edit-id)})
width (mf/use-state nil) [rowref limit] (hooks/use-dynamic-grid-item-width)
rowref (mf/use-ref)
itemsize (if (>= @width 1030)
280
230)
ratio (if (some? @width) (/ @width itemsize) 0)
nitems (mth/floor ratio)
limit (min 10 nitems)
limit (max 1 limit)
on-nav on-nav
(mf/use-fn (mf/use-fn
@ -189,9 +257,9 @@
toggle-pin toggle-pin
(mf/use-fn (mf/use-fn
(mf/deps project) (mf/deps project)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (dd/toggle-project-pin project)))) (st/emit! (dd/toggle-project-pin project))))
on-menu-click on-menu-click
(mf/use-fn (mf/use-fn
@ -256,101 +324,183 @@
(st/emit! (dd/fetch-files {:project-id project-id}) (st/emit! (dd/fetch-files {:project-id project-id})
(dd/fetch-recent-files (:id team)) (dd/fetch-recent-files (:id team))
(dd/fetch-projects (:id team)) (dd/fetch-projects (:id team))
(dd/clear-selected-files))))] (dd/clear-selected-files))))
(mf/with-effect handle-create-click
(let [node (mf/ref-val rowref) (mf/use-callback
mnt? (volatile! true) (mf/deps on-create-click)
sub (->> (wapi/observe-resize node) (fn [event]
(rx/observe-on :af) (when (kbd/enter? event)
(rx/subs (fn [entries] (on-create-click event))))
(let [row (first entries)
row-rect (.-contentRect ^js row)
row-width (.-width ^js row-rect)]
(when @mnt?
(reset! width row-width))))))]
(fn []
(vreset! mnt? false)
(rx/dispose! sub))))
[:article.dashboard-project-row
{:class (when first? "first")}
[:header.project {:ref rowref}
[:div.project-name-wrapper
(if (:edition? @local)
[:& inline-edition {:content (:name project)
:on-end on-edit}]
[:h2 {:on-click on-nav
:on-context-menu on-menu-click}
(if (:is-default project)
(tr "labels.drafts")
(:name project))])
[:& project-menu handle-menu-click
(mf/use-callback
(mf/deps on-menu-click)
(fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(on-menu-click event))))]
(if new-css-system
[:article {:class (stl/css-case :dashboard-project-row true :first first?)}
[:header {:class (stl/css :project) :ref rowref}
[:div {:class (stl/css :project-name-wrapper)}
(if (:edition? @local)
[:& inline-edition {:content (:name project)
:on-end on-edit}]
[:h2 {:on-click on-nav
:on-context-menu on-menu-click}
(if (:is-default project)
(tr "labels.drafts")
(:name project))])
[:& project-menu
{:project project
:show? (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:on-edit on-edit-open
:on-menu-close on-menu-close
:on-import on-import}]
[:span {:class (stl/css :info)} (str (tr "labels.num-of-files" (i18n/c file-count)))]
(let [time (-> (:modified-at project)
(dt/timeago {:locale locale}))]
[:span {:class (stl/css :recent-files-row-title-info)} (str ", " time)])
[:div {:class (stl/css :project-actions)}
(when-not (:is-default project)
[:button
{:class (stl/css-case :pin-icon true
:tooltip true
:tooltip-bottom true
:active (:is-pinned project))
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:aria-label (tr "dashboard.pin-unpin")
:tab-index "0"}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:button
{:class (stl/css :btn-secondary :btn-small :tooltip :tooltip-bottom)
:on-click on-create-click
:alt (tr "dashboard.new-file")
:aria-label (tr "dashboard.new-file")
:data-test "project-new-file"
:on-key-down handle-create-click}
i/close]
[:button
{:class (stl/css :btn-secondary :btn-small :tooltip :tooltip-bottom)
:on-click on-menu-click
:alt (tr "dashboard.options")
:aria-label (tr "dashboard.options")
:data-test "project-options"
:on-key-down handle-menu-click}
i/actions]]]]
[:& line-grid
{:project project {:project project
:show? (:menu-open @local) :team team
:left (+ 24 (:x (:menu-pos @local))) :files files
:top (:y (:menu-pos @local)) :create-fn create-file
:on-edit on-edit-open :limit limit}]
:on-menu-close on-menu-close
:on-import on-import}]
[:span.info (str (tr "labels.num-of-files" (i18n/c file-count)))] (when (and (> limit 0)
(let [time (-> (:modified-at project) (> file-count limit))
(dt/timeago {:locale locale}))] [:button
[:span.recent-files-row-title-info (str ", " time)]) {:class (stl/css :show-more)
[:div.project-actions :on-click on-nav
(when-not (:is-default project) :tab-index "0"
[:button.pin-icon.tooltip.tooltip-bottom :on-key-down (fn [event]
{:class (when (:is-pinned project) "active") (when (kbd/enter? event)
:on-click toggle-pin (on-nav)))}
:alt (tr "dashboard.pin-unpin") [:div {:class (stl/css :placeholder-label)} (tr "dashboard.show-all-files")]
:aria-label (tr "dashboard.pin-unpin") [:div {:class (stl/css :placeholder-icon)} i/arrow-down]])]
:tab-index "0"}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:button.btn-secondary.btn-small.tooltip.tooltip-bottom ;; OLD
{:on-click on-create-click [:article.dashboard-project-row
:alt (tr "dashboard.new-file") {:class (when first? "first")}
:aria-label (tr "dashboard.new-file") [:header.project {:ref rowref}
:data-test "project-new-file" [:div.project-name-wrapper
:tab-index "0" (if (:edition? @local)
:on-key-down (fn [event] [:& inline-edition {:content (:name project)
(when (kbd/enter? event) :on-end on-edit}]
(on-create-click event)))} [:h2 {:on-click on-nav
i/close] :on-context-menu on-menu-click}
(if (:is-default project)
(tr "labels.drafts")
(:name project))])
[:button.btn-secondary.btn-small.tooltip.tooltip-bottom [:& project-menu
{:on-click on-menu-click {:project project
:alt (tr "dashboard.options") :show? (:menu-open @local)
:aria-label (tr "dashboard.options") :left (+ 24 (:x (:menu-pos @local)))
:data-test "project-options" :top (:y (:menu-pos @local))
:tab-index "0" :on-edit on-edit-open
:on-key-down (fn [event] :on-menu-close on-menu-close
(when (kbd/enter? event) :on-import on-import}]
(dom/stop-propagation event)
(on-menu-click event)))}
i/actions]]]]
[:& line-grid [:span.info (str (tr "labels.num-of-files" (i18n/c file-count)))]
{:project project (let [time (-> (:modified-at project)
:team team (dt/timeago {:locale locale}))]
:files files [:span.recent-files-row-title-info (str ", " time)])
:create-fn create-file [:div.project-actions
:limit limit}] (when-not (:is-default project)
[:button.pin-icon.tooltip.tooltip-bottom
{:class (when (:is-pinned project) "active")
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:aria-label (tr "dashboard.pin-unpin")
:tab-index "0"}
(if (:is-pinned project)
i/pin-fill
i/pin)])
(when (and (> limit 0) [:button.btn-secondary.btn-small.tooltip.tooltip-bottom
(> file-count limit)) {:on-click on-create-click
[:button.show-more {:on-click on-nav :alt (tr "dashboard.new-file")
:tab-index "0" :aria-label (tr "dashboard.new-file")
:on-key-down (fn [event] :data-test "project-new-file"
(when (kbd/enter? event) :tab-index "0"
(on-nav)))} :on-key-down (fn [event]
[:div.placeholder-label (when (kbd/enter? event)
(tr "dashboard.show-all-files")] (on-create-click event)))}
[:div.placeholder-icon i/arrow-down]])])) i/close]
[:button.btn-secondary.btn-small.tooltip.tooltip-bottom
{:on-click on-menu-click
:alt (tr "dashboard.options")
:aria-label (tr "dashboard.options")
:data-test "project-options"
:tab-index "0"
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(on-menu-click event)))}
i/actions]]]]
[:& line-grid
{:project project
:team team
:files files
:create-fn create-file
:limit limit}]
(when (and (> limit 0)
(> file-count limit))
[:button.show-more {:on-click on-nav
:tab-index "0"
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-nav)))}
[:div.placeholder-label
(tr "dashboard.show-all-files")]
[:div.placeholder-icon i/arrow-down]])])))
(def recent-files-ref (def recent-files-ref
@ -358,7 +508,8 @@
(mf/defc projects-section (mf/defc projects-section
[{:keys [team projects profile default-project-id] :as props}] [{:keys [team projects profile default-project-id] :as props}]
(let [projects (->> (vals projects) (let [new-css-system (mf/use-ctx ctx/new-css-system)
projects (->> (vals projects)
(sort-by :modified-at) (sort-by :modified-at)
(reverse)) (reverse))
recent-map (mf/deref recent-files-ref) recent-map (mf/deref recent-files-ref)
@ -408,35 +559,68 @@
(st/emit! (dd/fetch-recent-files team-id) (st/emit! (dd/fetch-recent-files team-id)
(dd/clear-selected-files))) (dd/clear-selected-files)))
(when (seq projects) (if new-css-system
[:* (when (seq projects)
[:& header] [:*
[:& header]
(when team-hero? (when team-hero?
[:& team-hero {:team team :close-fn close-banner}]) [:& team-hero {:team team :close-fn close-banner}])
(when (and (contains? cf/flags :dashboard-templates-section) (when (and (contains? cf/flags :dashboard-templates-section)
(or (not tutorial-viewed?) (or (not tutorial-viewed?)
(not walkthrough-viewed?))) (not walkthrough-viewed?)))
[:div.hero-projects [:div {:class (stl/css :hero-projects)}
(when (and (not tutorial-viewed?) (:is-default team)) (when (and (not tutorial-viewed?) (:is-default team))
[:& tutorial-project [:& tutorial-project
{:close-tutorial close-tutorial {:close-tutorial close-tutorial
:default-project-id default-project-id}]) :default-project-id default-project-id}])
(when (and (not walkthrough-viewed?) (:is-default team)) (when (and (not walkthrough-viewed?) (:is-default team))
[:& interface-walkthrough [:& interface-walkthrough
{:close-walkthrough close-walkthrough}])]) {:close-walkthrough close-walkthrough}])])
[:div.dashboard-container.no-bg.dashboard-projects [:div {:class (stl/css :dashboard-container :no-bg :dashboard-projects)}
(for [{:keys [id] :as project} projects] (for [{:keys [id] :as project} projects]
(let [files (when recent-map (let [files (when recent-map
(->> (vals recent-map) (->> (vals recent-map)
(filterv #(= id (:project-id %))) (filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))] (sort-by :modified-at #(compare %2 %1))))]
[:& project-item {:project project [:& project-item {:project project
:team team :team team
:files files :files files
:first? (= project (first projects)) :first? (= project (first projects))
:key id}]))]]))) :key id}]))]])
;; OLD
(when (seq projects)
[:*
[:& header]
(when team-hero?
[:& team-hero {:team team :close-fn close-banner}])
(when (and (contains? cf/flags :dashboard-templates-section)
(or (not tutorial-viewed?)
(not walkthrough-viewed?)))
[:div.hero-projects
(when (and (not tutorial-viewed?) (:is-default team))
[:& tutorial-project
{:close-tutorial close-tutorial
:default-project-id default-project-id}])
(when (and (not walkthrough-viewed?) (:is-default team))
[:& interface-walkthrough
{:close-walkthrough close-walkthrough}])])
[:div.dashboard-container.no-bg.dashboard-projects
(for [{:keys [id] :as project} projects]
(let [files (when recent-map
(->> (vals recent-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:& project-item {:project project
:team team
:files files
:first? (= project (first projects))
:key id}]))]]))))

View File

@ -0,0 +1,391 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}
.dashboard-project-row {
margin-bottom: $s-24;
position: relative;
.project {
align-items: center;
background: $df-primary;
border-radius: $br-4;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: $s-16;
padding: $s-8 $s-8 $s-8 $s-16;
width: 99%;
max-height: $s-40;
gap: $s-8;
.project-name-wrapper {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: $s-32;
margin-left: $s-8;
}
.show-more {
align-items: center;
color: $df-secondary;
display: flex;
font-size: $fs-14;
justify-content: space-between;
cursor: pointer;
background-color: transparent;
border: none;
.placeholder-icon {
transform: rotate(-90deg);
margin-left: $s-12;
svg {
height: $s-16;
width: $s-16;
fill: $df-secondary;
}
}
&:hover {
color: $da-tertiary;
svg {
fill: $da-tertiary;
}
}
}
.btn-secondary {
border: none;
padding: $s-8;
}
h2 {
cursor: pointer;
font-size: $fs-16;
line-height: 0.8;
font-weight: $fw700;
color: $db-secondary;
margin-right: $s-4;
margin-right: $s-12;
}
.info,
.recent-files-row-title-info {
font-size: $fs-14;
line-height: 1.15;
font-weight: $fw400;
color: $db-primary;
@media (max-width: 760px) {
display: none;
}
}
.project-actions {
display: flex;
opacity: 1;
margin-left: $s-32;
.btn-small {
height: $s-32;
margin: 0 $s-8;
width: $s-32;
&:not(:hover) {
background: transparent;
}
svg {
height: $s-16;
width: $s-16;
}
}
}
.pin-icon {
cursor: pointer;
display: flex;
align-items: center;
margin-right: $s-16;
background-color: transparent;
border: none;
svg {
width: $s-16;
height: $s-16;
fill: $df-secondary;
}
&.active {
svg {
fill: $db-tertiary;
}
}
}
}
&:hover,
&:focus,
&:focus-within {
.project-actions {
opacity: 1;
}
}
.show-more {
align-items: center;
color: $df-secondary;
display: flex;
font-size: $fs-14;
justify-content: space-between;
cursor: pointer;
background-color: transparent;
border: none;
position: absolute;
top: $s-8;
right: $s-52;
.placeholder-icon {
transform: rotate(-90deg);
margin-left: $s-8;
svg {
height: $s-16;
width: $s-16;
fill: $df-secondary;
}
}
&:hover {
color: $da-tertiary;
svg {
fill: $da-tertiary;
}
}
}
}
.dashboard-project-row .project {
background-color: transparent;
h2 {
color: $df-primary;
font-weight: 400;
}
span,
.info,
.recent-files-row-title-info {
color: $df-secondary;
}
.project-actions {
svg {
fill: $df-primary;
}
.pin-icon svg {
fill: $df-secondary;
}
}
}
.team-hero {
background-color: $db-tertiary;
border-radius: $br-8;
border: none;
display: flex;
margin: $s-16;
padding: $s-8;
position: relative;
.text {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-grow: 1;
padding: $s-20 $s-20;
}
.title {
font-size: $fs-24;
color: $df-primary;
font-weight: $fw400;
}
.info {
flex: 1;
font-size: $fs-16;
span {
color: $df-secondary;
display: block;
}
a {
color: $da-primary;
}
padding-top: $s-8;
}
.close {
position: absolute;
top: $s-20;
right: $s-20;
background-color: transparent;
border: none;
cursor: pointer;
svg {
transform: rotate(45deg);
width: $s-16;
height: $s-16;
}
}
.invite {
height: $s-32;
width: $s-180;
}
.img-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: $s-200;
height: $s-200;
overflow: hidden;
border-radius: $br-4;
}
img {
border-radius: $br-4;
height: $s-200;
width: auto;
@media (max-width: 1200px) {
display: none;
width: 0;
}
}
svg {
fill: $df-secondary;
}
}
.hero-projects {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $s-32;
margin: 0 $s-16 $s-16 $s-20;
@media (max-width: 1366px) {
grid-template-columns: 1fr;
}
.tutorial,
.walkthrough {
display: grid;
grid-template-columns: auto 1fr;
position: relative;
border-radius: $br-8;
min-height: $s-216;
background-color: $db-tertiary;
padding: $s-8;
.thumbnail {
width: $s-200;
height: $s-200;
border-radius: $br-6;
padding: $s-32;
display: block;
background-color: var(--color-canvas);
}
img {
border-radius: $br-4;
margin-bottom: 0;
width: $s-232;
}
.text {
padding: $s-32;
display: flex;
flex-direction: column;
}
.title {
color: $df-primary;
font-size: $fs-24;
font-weight: $fw400;
margin-bottom: $s-8;
}
.info {
flex: 1;
color: $df-secondary;
margin-bottom: $s-20;
font-size: $fs-16;
}
.invite {
height: $s-32;
}
.action {
width: $s-180;
height: $s-40;
}
.close {
position: absolute;
top: 0;
right: 0;
width: $s-24;
cursor: pointer;
display: flex;
margin: $s-20;
justify-content: center;
align-items: center;
background-color: transparent;
border: none;
.icon {
svg {
fill: $df-secondary;
height: $s-16;
width: $s-16;
transform: rotate(45deg);
&:hover {
fill: $da-tertiary;
}
}
}
}
}
.walkthrough {
.thumbnail {
background-image: url("/images/walkthrough-cover.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
}
.tutorial {
.thumbnail {
background-image: url("/images/hands-on-tutorial.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.loader {
display: flex;
svg#loader-pencil {
width: $s-32;
}
}
}
}

View File

@ -5,87 +5,94 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.search (ns app.main.ui.dashboard.search
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.math :as mth]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.grid :refer [grid]] [app.main.ui.dashboard.grid :refer [grid]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc search-page (mf/defc search-page
[{:keys [team search-term] :as props}] [{:keys [team search-term] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
result (mf/deref refs/dashboard-search-result)
[rowref limit] (hooks/use-dynamic-grid-item-width)]
(mf/use-effect
(mf/deps team)
(fn []
(when team
(let [tname (if (:is-default team)
(tr "dashboard.your-penpot")
(:name team))]
(dom/set-html-title (tr "title.dashboard.search" tname))))))
(mf/use-effect
(mf/deps search-term)
(fn []
(st/emit! (dd/search {:search-term search-term})
(dd/clear-selected-files))))
(let [result (mf/deref refs/dashboard-search-result)
width (mf/use-state nil)
rowref (mf/use-ref)
itemsize (if (>= @width 1030)
280
230)
ratio (if (some? @width) (/ @width itemsize) 0)
nitems (mth/floor ratio)
limit (min 10 nitems)
limit (max 1 limit)]
(mf/use-effect (mf/use-effect
(mf/deps team)
(fn [] (fn []
(let [node (mf/ref-val rowref) (when team
mnt? (volatile! true) (let [tname (if (:is-default team)
sub (->> (wapi/observe-resize node) (tr "dashboard.your-penpot")
(rx/observe-on :af) (:name team))]
(rx/subs (fn [entries] (dom/set-html-title (tr "title.dashboard.search" tname))))))
(let [row (first entries)
row-rect (.-contentRect ^js row)
row-width (.-width ^js row-rect)]
(when @mnt?
(reset! width row-width))))))]
(fn []
(vreset! mnt? false)
(rx/dispose! sub)))))
[:*
[:header.dashboard-header
[:div.dashboard-title#dashboard-search-title
[:h1 (tr "dashboard.title-search")]]]
[:section.dashboard-container.search.no-bg {:ref rowref} (mf/use-effect
(cond (mf/deps search-term)
(empty? search-term) (fn []
[:div.grid-empty-placeholder.search (st/emit! (dd/search {:search-term search-term})
[:div.icon i/search] (dd/clear-selected-files))))
[:div.text (tr "dashboard.type-something")]] (if new-css-system
[:*
[:header {:class (stl/css :dashboard-header)}
[:div#dashboard-search-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.title-search")]]]
(nil? result) [:section {:class (stl/css :dashboard-container :search :no-bg)
[:div.grid-empty-placeholder.search :ref rowref}
[:div.icon i/search] (cond
[:div.text (tr "dashboard.searching-for" search-term)]] (empty? search-term)
[:div {:class (stl/css :grid-empty-placeholder :search)}
[:div {:class (stl/css :icon)} i/search]
[:div {:class (stl/css :text)} (tr "dashboard.type-something")]]
(empty? result) (nil? result)
[:div.grid-empty-placeholder.search [:div {:class (stl/css :grid-empty-placeholder :search)}
[:div.icon i/search] [:div {:class (stl/css :icon)} i/search]
[:div.text (tr "dashboard.no-matches-for" search-term)]] [:div {:class (stl/css :text)} (tr "dashboard.searching-for" search-term)]]
:else (empty? result)
[:& grid {:files result [:div {:class (stl/css :grid-empty-placeholder :search)}
:hide-new? true [:div {:class (stl/css :icon)} i/search]
:origin :search [:div {:class (stl/css :text)} (tr "dashboard.no-matches-for" search-term)]]
:limit limit}])]]))
:else
[:& grid {:files result
:hide-new? true
:origin :search
:limit limit}])]]
;; OLD
[:*
[:header.dashboard-header
[:div.dashboard-title#dashboard-search-title
[:h1 (tr "dashboard.title-search")]]]
[:section.dashboard-container.search.no-bg {:ref rowref}
(cond
(empty? search-term)
[:div.grid-empty-placeholder.search
[:div.icon i/search]
[:div.text (tr "dashboard.type-something")]]
(nil? result)
[:div.grid-empty-placeholder.search
[:div.icon i/search]
[:div.text (tr "dashboard.searching-for" search-term)]]
(empty? result)
[:div.grid-empty-placeholder.search
[:div.icon i/search]
[:div.text (tr "dashboard.no-matches-for" search-term)]]
:else
[:& grid {:files result
:hide-new? true
:origin :search
:limit limit}])]])))

View File

@ -0,0 +1,49 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
@use "./placeholder.scss";
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}
.grid-empty-placeholder.search {
align-items: center;
display: flex;
justify-content: center;
flex-direction: column;
height: $s-200;
background: transparent;
border: $s-1 solid $db-cuaternary;
border-radius: $br-8;
.text {
color: $df-primary;
}
.icon svg {
fill: $df-secondary;
width: $s-32;
height: $s-32;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,500 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-sidebar {
grid-row: 1 / span 2;
grid-column: 1 / span 2;
background-color: $db-primary;
border-right: $s-1 solid $db-cuaternary;
margin: 0 $s-16 0 0;
padding: $s-16 0 0 0;
z-index: $z-index-1;
display: flex;
flex-direction: column;
height: 100%;
}
.sidebar-content {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 0;
hr {
border-color: transparent;
margin: $s-12 $s-16;
}
}
.sidebar-team-switch {
position: relative;
display: flex;
margin: $s-4 $s-16;
.switch-content {
background-color: $db-tertiary;
border-radius: $br-8;
height: $s-48;
display: flex;
width: 100%;
border: $s-1 solid transparent;
align-items: center;
svg {
fill: #8f9da3;
}
}
.switch-icon {
display: flex;
align-items: center;
justify-content: center;
svg {
fill: $df-secondary;
width: $s-12;
height: $s-12;
}
}
.current-team {
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
flex-grow: 1;
font-size: $fs-14;
padding: 0 $s-12;
background-color: transparent;
border: none;
border-right: $s-1 solid $db-primary;
}
.team-name {
flex-grow: 1;
display: flex;
height: $s-40;
align-items: center;
}
.team-text {
color: $df-primary;
width: $s-144;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: left;
}
.team-icon {
display: flex;
align-items: center;
padding-right: $s-12;
img {
border-radius: 50%;
flex-shrink: 0;
height: $s-24;
width: $s-24;
}
svg {
width: $s-24;
height: $s-24;
}
}
.switch-options {
display: flex;
max-width: $s-24;
min-width: $s-28;
border-left: $s-1 solid $df-primary;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: transparent;
border: none;
height: 100%;
svg {
fill: $df-secondary;
width: $s-16;
height: $s-12;
}
}
.dropdown {
right: $s-2;
top: $s-52;
min-width: $s-160;
max-height: $s-480;
}
}
.dropdown {
@include menuShadow;
position: absolute;
z-index: $z-index-4;
background-color: $db-tertiary;
border: $s-1 solid $db-cuaternary;
border-radius: $br-8;
.separator {
border-color: transparent;
margin-top: $s-12;
}
li {
border-radius: $br-8;
height: $s-40;
margin: $s-6;
display: flex;
align-items: center;
cursor: pointer;
font-size: $fs-14;
padding: $s-6 $s-16;
.warning {
color: var(--dark-warning-color);
}
&:hover {
background-color: $db-cuaternary;
}
svg {
height: $s-12;
width: $s-12;
}
}
hr {
border-color: transparent;
margin: 0;
}
&.options-dropdown {
li {
color: $df-primary;
&.warning {
color: var(--dark-warning-color);
}
}
}
}
.teams-dropdown {
background-color: $db-tertiary;
border-radius: $br-8;
border: $s-1 solid $db-cuaternary;
min-width: $s-248;
left: 0;
top: $s-52;
max-height: $s-480;
overflow-x: hidden;
overflow-y: auto;
li {
border-radius: $br-8;
height: $s-40;
padding: 0 $s-6;
margin: $s-6;
svg {
fill: $df-secondary;
}
&:hover {
background-color: $db-cuaternary;
.team-icon {
&.new-team {
background-color: $da-primary;
svg {
fill: $db-secondary;
}
}
}
}
.team-icon {
display: flex;
align-items: center;
}
.team-text {
color: $df-primary;
width: $s-168;
}
.new-team {
background-color: $db-cuaternary;
}
&.action {
.team-icon {
background-color: #2e3434;
border-radius: 50%;
height: $s-24;
margin-right: $s-12;
padding: $s-6;
width: $s-24;
svg {
height: $s-12;
width: $s-12;
}
}
}
}
}
.sidebar-empty-placeholder {
padding: $s-12;
color: $df-secondary;
display: flex;
align-items: flex-start;
.icon {
padding: 0 $s-12;
svg {
fill: $df-secondary;
width: $s-12;
height: $s-12;
}
}
.text {
font-size: $fs-12;
}
}
.sidebar-search {
align-items: center;
border: $s-1 solid transparent;
display: flex;
margin: $s-6 $s-16;
background-color: $db-tertiary;
border-radius: $br-8;
margin-bottom: $s-32;
margin-top: 0;
position: relative;
.input-text {
background: transparent;
border: 0;
font-size: $fs-14;
margin: 0;
width: 100%;
height: $s-40;
border-radius: $br-8;
color: $df-primary;
max-width: 100%;
padding: $s-6 $s-12;
&:focus,
&:focus-within {
border: $s-1 solid $da-primary;
}
}
::placeholder {
color: $df-secondary;
}
.search,
.clear-search {
align-items: center;
cursor: pointer;
display: flex;
height: $s-24;
margin-left: auto;
padding: 0 $s-8;
width: $s-32;
position: absolute;
top: $s-12;
right: $s-2;
svg {
fill: $df-secondary;
height: $s-16;
width: $s-16;
}
}
.clear-search svg {
transform: rotate(45deg);
&:hover {
fill: var(--warning-color);
}
}
}
.sidebar-nav {
display: flex;
flex-direction: column;
overflow-y: auto;
margin: 0;
user-select: none;
&.no-overflow {
overflow: unset;
}
& > li {
align-items: center;
cursor: pointer;
display: flex;
flex-shrink: 0;
padding: $s-8 $s-8 $s-8 $s-24;
a {
font-weight: $fw400;
width: 100%;
&:hover {
text-decoration: none;
}
}
svg {
fill: $db-secondary;
margin-right: $s-8;
height: $s-12;
width: $s-12;
}
.element-title {
color: $df-secondary;
font-size: $fs-14;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.recent-projects {
svg {
fill: $df-primary;
}
}
input.element-title {
border: 0;
height: $s-32;
padding: $s-6;
margin: 0;
width: 100%;
background-color: $df-primary;
}
.close {
background-color: $df-primary;
cursor: pointer;
padding-left: $s-6;
svg {
fill: $df-secondary;
height: $s-16;
transform: rotate(45deg) translateY(7px);
width: $s-16;
margin: 0;
}
}
.element-subtitle {
color: $df-secondary;
font-style: italic;
}
&:hover {
background-color: $db-cuaternary;
}
&.current {
background-color: $db-cuaternary;
.element-title {
color: $da-primary;
}
}
}
}
.profile-section {
align-items: center;
cursor: pointer;
display: flex;
padding: $s-12 $s-16;
position: relative;
background-color: $db-tertiary;
border-top: $s-1 solid $db-cuaternary;
.profile {
align-items: center;
cursor: pointer;
display: flex;
flex-grow: 1;
span {
@include text-ellipsis;
color: $df-primary;
margin: $s-12;
font-size: $fs-14;
max-width: $s-160;
}
img {
border-radius: 50%;
flex-shrink: 0;
height: $s-40;
width: $s-40;
}
svg {
height: $s-12;
margin-left: auto;
margin-right: $s-8;
width: $s-12;
}
}
.dropdown {
left: $s-16;
bottom: $s-44;
background-color: $db-primary;
border: $s-1 solid $db-tertiary;
border-radius: $br-8;
min-width: $s-252;
@include animation(0, 0.2s, fadeInUp);
li {
font-size: $fs-14;
padding: $s-8 $s-16;
svg {
fill: $df-secondary;
margin-right: $s-8;
height: $s-12;
width: $s-12;
}
.text {
color: $df-primary;
}
&.separator {
border-top: $s-1 solid transparent;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,582 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss"; @use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}
.dashboard-team-webhooks {
display: flex;
flex-direction: column;
width: $s-800;
margin-left: $s-120;
margin-top: $s-80;
border: none;
align-items: flex-start;
}
.webhooks-empty {
align-items: center;
background-color: transparent;
border-radius: $br-8;
border: $s-1 solid $db-cuaternary;
color: $df-secondary;
display: flex;
flex-direction: column;
font-size: $fs-12;
justify-content: center;
margin-top: $s-32;
max-width: $s-1000;
min-height: $s-136;
padding: $s-32;
text-align: center;
width: $s-468;
}
.webhooks-hero-container {
max-width: $s-1000;
width: 100%;
display: flex;
flex-direction: column;
width: $s-468;
background-color: transparent;
.upload-button {
width: $s-100;
}
.btn-secondary {
margin-left: $s-12;
}
}
.webhooks-hero {
font-size: $fs-14;
display: flex;
flex-direction: column;
gap: $s-32;
justify-content: space-between;
margin-top: $s-32;
margin: 0;
padding: $s-32;
padding: 0;
width: $s-468;
.desc {
color: $df-secondary;
width: 100%;
h2 {
color: $df-primary;
font-size: $fs-24;
font-weight: regular;
margin-bottom: $s-32;
}
p {
color: $df-secondary;
margin-bottom: 0;
font-size: $fs-16;
}
}
.btn-primary {
@extends .button-primary;
height: $s-32;
}
}
.dropdown {
background-color: $db-tertiary;
border-radius: $br-8;
border: $s-1 solid $db-cuaternary;
box-shadow: 0 $s-2 $s-8 rgba(0, 0, 0, 0.25);
left: calc(-1 * $s-144);
max-height: $s-480;
min-width: $s-252;
overflow-y: auto;
position: absolute;
top: $s-32;
width: $s-152;
z-index: $z-index-4;
hr {
margin: 0;
border-color: $df-secondary;
}
li {
display: flex;
align-items: center;
cursor: pointer;
font-size: $fs-14;
padding: $s-4 $s-16;
border-radius: $br-8;
height: $s-40;
margin: $s-6;
color: $df-primary;
&:hover {
background-color: $db-cuaternary;
}
&.title {
font-weight: $fw700;
cursor: default;
}
}
.separator {
border-color: transparent;
margin-top: $s-8;
}
&.options-dropdown {
li {
color: $df-primary;
&.warning {
color: var(--warning-color);
}
}
}
}
.dashboard-table {
display: flex;
flex-direction: column;
align-items: center;
margin-top: $s-20;
font-size: $fs-16;
.table-header {
color: $df-secondary;
display: grid;
font-size: $fs-12;
grid-template-columns: 43% 1fr $s-108 $s-12;
height: $s-40;
max-width: $s-1000;
padding: 0 $s-16;
text-transform: uppercase;
user-select: none;
width: 100%;
}
.table-rows {
display: flex;
flex-direction: column;
max-width: $s-1000;
width: 100%;
margin-top: $s-16;
color: $db-secondary;
}
.table-row {
align-items: center;
background-color: $db-tertiary;
border-radius: $br-8;
color: $df-primary;
display: flex;
height: $s-64;
padding: 0 $s-16;
width: 100%;
&:not(:first-child) {
margin-top: $s-16;
}
}
.table-field {
display: flex;
align-items: center;
.icon {
padding-left: $s-12;
cursor: pointer;
}
&.name {
width: 43%;
min-width: $s-300;
display: flex;
}
&.roles {
flex-grow: 1;
cursor: default;
position: relative;
.rol-label {
user-select: none;
}
.rol-selector {
&.has-priv {
cursor: pointer;
}
min-width: $s-160;
height: $s-32;
display: flex;
justify-content: space-between;
align-items: center;
padding: $s-4 $s-8;
font-size: $fs-14;
background-color: $db-cuaternary;
border-color: transparent;
border-radius: $br-8;
}
.dropdown {
left: 0;
}
}
&.actions {
position: relative;
.actions-dropdown {
max-height: $s-480;
min-width: $s-180;
}
svg {
fill: $df-secondary;
}
}
&.status {
.status-badge {
min-width: $s-76;
height: $s-24;
display: flex;
justify-content: center;
align-items: center;
border-radius: $br-8;
color: $db-primary;
text-transform: uppercase;
&.pending {
background-color: var(--warning-color);
}
&.expired {
background-color: $df-secondary;
}
.status-label {
font-size: $fs-12;
}
}
}
&.uri {
flex-grow: 1;
}
&.active {
min-width: $s-100;
}
&.last-delivery {
display: flex;
justify-content: center;
width: $s-52;
position: relative;
.success svg {
fill: $da-tertiary;
width: $s-16;
height: $s-16;
}
.failure svg {
fill: var(--warning-color);
width: $s-16;
height: $s-16;
}
.icon-container {
width: $s-16;
height: $s-16;
overflow-x: visible;
}
.icon {
padding: 0;
}
}
.tooltip {
display: none;
position: absolute;
top: calc(-1 * $s-56);
left: 50%;
transform: translate(-50%, 0);
text-align: center;
.label {
border-radius: $br-4;
color: $df-primary;
background-color: $db-secondary;
white-space: nowrap;
padding: $s-12 $s-20;
}
.arrow-down {
margin: 0 auto;
width: 0;
height: 0;
border-left: $s-8 solid transparent;
border-right: $s-8 solid transparent;
border-top: $s-8 solid $db-secondary;
}
}
.last-delivery-icon:hover {
.tooltip {
display: block;
}
}
}
.member-info {
display: flex;
flex-direction: column;
margin-left: $s-16;
}
.member-name {
font-size: $fs-16;
}
.you {
color: $df-secondary;
margin-left: $s-6;
}
.member-email {
color: $df-secondary;
font-size: $fs-12;
}
.member-image {
height: $s-32;
width: $s-32;
img {
border-radius: 50%;
}
}
.dashboard-team-webhooks & {
width: $s-800;
.table-rows {
padding-top: 0;
.table-row {
font-size: $fs-16;
min-height: $s-40;
height: fit-content;
.name {
color: $df-primary;
max-width: $s-480;
}
.expiration-date {
color: $df-secondary;
}
}
}
}
&.team-members {
margin-bottom: $s-52;
}
&.invitations {
.table-row {
display: grid;
grid-template-columns: 43% 1fr $s-108 $s-12;
}
}
svg {
width: $s-12;
height: $s-12;
fill: $df-secondary;
}
}
.empty-invitations {
height: $s-156;
max-width: $s-1000;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: $s-16;
border: $s-1 solid $db-cuaternary;
border-radius: $br-8;
color: $df-secondary;
}
.team-settings {
display: flex;
justify-content: center;
margin-top: $s-16;
svg {
width: $s-20;
height: $s-20;
}
.horizontal-blocks {
display: flex;
max-width: $s-1000;
justify-content: space-between;
width: 100%;
flex-direction: column;
gap: $s-24;
}
.block {
display: flex;
max-width: $s-324;
width: $s-324;
flex-direction: column;
padding: $s-12;
.label {
color: $df-secondary;
font-size: $fs-12;
text-transform: uppercase;
}
.icon svg {
fill: $df-secondary;
}
.name,
.text {
color: $df-primary;
}
}
.info-block {
position: relative;
padding-top: $s-180;
.name {
margin-top: $s-12;
font-size: $fs-24;
color: $df-primary;
@include text-ellipsis;
margin-right: $s-88;
}
.icon {
position: absolute;
padding: $s-16;
right: 0;
top: 0;
left: 0;
height: $s-120;
width: $s-120;
img {
border-radius: 50%;
width: $s-120;
height: $s-120;
}
.update-overlay {
opacity: 0;
cursor: pointer;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
color: $df-primary;
z-index: $z-index-modal;
background-color: $da-primary;
height: $s-120;
width: $s-120;
svg {
fill: $db-primary;
}
}
&:hover {
.update-overlay {
opacity: 1;
width: $s-72;
height: $s-72;
top: $s-16;
left: $s-16;
}
}
}
}
.owner-block {
img {
width: $s-32;
height: $s-32;
border-radius: 50%;
}
svg {
width: $s-12;
height: $s-12;
fill: $db-secondary;
}
.owner {
margin-top: $s-6;
display: flex;
align-items: center;
color: $db-secondary;
.icon {
margin-right: $s-12;
}
}
.summary {
margin-top: $s-6;
color: $db-secondary;
.icon {
padding: 0 $s-12;
margin-right: $s-12;
}
}
}
.stats-block {
svg {
fill: $db-secondary;
}
.projects,
.files {
margin-top: $s-8;
display: flex;
align-items: center;
color: $db-secondary;
.icon {
display: flex;
align-items: center;
padding: 0 $s-2;
margin-right: $s-12;
}
}
}
}
.modal-team-container { .modal-team-container {
@extend .modal-container-base; @extend .modal-container-base;

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.templates (ns app.main.ui.dashboard.templates
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.math :as mth] [app.common.math :as mth]
@ -15,6 +16,7 @@
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
@ -57,7 +59,8 @@
(mf/defc title (mf/defc title
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [collapsed]}] [{:keys [collapsed]}]
(let [on-click (let [new-css-system (mf/use-ctx ctx/new-css-system)
on-click
(mf/use-fn (mf/use-fn
(mf/deps collapsed) (mf/deps collapsed)
(fn [_event] (fn [_event]
@ -73,17 +76,27 @@
(dom/prevent-default event) (dom/prevent-default event)
(on-click event))))] (on-click event))))]
[:div.title (if new-css-system
[:button {:tab-index "0" [:div {:class (stl/css :title)}
:on-click on-click [:button {:tab-index "0"
:on-key-down on-key-down} :on-click on-click
[:span (tr "dashboard.libraries-and-templates")] :on-key-down on-key-down}
[:span.icon (if ^boolean collapsed i/arrow-up i/arrow-down)]]])) [:span (tr "dashboard.libraries-and-templates")]
[:span {:class (stl/css :icon)} (if ^boolean collapsed i/arrow-up i/arrow-down)]]]
;; OLD
[:div.title
[:button {:tab-index "0"
:on-click on-click
:on-key-down on-key-down}
[:span (tr "dashboard.libraries-and-templates")]
[:span.icon (if ^boolean collapsed i/arrow-up i/arrow-down)]]])))
(mf/defc card-item (mf/defc card-item
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [item index is-visible collapsed on-import]}] [{:keys [item index is-visible collapsed on-import]}]
(let [id (dm/str "card-container-" index) (let [new-css-system (mf/use-ctx ctx/new-css-system)
id (dm/str "card-container-" index)
thb (assoc cf/public-uri :path (dm/str "/images/thumbnails/template-" (:id item) ".jpg")) thb (assoc cf/public-uri :path (dm/str "/images/thumbnails/template-" (:id item) ".jpg"))
on-click on-click
@ -100,23 +113,41 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(on-import item event))))] (on-import item event))))]
[:a.card-container (if new-css-system
{:tab-index (if (or (not is-visible) collapsed) "-1" "0") [:a
:id id {:class (stl/css :card-container)
:data-index index :tab-index (if (or (not is-visible) collapsed) "-1" "0")
:on-click on-click :id id
:on-key-down on-key-down} :data-index index
[:div.template-card :on-click on-click
[:div.img-container :on-key-down on-key-down}
[:img {:src (dm/str thb) [:div {:class (stl/css :template-card)}
:alt (:name item)}]] [:div {:class (stl/css :img-container)}
[:div.card-name [:span (:name item)] [:img {:src (dm/str thb)
[:span.icon i/download]]]])) :alt (:name item)}]]
[:div {:class (stl/css :card-name)}
[:span (:name item)]
[:span {:class (stl/css :icon)} i/download]]]]
;; OLD
[:a.card-container
{:tab-index (if (or (not is-visible) collapsed) "-1" "0")
:id id
:data-index index
:on-click on-click
:on-key-down on-key-down}
[:div.template-card
[:div.img-container
[:img {:src (dm/str thb)
:alt (:name item)}]]
[:div.card-name [:span (:name item)]
[:span.icon i/download]]]])))
(mf/defc card-item-link (mf/defc card-item-link
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [total is-visible collapsed section]}] [{:keys [total is-visible collapsed section]}]
(let [id (dm/str "card-container-" total) (let [new-css-system (mf/use-ctx ctx/new-css-system)
id (dm/str "card-container-" total)
on-click on-click
(mf/use-fn (mf/use-fn
@ -134,23 +165,39 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(on-click event))))] (on-click event))))]
[:div.card-container (if new-css-system
[:div.template-card [:div {:class (stl/css :card-container)}
[:div.img-container [:div {:class (stl/css :template-card)}
[:a {:id id [:div {:class (stl/css :img-container)}
:tab-index (if (or (not is-visible) collapsed) "-1" "0") [:a {:id id
:href "https://penpot.app/libraries-templates.html" :tab-index (if (or (not is-visible) collapsed) "-1" "0")
:target "_blank" :href "https://penpot.app/libraries-templates.html"
:on-click on-click :target "_blank"
:on-key-down on-key-down} :on-click on-click
[:div.template-link :on-key-down on-key-down}
[:div.template-link-title (tr "dashboard.libraries-and-templates")] [:div {:class (stl/css :template-link)}
[:div.template-link-text (tr "dashboard.libraries-and-templates.explore")]]]]]])) [:div {:class (stl/css :template-link-title)} (tr "dashboard.libraries-and-templates")]
[:div {:class (stl/css :template-link-text)} (tr "dashboard.libraries-and-templates.explore")]]]]]]
;; OLD
[:div.card-container
[:div.template-card
[:div.img-container
[:a {:id id
:tab-index (if (or (not is-visible) collapsed) "-1" "0")
:href "https://penpot.app/libraries-templates.html"
:target "_blank"
:on-click on-click
:on-key-down on-key-down}
[:div.template-link
[:div.template-link-title (tr "dashboard.libraries-and-templates")]
[:div.template-link-text (tr "dashboard.libraries-and-templates.explore")]]]]]])))
(mf/defc templates-section (mf/defc templates-section
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [default-project-id profile project-id team-id content-width]}] [{:keys [default-project-id profile project-id team-id content-width]}]
(let [templates (mf/deref builtin-templates) (let [new-css-system (mf/use-ctx ctx/new-css-system)
templates (mf/deref builtin-templates)
templates (mf/with-memo [templates] templates (mf/with-memo [templates]
(filterv #(not= (:id %) "tutorial-for-beginners") templates)) (filterv #(not= (:id %) "tutorial-for-beginners") templates))
@ -234,42 +281,86 @@
(when (and profile (not collapsed)) (when (and profile (not collapsed))
(st/emit! (dd/fetch-builtin-templates)))) (st/emit! (dd/fetch-builtin-templates))))
[:div.dashboard-templates-section (if new-css-system
{:class (when ^boolean collapsed "collapsed")} [:div
[:& title {:collapsed collapsed}] {:class (stl/css-case :dashboard-templates-section true
:collapsed collapsed)}
[:& title {:collapsed collapsed}]
[:div.content {:ref content-ref [:div {:class (stl/css :content)
:style {:left card-offset :ref content-ref
:width (dm/str container-size "px")}} :style {:left card-offset
:width (dm/str container-size "px")}}
(for [index (range (count templates))] (for [index (range (count templates))]
[:& card-item [:& card-item
{:on-import on-import-template {:on-import on-import-template
:item (nth templates index) :item (nth templates index)
:index index :index index
:key index :key index
:is-visible (and (>= index first-card) :is-visible (and (>= index first-card)
(<= index last-card)) (<= index last-card))
:collapsed collapsed}]) :collapsed collapsed}])
[:& card-item-link [:& card-item-link
{:is-visible (and (>= total first-card) (<= total last-card)) {:is-visible (and (>= total first-card) (<= total last-card))
:collapsed collapsed :collapsed collapsed
:section section :section section
:total total}]] :total total}]]
(when (< card-offset 0) (when (< card-offset 0)
[:button.button.left [:button
{:tab-index (if ^boolean collapsed "-1" "0") {:class (stl/css :button :left)
:on-click on-move-left :tab-index (if ^boolean collapsed "-1" "0")
:on-key-down on-move-left-key-down} :on-click on-move-left
i/go-prev]) :on-key-down on-move-left-key-down}
i/go-prev])
(when more-cards (when more-cards
[:button.button.right [:button
{:tab-index (if collapsed "-1" "0") {:class (stl/css :button :right)
:on-click on-move-right :tab-index (if collapsed "-1" "0")
:aria-label (tr "labels.next") :on-click on-move-right
:on-key-down on-move-right-key-down} :aria-label (tr "labels.next")
i/go-next])])) :on-key-down on-move-right-key-down}
i/go-next])]
;; OLD
[:div.dashboard-templates-section
{:class (when ^boolean collapsed "collapsed")}
[:& title {:collapsed collapsed}]
[:div.content {:ref content-ref
:style {:left card-offset
:width (dm/str container-size "px")}}
(for [index (range (count templates))]
[:& card-item
{:on-import on-import-template
:item (nth templates index)
:index index
:key index
:is-visible (and (>= index first-card)
(<= index last-card))
:collapsed collapsed}])
[:& card-item-link
{:is-visible (and (>= total first-card) (<= total last-card))
:collapsed collapsed
:section section
:total total}]]
(when (< card-offset 0)
[:button.button.left
{:tab-index (if ^boolean collapsed "-1" "0")
:on-click on-move-left
:on-key-down on-move-left-key-down}
i/go-prev])
(when more-cards
[:button.button.right
{:tab-index (if collapsed "-1" "0")
:on-click on-move-right
:aria-label (tr "labels.next")
:on-key-down on-move-right-key-down}
i/go-next])])))

View File

@ -0,0 +1,191 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
.dashboard-templates-section {
position: absolute;
display: flex;
flex-direction: column;
justify-content: flex-end;
bottom: 0;
width: 100%;
height: $s-228;
transition: bottom 300ms;
pointer-events: none;
&.collapsed {
bottom: calc(-1 * $s-228);
transition: bottom 300ms;
}
.title {
pointer-events: all;
width: fit-content;
top: calc(-1 * $s-56);
text-align: right;
height: $s-56;
position: absolute;
right: calc(-1 * $s-24);
button {
border: none;
cursor: pointer;
height: $s-56;
display: inline-flex;
align-items: center;
border-top-left-radius: $br-10;
border-top-right-radius: $br-10;
margin-right: $s-32;
position: relative;
z-index: 1;
background-color: $db-cuaternary;
span {
display: inline-block;
vertical-align: middle;
line-height: 1.2;
font-size: $fs-16;
margin-left: $s-16;
margin-right: $s-8;
color: $df-primary;
font-weight: $fw400;
&.icon {
margin-left: $s-16;
margin-right: $s-16;
}
}
svg {
width: $s-12;
height: $s-12;
fill: $df-secondary;
}
}
}
.button {
position: absolute;
top: $s-136;
border: $s-2 solid $df-secondary;
border-radius: 50%;
text-align: center;
width: $s-36;
height: $s-36;
cursor: pointer;
background-color: $df-primary;
display: flex;
align-items: center;
justify-content: center;
pointer-events: all;
svg {
width: $s-12;
height: $s-12;
fill: $df-secondary;
}
&.left {
left: 0;
margin-left: $s-44;
}
&.right {
right: 0;
margin-right: $s-44;
}
&:hover {
border: $s-2 solid $da-tertiary;
}
}
.content {
pointer-events: all;
width: 200%;
height: $s-228;
margin-left: $s-6;
position: absolute;
border-top-left-radius: $s-8;
background-color: $db-cuaternary;
.card-container {
width: $s-276;
margin-top: $s-20;
display: inline-block;
text-align: center;
vertical-align: top;
background-color: transparent;
border: none;
padding: 0;
}
.template-card {
display: inline-block;
width: $s-256;
font-size: $fs-16;
cursor: pointer;
color: $df-primary;
padding: $s-3 $s-6 $s-16 $s-6;
border-radius: $br-8;
.img-container {
width: 100%;
height: $s-136;
margin-bottom: $s-16;
border-radius: $br-5;
display: flex;
justify-content: center;
flex-direction: column;
img {
border-radius: $br-4;
}
}
.card-name {
padding: 0 $s-6;
display: flex;
justify-content: space-between;
height: $s-24;
align-items: center;
.icon {
width: $s-16;
height: $s-16;
}
svg {
width: $s-16;
height: $s-16;
fill: $df-secondary;
}
span {
font-weight: $fw500;
font-size: $fs-16;
}
}
.template-link {
border: $s-2 solid transparent;
margin: $s-32;
padding: $s-32 0;
}
.template-link-title {
font-size: $fs-14;
color: $df-primary;
font-weight: $fw400;
}
.template-link-text {
font-size: $fs-12;
margin-top: $s-8;
color: $df-secondary;
}
&:hover {
background-color: $db-tertiary;
}
}
}
}

View File

@ -8,6 +8,7 @@
"A collection of general purpose react hooks." "A collection of general purpose react hooks."
(:require (:require
[app.common.files.focus :as cpf] [app.common.files.focus :as cpf]
[app.common.math :as mth]
[app.main.broadcast :as mbc] [app.main.broadcast :as mbc]
[app.main.data.shortcuts :as dsc] [app.main.data.shortcuts :as dsc]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -16,6 +17,7 @@
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.storage :refer [storage]] [app.util.storage :refer [storage]]
[app.util.timers :as ts] [app.util.timers :as ts]
[app.util.webapi :as wapi]
[beicon.core :as rx] [beicon.core :as rx]
[goog.functions :as f] [goog.functions :as f]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -345,3 +347,38 @@
state)) state))
(defn use-dynamic-grid-item-width
([]
(use-dynamic-grid-item-width nil))
([itemsize]
(let [width (mf/use-state (:items-width @storage))
rowref (mf/use-ref)
itemsize (cond
(some? itemsize) itemsize
(>= @width 1030) 280
:else 230)
ratio (if (some? @width) (/ @width itemsize) 0)
nitems (mth/floor ratio)
limit (min 10 nitems)
limit (max 1 limit)]
(mf/with-effect
(let [node (mf/ref-val rowref)
mnt? (volatile! true)
sub (->> (wapi/observe-resize node)
(rx/observe-on :af)
(rx/subs (fn [entries]
(let [row (first entries)
row-rect (.-contentRect ^js row)
row-width (.-width ^js row-rect)]
(when @mnt?
(reset! width row-width)
(swap! storage assoc :items-width row-width))))))]
(fn []
(vreset! mnt? false)
(rx/dispose! sub))))
[rowref limit])))

View File

@ -170,6 +170,7 @@
(def lock (icon-xref :lock)) (def lock (icon-xref :lock))
(def logo (icon-xref :penpot-logo)) (def logo (icon-xref :penpot-logo))
(def logo-icon (icon-xref :penpot-logo-icon)) (def logo-icon (icon-xref :penpot-logo-icon))
(def logo-error-screen (icon-xref :logo-error-screen))
(def logout (icon-xref :logout)) (def logout (icon-xref :logout))
(def lowercase (icon-xref :lowercase)) (def lowercase (icon-xref :lowercase))
(def mail (icon-xref :mail)) (def mail (icon-xref :mail))

View File

@ -5,10 +5,13 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.settings (ns app.main.ui.settings
(:require-macros [app.main.style :as stl])
(:require (:require
[app.main.data.dashboard.shortcuts :as sc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks]
[app.main.ui.settings.access-tokens :refer [access-tokens-page]] [app.main.ui.settings.access-tokens :refer [access-tokens-page]]
[app.main.ui.settings.change-email] [app.main.ui.settings.change-email]
[app.main.ui.settings.delete-account] [app.main.ui.settings.delete-account]
@ -25,9 +28,16 @@
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[] []
[:header.dashboard-header (let [new-css-system (mf/use-ctx ctx/new-css-system)]
[:div.dashboard-title (if new-css-system
[:h1 {:data-test "account-title"} (tr "dashboard.your-account-title")]]]) [:header {:class (stl/css :dashboard-header)}
[:div {:class (stl/css :dashboard-title)}
[:h1 {:data-test "account-title"} (tr "dashboard.your-account-title")]]]
;; OLD
[:header.dashboard-header
[:div.dashboard-title
[:h1 {:data-test "account-title"} (tr "dashboard.your-account-title")]]])))
(mf/defc settings (mf/defc settings
[{:keys [route] :as props}] [{:keys [route] :as props}]
@ -36,32 +46,59 @@
profile (mf/deref refs/profile) profile (mf/deref refs/profile)
locale (mf/deref i18n/locale)] locale (mf/deref i18n/locale)]
(hooks/use-shortcuts ::dashboard sc/shortcuts)
(mf/use-effect (mf/use-effect
#(when (nil? profile) #(when (nil? profile)
(st/emit! (rt/nav :auth-login)))) (st/emit! (rt/nav :auth-login))))
[:section {:class (dom/classnames :dashboard-layout (not new-css-system) (if new-css-system
:dashboard-layout-refactor new-css-system)} [:section {:class (stl/css :dashboard-layout-refactor :dashboard)}
[:& sidebar {:profile profile [:& sidebar {:profile profile
:locale locale :locale locale
:section section}] :section section}]
[:div.dashboard-content [:div {:class (stl/css :dashboard-content)}
[:& header] [:& header]
[:section.dashboard-container [:section {:class (stl/css :dashboard-container)}
(case section (case section
:settings-profile :settings-profile
[:& profile-page {:locale locale}] [:& profile-page {:locale locale}]
:settings-feedback :settings-feedback
[:& feedback-page] [:& feedback-page]
:settings-password :settings-password
[:& password-page {:locale locale}] [:& password-page {:locale locale}]
:settings-options :settings-options
[:& options-page {:locale locale}] [:& options-page {:locale locale}]
:settings-access-tokens :settings-access-tokens
[:& access-tokens-page])]]])) [:& access-tokens-page])]]]
;; OLD
[:section {:class (dom/classnames :dashboard-layout (not new-css-system)
:dashboard-layout-refactor new-css-system)}
[:& sidebar {:profile profile
:locale locale
:section section}]
[:div.dashboard-content
[:& header]
[:section.dashboard-container
(case section
:settings-profile
[:& profile-page {:locale locale}]
:settings-feedback
[:& feedback-page]
:settings-password
[:& password-page {:locale locale}]
:settings-options
[:& options-page {:locale locale}]
:settings-access-tokens
[:& access-tokens-page])]]])))

View File

@ -0,0 +1,273 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard";
.dashboard {
background-color: $db-primary;
display: grid;
grid-template-rows: $s-48 1fr;
grid-template-columns: $s-40 $s-256 1fr;
height: 100vh;
}
.dashboard-content {
display: flex;
flex-direction: column;
position: relative;
grid-row: 1 / span 2;
padding: $s-16 $s-16 0 0;
}
.dashboard-container {
flex: 1 0 0;
margin-right: $s-16;
overflow-y: auto;
width: 100%;
border-top: $s-1 solid $db-cuaternary;
&.dashboard-projects {
user-select: none;
}
&.dashboard-shared {
width: calc(100vw - $s-320);
margin-right: $s-52;
}
&.search {
margin-top: $s-12;
}
}
.dashboard-settings {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
a {
color: $df-secondary;
}
}
.form-container {
width: $s-800;
margin: $s-48 auto $s-32 $s-120;
display: flex;
max-width: $s-368;
width: 100%;
&.two-columns {
max-width: $s-520;
justify-content: space-between;
flex-direction: row;
}
h2 {
margin-bottom: $s-16;
}
form {
width: $s-468;
.custom-input,
.custom-select {
flex-direction: column-reverse;
label {
position: relative;
text-transform: uppercase;
color: $df-primary;
font-size: $fs-11;
margin-bottom: $s-12;
margin-left: calc(-1 * $s-4);
}
input,
select {
background-color: $db-tertiary;
border-radius: $s-8;
border-color: transparent;
color: $df-primary;
padding: 0 $s-16;
&:focus {
outline: $s-1 solid $da-primary;
}
::placeholder {
color: $df-secondary;
}
}
.help-icon {
bottom: $s-12;
top: auto;
svg {
fill: $df-secondary;
}
}
&.disabled {
input {
background-color: $db-primary;
border-color: $db-cuaternary;
color: $df-secondary;
}
}
.input-container {
background-color: $db-tertiary;
border-radius: $s-8;
border-color: transparent;
margin-top: $s-24;
.main-content {
label {
position: absolute;
top: calc(-1 * $s-24);
}
span {
color: $df-primary;
}
}
&:focus {
border: $s-1 solid $da-primary;
}
}
textarea {
border-radius: $s-8;
padding: $s-12 $s-16;
background-color: $db-tertiary;
color: $df-primary;
border: none;
&:focus {
outline: $s-1 solid $da-primary;
}
}
}
.field-title {
color: $df-primary;
}
.field-title:not(:first-child) {
margin-top: $s-64;
}
.field-text {
color: $df-secondary;
}
button,
.btn-secondary {
width: 100%;
font-size: $fs-11;
text-transform: uppercase;
background-color: $db-tertiary;
color: $df-primary;
&:hover {
color: $da-primary;
background-color: $db-cuaternary;
}
}
hr {
display: none;
}
}
.links {
margin-top: $s-12;
}
}
.profile-form {
display: flex;
flex-direction: column;
max-width: $s-368;
width: 100%;
.newsletter-subs {
border-bottom: $s-1 solid $df-secondary;
border-top: $s-1 solid $df-secondary;
padding: $s-32 0;
margin-bottom: $s-32;
.newsletter-title {
font-family: "worksans", sans-serif;
color: $df-secondary;
font-size: $fs-14;
}
label {
font-family: "worksans", sans-serif;
color: $db-primary;
font-size: $fs-12;
margin-right: calc(-1 * $s-16);
margin-bottom: $s-12;
}
.info {
font-family: "worksans", sans-serif;
color: $df-secondary;
font-size: $fs-12;
margin-bottom: $s-8;
}
.input-checkbox label {
align-items: flex-start;
}
}
}
.avatar-form {
display: flex;
flex-direction: column;
width: $s-120;
min-width: $s-120;
img {
border-radius: 50%;
flex-shrink: 0;
height: $s-120;
margin-right: $s-16;
width: $s-120;
}
.image-change-field {
position: relative;
width: $s-120;
height: $s-120;
.update-overlay {
opacity: 0;
cursor: pointer;
position: absolute;
width: $s-120;
height: $s-120;
border-radius: 50%;
font-size: $fs-24;
color: $df-primary;
line-height: 5;
text-align: center;
background: $da-tertiary;
z-index: 14;
}
input[type="file"] {
width: $s-120;
height: $s-120;
position: absolute;
opacity: 0;
cursor: pointer;
top: 0;
z-index: 15;
}
&:hover {
.update-overlay {
opacity: 0.8;
}
}
}
}
.options-form,
.password-form {
h2 {
font-size: $fs-14;
margin-bottom: $s-20;
}
}

View File

@ -247,20 +247,35 @@
(mf/defc access-tokens-hero (mf/defc access-tokens-hero
[] []
(let [on-click (mf/use-fn #(st/emit! (modal/show :access-token {})))] (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:div.access-tokens-hero-container on-click (mf/use-fn #(st/emit! (modal/show :access-token {})))]
[:div.access-tokens-hero (if new-css-system
[:div.desc [:div {:class (stl/css :access-tokens-hero-container)}
[:h2 (tr "dashboard.access-tokens.personal")] [:div {:class (stl/css :access-tokens-hero)}
[:p (tr "dashboard.access-tokens.personal.description")]] [:div {:class (stl/css :desc)}
[:h2 (tr "dashboard.access-tokens.personal")]
[:p (tr "dashboard.access-tokens.personal.description")]]
[:button.btn-primary [:button
{:on-click on-click} {:class (stl/css :btn-primary)
[:span (tr "dashboard.access-tokens.create")]]]])) :on-click on-click}
[:span (tr "dashboard.access-tokens.create")]]]]
;; OLD
[:div.access-tokens-hero-container
[:div.access-tokens-hero
[:div.desc
[:h2 (tr "dashboard.access-tokens.personal")]
[:p (tr "dashboard.access-tokens.personal.description")]]
[:button.btn-primary
{:on-click on-click}
[:span (tr "dashboard.access-tokens.create")]]]])))
(mf/defc access-token-actions (mf/defc access-token-actions
[{:keys [on-delete]}] [{:keys [on-delete]}]
(let [local (mf/use-state {:menu-open false}) (let [new-css-system (mf/use-ctx ctx/new-css-system)
local (mf/use-state {:menu-open false})
show? (:menu-open @local) show? (:menu-open @local)
options (mf/with-memo [on-delete] options (mf/with-memo [on-delete]
[{:option-name (tr "labels.delete") [{:option-name (tr "labels.delete")
@ -276,30 +291,57 @@
(mf/use-fn (mf/use-fn
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(swap! local assoc :menu-open true)))] (swap! local assoc :menu-open true)))
[:div.icon on-keydown
{:tab-index "0" (mf/use-callback
:ref menu-ref (mf/deps on-menu-click)
:on-click on-menu-click (fn [event]
:on-key-down (fn [event] (when (kbd/enter? event)
(when (kbd/enter? event) (dom/stop-propagation event)
(dom/stop-propagation event) (on-menu-click event))))]
(on-menu-click event)))}
i/actions (if new-css-system
[:& context-menu-a11y [:div
{:on-close on-menu-close {:class (stl/css :icon)
:show show? :tab-index "0"
:fixed? true :ref menu-ref
:min-width? true :on-click on-menu-click
:top "auto" :on-key-down on-keydown}
:left "auto" i/actions
:options options}]])) [:& context-menu-a11y
{:on-close on-menu-close
:show show?
:fixed? true
:min-width? true
:top "auto"
:left "auto"
:options options}]]
;; OLD
[:div.icon
{:tab-index "0"
:ref menu-ref
:on-click on-menu-click
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(on-menu-click event)))}
i/actions
[:& context-menu-a11y
{:on-close on-menu-close
:show show?
:fixed? true
:min-width? true
:top "auto"
:left "auto"
:options options}]])))
(mf/defc access-token-item (mf/defc access-token-item
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [token] :as props}] [{:keys [token] :as props}]
(let [locale (mf/deref i18n/locale) (let [new-css-system (mf/use-ctx ctx/new-css-system)
locale (mf/deref i18n/locale)
expires-at (:expires-at token) expires-at (:expires-at token)
expires-txt (some-> expires-at (dt/format-date-locale {:locale locale})) expires-txt (some-> expires-at (dt/format-date-locale {:locale locale}))
expired? (and (some? expires-at) (> (dt/now) expires-at)) expired? (and (some? expires-at) (> (dt/now) expires-at))
@ -323,36 +365,66 @@
:accept-label (tr "modals.delete-acces-token.accept") :accept-label (tr "modals.delete-acces-token.accept")
:on-accept delete-fn}))))] :on-accept delete-fn}))))]
[:div.table-row (if new-css-system
[:div.table-field.name [:div {:class (stl/css :table-row)}
(str (:name token))] [:div {:class (stl/css :table-field :name)}
[:div.table-field.expiration-date (str (:name token))]
[:span.content {:class (when expired? "expired")}
(cond [:div {:class (stl/css :table-field :expiration-date)}
(nil? expires-at) (tr "dashboard.access-tokens.no-expiration") [:span {:class (stl/css-case :expired expired? :content true)}
expired? (tr "dashboard.access-tokens.expired-on" expires-txt) (cond
:else (tr "dashboard.access-tokens.expires-on" expires-txt))]] (nil? expires-at) (tr "dashboard.access-tokens.no-expiration")
[:div.table-field.actions expired? (tr "dashboard.access-tokens.expired-on" expires-txt)
[:& access-token-actions :else (tr "dashboard.access-tokens.expires-on" expires-txt))]]
{:on-delete on-delete}]]])) [:div {:class (stl/css :table-field :actions)}
[:& access-token-actions
{:on-delete on-delete}]]]
;; OLD
[:div.table-row
[:div.table-field.name
(str (:name token))]
[:div.table-field.expiration-date
[:span.content {:class (when expired? "expired")}
(cond
(nil? expires-at) (tr "dashboard.access-tokens.no-expiration")
expired? (tr "dashboard.access-tokens.expired-on" expires-txt)
:else (tr "dashboard.access-tokens.expires-on" expires-txt))]]
[:div.table-field.actions
[:& access-token-actions
{:on-delete on-delete}]]])))
(mf/defc access-tokens-page (mf/defc access-tokens-page
[] []
(mf/with-effect [] (let [new-css-system (mf/use-ctx ctx/new-css-system)
(dom/set-html-title (tr "title.settings.access-tokens")) tokens (mf/deref tokens-ref)]
(st/emit! (du/fetch-access-tokens))) (mf/with-effect []
(dom/set-html-title (tr "title.settings.access-tokens"))
(st/emit! (du/fetch-access-tokens)))
(let [tokens (mf/deref tokens-ref)] (if new-css-system
[:div.dashboard-access-tokens [:div {:class (stl/css :dashboard-access-tokens)}
[:div [:div
[:& access-tokens-hero] [:& access-tokens-hero]
(if (empty? tokens) (if (empty? tokens)
[:div.access-tokens-empty [:div {:class (stl/css :access-tokens-empty)}
[:div (tr "dashboard.access-tokens.empty.no-access-tokens")] [:div (tr "dashboard.access-tokens.empty.no-access-tokens")]
[:div (tr "dashboard.access-tokens.empty.add-one")]] [:div (tr "dashboard.access-tokens.empty.add-one")]]
[:div.dashboard-table [:div {:class (stl/css :dashboard-table)}
[:div.table-rows [:div {:class (stl/css :table-rows)}
(for [token tokens] (for [token tokens]
[:& access-token-item {:token token :key (:id token)}])]])]])) [:& access-token-item {:token token :key (:id token)}])]])]]
;; OLD
[:div.dashboard-access-tokens
[:div
[:& access-tokens-hero]
(if (empty? tokens)
[:div.access-tokens-empty
[:div (tr "dashboard.access-tokens.empty.no-access-tokens")]
[:div (tr "dashboard.access-tokens.empty.add-one")]]
[:div.dashboard-table
[:div.table-rows
(for [token tokens]
[:& access-token-item {:token token :key (:id token)}])]])]])))

View File

@ -4,7 +4,170 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss"; @use "common/refactor/common-refactor.scss" as *;
.dashboard-table {
display: flex;
flex-direction: column;
font-size: $fs-16;
margin-top: $s-20;
width: $s-800;
.table-header {
color: $df-secondary;
display: grid;
font-size: $fs-12;
grid-template-columns: 43% 1fr $s-108 $s-12;
height: $s-40;
max-width: $s-1000;
padding: 0 $s-16;
text-transform: uppercase;
width: 100%;
}
.table-rows {
color: $db-secondary;
display: flex;
flex-direction: column;
margin-top: $s-16;
max-width: $s-1000;
padding-top: 0;
width: 100%;
}
.table-row {
align-items: center;
background-color: $db-tertiary;
border-radius: $br-8;
color: $df-primary;
display: grid;
font-size: $fs-14;
grid-template-columns: 1fr 43% $s-12;
height: fit-content;
min-height: $s-40;
padding: 0 $s-16;
width: 100%;
&:not(:first-child) {
margin-top: $s-8;
}
}
.table-field {
display: flex;
align-items: center;
.icon {
padding-left: $s-12;
cursor: pointer;
}
&.name {
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
color: $df-primary;
display: -webkit-box;
max-width: $s-480;
overflow: hidden;
text-overflow: ellipsis;
}
&.expiration-date {
color: $df-secondary;
font-size: $fs-14;
.content {
padding: $s-2 $s-6;
&.expired {
background-color: var(--warning-color);
border-radius: $br-4;
color: $db-secondary;
}
}
}
&.access-token-created {
word-break: break-all;
}
&.actions {
position: relative;
}
}
svg {
width: $s-12;
height: $s-12;
fill: $df-primary;
}
}
.dashboard-access-tokens {
display: flex;
flex-direction: column;
margin-left: $s-120;
margin-top: $s-80;
width: $s-800;
}
.access-tokens-hero-container {
background-color: transparent;
display: flex;
flex-direction: column;
max-width: $s-1000;
width: 100%;
}
.access-tokens-hero {
display: flex;
flex-direction: column;
font-size: $fs-14;
gap: $s-32;
justify-content: space-between;
width: $s-468;
.desc {
background-color: transparent;
color: $df-secondary;
width: 100%;
h2 {
color: $df-primary;
font-size: $fs-24;
font-weight: regular;
margin-bottom: $s-32;
}
p {
color: $df-secondary;
margin-bottom: 0;
font-size: $fs-14;
}
}
.btn-primary {
flex-shrink: 0;
}
}
.access-tokens-empty {
align-items: center;
border-radius: $br-8;
border: $s-1 solid $db-cuaternary;
color: $df-secondary;
display: flex;
flex-direction: column;
font-size: $fs-14;
justify-content: center;
margin-top: $s-32;
max-width: $s-1000;
min-height: $s-136;
padding: $s-32;
text-align: center;
width: $s-468;
}
.btn-primary {
@extend .button-primary;
height: $s-32;
}
.modal-overlay { .modal-overlay {
@extend .modal-overlay-base; @extend .modal-overlay-base;

View File

@ -6,6 +6,7 @@
(ns app.main.ui.settings.feedback (ns app.main.ui.settings.feedback
"Feedback form." "Feedback form."
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.spec :as us] [app.common.spec :as us]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
@ -13,6 +14,7 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.context :as ctx]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[beicon.core :as rx] [beicon.core :as rx]
@ -27,7 +29,8 @@
(mf/defc feedback-form (mf/defc feedback-form
[] []
(let [profile (mf/deref refs/profile) (let [new-css-system (mf/use-ctx ctx/new-css-system)
profile (mf/deref refs/profile)
form (fm/use-form :spec ::feedback-form form (fm/use-form :spec ::feedback-form
:validators [(fm/validate-length :subject fm/max-length-allowed (tr "auth.name.too-long")) :validators [(fm/validate-length :subject fm/max-length-allowed (tr "auth.name.too-long"))
(fm/validate-not-empty :subject (tr "auth.name.not-all-space"))]) (fm/validate-not-empty :subject (tr "auth.name.not-all-space"))])
@ -59,49 +62,100 @@
(->> (rp/cmd! :send-user-feedback data) (->> (rp/cmd! :send-user-feedback data)
(rx/subs on-succes on-error)))))] (rx/subs on-succes on-error)))))]
[:& fm/form {:class "feedback-form" (if new-css-system
:on-submit on-submit [:& fm/form {:class (stl/css :feedback-form)
:form form} :on-submit on-submit
:form form}
;; --- Feedback section ;; --- Feedback section
[:h2.field-title (tr "feedback.title")] [:h2 {:class (stl/css :field-title)} (tr "feedback.title")]
[:p.field-text (tr "feedback.subtitle")] [:p {:class (stl/css :field-text)} (tr "feedback.subtitle")]
[:div.fields-row [:div {:class (stl/css :fields-row)}
[:& fm/input {:label (tr "feedback.subject") [:& fm/input {:label (tr "feedback.subject")
:name :subject}]] :name :subject}]]
[:div.fields-row [:div {:class (stl/css :fields-row :description)}
[:& fm/textarea [:& fm/textarea
{:label (tr "feedback.description") {:label (tr "feedback.description")
:name :content :name :content
:rows 5}]] :rows 5}]]
[:> fm/submit-button* [:> fm/submit-button*
{:label (if @loading (tr "labels.sending") (tr "labels.send")) {:label (if @loading (tr "labels.sending") (tr "labels.send"))
:disabled @loading}] :disabled @loading}]
[:hr] [:hr]
[:h2.field-title (tr "feedback.discourse-title")] [:h2 {:class (stl/css :field-title)} (tr "feedback.discourse-title")]
[:p.field-text (tr "feedback.discourse-subtitle1")] [:p {:class (stl/css :field-text)} (tr "feedback.discourse-subtitle1")]
[:a.btn-secondary.btn-large [:a
{:href "https://community.penpot.app" :target "_blank"} {:class (stl/css :btn-secondary :btn-large)
(tr "feedback.discourse-go-to")] :href "https://community.penpot.app"
[:hr] :target "_blank"}
(tr "feedback.discourse-go-to")]
[:hr]
[:h2.field-title (tr "feedback.twitter-title")] [:h2 {:class (stl/css :field-title)} (tr "feedback.twitter-title")]
[:p.field-text (tr "feedback.twitter-subtitle1")] [:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")]
[:a.btn-secondary.btn-large [:a
{:href "https://twitter.com/penpotapp" :target "_blank"} {:class (stl/css :btn-secondary :btn-large)
(tr "feedback.twitter-go-to")]])) :href "https://twitter.com/penpotapp"
:target "_blank"}
(tr "feedback.twitter-go-to")]]
;; OLD
[:& fm/form {:class "feedback-form"
:on-submit on-submit
:form form}
;; --- Feedback section
[:h2.field-title (tr "feedback.title")]
[:p.field-text (tr "feedback.subtitle")]
[:div.fields-row
[:& fm/input {:label (tr "feedback.subject")
:name :subject}]]
[:div.fields-row
[:& fm/textarea
{:label (tr "feedback.description")
:name :content
:rows 5}]]
[:> fm/submit-button*
{:label (if @loading (tr "labels.sending") (tr "labels.send"))
:disabled @loading}]
[:hr]
[:h2.field-title (tr "feedback.discourse-title")]
[:p.field-text (tr "feedback.discourse-subtitle1")]
[:a.btn-secondary.btn-large
{:href "https://community.penpot.app" :target "_blank"}
(tr "feedback.discourse-go-to")]
[:hr]
[:h2.field-title (tr "feedback.twitter-title")]
[:p.field-text (tr "feedback.twitter-subtitle1")]
[:a.btn-secondary.btn-large
{:href "https://twitter.com/penpotapp" :target "_blank"}
(tr "feedback.twitter-go-to")]])))
(mf/defc feedback-page (mf/defc feedback-page
[] []
(mf/use-effect (let [new-css-system (mf/use-ctx ctx/new-css-system)]
#(dom/set-html-title (tr "title.settings.feedback"))) (mf/use-effect
#(dom/set-html-title (tr "title.settings.feedback")))
[:div.dashboard-settings (if new-css-system
[:div.form-container [:div {:class (stl/css :dashboard-settings)}
[:& feedback-form]]]) [:div {:class (stl/css :form-container)}
[:& feedback-form]]]
;; OLD
[:div.dashboard-settings
[:div.form-container
[:& feedback-form]]])))

View File

@ -0,0 +1,25 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor" as *;
@use "./profile";
.feedback-form {
textarea {
border-radius: $br-8;
padding: $br-12;
background-color: $db-tertiary;
color: $df-primary;
border: none;
::placeholder {
color: $db-disabled;
}
&:focus {
outline: $s-1 solid $da-primary;
}
}
}

View File

@ -5,14 +5,15 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.settings.options (ns app.main.ui.settings.options
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.spec :as us] [app.common.spec :as us]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.context :as ctx]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
@ -37,48 +38,78 @@
(mf/defc options-form (mf/defc options-form
{::mf/wrap-props false} {::mf/wrap-props false}
[] []
(let [profile (mf/deref refs/profile) (let [new-css-system (mf/use-ctx ctx/new-css-system)
profile (mf/deref refs/profile)
initial (mf/with-memo [profile] initial (mf/with-memo [profile]
(update profile :lang #(or % ""))) (update profile :lang #(or % "")))
form (fm/use-form :spec ::options-form form (fm/use-form :spec ::options-form
:initial initial) :initial initial)]
new-css-system (features/use-feature "styles/v2")]
[:& fm/form {:class "options-form" (if new-css-system
:on-submit on-submit [:& fm/form {:class (stl/css :options-form)
:form form} :on-submit on-submit
:form form}
[:h2 (tr "labels.language")] [:h3 (tr "labels.language")]
[:div.fields-row [:div {:class (stl/css :fields-row)}
[:& fm/select {:options (into [{:label "Auto (browser)" :value ""}] [:& fm/select {:options (into [{:label "Auto (browser)" :value ""}]
i18n/supported-locales) i18n/supported-locales)
:label (tr "dashboard.select-ui-language") :label (tr "dashboard.select-ui-language")
:default "" :default ""
:name :lang :name :lang
:data-test "setting-lang"}]] :data-test "setting-lang"}]]
(when new-css-system [:h3 (tr "dashboard.theme-change")]
[:h2 (tr "dashboard.theme-change")] [:div {:class (stl/css :fields-row)}
[:div.fields-row
[:& fm/select {:label (tr "dashboard.select-ui-theme") [:& fm/select {:label (tr "dashboard.select-ui-theme")
:name :theme :name :theme
:default "default" :default "default"
:options [{:label "Penpot Dark (default)" :value "default"} :options [{:label "Penpot Dark (default)" :value "default"}
{:label "Penpot Light" :value "light"}] {:label "Penpot Light" :value "light"}]
:data-test "setting-theme"}]]) :data-test "setting-theme"}]]
[:> fm/submit-button*
{:label (tr "dashboard.update-settings") [:> fm/submit-button*
:data-test "submit-lang-change"}]])) {:label (tr "dashboard.update-settings")
:data-test "submit-lang-change"
:class (stl/css :btn-primary)}]]
;; OLD
[:& fm/form {:class "options-form"
:on-submit on-submit
:form form}
[:h2 (tr "labels.language")]
[:div.fields-row
[:& fm/select {:options (into [{:label "Auto (browser)" :value ""}]
i18n/supported-locales)
:label (tr "dashboard.select-ui-language")
:default ""
:name :lang
:data-test "setting-lang"}]]
[:> fm/submit-button*
{:label (tr "dashboard.update-settings")
:data-test "submit-lang-change"}]])))
;; --- Password Page ;; --- Password Page
(mf/defc options-page (mf/defc options-page
[] []
(mf/use-effect (let [new-css-system (mf/use-ctx ctx/new-css-system)]
#(dom/set-html-title (tr "title.settings.options"))) (mf/use-effect
#(dom/set-html-title (tr "title.settings.options")))
(if new-css-system
[:div {:class (stl/css :dashboard-settings)}
[:div {:class (stl/css :form-container) :data-test "settings-form"}
[:h2 (tr "labels.settings")]
[:& options-form {}]]]
;; OLD
[:div.dashboard-settings
[:div.form-container
{:data-test "settings-form"}
[:& options-form {}]]])))
[:div.dashboard-settings
[:div.form-container
{:data-test "settings-form"}
[:& options-form {}]]])

View File

@ -0,0 +1,7 @@
// 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) KALEIDOS INC
@use "./profile" as *;

View File

@ -5,12 +5,14 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.settings.password (ns app.main.ui.settings.password
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.spec :as us] [app.common.spec :as us]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
[app.main.data.users :as udu] [app.main.data.users :as udu]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.context :as ctx]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t tr]] [app.util.i18n :as i18n :refer [t tr]]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
@ -69,47 +71,86 @@
(mf/defc password-form (mf/defc password-form
[{:keys [locale] :as props}] [{:keys [locale] :as props}]
(let [initial (mf/use-memo (constantly {:password-old nil})) (let [new-css-system (mf/use-ctx ctx/new-css-system)
initial (mf/use-memo (constantly {:password-old nil}))
form (fm/use-form :spec ::password-form form (fm/use-form :spec ::password-form
:validators [(fm/validate-not-all-spaces :password-old (tr "auth.password-not-empty")) :validators [(fm/validate-not-all-spaces :password-old (tr "auth.password-not-empty"))
(fm/validate-not-empty :password-1 (tr "auth.password-not-empty")) (fm/validate-not-empty :password-1 (tr "auth.password-not-empty"))
(fm/validate-not-empty :password-2 (tr "auth.password-not-empty")) (fm/validate-not-empty :password-2 (tr "auth.password-not-empty"))
password-equality] password-equality]
:initial initial)] :initial initial)]
[:& fm/form {:class "password-form" (if new-css-system
:on-submit on-submit [:& fm/form {:class (stl/css :password-form)
:form form} :on-submit on-submit
[:h2 (t locale "dashboard.password-change")] :form form}
[:div.fields-row
[:& fm/input
{:type "password"
:name :password-old
:auto-focus? true
:label (t locale "labels.old-password")}]]
[:div.fields-row [:div {:class (stl/css :fields-row)}
[:& fm/input [:& fm/input
{:type "password" {:type "password"
:name :password-1 :name :password-old
:label (t locale "labels.new-password")}]] :auto-focus? true
:label (t locale "labels.old-password")}]]
[:div.fields-row [:div {:class (stl/css :fields-row)}
[:& fm/input [:& fm/input
{:type "password" {:type "password"
:name :password-2 :name :password-1
:label (t locale "labels.confirm-password")}]] :label (t locale "labels.new-password")}]]
[:> fm/submit-button* [:div {:class (stl/css :fields-row)}
{:label (t locale "dashboard.update-settings") [:& fm/input
:data-test "submit-password"}]])) {:type "password"
:name :password-2
:label (t locale "labels.confirm-password")}]]
[:> fm/submit-button*
{:label (t locale "dashboard.update-settings")
:data-test "submit-password"}]]
;; OLD
[:& fm/form {:class "password-form"
:on-submit on-submit
:form form}
[:h2 (t locale "dashboard.password-change")]
[:div.fields-row
[:& fm/input
{:type "password"
:name :password-old
:auto-focus? true
:label (t locale "labels.old-password")}]]
[:div.fields-row
[:& fm/input
{:type "password"
:name :password-1
:label (t locale "labels.new-password")}]]
[:div.fields-row
[:& fm/input
{:type "password"
:name :password-2
:label (t locale "labels.confirm-password")}]]
[:> fm/submit-button*
{:label (t locale "dashboard.update-settings")
:data-test "submit-password"}]])))
;; --- Password Page ;; --- Password Page
(mf/defc password-page (mf/defc password-page
[{:keys [locale]}] [{:keys [locale]}]
(mf/use-effect (let [new-css-system (mf/use-ctx ctx/new-css-system)]
#(dom/set-html-title (tr "title.settings.password"))) (mf/use-effect
#(dom/set-html-title (tr "title.settings.password")))
(if new-css-system
[:section {:class (stl/css :dashboard-settings)}
[:div {:class (stl/css :form-container)}
[:h2 (t locale "dashboard.password-change")]
[:& password-form {:locale locale}]]]
;; old
[:section.dashboard-settings.form-container
[:div.form-container
[:& password-form {:locale locale}]]])))
[:section.dashboard-settings.form-container
[:div.form-container
[:& password-form {:locale locale}]]])

View File

@ -0,0 +1,7 @@
// 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) KALEIDOS INC
@use "./profile" as *;

View File

@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.settings.profile (ns app.main.ui.settings.profile
(:require-macros [app.main.style :as stl])
(:require (:require
[app.common.spec :as us] [app.common.spec :as us]
[app.config :as cf] [app.config :as cf]
@ -15,6 +16,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -41,48 +43,95 @@
(mf/defc profile-form (mf/defc profile-form
[] []
(let [profile (mf/deref refs/profile) (let [new-css-system (mf/use-ctx ctx/new-css-system)
profile (mf/deref refs/profile)
form (fm/use-form :spec ::profile-form form (fm/use-form :spec ::profile-form
:initial profile :initial profile
:validators [(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long")) :validators [(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))
(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))])] (fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))])
[:& fm/form {:on-submit on-submit handle-show-change-email
:form form (mf/use-callback
:class "profile-form"} #(modal/show! :change-email {}))
[:div.fields-row
[:& fm/input
{:type "text"
:name :fullname
:label (tr "dashboard.your-name")}]]
[:div.fields-row handle-show-delete-account
[:& fm/input (mf/use-callback
{:type "email" #(modal/show! :delete-account {}))]
:name :email
:disabled true
:help-icon i/at
:label (tr "dashboard.your-email")}]
[:div.options (if new-css-system
[:div.change-email [:& fm/form {:on-submit on-submit
[:a {:on-click #(modal/show! :change-email {})} :form form
(tr "dashboard.change-email")]]]] :class (stl/css :profile-form)}
[:div {:class (stl/css :fields-row)}
[:& fm/input
{:type "text"
:name :fullname
:label (tr "dashboard.your-name")}]]
[:> fm/submit-button* [:div {:class (stl/css :fields-row)
{:label (tr "dashboard.save-settings") :on-click handle-show-change-email}
:disabled (empty? (:touched @form))}] [:& fm/input
{:type "email"
:name :email
:disabled true
:help-icon i/at
:label (tr "dashboard.your-email")}]
[:div.links [:div {:class (stl/css :options)}
[:div.link-item [:div.change-email
[:a {:on-click #(modal/show! :delete-account {}) [:a {:on-click handle-show-change-email}
:data-test "remove-acount-btn"} (tr "dashboard.change-email")]]]]
(tr "dashboard.remove-account")]]]]))
[:> fm/submit-button*
{:label (tr "dashboard.save-settings")
:disabled (empty? (:touched @form))
:className (stl/css :btn-primary)}]
[:div {:class (stl/css :links)}
[:div {:class (stl/css :link-item)}
[:a {:on-click handle-show-delete-account
:data-test "remove-acount-btn"}
(tr "dashboard.remove-account")]]]]
;; OLD
[:& fm/form {:on-submit on-submit
:form form
:class "profile-form"}
[:div.fields-row
[:& fm/input
{:type "text"
:name :fullname
:label (tr "dashboard.your-name")}]]
[:div.fields-row
[:& fm/input
{:type "email"
:name :email
:disabled true
:help-icon i/at
:label (tr "dashboard.your-email")}]
[:div.options
[:div.change-email
[:a {:on-click #(modal/show! :change-email {})}
(tr "dashboard.change-email")]]]]
[:> fm/submit-button*
{:label (tr "dashboard.save-settings")
:disabled (empty? (:touched @form))}]
[:div.links
[:div.link-item
[:a {:on-click #(modal/show! :delete-account {})
:data-test "remove-acount-btn"}
(tr "dashboard.remove-account")]]]])))
;; --- Profile Photo Form ;; --- Profile Photo Form
(mf/defc profile-photo-form [] (mf/defc profile-photo-form []
(let [file-input (mf/use-ref nil) (let [new-css-system (mf/use-ctx ctx/new-css-system)
file-input (mf/use-ref nil)
profile (mf/deref refs/profile) profile (mf/deref refs/profile)
photo (cf/resolve-profile-photo-url profile) photo (cf/resolve-profile-photo-url profile)
on-image-click #(dom/click (mf/ref-val file-input)) on-image-click #(dom/click (mf/ref-val file-input))
@ -91,23 +140,44 @@
(fn [file] (fn [file]
(st/emit! (du/update-photo file)))] (st/emit! (du/update-photo file)))]
[:form.avatar-form (if new-css-system
[:div.image-change-field [:form {:class (stl/css :avatar-form)}
[:span.update-overlay {:on-click on-image-click} (tr "labels.update")] [:div {:class (stl/css :image-change-field)}
[:img {:src photo}] [:span {:class (stl/css :update-overlay)
[:& file-uploader {:accept "image/jpeg,image/png" :on-click on-image-click} (tr "labels.update")]
:multi false [:img {:src photo}]
:ref file-input [:& file-uploader {:accept "image/jpeg,image/png"
:on-selected on-file-selected :multi false
:data-test "profile-image-input"}]]])) :ref file-input
:on-selected on-file-selected
:data-test "profile-image-input"}]]]
;; OLD
[:form.avatar-form
[:div.image-change-field
[:span.update-overlay {:on-click on-image-click} (tr "labels.update")]
[:img {:src photo}]
[:& file-uploader {:accept "image/jpeg,image/png"
:multi false
:ref file-input
:on-selected on-file-selected
:data-test "profile-image-input"}]]])))
;; --- Profile Page ;; --- Profile Page
(mf/defc profile-page [] (mf/defc profile-page []
(mf/with-effect [] (let [new-css-system (mf/use-ctx ctx/new-css-system)]
(dom/set-html-title (tr "title.settings.profile"))) (mf/with-effect []
[:div.dashboard-settings (dom/set-html-title (tr "title.settings.profile")))
[:div.form-container.two-columns (if new-css-system
[:& profile-photo-form] [:div {:class (stl/css :dashboard-settings)}
[:& profile-form]]]) [:div {:class (stl/css :form-container)}
[:h2 (tr "labels.profile")]
[:& profile-photo-form]
[:& profile-form]]]
;; OLD
[:div.dashboard-settings
[:div.form-container.two-columns
[:& profile-photo-form]
[:& profile-form]]])))

View File

@ -0,0 +1,335 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor" as *;
.dashboard-settings {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
a {
color: $df-secondary;
}
}
.form-container {
display: flex;
justify-content: center;
flex-direction: column;
max-width: $s-368;
margin-bottom: $s-32;
width: $s-580;
margin: $s-80 auto auto $s-120;
justify-content: center;
form {
display: flex;
flex-direction: column;
width: $s-500;
.btn-secondary {
width: 100%;
font-size: $fs-11;
text-transform: uppercase;
background-color: $db-tertiary;
color: $df-primary;
&:hover {
color: $da-primary;
background-color: $db-cuaternary;
}
}
hr {
display: none;
}
}
.fields-row {
margin-bottom: $s-20;
flex-direction: column;
.options {
display: flex;
justify-content: flex-end;
font-size: $fs-14;
margin-top: $s-12;
}
:global(input) {
height: $s-40;
}
}
.field {
margin-bottom: $s-20;
}
.field-title {
color: $df-primary;
&:not(:first-child) {
margin-top: $s-64;
}
}
.field-text {
color: $df-secondary;
}
.custom-input,
.custom-select {
flex-direction: column-reverse;
label {
position: relative;
text-transform: uppercase;
color: $df-primary;
font-size: $fs-11;
margin-bottom: $s-12;
margin-left: calc(-1 * $s-4);
}
input,
select {
background-color: $db-tertiary;
border-radius: $br-8;
border-color: transparent;
color: $df-primary;
padding: 0 $s-16;
&:focus {
outline: $s-1 solid $da-primary;
}
::placeholder {
color: $df-secondary;
}
}
.help-icon {
bottom: $s-12;
top: auto;
svg {
fill: $df-secondary;
}
}
&.disabled {
input {
background-color: $db-primary;
border-color: $db-cuaternary;
color: $df-secondary;
}
}
.input-container {
background-color: $db-tertiary;
border-radius: $br-8;
border-color: transparent;
margin-top: $s-24;
.main-content {
label {
position: absolute;
top: calc(-1 * $s-24);
}
span {
color: $df-primary;
}
}
&:focus {
border: $s-1 solid $da-primary;
}
}
textarea {
border-radius: $br-8;
padding: $s-12 $s-16;
background-color: $db-tertiary;
color: $df-primary;
border: none;
&:focus {
outline: $s-1 solid $da-primary;
}
}
}
&.two-columns {
max-width: $s-520;
justify-content: space-between;
flex-direction: row;
}
h1 {
font-size: $fs-36;
color: $db-tertiary;
margin-bottom: $s-20;
}
.subtitle {
font-size: $fs-24;
color: $db-tertiary;
margin-bottom: $s-20;
}
.notification-icon {
justify-content: center;
display: flex;
margin-bottom: $s-48;
svg {
fill: $db-primary;
height: 40%;
width: 40%;
}
}
.notification-text {
font-size: $fs-16;
color: $db-primary;
margin-bottom: $s-20;
}
.notification-text-email {
background: $df-primary;
border-radius: $br-4;
color: $db-primary;
font-size: $fs-16;
font-weight: $fw500;
margin: $s-24 0 $s-40 0;
padding: $s-16;
text-align: center;
}
h2 {
font-size: $fs-24;
font-weight: $fw400;
color: $df-primary;
display: flex;
align-items: center;
margin: $s-16 0;
}
h3 {
font-size: $fs-12;
font-weight: $fw400;
color: $df-primary;
display: flex;
align-items: center;
margin: $s-8 0;
text-transform: uppercase;
}
a {
&:hover {
text-decoration: underline;
}
}
p {
color: $db-primary;
}
hr {
border-color: $df-secondary;
}
.links {
margin-top: $s-12;
}
}
form.avatar-form {
display: flex;
flex-direction: column;
width: $s-148;
height: $s-148;
margin: $s-16 0;
img {
border-radius: 50%;
flex-shrink: 0;
height: 100%;
margin-right: $s-16;
width: 100%;
}
}
.image-change-field {
position: relative;
width: 100%;
height: 100%;
.update-overlay {
opacity: 0;
cursor: pointer;
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
font-size: $fs-24;
color: $df-primary;
line-height: 6;
text-align: center;
background: $da-tertiary;
z-index: $z-index-modal;
}
input[type="file"] {
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
cursor: pointer;
top: 0;
z-index: $z-index-modal;
}
&:hover {
.update-overlay {
opacity: 0.8;
}
}
}
.profile-form {
display: flex;
flex-direction: column;
max-width: $s-368;
width: 100%;
}
.newsletter-subs {
border-bottom: $s-1 solid $df-secondary;
border-top: $s-1 solid $df-secondary;
padding: $s-32 0;
margin-bottom: $s-32;
.newsletter-title {
font-family: "worksans", sans-serif;
color: $df-secondary;
font-size: $fs-14;
}
label {
font-family: "worksans", sans-serif;
color: $db-primary;
font-size: $fs-12;
margin-right: calc(-1 * $s-16);
margin-bottom: $s-12;
}
.info {
color: $df-secondary;
font-size: $fs-12;
margin-bottom: $s-8;
}
.input-checkbox label {
align-items: flex-start;
}
}
.btn-secondary {
@extend .button-secondary;
height: $s-32;
}
.btn-primary {
@extend .button-primary;
height: $s-32;
}

View File

@ -5,12 +5,14 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.settings.sidebar (ns app.main.ui.settings.sidebar
(:require-macros [app.main.style :as stl])
(:require (:require
[app.config :as cf] [app.config :as cf]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.sidebar :refer [profile-section]] [app.main.ui.dashboard.sidebar :refer [profile-section]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -21,7 +23,8 @@
(mf/defc sidebar-content (mf/defc sidebar-content
[{:keys [profile section] :as props}] [{:keys [profile section] :as props}]
(let [profile? (= section :settings-profile) (let [new-css-system (mf/use-ctx ctx/new-css-system)
profile? (= section :settings-profile)
password? (= section :settings-password) password? (= section :settings-password)
options? (= section :settings-options) options? (= section :settings-options)
feedback? (= section :settings-feedback) feedback? (= section :settings-feedback)
@ -66,55 +69,107 @@
(st/emit! (modal/show {:type :onboarding})) (st/emit! (modal/show {:type :onboarding}))
(st/emit! (modal/show {:type :release-notes :version version}))))))] (st/emit! (modal/show {:type :release-notes :version version}))))))]
[:div.sidebar-content (if new-css-system
[:div.sidebar-content-section [:div {:class (stl/css :sidebar-content)}
[:div.back-to-dashboard {:on-click go-dashboard} [:div {:class (stl/css :sidebar-content-section)}
[:span.icon i/arrow-down] [:div {:class (stl/css :back-to-dashboard)
[:span.text (tr "labels.dashboard")]]] :on-click go-dashboard}
[:hr] [:span {:class (stl/css :icon)} i/arrow-down]
[:span {:class (stl/css :text)} (tr "labels.dashboard")]]]
[:div.sidebar-content-section
[:ul.sidebar-nav.no-overflow
[:li {:class (when profile? "current")
:on-click go-settings-profile}
i/user
[:span.element-title (tr "labels.profile")]]
[:li {:class (when password? "current")
:on-click go-settings-password}
i/lock
[:span.element-title (tr "labels.password")]]
[:li {:class (when options? "current")
:on-click go-settings-options
:data-test "settings-profile"}
i/tree
[:span.element-title (tr "labels.settings")]]
(when (contains? cf/flags :access-tokens)
[:li {:class (when access-tokens? "current")
:on-click go-settings-access-tokens
:data-test "settings-access-tokens"}
i/icon-key
[:span.element-title (tr "labels.access-tokens")]])
[:hr] [:hr]
[:li {:on-click show-release-notes :data-test "release-notes"} [:div {:class (stl/css :sidebar-content-section)}
i/pencil [:ul {:class (stl/css :sidebar-nav :no-overflow)}
[:span.element-title (tr "labels.release-notes")]] [:li {:class (when profile? (stl/css :current))
:on-click go-settings-profile}
[:span {:class (stl/css :element-title)} (tr "labels.profile")]]
(when (contains? cf/flags :user-feedback) [:li {:class (when password? (stl/css :current))
[:li {:class (when feedback? "current") :on-click go-settings-password}
:on-click go-settings-feedback} [:span {:class (stl/css :element-title)} (tr "labels.password")]]
i/msg-info
[:span.element-title (tr "labels.give-feedback")]])]]])) [:li {:class (when options? (stl/css :current))
:on-click go-settings-options
:data-test "settings-profile"}
[:span {:class (stl/css :element-title)} (tr "labels.settings")]]
(when (contains? cf/flags :access-tokens)
[:li {:class (when access-tokens? (stl/css :current))
:on-click go-settings-access-tokens
:data-test "settings-access-tokens"}
[:span {:class (stl/css :element-title)} (tr "labels.access-tokens")]])
[:hr]
[:li {:on-click show-release-notes :data-test "release-notes"}
[:span {:class (stl/css :element-title)} (tr "labels.release-notes")]]
(when (contains? cf/flags :user-feedback)
[:li {:class (when feedback? (stl/css :current))
:on-click go-settings-feedback}
i/msg-info
[:span {:class (stl/css :element-title)} (tr "labels.give-feedback")]])]]]
;; OLD
[:div.sidebar-content
[:div.sidebar-content-section
[:div.back-to-dashboard {:on-click go-dashboard}
[:span.icon i/arrow-down]
[:span.text (tr "labels.dashboard")]]]
[:hr]
[:div.sidebar-content-section
[:ul.sidebar-nav.no-overflow
[:li {:class (when profile? "current")
:on-click go-settings-profile}
i/user
[:span.element-title (tr "labels.profile")]]
[:li {:class (when password? "current")
:on-click go-settings-password}
i/lock
[:span.element-title (tr "labels.password")]]
[:li {:class (when options? "current")
:on-click go-settings-options
:data-test "settings-profile"}
i/tree
[:span.element-title (tr "labels.settings")]]
(when (contains? cf/flags :access-tokens)
[:li {:class (when access-tokens? "current")
:on-click go-settings-access-tokens
:data-test "settings-access-tokens"}
i/icon-key
[:span.element-title (tr "labels.access-tokens")]])
[:hr]
[:li {:on-click show-release-notes :data-test "release-notes"}
i/pencil
[:span.element-title (tr "labels.release-notes")]]
(when (contains? cf/flags :user-feedback)
[:li {:class (when feedback? "current")
:on-click go-settings-feedback}
i/msg-info
[:span.element-title (tr "labels.give-feedback")]])]]])))
(mf/defc sidebar (mf/defc sidebar
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [profile locale section]}] [{:keys [profile locale section]}]
[:div.dashboard-sidebar.settings (let [new-css-system (mf/use-ctx ctx/new-css-system)]
[:& sidebar-content {:profile profile (if new-css-system
:section section}] [:div {:class (stl/css :dashboard-sidebar :settings)}
[:& profile-section {:profile profile [:& sidebar-content {:profile profile
:locale locale}]]) :section section}]
[:& profile-section {:profile profile
:locale locale}]]
;; OLD
[:div.dashboard-sidebar.settings
[:& sidebar-content {:profile profile
:section section}]
[:& profile-section {:profile profile
:locale locale}]])))

View File

@ -0,0 +1,159 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
.dashboard-sidebar {
background-color: $db-primary;
border-right: $s-1 solid $db-cuaternary;
display: flex;
flex-direction: column;
grid-column: 1 / span 2;
grid-row: 1 / span 2;
height: 100%;
margin: 0 $s-16 0 0;
padding: $s-16 0 0 0;
z-index: $z-index-1;
}
.sidebar-content {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 0;
hr {
border-color: transparent;
margin: $s-12 $s-16;
}
}
.sidebar-nav {
display: flex;
flex-direction: column;
margin: 0;
overflow-y: auto;
user-select: none;
&.no-overflow {
overflow: unset;
}
li {
align-items: center;
cursor: pointer;
display: flex;
flex-shrink: 0;
padding: $s-8 $s-8 $s-8 $s-24;
a {
font-weight: $fw400;
width: 100%;
&:hover {
text-decoration: none;
}
}
svg {
fill: $db-secondary;
margin-right: $s-8;
height: $s-12;
width: $s-12;
}
span {
color: $df-secondary;
font-size: $fs-14;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&::before {
display: none;
background-color: transparent;
border-radius: $br-4;
content: "";
height: $s-24;
margin-right: $s-8;
width: $s-4;
}
&.recent-projects {
svg {
fill: $df-primary;
}
}
& .edit-wrapper {
border: $s-1 solid $df-primary;
border-radius: $br-4;
display: flex;
width: 100%;
}
input.element-title {
border: 0;
height: $s-32;
padding: $s-6;
margin: 0;
width: 100%;
background-color: $df-primary;
}
.element-subtitle {
color: $df-secondary;
font-style: italic;
}
&:hover {
background-color: $db-cuaternary;
&::before {
background-color: $df-primary;
}
}
&.current {
background-color: $db-cuaternary;
a {
font-weight: $fw400;
color: $da-primary;
}
span {
color: $da-primary;
}
&::before {
background-color: $da-tertiary;
}
}
}
}
.back-to-dashboard {
padding: $s-12 $s-16;
font-size: $fs-14;
cursor: pointer;
display: flex;
.icon {
display: flex;
align-items: center;
margin-right: $s-12;
}
.text {
color: $df-primary;
}
svg {
fill: $df-secondary;
transform: rotate(90deg);
width: $s-12;
height: $s-12;
}
}

View File

@ -5,8 +5,11 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.main.ui.static (ns app.main.ui.static
(:require-macros [app.main.style :as stl])
(:require (:require
[app.main.features :as features]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.globals :as globals] [app.util.globals :as globals]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
@ -17,66 +20,112 @@
(mf/defc static-header (mf/defc static-header
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [children (obj/get props "children") (let [new-css-system (mf/use-ctx ctx/new-css-system)
children (obj/get props "children")
on-click (mf/use-callback #(set! (.-href globals/location) "/"))] on-click (mf/use-callback #(set! (.-href globals/location) "/"))]
[:section.exception-layout (if new-css-system
[:div.exception-header [:section {:class (stl/css :exception-layout)}
{:on-click on-click} [:div
i/logo] {:class (stl/css :exception-header)
[:div.exception-content :on-click on-click}
[:div.container children]]])) i/logo-icon]
[:div {:class (stl/css :deco-before)} i/logo-error-screen]
[:div {:class (stl/css :exception-content)}
[:div {:class (stl/css :container)} children]]
[:div {:class (stl/css :deco-after)} i/logo-error-screen]]
[:section.exception-layout
[:div.exception-header
{:on-click on-click}
i/logo]
[:div.exception-content
[:div.container children]]])))
(mf/defc not-found (mf/defc not-found
[] []
[:> static-header {} (let [new-css-system (mf/use-ctx ctx/new-css-system)]
[:div.image i/icon-empty] (if new-css-system
[:div.main-message (tr "labels.not-found.main-message")] [:> static-header {}
[:div.desc-message (tr "labels.not-found.desc-message")]]) [:div {:class (stl/css :main-message)} (tr "labels.not-found.main-message")]
[:div {:class (stl/css :desc-message)} (tr "labels.not-found.desc-message")]]
[:> static-header {}
[:div.image i/icon-empty]
[:div.main-message (tr "labels.not-found.main-message")]
[:div.desc-message (tr "labels.not-found.desc-message")]])))
(mf/defc bad-gateway (mf/defc bad-gateway
[] []
[:> static-header {} (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:div.image i/icon-empty] handle-retry
[:div.main-message (tr "labels.bad-gateway.main-message")] (mf/use-callback
[:div.desc-message (tr "labels.bad-gateway.desc-message")] (fn [] (st/emit! (rt/assign-exception nil))))]
[:div.sign-info (if new-css-system
[:a.btn-primary.btn-small [:> static-header {}
{:on-click (fn [] (st/emit! #(dissoc % :exception)))} [:div {:class (stl/css :main-message)} (tr "labels.bad-gateway.main-message")]
(tr "labels.retry")]]]) [:div {:class (stl/css :desc-message)} (tr "labels.bad-gateway.desc-message")]
[:div {:class (stl/css :sign-info)}
[:button {:on-click handle-retry} (tr "labels.retry")]]]
[:> static-header {}
[:div.image i/icon-empty]
[:div.main-message (tr "labels.bad-gateway.main-message")]
[:div.desc-message (tr "labels.bad-gateway.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small {:on-click handle-retry} (tr "labels.retry")]]])))
(mf/defc service-unavailable (mf/defc service-unavailable
[] []
[:> static-header {} (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:div.image i/icon-empty] handle-retry
[:div.main-message (tr "labels.service-unavailable.main-message")] (mf/use-callback
[:div.desc-message (tr "labels.service-unavailable.desc-message")] (fn [] (st/emit! (rt/assign-exception nil))))]
[:div.sign-info (if new-css-system
[:a.btn-primary.btn-small [:> static-header {}
{:on-click (fn [] (st/emit! #(dissoc % :exception)))} [:div {:class (stl/css :main-message)} (tr "labels.service-unavailable.main-message")]
(tr "labels.retry")]]]) [:div {:class (stl/css :desc-message)} (tr "labels.service-unavailable.desc-message")]
[:div {:class (stl/css :sign-info)}
[:button {:on-click handle-retry} (tr "labels.retry")]]]
[:> static-header {}
[:div.main-message (tr "labels.service-unavailable.main-message")]
[:div.desc-message (tr "labels.service-unavailable.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small {:on-click handle-retry} (tr "labels.retry")]]])))
(mf/defc internal-error (mf/defc internal-error
[] []
[:> static-header {} (let [new-css-system (mf/use-ctx ctx/new-css-system)
[:div.image i/icon-empty] handle-retry
[:div.main-message (tr "labels.internal-error.main-message")] (mf/use-callback
[:div.desc-message (tr "labels.internal-error.desc-message")] (fn [] (st/emit! (rt/assign-exception nil))))]
[:div.sign-info (if new-css-system
[:a.btn-primary.btn-small [:> static-header {}
{:on-click (fn [] (st/emit! (rt/assign-exception nil)))} [:div {:class (stl/css :main-message)} (tr "labels.internal-error.main-message")]
(tr "labels.retry")]]]) [:div {:class (stl/css :desc-message)} (tr "labels.internal-error.desc-message")]
[:div {:class (stl/css :sign-info)}
[:button {:on-click handle-retry} (tr "labels.retry")]]]
[:> static-header {}
[:div.image i/icon-empty]
[:div.main-message (tr "labels.internal-error.main-message")]
[:div.desc-message (tr "labels.internal-error.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small {:on-click handle-retry} (tr "labels.retry")]]])))
(mf/defc exception-page (mf/defc exception-page
[{:keys [data] :as props}] [{:keys [data] :as props}]
(case (:type data) (let [new-css-system (features/use-feature "styles/v2")]
:not-found [:& (mf/provider ctx/new-css-system) {:value new-css-system}
[:& not-found] (case (:type data)
:not-found
[:& not-found]
:bad-gateway :bad-gateway
[:& bad-gateway] [:& bad-gateway]
:service-unavailable :service-unavailable
[:& service-unavailable] [:& service-unavailable]
[:& internal-error]))
[:& internal-error])]))

View File

@ -0,0 +1,91 @@
// 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) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as *;
.exception-layout {
width: 100%;
height: 100%;
background-color: $db-secondary;
}
.deco-before,
.deco-after {
position: absolute;
left: calc(50% - $s-40);
svg {
position: absolute;
fill: $df-secondary;
height: 1537px;
width: $s-80;
}
}
.deco-before {
height: 34vh;
top: 0;
svg {
bottom: 0;
}
}
.deco-after {
height: 34vh;
bottom: 0;
svg {
top: 0;
}
}
.exception-header {
padding: $s-24 $s-32;
position: fixed;
svg {
fill: $df-primary;
width: $s-48;
height: auto;
}
}
.exception-content {
display: flex;
height: 100%;
justify-content: center;
width: 100%;
.container {
align-items: center;
display: flex;
flex-direction: column;
gap: $s-16;
height: 34vh;
justify-content: center;
margin-top: 33vh;
text-align: center;
width: $s-640;
}
}
.main-message {
@include bigTitleTipography;
color: $df-primary;
}
.desc-message {
@include bigTitleTipography;
color: $df-secondary;
}
.sign-info {
text-align: center;
button {
@extend .button-primary;
text-transform: uppercase;
padding: $s-8 $s-16;
font-size: $fs-11;
}
}

View File

@ -3249,6 +3249,11 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0" clean-stack "^2.0.0"
indent-string "^4.0.0" indent-string "^4.0.0"
animate.css@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075"
integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==
ansi-colors@^1.0.1: ansi-colors@^1.0.1:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9"