├── src
├── server
│ ├── config
│ │ ├── dev.edn
│ │ ├── defaults.edn
│ │ └── prod.edn
│ └── untangled_template
│ │ ├── core.clj
│ │ ├── api
│ │ ├── read.clj
│ │ └── mutations.clj
│ │ └── system.clj
├── cards
│ └── untangled_template
│ │ ├── cards.cljs
│ │ └── intro.cljs
└── client
│ └── untangled_template
│ ├── main.cljs
│ ├── state
│ └── mutations.cljs
│ ├── ui
│ ├── components.cljs
│ ├── main.cljs
│ ├── new_user.cljs
│ ├── login.cljs
│ └── root.cljs
│ └── core.cljs
├── Procfile
├── script
└── figwheel.clj
├── docs
└── img
│ ├── server.png
│ └── figwheel.png
├── specs
├── client
│ └── untangled_template
│ │ ├── tests_to_run.cljs
│ │ ├── all_tests.cljs
│ │ ├── sample_spec.cljs
│ │ ├── spec_main.cljs
│ │ └── ui
│ │ └── root_spec.cljs
└── server
│ └── sample
│ └── sample_spec.clj
├── .gitignore
├── resources
└── public
│ ├── cards.html
│ ├── index-dev.html
│ ├── index.html
│ ├── test.html
│ └── css
│ ├── edn.css
│ └── test.css
├── package.json
├── RELEASE_CHECKLIST.md
├── LICENSE
├── Makefile
├── bin
└── rename-project.sh
├── dev
├── client
│ └── cljs
│ │ └── user.cljs
└── server
│ └── user.clj
├── project.clj
└── README.md
/src/server/config/dev.edn:
--------------------------------------------------------------------------------
1 | { :port 3000}
2 |
--------------------------------------------------------------------------------
/src/server/config/defaults.edn:
--------------------------------------------------------------------------------
1 | { :port 3000}
2 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java $JVM_OPTS -Dconfig=config/prod.edn -jar target/untangled_template.jar
2 |
--------------------------------------------------------------------------------
/script/figwheel.clj:
--------------------------------------------------------------------------------
1 | (require '[user :refer [start-figwheel]])
2 |
3 | (start-figwheel)
4 |
--------------------------------------------------------------------------------
/docs/img/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untangled-web/untangled-template/HEAD/docs/img/server.png
--------------------------------------------------------------------------------
/docs/img/figwheel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untangled-web/untangled-template/HEAD/docs/img/figwheel.png
--------------------------------------------------------------------------------
/src/cards/untangled_template/cards.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.cards
2 | (:require [untangled-template.intro]))
3 |
--------------------------------------------------------------------------------
/src/server/config/prod.edn:
--------------------------------------------------------------------------------
1 | ; Config files can read raw strings or EDN from env variables (:env/VAR and :env.edn/VAR)
2 | {:user :env/USER
3 | :port :env.edn/PORT}
4 |
--------------------------------------------------------------------------------
/specs/client/untangled_template/tests_to_run.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.tests-to-run
2 | (:require
3 | untangled-template.ui.root-spec
4 | untangled-template.sample-spec))
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.iml
3 | *.jar
4 | .idea
5 | checkouts
6 | classes
7 | figwheel_server.log
8 | lein-template.iml
9 | node_modules
10 | pom.xml
11 | pom.xml.asc
12 | resources/public/js
13 | target
14 |
--------------------------------------------------------------------------------
/specs/client/untangled_template/all_tests.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.all-tests
2 | (:require
3 | untangled-template.tests-to-run
4 | [doo.runner :refer-macros [doo-all-tests]]))
5 |
6 | (doo-all-tests #".*-spec")
7 |
--------------------------------------------------------------------------------
/src/client/untangled_template/main.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.main
2 | (:require [untangled-template.core :refer [app]]
3 | [untangled.client.core :as core]
4 | [untangled-template.ui.root :as root]))
5 |
6 | (reset! app (core/mount @app root/Root "app"))
7 |
--------------------------------------------------------------------------------
/resources/public/cards.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Template Devcards
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/specs/client/untangled_template/sample_spec.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.sample-spec
2 | (:require
3 | [untangled-spec.core :refer-macros [specification behavior assertions]]))
4 |
5 | (specification "a sample spec file"
6 | (behavior "for you to tinker with"
7 | (assertions "Silly test"
8 | 1 => 1)))
9 |
--------------------------------------------------------------------------------
/specs/client/untangled_template/spec_main.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.spec-main
2 | (:require-macros
3 | [untangled-spec.reporters.suite :as ts])
4 | (:require
5 | untangled-spec.reporters.impl.suite
6 | untangled-template.tests-to-run))
7 |
8 | (enable-console-print!)
9 |
10 | (ts/deftest-all-suite specs #".*-spec")
11 |
12 | (specs)
13 |
--------------------------------------------------------------------------------
/src/cards/untangled_template/intro.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.intro
2 | (:require [devcards.core :as rc :refer-macros [defcard]]
3 | [om.next :as om :refer-macros [defui]]
4 | [untangled-template.ui.components :as comp]
5 | [om.dom :as dom]))
6 |
7 | (defcard SVGPlaceholder
8 | "# SVG Placeholder"
9 | (comp/ui-placeholder {:w 200 :h 200}))
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "untangled-template-spec",
3 | "version": "1.0.0",
4 | "description": "Testing",
5 | "main": "index.js",
6 | "directories": {},
7 | "dependencies": {
8 | },
9 | "devDependencies": {
10 | "karma": "^0.13.19",
11 | "karma-chrome-launcher": "^0.2.2",
12 | "karma-cljs-test": "^0.1.0",
13 | "karma-firefox-launcher": "^0.1.7"
14 | },
15 | "author": "",
16 | "license": "MIT"
17 | }
18 |
--------------------------------------------------------------------------------
/src/client/untangled_template/state/mutations.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.state.mutations
2 | (:require [om.next :as om]
3 | [untangled.client.mutations :refer [mutate post-mutate]]
4 | [untangled.client.impl.data-fetch :as df]))
5 |
6 | (comment
7 | (defmethod mutate 'app/do-thing [{:keys [state ast] :as env} mut-name params]
8 | {:remote true; or modify (env :ast)
9 | :action (fn [] (swap! state assoc :thing params))}))
10 |
--------------------------------------------------------------------------------
/src/server/untangled_template/core.clj:
--------------------------------------------------------------------------------
1 | (ns untangled-template.core
2 | (:require
3 | [com.stuartsierra.component :as component]
4 | [untangled.server.core :as c]
5 | [untangled.server.impl.components.config :refer [load-config]]
6 | [taoensso.timbre :as timbre]
7 | [untangled-template.system :as sys])
8 | (:gen-class))
9 |
10 | (def config-path "/usr/local/etc/untangled_template.edn")
11 |
12 | (defn -main [& args]
13 | (let [system (sys/make-system config-path)]
14 | (component/start system)))
15 |
--------------------------------------------------------------------------------
/specs/server/sample/sample_spec.clj:
--------------------------------------------------------------------------------
1 | (ns sample.sample-spec
2 | (:require
3 | ;[untangled-spec.stub]
4 | [clojure.test :refer [is]]
5 | [untangled-spec.core :refer [specification provided behavior assertions]]))
6 |
7 | (specification "Server Math"
8 | (behavior "addition computes addition correctly"
9 | (assertions
10 | "with positive integers"
11 | (+ 1 5 3) => 9
12 | "with negative integers"
13 | (+ -1 -3 -5) => -9
14 | "with a mix of signed integers"
15 | (+ +5 -3) => 2)))
16 |
17 |
--------------------------------------------------------------------------------
/resources/public/index-dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Home Page (Dev Mode)
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/server/untangled_template/api/read.clj:
--------------------------------------------------------------------------------
1 | (ns untangled-template.api.read
2 | (:require
3 | ;[untangled.datomic.protocols :as udb]
4 | [untangled-template.api.mutations :as m]
5 | [taoensso.timbre :as timbre]))
6 |
7 | (timbre/info "Loading API definitions for untangled-template.api.read")
8 |
9 |
10 | (defn api-read [{:keys [query request] :as env} disp-key params]
11 | ;(let [connection (udb/get-connection survey-database)])
12 | (case disp-key
13 | :logged-in? {:value @m/logged-in?}
14 | :hello-world {:value 42}
15 | :current-user {:value {:id 42 :name "Tony Kay"}}
16 | (throw (ex-info "Invalid request" {:query query :key disp-key}))))
17 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Home Page
8 |
9 |
10 | Loading...TODO: Put your loading marker for production here. If you're in dev
11 | mode, use
index-dev.html instead.
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/resources/public/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/specs/client/untangled_template/ui/root_spec.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.root-spec
2 | (:require
3 | [untangled-template.ui.root :as root]
4 | [untangled-spec.core :refer-macros [specification component behavior assertions]]))
5 |
6 | (specification "Root level mutations"
7 | (component "Navigation - nav-to helper function"
8 | (let [state-atom (atom {})
9 | env {:state state-atom}]
10 |
11 | (root/nav-to env :my-page)
12 |
13 | (assertions
14 | "Sets the current-page ident to have a second element of :page"
15 | (-> state-atom deref :current-page second) => :page
16 | "Sets the current-page ident to have the selected page as the first element"
17 | (-> state-atom deref :current-page first) => :my-page))))
18 |
--------------------------------------------------------------------------------
/src/client/untangled_template/ui/components.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.components
2 | (:require
3 | [om.next :as om :refer-macros [defui]]
4 | [om.dom :as dom]))
5 |
6 | (defui PlaceholderImage
7 | Object
8 | (render [this]
9 | (let [{:keys [w h label]} (om/props this)
10 | label (or label (str w "x" h))]
11 | (dom/svg #js {:width w :height h}
12 | (dom/rect #js {:width w :height h :style #js {:fill "rgb(200,200,200)"
13 | :strokeWidth 2
14 | :stroke "black"}})
15 | (dom/text #js {:textAnchor "middle" :x (/ w 2) :y (/ h 2)} label)))))
16 |
17 | (def ui-placeholder (om/factory PlaceholderImage))
18 |
--------------------------------------------------------------------------------
/src/server/untangled_template/api/mutations.clj:
--------------------------------------------------------------------------------
1 | (ns untangled-template.api.mutations
2 | (:require
3 |
4 | [om.next.server :as oms]
5 | [taoensso.timbre :as timbre]
6 | [untangled.server.core :as core]))
7 |
8 | (defmulti apimutate oms/dispatch)
9 |
10 | (defonce logged-in? (atom false))
11 |
12 | (defmethod apimutate 'login/attempt-login [env k {:keys [u p uid]}]
13 | {:action (fn []
14 | (reset! logged-in? true)
15 | {:uid 42
16 | :tempids {uid 42}})})
17 |
18 | (defmethod apimutate 'login/logout [{:keys [sample-component] :as env} k {:keys [u p uid]}]
19 | ; Demo that the injected component appears in env
20 | (timbre/info "Sample component data: " (:stuff sample-component))
21 | {:action (fn [] (reset! logged-in? false))})
22 |
--------------------------------------------------------------------------------
/src/client/untangled_template/ui/main.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.main
2 | (:require [om.next :as om :refer-macros [defui]]
3 | [untangled.client.core :as u]
4 | [om.dom :as dom]
5 | [om-css.core :as css :refer-macros [localize-classnames]]
6 | [untangled.client.mutations :as m]))
7 |
8 | (defui ^:once MainPage
9 | static u/InitialAppState
10 | (initial-state [this params] {:id :main})
11 | static om/IQuery
12 | (query [this] [:id [:current-user '_]])
13 | static css/CSS
14 | (css [this] [[(css/local-kw MainPage :x)]])
15 | static om/Ident
16 | (ident [this props] [:main :page])
17 | Object
18 | (render [this]
19 | (localize-classnames MainPage
20 | (let [{:keys [current-user]} (om/props this)]
21 | (dom/div #js {:class :form} "MAIN PAGE")))))
22 |
23 | (def ui-main (om/factory MainPage))
24 |
--------------------------------------------------------------------------------
/src/client/untangled_template/core.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.core
2 | (:require [om.next :as om]
3 | [untangled.client.core :as uc]
4 | [untangled.client.data-fetch :as f]
5 | untangled-template.state.mutations
6 | [untangled.client.logging :as log]))
7 |
8 | (defn merge-mutations [state k p]
9 | (log/info "Got return value for " k " -> " p)
10 | state)
11 |
12 | (defonce app
13 | (atom (uc/new-untangled-client
14 | :mutation-merge merge-mutations
15 | :started-callback (fn [{:keys [reconciler]}]
16 | (f/load-data reconciler [:logged-in? :current-user]
17 | :post-mutation 'login/login-complete)
18 | ;;TODO: initial load of data
19 | ))))
20 |
--------------------------------------------------------------------------------
/RELEASE_CHECKLIST.md:
--------------------------------------------------------------------------------
1 | # RELEASE CHECKLIST
2 |
3 | Before releasing a new version of this template, you must ensure the
4 | following things work as expected:
5 |
6 | - Versions
7 | - All deps are up-to-date
8 | - All tooling (lein plugins, etc) are up-to-date
9 | - There are NO warnings from `lein deps :tree`
10 | - IDE Development for IntelliJ, Emacs, and VIM
11 | - Figwheel
12 | - Server run AND code reload
13 | - Uberjar build:
14 | - Does a production cljs build
15 | - Includes all necessary resources and files
16 | - Is runnable according to the README instructions
17 | - The UI works
18 | - The server can be configured (e.g. PORT) as described in README
19 | - The following figwheel configs work as expected, and hot reload:
20 | - CSS
21 | - Cards
22 | - General development
23 | - Tests
24 | - Tests
25 | - Are runnable from UI (client)
26 | - Work with test-refresh (server)
27 | - Are runnable from CI (server and client)
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 NAVIS
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6 | persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
11 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | LEIN_RUN = rlwrap lein run -m clojure.main ./script/figwheel.clj
2 |
3 | # Run the dev and test cljs builds in figwheel
4 | dev:
5 | lein do clean, -U deps ; JVM_OPTS="-server -Ddev -Dtest" ${LEIN_RUN}
6 |
7 | # Run the test cljs builds in figwheel
8 | test:
9 | JVM_OPTS="-server -Dtest" ${LEIN_RUN}
10 |
11 | # Run the cards cljs builds in figwheel
12 | cards:
13 | JVM_OPTS="-server -Dcards" ${LEIN_RUN}
14 |
15 | # Run a REPL capable of running the web server
16 | server:
17 | rlwrap lein run -m clojure.main
18 |
19 | server-tests:
20 | lein test-refresh :run-once
21 |
22 | # Run the command-line (karma-based) automated cljs tests
23 | ci-cljs-tests:
24 | npm install
25 | lein do clean, doo chrome automated-tests once
26 |
27 | # Run all tests (once) from the command line. Useful for CI
28 | ci-tests: ci-cljs-tests server-tests
29 |
30 | clean:
31 | lein clean
32 |
33 | rename:
34 | bin/rename-project.sh
35 |
36 | help:
37 | @ make -rpn | sed -n -e '/^$$/ { n ; /^[^ ]*:/p; }' | sort | egrep --color '^[^ ]*:'
38 |
39 | .PHONY: dev test cards server server-tests ci-cljs-tests ci-tests clean rename help
40 |
--------------------------------------------------------------------------------
/resources/public/css/edn.css:
--------------------------------------------------------------------------------
1 | .rendered-edn .collection {
2 | display: flex;
3 | display: -webkit-flex;
4 | }
5 |
6 | .rendered-edn .keyval {
7 | display: flex;
8 | display: -webkit-flex;
9 | flex-wrap: wrap;
10 | -webkit-flex-wrap: wrap;
11 | }
12 |
13 | .rendered-edn .keyval > .keyword {
14 | color: #a94442;
15 | }
16 |
17 | .rendered-edn .keyval > *:first-child {
18 | margin: 0px 3px;
19 | flex-shrink: 0;
20 | -webkit-flex-shrink: 0;
21 | }
22 |
23 | .rendered-edn .keyval > *:last-child {
24 | margin: 0px 3px;
25 | }
26 |
27 | .rendered-edn .opener {
28 | color: #999;
29 | margin: 0px 4px;
30 | flex-shrink: 0;
31 | -webkit-flex-shrink: 0;
32 | }
33 |
34 | .rendered-edn .closer {
35 | display: flex;
36 | display: -webkit-flex;
37 | flex-direction: column-reverse;
38 | -webkit-flex-direction: column-reverse;
39 | margin: 0px 3px;
40 | color: #999;
41 | }
42 |
43 | .rendered-edn .string {
44 | color: #428bca;
45 | }
46 |
47 | .rendered-edn .string .opener,
48 | .rendered-edn .string .closer {
49 | display: inline;
50 | margin: 0px;
51 | color: #428bca;
52 | }
53 |
--------------------------------------------------------------------------------
/bin/rename-project.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ([[ -n "$DEBUG" ]] || [[ -n "$TRACE" ]]) && set -x
3 | set -e
4 |
5 | assert_clean_work_tree () {
6 | if [[ -z "$OVERRIDE" ]] && [[ -n "$(git status -s)" ]]; then
7 | echo "[ERROR]: Uncommited changes!"
8 | git status
9 | exit 1
10 | fi
11 | }
12 |
13 | search_and_replace () {
14 | #LC_ALL=C is important because we have utf-8 symbols in the readme
15 | #and sed will corrupt them & git will explode otherwise
16 | #stackoverflow.com/questions/19242275/re-error-illegal-byte-sequence-on-mac-os-x
17 | #stackoverflow.com/questions/1115854/how-to-resolve-error-bad-index-fatal-index-file-corrupt-when-using-git
18 | export LC_ALL=C
19 | for f in $(grep --exclude="$0" --exclude-dir=".git" -lr "$1" *); do
20 | #using .bak in place extension for portability between sed versions
21 | #is more portable (& easier) than no backup
22 | sed -i.bak "s/$1/$2/g" "$f"
23 | rm ${f}.bak
24 | done
25 | unset LC_ALL
26 | }
27 |
28 | rename_matching_dirs () {
29 | for d in $(find . -type d -name "$1"); do
30 | mv "$d" "$(dirname $d)/$2"
31 | done
32 | }
33 |
34 | main () {
35 | assert_clean_work_tree
36 | read -p "Renaming 'untangled-template' to: " ns
37 | search_and_replace "untangled-template" "$ns"
38 | local fdir="${ns//-/_}"
39 | search_and_replace "untangled_template" "$fdir"
40 | rename_matching_dirs "untangled_template" "$fdir"
41 | }
42 |
43 | main "$@"
44 |
--------------------------------------------------------------------------------
/dev/client/cljs/user.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs.user
2 | (:require
3 | [untangled.client.core :as uc]
4 | [om.next :as om]
5 |
6 | [untangled-template.core :as core]
7 | [untangled-template.ui.root :as root]
8 |
9 | [cljs.pprint :refer [pprint]]))
10 |
11 | (enable-console-print!)
12 |
13 | (reset! core/app (uc/mount @core/app root/Root "app"))
14 |
15 | (defn app-state [] @(:reconciler @core/app))
16 |
17 | (defn log-app-state [& keywords]
18 | (pprint (let [app-state (app-state)]
19 | (if (= 0 (count keywords))
20 | app-state
21 | (select-keys app-state keywords)))))
22 |
23 | (defn dump-query [comp]
24 | (let [component (om/class->any (:reconciler @core/app) comp)]
25 | (om/full-query component)))
26 |
27 | (defn dump-query-kw [kw]
28 | (let [component (om/ref->any (:reconciler @core/app) kw)]
29 | (om/full-query component)))
30 |
31 | (defn q
32 | "Run the query of the given UI class and return the result as a map of the query that was run and the data that was returned.
33 | NOTE: If the component is mounted in several places on the UI, you may not get the expected result. Be sure to check
34 | the QUERY part of the result to see the query used."
35 | [ui-class]
36 | (let [query (dump-query ui-class)
37 | state (app-state)]
38 | {:QUERY query
39 | :RESULT (om/db->tree query state state)}))
40 |
41 | (defn qkw
42 | "Find a component that uses the given keyword in its query, then run that query against the app database and show
43 | the result. NOTE: If more than one component matches, your results may vary. Be sure to examine the query that
44 | was used."
45 | [query-kw]
46 | (let [query (dump-query-kw query-kw)
47 | state (app-state)]
48 | {:QUERY query
49 | :RESULT (om/db->tree query state state)}))
50 |
--------------------------------------------------------------------------------
/resources/public/css/test.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Lato);
2 | @import url(https://fonts.googleapis.com/css?family=Cutive+Mono);
3 |
4 | .overlay {
5 | background-color: rgb(230, 230, 240);
6 | width: 194px;
7 | }
8 |
9 | .hidden {
10 | display: none;
11 | }
12 |
13 | .test-report {
14 | font-family: 'Lato' sans-serif;
15 | display: block;
16 | font-size: 20pt;
17 | }
18 |
19 | .test-item {
20 | display: block;
21 | font-size: 13pt;
22 | }
23 |
24 | .test-namespace {
25 | margin-top: 20px;
26 | font-weight: 400;
27 | display: block;
28 | font-size: 18pt;
29 | }
30 |
31 | .test-header {
32 | font-weight: 700;
33 | display: block;
34 | font-size: 18pt;
35 | }
36 |
37 | .test-manually {
38 | color: orange;
39 | }
40 |
41 | .filter-controls {
42 | position: fixed;
43 | top: 0px;
44 | right: 0px;
45 | }
46 |
47 | .filter-controls label {
48 | font-size: 10pt;
49 | }
50 |
51 | .filter-controls a {
52 | font-size: 10pt;
53 | padding-left: 5pt;
54 | }
55 |
56 | .selected {
57 | color: green;
58 | text-decoration: underline;
59 | }
60 |
61 | .test-pending {
62 | color: gray;
63 | }
64 |
65 | .test-passed {
66 | color: limegreen;
67 | }
68 |
69 | .test-error {
70 | color: red;
71 | }
72 |
73 | .test-failed {
74 | color: red;
75 | }
76 |
77 | .test-list {
78 | list-style-type: none;
79 | }
80 |
81 | .test-result {
82 | margin: 12px;
83 | font-size: 16px;
84 | }
85 |
86 | .test-result-title {
87 | width: 100px;
88 | font-size: 16px;
89 | }
90 |
91 | .test-count {
92 | margin: 20px 0 20px 20px;
93 | }
94 |
95 | .test-report ul {
96 | padding-left: 10px;
97 | margin-bottom: 10px;
98 | }
99 |
100 | .test-report ul:empty {
101 | display: none;
102 | }
103 |
104 | .test-report h2 {
105 | font-size: 24px;
106 | margin-bottom: 15px;
107 | }
108 |
109 | #test-app {
110 | display: none;
111 | }
112 |
--------------------------------------------------------------------------------
/src/client/untangled_template/ui/new_user.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.new-user
2 | (:require [om.next :as om :refer-macros [defui]]
3 | [untangled.client.core :as u]
4 | [untangled.client.data-fetch :as f]
5 | [om.dom :as dom]
6 | [om-css.core :as css :refer-macros [localize-classnames]]
7 | [untangled.client.mutations :as m]))
8 |
9 | (defui ^:once NewUser
10 | static u/InitialAppState
11 | (initial-state [this params] {:id :new-user :ui/username "" :ui/password "" :ui/password2 ""})
12 | static om/IQuery
13 | (query [this] [:id :ui/username :ui/password :ui/password2])
14 | static om/Ident
15 | (ident [this props] [:new-user :page])
16 | Object
17 | (render [this]
18 | (localize-classnames NewUser
19 | (let [{:keys [ui/username ui/password ui/password2]} (om/props this)]
20 | (dom/div nil
21 | (dom/div #js {:className "row"}
22 | (dom/div #js {:className "col-xs-4"} "")
23 | (dom/div #js {:class [:form :$col-xs-4]}
24 | (dom/div #js {:className "form-group"}
25 | (dom/label #js {:htmlFor "username"} "Email Address")
26 | (dom/input #js {:className "form-control" :type "email" :name "username" :value username
27 | :onChange #(m/set-string! this :ui/username :event %)}))
28 | (dom/div #js {:className "form-group"}
29 | (dom/label #js {:htmlFor "password"} "Password")
30 | (dom/input #js {:name "password" :className "form-control"
31 | :type "password" :value password
32 | :onChange #(m/set-string! this :ui/password :event %)}))
33 | (dom/div #js {:className "form-group"}
34 | (dom/label #js {:htmlFor "password2"} "Verify your Password")
35 | (dom/input #js {:name "password2" :className "form-control"
36 | :type "password" :value password2
37 | :onChange #(m/set-string! this :ui/password2 :event %)}))
38 | (dom/button #js {:onClick #(om/transact! this `[(new-user/sign-up {:uid ~(om/tempid) :u ~username :p ~password})])} "Sign Up!"))))))))
39 |
40 | (def ui-new-user (om/factory NewUser))
41 |
--------------------------------------------------------------------------------
/dev/server/user.clj:
--------------------------------------------------------------------------------
1 | (ns user
2 | (:require
3 | [clojure.pprint :refer [pprint]]
4 | [clojure.stacktrace :refer [print-stack-trace]]
5 |
6 | [clojure.tools.namespace.repl :as tools-ns :refer [set-refresh-dirs]]
7 | [com.stuartsierra.component :as component]
8 | [figwheel-sidecar.system :as fig]
9 | [untangled-template.system :as sys]))
10 |
11 | ;;FIGWHEEL
12 | (def figwheel (atom nil))
13 |
14 | ; Usable from a REPL to start one-or-more figwheel builds
15 | (defn start-figwheel
16 | "Start Figwheel on the given builds, or defaults to build-ids in `figwheel-config`."
17 | ([]
18 | (let [figwheel-config (fig/fetch-config)
19 | props (System/getProperties)
20 | all-builds (->> figwheel-config :data :all-builds (mapv :id))]
21 | (start-figwheel (keys (select-keys props all-builds)))))
22 | ([build-ids]
23 | (let [figwheel-config (fig/fetch-config)
24 | default-build-ids (-> figwheel-config :data :build-ids)
25 | build-ids (if (empty? build-ids) default-build-ids build-ids)
26 | preferred-config (assoc-in figwheel-config [:data :build-ids] build-ids)]
27 | (reset! figwheel (component/system-map
28 | :css-watcher (fig/css-watcher {:watch-paths ["resources/public/css"]})
29 | :figwheel-system (fig/figwheel-system preferred-config)))
30 | (println "STARTING FIGWHEEL ON BUILDS: " build-ids)
31 | (swap! figwheel component/start)
32 | (fig/cljs-repl (:figwheel-system @figwheel)))))
33 |
34 | ;; ==================== SERVER ====================
35 |
36 | (set-refresh-dirs "dev/server" "src/server" "specs/server")
37 |
38 | (defn started? [sys]
39 | (-> sys :config :value))
40 |
41 | (defonce system (atom nil))
42 | (def cfg-paths {:dev "config/dev.edn"})
43 |
44 | (defn refresh [& args]
45 | {:pre [(not @system)]}
46 | (apply tools-ns/refresh args))
47 |
48 | (defn init [path]
49 | {:pre [(not (started? @system))
50 | (get cfg-paths path)]}
51 | (when-let [new-system (sys/make-system (get cfg-paths path))]
52 | (reset! system new-system)))
53 |
54 | (defn start []
55 | {:pre [@system (not (started? @system))]}
56 | (swap! system component/start))
57 |
58 | (defn stop []
59 | (when (started? @system)
60 | (swap! system component/stop))
61 | (reset! system nil))
62 |
63 | (defn go
64 | ([] (go :dev))
65 | ([path] {:pre [(not @system) (not (started? @system))]}
66 | (init path)
67 | (start)))
68 |
69 | (defn reset []
70 | (stop)
71 | (refresh :after 'user/go))
72 |
73 | (defn engage [path & build-ids]
74 | (stop) (go path) (start-figwheel build-ids))
75 |
--------------------------------------------------------------------------------
/src/server/untangled_template/system.clj:
--------------------------------------------------------------------------------
1 | (ns untangled-template.system
2 | (:require
3 | [com.stuartsierra.component :as component]
4 | [untangled-template.api.mutations :as m]
5 | [untangled-template.api.read :as r]
6 | [ring.middleware.content-type :refer [wrap-content-type]]
7 | [ring.middleware.gzip :refer [wrap-gzip]]
8 | [ring.middleware.not-modified :refer [wrap-not-modified]]
9 | [ring.middleware.params :refer [wrap-params]]
10 | [ring.middleware.resource :refer [wrap-resource]]
11 | [untangled.server.core :as core]
12 | [untangled.server.impl.middleware :as middleware]
13 | [clojure.java.io :as io]
14 | [taoensso.timbre :as timbre]))
15 |
16 | ; This is both a server module AND hooks into the Om parser for the incoming /api read/mutate requests. The
17 | ; modular server support lets you chain as many of these together as you want, allowing you to define
18 | ; reusable Om server components.
19 | (defrecord ApiHandler []
20 | core/Module
21 | (system-key [this] ::api)
22 | (components [this] {})
23 | core/APIHandler
24 | (api-read [this] r/api-read)
25 | (api-mutate [this] m/apimutate))
26 |
27 | (defn build-api-handler [& [deps]]
28 | "`deps`: Vector of keys passed as arguments to be
29 | included as dependecies in `env`."
30 | (component/using
31 | (map->ApiHandler {}) deps))
32 |
33 | (defn MIDDLEWARE [handler component]
34 | ((get component :middleware) handler))
35 |
36 | (defn not-found [req]
37 | {:status 404
38 | :headers {"Content-Type" "text/plain"}
39 | :body "Resource not found."})
40 |
41 | (defrecord CustomMiddleware [middleware api-handler]
42 | component/Lifecycle
43 | (stop [this] (dissoc this :middleware))
44 | (start [this]
45 | (assoc this :middleware
46 | (-> not-found
47 | (MIDDLEWARE api-handler)
48 | ;; TRANSIT
49 | middleware/wrap-transit-params
50 | middleware/wrap-transit-response
51 | ;; RESOURCES
52 | (wrap-resource "public")
53 | ;; HELPERS
54 | wrap-content-type
55 | wrap-not-modified
56 | wrap-params
57 | wrap-gzip))))
58 |
59 | ; IMPORTANT: You want to inject the built-in API handler (which is where modular API handlers get chained)
60 | (defn build-middleware []
61 | (component/using
62 | (map->CustomMiddleware {})
63 | {:api-handler ::core/api-handler}))
64 |
65 | (defrecord Sample [stuff]
66 | component/Lifecycle
67 | (start [this] (assoc this :stuff {:data 42}))
68 | (stop [this] this))
69 |
70 | (defn make-system [config-path]
71 | (core/untangled-system
72 | {:components {:config (core/new-config config-path) ; you MUST use config if you use our server
73 | ::middleware (build-middleware) ; complete Ring stack
74 | :sample-component (map->Sample {})
75 | :web-server (core/make-web-server ::middleware)} ; must provide the component key to your middleware
76 | ; Modules are composable into the Om API handler (can have their own read/mutate) and
77 | ; are joined together in a chain. Any components you declare as deps will appear in the parser env.
78 | :modules [(build-api-handler [:sample-component])]}))
79 |
--------------------------------------------------------------------------------
/src/client/untangled_template/ui/login.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.login
2 | (:require [om.next :as om :refer-macros [defui]]
3 | [untangled.client.core :as u]
4 | [untangled.client.data-fetch :as f]
5 | [om.dom :as dom]
6 | [om-css.core :as css :refer-macros [localize-classnames]]
7 | [untangled.client.mutations :as m]))
8 |
9 | (defmethod m/mutate 'login/attempt-login [{:keys [state]} k {:keys [uid]}]
10 | {:remote true
11 | :action (fn [] (swap! state assoc
12 | :current-user {:id uid :name "???"}
13 | :server-down false))})
14 |
15 | (defmethod m/mutate 'login/server-down [{:keys [state]} k p]
16 | {:action (fn [] (swap! state assoc :server-down true))})
17 |
18 | (defmethod m/mutate 'login/login-complete [{:keys [state]} k p]
19 | {:action (fn []
20 | (let [{:keys [logged-in? current-user]} @state]
21 | (if logged-in?
22 | (swap! state assoc :current-page [:main :page])
23 | (swap! state assoc :current-page [:login :page]))))})
24 |
25 | (defmethod m/mutate 'login/logout [{:keys [state]} k p]
26 | {:remote true
27 | :action (fn []
28 | (swap! state assoc
29 | :current-user {}
30 | :logged-in? false
31 | :current-page [:login :page]))})
32 |
33 | (defui ^:once LoginPage
34 | static u/InitialAppState
35 | (initial-state [this params] {:id :login :ui/username "" :ui/password "" :ui/server-down false :ui/error nil})
36 | static om/IQuery
37 | (query [this] [:id :ui/username :ui/password [:server-down '_] [:ui/loading-data '_]])
38 | static css/CSS
39 | (css [this] [[(css/local-kw LoginPage :form)]])
40 | static om/Ident
41 | (ident [this props] [:login :page])
42 | Object
43 | (render [this]
44 | (localize-classnames LoginPage
45 | (let [{:keys [ui/username ui/password server-down ui/loading-data]} (om/props this)]
46 | (dom/div nil
47 | (dom/div #js {:className "row"}
48 | (dom/div #js {:className "col-xs-4"} "")
49 | (dom/div #js {:class [:form :$col-xs-4]}
50 | (when server-down
51 | (dom/div nil "Unable to contact server. Try again later."))
52 | (when loading-data
53 | (dom/div nil "Working..."))
54 | (dom/div #js {:className "form-group"}
55 | (dom/label #js {:htmlFor "username"} "Username")
56 | (dom/input #js {:className "form-control" :name "username" :value username
57 | :onChange #(m/set-string! this :ui/username :event %)}))
58 | (dom/div #js {:className "form-group"}
59 | (dom/label #js {:htmlFor "password"} "Password")
60 | (dom/input #js {:name "password" :className "form-control" :type "password" :value password
61 | :onChange #(m/set-string! this :ui/password :event %)}))
62 | (dom/button #js {:onClick #(om/transact! this `[(login/attempt-login {:uid ~(om/tempid) :u ~username :p ~password})
63 | (tx/fallback {:action login/server-down})
64 | (untangled/load {:query [:logged-in? :current-user] :post-mutation login/login-complete})
65 | :ui/react-key
66 | :logged-in?
67 | :current-user
68 | ])} "Login")))
69 | (dom/div #js {:className "row"}
70 | (dom/div #js {:className "col-xs-4"} "")
71 | (dom/div #js {:className "col-xs-4"}
72 | "Don't have a login yet? "
73 | (dom/a #js {:onClick #(om/transact! this '[(nav/new-user) :ui/react-key])} "Sign up!"))))))))
74 |
75 | (def ui-login (om/factory LoginPage))
76 |
--------------------------------------------------------------------------------
/src/client/untangled_template/ui/root.cljs:
--------------------------------------------------------------------------------
1 | (ns untangled-template.ui.root
2 | (:require
3 | [untangled.client.mutations :as mut]
4 | [om.dom :as dom]
5 | [untangled-template.ui.login :as l]
6 | [untangled-template.ui.main :as main]
7 | [untangled-template.ui.new-user :as nu]
8 | [om.next :as om :refer-macros [defui]]
9 | [untangled.client.core :as u]
10 | [untangled.client.mutations :as m]))
11 |
12 | (defn nav-to [env page] (swap! (:state env) assoc :current-page [page :page]))
13 |
14 | (defmethod m/mutate 'nav/new-user [env k p] {:action (fn [] (nav-to env :new-user))})
15 | (defmethod m/mutate 'nav/login [env k p] {:action (fn [] (nav-to env :login))})
16 | (defmethod m/mutate 'nav/main [env k p] {:action (fn [] (nav-to env :main))})
17 |
18 | (defui ^:once Loading
19 | static u/InitialAppState
20 | (initial-state [this params] {:id :loading})
21 | static om/IQuery
22 | (query [this] [:id])
23 | static om/Ident
24 | (ident [this props] [:loading :page])
25 | Object
26 | (render [this]
27 | (dom/div nil "Loading...")))
28 |
29 | (def ui-loading (om/factory Loading))
30 |
31 | (defui ^:once Pages
32 | static u/InitialAppState
33 | (initial-state [this params] (u/initial-state Loading nil))
34 | static om/IQuery
35 | (query [this] {:loading (om/get-query Loading)
36 | :new-user (om/get-query nu/NewUser)
37 | :login (om/get-query l/LoginPage)
38 | :main (om/get-query main/MainPage)})
39 | static om/Ident
40 | (ident [this props] [(:id props) :page])
41 | Object
42 | (render [this]
43 | (let [{:keys [id login] :as props} (om/props this)]
44 | (case id
45 | :new-user (nu/ui-new-user props)
46 | :loading (ui-loading props)
47 | :login (l/ui-login props)
48 | :main (main/ui-main props)
49 | (dom/div nil "MISSING PAGE")))))
50 |
51 | (def ui-pages (om/factory Pages))
52 |
53 | (defn ui-login-stats [loading? user logout-fn]
54 | (dom/p #js {:className "navbar-text navbar-right"}
55 | (when loading?
56 | (dom/span #js {:className "badge"} "..."))
57 | (:name user)
58 | (dom/br nil)
59 | (dom/a #js {:onClick logout-fn} " Log out")))
60 |
61 | (defn ui-login-button [loading? login-fn]
62 | (dom/p #js {:className "navbar-right"}
63 | (when loading?
64 | (dom/span #js {:className "navbar-text badge"} "..."))
65 | (dom/button #js {:type "button"
66 | :onClick login-fn
67 | :className "btn btn-default navbar-btn "}
68 | "Sign in")))
69 |
70 | (defn ui-navbar [this]
71 | (let [login #(om/transact! this '[(nav/login)])
72 | logout #(om/transact! this '[(login/logout)])
73 | {:keys [ui/loading-data current-user logged-in?]} (om/props this)]
74 | (dom/div #js {:className "navbar navbar-default"}
75 | (dom/div #js {:className "container-fluid"}
76 | (dom/div #js {:className "navbar-header"}
77 | (dom/span #js {:className "navbar-brand"}
78 | (dom/span nil "Template Brand")))
79 | (dom/div #js {:className "collapse navbar-collapse"}
80 | (when logged-in?
81 | (dom/ul #js {:className "nav navbar-nav"}
82 | ;; More nav links here
83 | (dom/li nil (dom/a #js {:className "active" :onClick #(om/transact! this '[(nav/main)]) :href "#"} "Main"))))
84 | (if logged-in?
85 | (ui-login-stats loading-data current-user logout)
86 | (ui-login-button loading-data login)))))))
87 |
88 | (defui ^:once Root
89 | static om/IQuery
90 | (query [this] [:ui/react-key :logged-in? :current-user :ui/loading-data {:current-page (om/get-query Pages)}])
91 | static u/InitialAppState
92 | (initial-state [this params] {:logged-in? false :current-user {} :current-page (u/initial-state Pages nil)})
93 | Object
94 | (render [this]
95 | (let [{:keys [ui/loading-data ui/react-key current-page current-user logged-in?] :or {ui/react-key "ROOT"}} (om/props this)
96 | logout #(om/transact! this '[(login/logout)])]
97 | (dom/div #js {:key react-key}
98 | (ui-navbar this)
99 | (ui-pages (om/computed current-page {:logout logout}))))))
100 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject untangled-template "0.1.0-SNAPSHOT"
2 | :description "A clonable & upstream untangled template"
3 | :license {:name "MIT" :url "https://opensource.org/licenses/MIT"}
4 |
5 | :dependencies [[org.clojure/clojure "1.9.0-alpha14"]
6 | [org.clojure/clojurescript "1.9.494"]
7 | [commons-io "2.5"]
8 |
9 | [navis/untangled-client "0.7.0"]
10 | [untangled/om-css "1.0.0"]
11 | [org.omcljs/om "1.0.0-alpha48"]
12 |
13 | [navis/untangled-spec "0.3.9" :scope "test" :exclusions [io.aviso/pretty]]
14 | [lein-doo "0.1.7" :scope "test"]
15 | [org.clojure/core.async "0.2.395"]
16 | [http-kit "2.2.0"]
17 | [com.taoensso/timbre "4.7.4"]
18 | [navis/untangled-server "0.7.0"]]
19 |
20 | :plugins [[lein-cljsbuild "1.1.5"]
21 | [lein-doo "0.1.7"]
22 | [com.jakemccrary/lein-test-refresh "0.17.0"]]
23 |
24 | :doo {:build "automated-tests"
25 | :paths {:karma "node_modules/karma/bin/karma"}}
26 |
27 | :uberjar-name "untangled_template.jar"
28 |
29 | :test-refresh {:report untangled-spec.reporters.terminal/untangled-report
30 | :with-repl true
31 | :changes-only true}
32 |
33 | :source-paths ["src/server"]
34 | :test-paths ["specs" "specs/server" "specs/config"]
35 | :clean-targets ^{:protect false} ["target" "resources/public/js" "resources/private"]
36 |
37 | :figwheel {:css-dirs ["resources/public/css"]}
38 |
39 | :cljsbuild {:builds [{:id "production"
40 | :source-paths ["src/client"]
41 | :jar true
42 | :compiler {:asset-path "js/prod"
43 | :main untangled-template.main
44 | :optimizations :simple
45 | :output-dir "resources/public/js/prod"
46 | :output-to "resources/public/js/untangled_template.min.js"}}
47 | {:id "dev"
48 | :figwheel true
49 | :source-paths ["src/client" "dev/client"]
50 | :compiler {:asset-path "js/dev"
51 | :external-config
52 | {:devtools/config
53 | ;;github.com/binaryage/cljs-devtools/blob/master/docs/configuration.md
54 | {:print-config-overrides true}}
55 | :main cljs.user
56 | :optimizations :none
57 | :output-dir "resources/public/js/dev"
58 | :output-to "resources/public/js/untangled_template.js"
59 | :preloads [devtools.preload]
60 | :source-map-timestamp true}}
61 | {:id "test"
62 | :source-paths ["specs/client" "src/client"]
63 | :figwheel true
64 | :compiler {:asset-path "js/specs"
65 | :main untangled-template.spec-main
66 | :optimizations :none
67 | :output-dir "resources/public/js/specs"
68 | :output-to "resources/public/js/specs.js"
69 | :preloads [devtools.preload]}}
70 | {:id "automated-tests"
71 | :source-paths ["specs/client" "src/client"]
72 | :compiler {:asset-path "js/ci"
73 | :main untangled-template.all-tests
74 | :optimizations :none
75 | :output-dir "resources/private/js/ci"
76 | :output-to "resources/private/js/unit-tests.js"}}
77 | {:id "cards"
78 | :figwheel {:devcards true}
79 | :source-paths ["src/client" "src/cards"]
80 | :compiler {:asset-path "js/cards"
81 | :main untangled-template.cards
82 | :optimizations :none
83 | :output-dir "resources/public/js/cards"
84 | :output-to "resources/public/js/cards.js"
85 | :preloads [devtools.preload]
86 | :source-map-timestamp true}}]}
87 |
88 | :profiles {:uberjar {:main untangled-template.core
89 | :aot :all
90 | :prep-tasks ["compile"
91 | ["cljsbuild" "once" "production"]]}
92 | :dev {:source-paths ["dev/client" "dev/server" "src/client" "src/server"]
93 | :dependencies [[binaryage/devtools "0.9.1"]
94 | [org.clojure/tools.namespace "0.2.11"]
95 | [com.cemerick/piggieback "0.2.1"]
96 | [figwheel-sidecar "0.5.9" :exclusions [org.clojure/tools.reader]]
97 | [devcards "0.2.2" :exclusions [org.omcljs/om]]]
98 | :repl-options {:init-ns user
99 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}})
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Template
2 |
3 | This is a Full Stack template with specs, dev cards, and client code.
4 | It contains a mock login/signup screen, top-level tab routing (once logged in), etc.
5 |
6 | You must run the server (and use it through the server) for login to work, but ANY username/password are accepted. The
7 | server always approves login.
8 |
9 | It is set up to be deployable to Heroku (or anywhere) as a standalone jar.
10 |
11 | ## Contents
12 |
13 | ```
14 | ├── Makefile Convenience targets
15 | ├── Procfile Sample Heroku deployment file
16 | ├── dev
17 | │ ├── client
18 | │ │ └── cljs
19 | │ │ └── user.cljs REPL helpers and entry point for cljs dev mode
20 | │ └── server
21 | │ └── user.clj REPL functions for starting server and cljs builds
22 | ├── package.json NODE config, used for running CI cljs tests
23 | ├── project.clj
24 | ├── resources
25 | │ └── public
26 | │ ├── cards.html Devcards HTML page
27 | │ ├── css
28 | │ │ ├── edn.css CSS files for rendering specs in browser
29 | │ │ └── test.css
30 | │ ├── index-dev.html Dev mode application home page
31 | │ ├── index.html Production mode application home page
32 | │ └── test.html Tests HTML page
33 | ├── script
34 | │ └── figwheel.clj CLJ script for starting figwheel automatically
35 | ├── specs
36 | │ ├── client
37 | │ │ └── untangled_template
38 | │ │ ├── all_tests.cljs CI file for running all tests
39 | │ │ ├── sample_spec.cljs Sample CLJS specification
40 | │ │ ├── spec_main.cljs File to join all specs into a browser-runnable spec
41 | │ │ ├── tests_to_run.cljs Common file (for CI and Browser) to ensure all tests are loaded
42 | │ │ └── ui
43 | │ │ └── root_spec.cljs Sample Specification
44 | │ ├── config
45 | │ └── server
46 | │ └── sample
47 | │ └── sample_spec.clj Sample Server-side specification
48 | ├── src
49 | │ ├── cards
50 | │ │ └── untangled_template
51 | │ │ ├── cards.cljs Devcards setup
52 | │ │ └── intro.cljs Sample Devcard
53 | │ ├── client
54 | │ │ └── untangled_template
55 | │ │ ├── core.cljs Definition of app. Used by production and dev modes
56 | │ │ ├── main.cljs Production entry point for cljs app
57 | │ │ ├── state
58 | │ │ │ └── mutations.cljs A place to put Om mutations
59 | │ │ └── ui
60 | │ │ ├── components.cljs Sample UI component
61 | │ │ ├── login.cljs UI Login screen. Includes some mutations.
62 | │ │ ├── main.cljs UI Main screen
63 | │ │ ├── new_user.cljs UI New User Screen
64 | │ │ └── root.cljs Root UI with Union query for tab switching. Includes nav mutations.
65 | │ └── server
66 | │ ├── config Server EDN configuration files
67 | │ │ ├── defaults.edn Always applied (but always used as a base for config merge)
68 | │ │ ├── dev.edn Dev-mode config (auto-selected by user.clj setup)
69 | │ │ └── prod.edn Production-mode config. Selected via -Dconfig=config/prod.edn
70 | │ └── untangled_template
71 | │ ├── api
72 | │ │ ├── mutations.clj Server-side Om mutations
73 | │ │ └── read.clj Server-side Om queries
74 | │ ├── core.clj Server-side entry point for production mode
75 | │ └── system.clj Server-side system configuration (shared for dev and production)
76 | ```
77 |
78 | ## Setting up Run Configurations (IntelliJ)
79 |
80 | Add a figwheel config:
81 |
82 |
83 |
84 | Add a server config:
85 |
86 |
87 |
88 | Then run *both* from IntelliJ.
89 |
90 | ## Using from other editors
91 |
92 | See the Makefile for useful command-line targets, which are useful for
93 | when working from a lower-level system editor.
94 |
95 | The simplest approach is to start a REPL:
96 |
97 | ```
98 | lein repl
99 | ```
100 |
101 | *You will need two REPLs*: one for the server, and one for you dev builds of the client.
102 |
103 | There is a pre-supplied function named `start-figwheel` that will start the cljs builds and figwheel hot code push.
104 |
105 | ## Using the server
106 |
107 | In the server REPL, start the server with:
108 |
109 | ```
110 | (go)
111 | ```
112 |
113 | To reload the server code:
114 |
115 | ```
116 | (reset)
117 | ```
118 |
119 | IF your compile fails, Recompile after failed compile:
120 |
121 | ```
122 | (refresh)
123 | (go)
124 | ```
125 |
126 | If you cannot find `refresh`, try:
127 |
128 | ```
129 | (tools-ns/refresh)
130 | ```
131 |
132 | ## Using the Full Stack App (dev mode)
133 |
134 | Open a browser on:
135 |
136 | ```
137 | http://localhost:3000/index-dev.html
138 | ```
139 |
140 | ## Dev Cards
141 |
142 | Open a browser on:
143 |
144 | ```
145 | http://localhost:3449/cards.html
146 | ```
147 |
148 | ## Specs
149 |
150 | Open a browser on:
151 |
152 | ```
153 | http://localhost:3449/test.html
154 | ```
155 |
156 | ## Continuous Integration Tests
157 |
158 | The project is set up to be able to run both the UI and Server tests from a
159 | standard *NIX command-line (untested with Windows, but works with OSX and
160 | Linux).
161 |
162 | The UI tests use node, karma, and doo to accomplish tests.
163 |
164 | The Makefile has targets for running the various CI tests modes. You
165 | must install Node and NPM. In OSX, Home Brew can make quick work of that.
166 |
167 | ## Makefile
168 |
169 | There is a GNU `Makefile` in the project that can start various command
170 | line interactions. This file is commented so you can see what targets
171 | are valid.
172 |
173 | Example: Run a REPL that is ready to run the Untangled Server:
174 |
175 | ```
176 | make server
177 | ```
178 |
179 | # Deploying
180 |
181 | Build the standalone Jar with:
182 |
183 | ```
184 | lein uberjar
185 | ```
186 |
187 | will build `target/untangled_template.jar`.
188 |
189 | The production `prod.edn` file (in src/config) grabs the web PORT from
190 | the environment (as required by Heroku). So, this jar can be run with:
191 |
192 | ```
193 | export PORT=8080 # the web server port to use
194 | java -Dconfig=config/prod.edn -jar untangled_template.jar
195 | ```
196 |
197 | The `Procfile` gives the correct information to heroku, so if you've
198 | configured the app (see Heroku docs) you should be able to deploy with
199 | git:
200 |
201 | ```
202 | git push heroku master
203 | ```
204 |
--------------------------------------------------------------------------------