mirror of
https://github.com/penpot/penpot.git
synced 2025-02-07 06:48:19 -05:00
🎉 Add themes infraestructure.
This commit is contained in:
parent
cd61269cd5
commit
ea3e17f7fe
20 changed files with 207 additions and 33 deletions
|
@ -11,6 +11,7 @@ CREATE TABLE profile (
|
|||
password text NOT NULL,
|
||||
|
||||
lang text NULL,
|
||||
theme text NULL,
|
||||
is_demo boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
(s/def ::profile-id ::us/uuid)
|
||||
(s/def ::password ::us/string)
|
||||
(s/def ::old-password ::us/string)
|
||||
(s/def ::theme ::us/string)
|
||||
|
||||
|
||||
;; --- Mutation: Login
|
||||
|
@ -96,20 +97,21 @@
|
|||
(def ^:private sql:update-profile
|
||||
"update profile
|
||||
set fullname = $2,
|
||||
lang = $3
|
||||
lang = $3,
|
||||
theme = $4
|
||||
where id = $1
|
||||
and deleted_at is null
|
||||
returning *")
|
||||
|
||||
(defn- update-profile
|
||||
[conn {:keys [id fullname lang] :as params}]
|
||||
(let [sqlv [sql:update-profile id fullname lang]]
|
||||
[conn {:keys [id fullname lang theme] :as params}]
|
||||
(let [sqlv [sql:update-profile id fullname lang theme]]
|
||||
(-> (db/query-one conn sqlv)
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' profile/strip-private-attrs))))
|
||||
|
||||
(s/def ::update-profile
|
||||
(s/keys :req-un [::id ::fullname ::lang]))
|
||||
(s/keys :req-un [::id ::fullname ::lang ::theme]))
|
||||
|
||||
(sm/defmutation ::update-profile
|
||||
[params]
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
(s/def ::path ::us/string)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::profile-id ::us/uuid)
|
||||
(s/def ::theme ::us/string)
|
||||
|
||||
;; --- Query: Profile (own)
|
||||
|
||||
|
@ -93,4 +94,4 @@
|
|||
(defn strip-private-attrs
|
||||
"Only selects a publicy visible profile attrs."
|
||||
[profile]
|
||||
(select-keys profile [:id :fullname :lang :email :created-at :photo]))
|
||||
(select-keys profile [:id :fullname :lang :email :created-at :photo :theme]))
|
||||
|
|
|
@ -87,7 +87,9 @@
|
|||
[^Row row]
|
||||
(reduce (fn [acc index]
|
||||
(let [cname (.getColumnName row index)]
|
||||
(assoc acc cname (.getValue row ^int index))))
|
||||
(if-some [value (.getValue row ^int index)]
|
||||
(assoc acc cname value)
|
||||
acc)))
|
||||
{}
|
||||
(range (.size row))))
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@
|
|||
out (th/try-on! (sm/handle event))]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (= (:id profile) (get-in out [:result :id])))))
|
||||
))
|
||||
(t/is (= (:id profile) (get-in out [:result :id])))))))
|
||||
|
||||
|
||||
(t/deftest profile-query-and-manipulation
|
||||
(let [profile @(th/create-profile db/pool 1)]
|
||||
|
@ -75,7 +75,8 @@
|
|||
::sm/type :update-profile
|
||||
:fullname "Full Name"
|
||||
:name "profile222"
|
||||
:lang "en")
|
||||
:lang "en"
|
||||
:theme "dark")
|
||||
out (th/try-on! (sm/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -84,6 +85,7 @@
|
|||
(let [result (:result out)]
|
||||
(t/is (= (:fullname data) (:fullname result)))
|
||||
(t/is (= (:email data) (:email result)))
|
||||
(t/is (= (:theme data) (:theme result)))
|
||||
(t/is (not (contains? result :password))))))
|
||||
|
||||
(t/testing "update photo"
|
||||
|
@ -99,8 +101,8 @@
|
|||
(t/is (nil? (:error out)))
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (= (:id profile) (:id result))))))
|
||||
))
|
||||
(t/is (= (:id profile) (:id result))))))))
|
||||
|
||||
|
||||
(t/deftest profile-deletion
|
||||
(let [prof @(th/create-profile db/pool 1)
|
||||
|
@ -189,8 +191,8 @@
|
|||
out (th/try-on! (sq/handle data))]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (= 0 (count (:result out))))))
|
||||
))
|
||||
(t/is (= 0 (count (:result out))))))))
|
||||
|
||||
|
||||
(t/deftest registration-domain-whitelist
|
||||
(let [whitelist "gmail.com, hey.com, ya.ru"]
|
||||
|
|
|
@ -39,6 +39,8 @@ services:
|
|||
- UXBOX_DATABASE_URI=postgresql://postgres/uxbox
|
||||
- UXBOX_DATABASE_USERNAME=uxbox
|
||||
- UXBOX_DATABASE_PASSWORD=uxbox
|
||||
- UXBOX_THEME=light
|
||||
#- UXBOX_THEME=dark
|
||||
|
||||
smtp:
|
||||
container_name: 'uxbox-devenv-smtp'
|
||||
|
|
|
@ -24,6 +24,6 @@ tmux send-keys -t uxbox './bin/start-dev' enter
|
|||
tmux rename-window -t uxbox:0 'gulp'
|
||||
tmux select-window -t uxbox:0
|
||||
tmux send-keys -t uxbox 'cd uxbox/frontend' enter C-l
|
||||
tmux send-keys -t uxbox 'npx gulp watch' enter
|
||||
tmux send-keys -t uxbox 'npx gulp --theme=${UXBOX_THEME} watch' enter
|
||||
|
||||
tmux -2 attach-session -t uxbox
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
**TODO**
|
||||
|
||||
## Frontend configuration parameters ##
|
||||
|
||||
**Only available at build time!**
|
||||
|
||||
- `-e UXBOX_PUBLIC_URL=...` (defaults to `http://localhost:6060`)
|
||||
- `-e UXBOX_DEMO_WARNING=...` (defaults to `true`)
|
||||
- `-e UXBOX_THEME=...` (defaults to `light`, accepts `dark` to enable UXBOX dark theme)
|
||||
|
||||
## Backend configuration parameters ##
|
||||
|
||||
|
|
|
@ -123,14 +123,18 @@ function templatePipeline(options) {
|
|||
const input = options.input;
|
||||
const output = options.output;
|
||||
const ts = Math.floor(new Date());
|
||||
const th = process.env.UXBOX_THEME || 'light';
|
||||
const themes = ['light', 'dark'];
|
||||
|
||||
const locales = readLocales();
|
||||
const config = readConfig();
|
||||
|
||||
const tmpl = mustache({
|
||||
ts: ts,
|
||||
th: th,
|
||||
config: JSON.stringify(config),
|
||||
translations: JSON.stringify(locales),
|
||||
themes: JSON.stringify(themes),
|
||||
});
|
||||
|
||||
return gulp.src(input)
|
||||
|
@ -144,12 +148,17 @@ function templatePipeline(options) {
|
|||
* Generic
|
||||
***********************************************/
|
||||
|
||||
gulp.task("scss:main", scssPipeline({
|
||||
input: paths.resources + "styles/main.scss",
|
||||
output: paths.output + "css/main.css"
|
||||
gulp.task("scss:main-light", scssPipeline({
|
||||
input: paths.resources + "styles/main-light.scss",
|
||||
output: paths.output + "css/main-light.css"
|
||||
}));
|
||||
|
||||
gulp.task("scss", gulp.parallel("scss:main"));
|
||||
gulp.task("scss:main-dark", scssPipeline({
|
||||
input: paths.resources + "styles/main-dark.scss",
|
||||
output: paths.output + "css/main-dark.css"
|
||||
}));
|
||||
|
||||
gulp.task("scss", gulp.parallel("scss:main-light", "scss:main-dark"));
|
||||
|
||||
gulp.task("svg:sprite", function() {
|
||||
return gulp.src(paths.resources + "images/icons/*.svg")
|
||||
|
|
|
@ -634,6 +634,13 @@
|
|||
"fr" : "Langue par défaut"
|
||||
}
|
||||
},
|
||||
"settings.profile.section-theme-data" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:102" ],
|
||||
"translations" : {
|
||||
"en" : "Default theme",
|
||||
"fr" : "Thème par défaut"
|
||||
}
|
||||
},
|
||||
"settings.profile.profile-saved" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:47" ],
|
||||
"translations" : {
|
||||
|
|
68
frontend/resources/styles/main-dark.scss
Normal file
68
frontend/resources/styles/main-dark.scss
Normal file
|
@ -0,0 +1,68 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
// Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
// UXBOX MAIN STYLES
|
||||
//#################################################
|
||||
//
|
||||
//#################################################
|
||||
|
||||
@import 'common/dependencies/colors';
|
||||
//@import 'common/dependencies/uxbox-light';
|
||||
@import 'common/dependencies/uxbox-dark';
|
||||
@import 'common/dependencies/helpers';
|
||||
@import 'common/dependencies/mixin';
|
||||
@import 'common/dependencies/fonts';
|
||||
@import 'common/dependencies/reset';
|
||||
@import 'common/dependencies/animations';
|
||||
@import 'common/dependencies/z-index';
|
||||
|
||||
//#################################################
|
||||
// Layouts
|
||||
//#################################################
|
||||
|
||||
@import 'common/base';
|
||||
@import 'main/layouts/main-layout';
|
||||
@import 'main/layouts/login';
|
||||
|
||||
//#################################################
|
||||
// Commons
|
||||
//#################################################
|
||||
|
||||
@import 'common/framework';
|
||||
|
||||
//#################################################
|
||||
// Partials
|
||||
//#################################################
|
||||
|
||||
@import 'main/partials/main-bar';
|
||||
@import 'main/partials/workspace-bar';
|
||||
@import 'main/partials/workspace';
|
||||
@import 'main/partials/tool-bar';
|
||||
@import 'main/partials/project-bar';
|
||||
@import 'main/partials/sidebar';
|
||||
@import 'main/partials/sidebar-tools';
|
||||
@import 'main/partials/sidebar-element-options';
|
||||
@import 'main/partials/sidebar-icons';
|
||||
@import 'main/partials/sidebar-layers';
|
||||
@import 'main/partials/sidebar-sitemap';
|
||||
@import 'main/partials/sidebar-document-history';
|
||||
@import 'main/partials/dashboard-bar';
|
||||
@import 'main/partials/dashboard-grid';
|
||||
@import 'main/partials/user-settings';
|
||||
@import 'main/partials/activity-bar';
|
||||
@import 'main/partials/library-bar';
|
||||
@import 'main/partials/lightbox';
|
||||
@import 'main/partials/color-palette';
|
||||
@import 'main/partials/colorpicker';
|
||||
@import 'main/partials/forms';
|
||||
@import 'main/partials/loader';
|
||||
|
||||
//#################################################
|
||||
// Resources
|
||||
//#################################################
|
||||
|
||||
@import 'collection/font-collection';
|
|
@ -11,7 +11,7 @@
|
|||
//#################################################
|
||||
|
||||
@import 'common/dependencies/colors';
|
||||
//@import 'common/dependencies/uxbox-light';
|
||||
@import 'common/dependencies/uxbox-light';
|
||||
//@import 'common/dependencies/uxbox-dark';
|
||||
@import 'common/dependencies/helpers';
|
||||
@import 'common/dependencies/mixin';
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>UXBOX - The Open-Source prototyping tool</title>
|
||||
<link href="/css/main.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link id="theme" href="/css/main-{{& th}}.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="/images/favicon.png" />
|
||||
<!-- <link rel="preload" as="image" type="image/svg+xml" -->
|
||||
<!-- href="/images/svg-sprite/symbol/svg/sprite.symbol.svg" /> -->
|
||||
|
@ -16,6 +16,7 @@
|
|||
<script>
|
||||
window.uxboxConfig = JSON.parse({{& config }});
|
||||
window.uxboxTranslations = JSON.parse({{& translations }});
|
||||
window.uxboxThemes = {{& themes }};
|
||||
</script>
|
||||
<script src="/js/main.js?ts={{& ts}}"></script>
|
||||
<script>uxbox.main.init()</script>
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UXBOX View</title>
|
||||
<link href="/css/view.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link href="/css/view-{{& th}}.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="/images/favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -20,4 +20,5 @@
|
|||
|
||||
(def default-language "en")
|
||||
(def demo-warning (gobj/get config "demoWarning" true))
|
||||
(def url public-url))
|
||||
(def url public-url)
|
||||
(def default-theme "light"))
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.html.history :as html-history]
|
||||
[uxbox.util.i18n :as i18n]
|
||||
[uxbox.util.theme :as theme]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.storage :refer [storage]]
|
||||
[uxbox.util.timers :as ts]))
|
||||
|
@ -65,8 +66,10 @@
|
|||
|
||||
(defn ^:export init
|
||||
[]
|
||||
(let [translations (gobj/get goog.global "uxboxTranslations")]
|
||||
(let [translations (gobj/get goog.global "uxboxTranslations")
|
||||
themes (gobj/get goog.global "uxboxThemes")]
|
||||
(i18n/init! translations)
|
||||
(theme/init! themes)
|
||||
(unchecked-set js/window app-sym "main")
|
||||
(st/init)
|
||||
(init-ui)))
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||
[uxbox.util.storage :refer [storage]]))
|
||||
[uxbox.util.storage :refer [storage]]
|
||||
[uxbox.util.theme :as theme]))
|
||||
|
||||
;; --- Common Specs
|
||||
|
||||
|
@ -21,13 +23,13 @@
|
|||
(s/def ::fullname ::us/string)
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::password ::us/string)
|
||||
(s/def ::language ::us/string)
|
||||
(s/def ::lang ::us/string)
|
||||
(s/def ::theme ::us/string)
|
||||
(s/def ::photo ::us/string)
|
||||
(s/def ::created-at ::us/inst)
|
||||
(s/def ::password-1 ::us/string)
|
||||
(s/def ::password-2 ::us/string)
|
||||
(s/def ::password-old ::us/string)
|
||||
(s/def ::lang (s/nilable ::us/string))
|
||||
|
||||
(s/def ::profile
|
||||
(s/keys :req-un [::id]
|
||||
|
@ -35,7 +37,8 @@
|
|||
::fullname
|
||||
::photo
|
||||
::email
|
||||
::lang]))
|
||||
::lang
|
||||
::theme]))
|
||||
|
||||
;; --- Profile Fetched
|
||||
|
||||
|
@ -45,13 +48,16 @@
|
|||
(ptk/reify ::profile-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :profile data))
|
||||
(assoc state :profile (cond-> data
|
||||
(nil? (:land data)) (assoc :lang cfg/default-language)
|
||||
(nil? (:theme data)) (assoc :theme cfg/default-theme))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(swap! storage assoc :profile data)
|
||||
(when-let [lang (:lang data)]
|
||||
(i18n/set-current-locale! lang)))))
|
||||
(let [profile (:profile state)]
|
||||
(swap! storage assoc :profile profile)
|
||||
(i18n/set-current-locale! (:lang profile))
|
||||
(theme/set-current-theme! (:theme profile))))))
|
||||
|
||||
;; --- Fetch Profile
|
||||
|
||||
|
|
|
@ -22,10 +22,11 @@
|
|||
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::lang (s/nilable ::fm/not-empty-string))
|
||||
(s/def ::theme ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::profile-form
|
||||
(s/keys :req-un [::fullname ::lang ::email]))
|
||||
(s/keys :req-un [::fullname ::lang ::theme ::email]))
|
||||
|
||||
(defn- on-error
|
||||
[error form]
|
||||
|
@ -86,7 +87,7 @@
|
|||
:field :email}]
|
||||
|
||||
[:span.settings-label (t locale "settings.profile.lang")]
|
||||
[:select.input-select {:value (or (:lang data) "en")
|
||||
[:select.input-select {:value (:lang data)
|
||||
:name "lang"
|
||||
:class (fm/error-class form :lang)
|
||||
:on-blur (fm/on-input-blur form :lang)
|
||||
|
@ -94,6 +95,15 @@
|
|||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
|
||||
[:span.user-settings-label (tr "settings.profile.section-theme-data")]
|
||||
[:select.input-select {:value (:theme data)
|
||||
:name "theme"
|
||||
:class (fm/error-class form :theme)
|
||||
:on-blur (fm/on-input-blur form :theme)
|
||||
:on-change (fm/on-input-change form :theme)}
|
||||
[:option {:value "light"} "Light"]
|
||||
[:option {:value "dark"} "Dark"]]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
|
|
50
frontend/src/uxbox/util/theme.cljs
Normal file
50
frontend/src/uxbox/util/theme.cljs
Normal file
|
@ -0,0 +1,50 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2019-2020 Mathieu BRUNOT <mathieu.brunot@monogramm.io>
|
||||
|
||||
(ns uxbox.util.theme
|
||||
"A theme manager."
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[goog.object :as gobj]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.transit :as t]
|
||||
[uxbox.util.storage :refer [storage]]))
|
||||
|
||||
(defonce theme (get storage ::theme cfg/default-theme))
|
||||
(defonce theme-sub (rx/subject))
|
||||
(defonce themes #js {})
|
||||
|
||||
(defn init!
|
||||
[data]
|
||||
(set! themes data))
|
||||
|
||||
(defn set-current-theme!
|
||||
[v]
|
||||
(when (not= theme v)
|
||||
(when-some [el (dom/get-element "theme")]
|
||||
(set! (.-href el) (str "css/main-" v ".css")))
|
||||
(swap! storage assoc ::theme v)
|
||||
(set! theme v)
|
||||
(rx/push! theme-sub v)))
|
||||
|
||||
(defn set-default-theme!
|
||||
[]
|
||||
(set-current-theme! cfg/default-theme))
|
||||
|
||||
(defn use-theme
|
||||
[]
|
||||
(let [[theme set-theme] (mf/useState theme)]
|
||||
(mf/useEffect (fn []
|
||||
(let [sub (rx/sub! theme-sub #(set-theme %))]
|
||||
#(rx/dispose! sub)))
|
||||
#js [])
|
||||
theme))
|
||||
|
|
@ -59,6 +59,7 @@ function build-frontend {
|
|||
-e UXBOX_DEMO_WARNING=${UXBOX_DEMO_WARNING} \
|
||||
-e UXBOX_DEPLOY_DATE=${UXBOX_DEPLOY_DATE} \
|
||||
-e UXBOX_DEPLOY_COMMIT=${UXBOX_DEPLOY_COMMIT} \
|
||||
-e UXBOX_THEME=${UXBOX_THEME} \
|
||||
$IMAGE ./scripts/build-app.sh
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue