├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── dev ├── client │ └── cljs │ │ └── user.cljs └── server │ └── user.clj ├── docs ├── GettingStarted.md └── Overview.md ├── i18n └── msgs │ ├── es_MX.mo │ ├── es_MX.po │ └── messages.pot ├── package.json ├── project.clj ├── resources ├── config │ └── defaults.edn └── public │ ├── cards.html │ ├── css │ ├── app.css │ ├── edn.css │ └── test.css │ ├── index.html │ └── test.html ├── script └── figwheel.clj ├── specs ├── client │ └── app │ │ ├── all_tests.cljs │ │ ├── sample_spec.cljs │ │ ├── suite.cljs │ │ └── tests_to_run.cljs ├── server │ └── app │ │ └── server_spec.clj └── shared │ └── .keep └── src ├── cards └── app_cards │ ├── cards_ui.cljs │ └── sample_card.cljs ├── client └── app │ ├── core.cljs │ ├── i18n │ ├── default_locale.cljs │ ├── en_US.cljs │ ├── es_MX.cljs │ └── locales.cljs │ ├── main.cljs │ ├── mutations.cljs │ ├── support_viewer.cljs │ └── ui.cljs ├── server └── app │ ├── api.clj │ ├── main.clj │ └── system.clj └── shared └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | checkouts 2 | [#]* 3 | examples/calendar/src/quiescent_model 4 | .DS_Store 5 | compiled 6 | figwheel_server.log 7 | pom.xml 8 | *jar 9 | lib 10 | classes 11 | out 12 | i18n/out 13 | i18n/*.js 14 | target 15 | .lein-deps-sum 16 | .lein-repl-history 17 | .lein-failures 18 | .lein-plugins/ 19 | .repl 20 | .nrepl-port 21 | .repl* 22 | .cljs_rhino_repl 23 | *.swp 24 | .idea 25 | *.iml 26 | .lein-env 27 | examples/calendar/resources/public/js/specs 28 | examples/todo/src/quiescent_model 29 | resources/public/js 30 | *.bak 31 | node_modules 32 | resources/private/js 33 | TAGS 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you'd like to submit a PR, please follow these general guidelines: 4 | 5 | - Either talk to use about it in Slack, or open a github issue 6 | - Do development against the develop branch (we use git flow). PRs should be directed at the develop branch. Master 7 | is the latest release, not the live development. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 NAVIS 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 7 | persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LEIN_RUN = rlwrap lein run -m clojure.main ./script/figwheel.clj 2 | 3 | dev: 4 | JVM_OPTS="-server -Ddev -Dtest" ${LEIN_RUN} 5 | 6 | test: 7 | JVM_OPTS="-server -Dtest" ${LEIN_RUN} 8 | 9 | cards: 10 | JVM_OPTS="-server -Dcards" ${LEIN_RUN} 11 | 12 | server: 13 | rlwrap lein run -m clojure.main 14 | 15 | tests: 16 | npm install 17 | lein doo chrome automated-tests once 18 | 19 | help: 20 | @ make -rpn | sed -n -e '/^$$/ { n ; /^[^ ]*:/p; }' | sort | egrep --color '^[^ ]*:' 21 | 22 | .PHONY: dev test cards server tests help 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Untangled Web Framework 2 | 3 | See [the promotional video](https://youtu.be/CoMyszwN50g) for a quick introduction. 4 | 5 | The Untangled web framework is a ClojureScript web framework that blends various web development libraries together 6 | with a good bit of glue code to make single-page webapps a breeze. It is a full-stack environment, but you can 7 | pick and choose the pieces you wish to use. 8 | 9 | NOTE: THIS IS ALPHA SOFTWARE, BASED UPON ALPHA VERSIONS OF Om. THIS IS NOT READY FOR PRODUCTION. 10 | 11 | We're developing this framework as part of our core software stack at NAVIS for production applications. 12 | We will not consider this framework stable until Om becomes stable. 13 | 14 | The full stack framework includes a number of libraries (See [untangled-web](https://github.com/untangled-web) on Github): 15 | 16 | - Untangled Client: A library that builds upon Om 1.0, but greatly simplifies overall application development. 17 | This results in the following core features: 18 | - Co-located UI queries 19 | - The elimination of controller logic 20 | - The elimination of event systems 21 | - Fully synchronous UI model 22 | - One-at-a-time network semantics (no out of order network requests, even in the presence of 23 | optimistic updates) 24 | - Pure functions for rendering 25 | - Internationalization 26 | - GNU gettext-style internationalization. Trivial to use. 27 | - Formatted strings 28 | - Plural support 29 | - Date, time, percent, and number formatting 30 | - Support VCR viewer: Users can submit problem reports that include a configurable number of replayable UI frames so 31 | that support can diagnose issues by watching what the user saw. Including server timestamps for log correlation. 32 | - A dramatically simpler overall application 33 | - Untangled Server: A component-based server 34 | - Uses components for hot reload during development 35 | - Pre-plumbed to support untangled clients with almost no work 36 | - Easy to customize configuration 37 | - Pluggable components for adding databases, handlers, API routes, etc. 38 | - Components can be made easily available to all server logic that processes client requests. 39 | - Untangled Datomic: A plugin component for Untangled Server for Datomic persistence 40 | - Extended schema support 41 | - Schema migration support 42 | - Component for the server to connect any number of databases 43 | - Databases are automatically injected into the processing pipeline 44 | - Untangled Spec: A behavior specification system 45 | - Write tests that read like specifications 46 | - Render the client tests as a specification outline in any number of browsers 47 | - Run client tests via CI systems. 48 | - Run/Render server tests as a specification outline (using test-refresh). 49 | - Human readable data output (pretty printed structure) and data diffs 50 | - Easy-to-use mocking 51 | - Async timeline simulation 52 | - Untangled Lein i18n: A leiningen plugin for extracting/compiling translations (IN PROGRESS: 75%) 53 | - Lein task to extract gettext-style POT files 54 | - Use standard gettext tools like PoEdit to generate translations (.po files) 55 | - Lein task to turn po files into loadable cljs translation modules. 56 | - Untangled Lein Template (IN PROGRESS: 50%, not deployed to clojars) 57 | - A full stack sample application 58 | - Untangled TodoMVC 59 | - An implementation of the standard Todo MVC application. 60 | - Two versions: One client-only. One with full-stack persistence, optimistic updates, support VCR Viewer. 61 | 62 | ## This Repository 63 | 64 | This Repository is meant to house the website files. 65 | 66 | See untangled-todomvc for a complete project. 67 | 68 | -------------------------------------------------------------------------------- /dev/client/cljs/user.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs.user 2 | (:require 3 | [app.core :refer [app]] 4 | [untangled.client.core :as core] 5 | app.i18n.default-locale 6 | app.i18n.locales 7 | [cljs.pprint :refer [pprint]] 8 | [devtools.core :as devtools] 9 | [untangled.client.logging :as log] 10 | [app.ui :as ui])) 11 | 12 | (enable-console-print!) 13 | 14 | ; Use Chrome...these enable proper formatting of cljs data structures! 15 | (devtools/enable-feature! :sanity-hints) 16 | (devtools/install!) 17 | 18 | ; Mount the app and remember it. 19 | (reset! app (core/mount @app ui/Root "app")) 20 | 21 | ; use this from REPL to view bits of the application db 22 | (defn log-app-state 23 | "Helper for logging the app-state, pass in top-level keywords from the app-state and it will print only those 24 | keys and their values." 25 | [& keywords] 26 | (pprint (let [app-state @(:reconciler @app)] 27 | (if (= 0 (count keywords)) 28 | app-state 29 | (select-keys app-state keywords))))) 30 | 31 | ; Om/dev logging level 32 | (log/set-level :none) 33 | -------------------------------------------------------------------------------- /dev/server/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.pprint :refer (pprint)] 4 | [clojure.stacktrace :refer (print-stack-trace)] 5 | [clojure.tools.namespace.repl :refer [disable-reload! refresh clear set-refresh-dirs]] 6 | [clojure.tools.nrepl.server :as nrepl] 7 | [com.stuartsierra.component :as component] 8 | [datomic-helpers :refer [to-transaction to-schema-transaction ext]] 9 | [datomic.api :as d] 10 | [environ.core :refer [env]] 11 | [taoensso.timbre :refer [info set-level!]] 12 | [untangled.datomic.schema :refer [dump-schema dump-entity]] 13 | [clojure.java.io :as io] 14 | [figwheel-sidecar.repl-api :as ra] 15 | [app.system :as sys])) 16 | 17 | ;;FIGWHEEL 18 | 19 | (def figwheel-config 20 | {:figwheel-options {:css-dirs ["resources/public/css"] 21 | :open-file-command "/Users/tonykay/projects/team/scripts/figwheel-intellij.sh"} 22 | :build-ids ["dev" "test" "cards"] 23 | :all-builds (figwheel-sidecar.repl/get-project-cljs-builds)}) 24 | 25 | (defn start-figwheel 26 | "Start Figwheel on the given builds, or defaults to build-ids in `figwheel-config`." 27 | ([] 28 | (let [props (System/getProperties) 29 | all-builds (->> figwheel-config :all-builds (mapv :id))] 30 | (start-figwheel (keys (select-keys props all-builds))))) 31 | ([build-ids] 32 | (let [default-build-ids (:build-ids figwheel-config) 33 | build-ids (if (empty? build-ids) default-build-ids build-ids)] 34 | (println "STARTING FIGWHEEL ON BUILDS: " build-ids) 35 | (ra/start-figwheel! (assoc figwheel-config :build-ids build-ids)) 36 | (ra/cljs-repl)))) 37 | 38 | ;;SERVER 39 | 40 | (set-refresh-dirs "dev/server" "src/server" "src/shared" "specs/server" "specs/shared") 41 | 42 | (defonce system (atom nil)) 43 | 44 | (set-level! :info) 45 | 46 | (defn init 47 | "Create a web server from configurations. Use `start` to start it." 48 | [] 49 | (reset! system (sys/make-system))) 50 | 51 | (defn start "Start (an already initialized) web server." [] (swap! system component/start)) 52 | (defn stop "Stop the running web server." [] 53 | (swap! system component/stop) 54 | (reset! system nil)) 55 | 56 | (defn go "Load the overall web server system and start it." [] 57 | (init) 58 | (start)) 59 | 60 | (defn reset 61 | "Stop the web server, refresh all namespace source code from disk, then restart the web server." 62 | [] 63 | (stop) 64 | (refresh :after 'user/go)) 65 | 66 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Untangled leverages the concepts defined by [Om](http://www.github.com/omcljs/om). As such, there 4 | are a few concepts that you should understand from that system. Untangled is intended to 5 | fill in a number of gaps where Om does not have an opinion, and does make a number 6 | of concrete decisions the dramatically reduce the overhead of getting a full stack webapp going. 7 | 8 | You should watch the tour video to get an some overall exposure to the 9 | pieces. In order to build a full-stack app there are a lot of pieces to 10 | understand. 11 | 12 | Specifically, you must understand the following to get anywhere: 13 | 14 | - Basic Clojure/ClojureScript 15 | - How to set up a project 16 | - The default Om client application graph database format 17 | - Co-located queries on the UI, and how those relate to the database format 18 | - How to set up a basic Untangled Client 19 | 20 | Interestingly, you don't have to write much of the standard Om plumbing 21 | (parsers, networking code, etc). 22 | 23 | With these topics under your belt you'll be able to generate 24 | a general application that appears on the screen and can interact with 25 | the user. 26 | 27 | From there, these additions: 28 | 29 | - How to make an untangled server 30 | - How to respond to queries in the server code 31 | - How to handle mutations in the server code 32 | 33 | will get you very quickly to a full-stack application. 34 | 35 | ## Setting up the Project 36 | 37 | Our project template is under development. Here is the list of features 38 | we want in any project: 39 | 40 | - Server and client tests 41 | - Devtools integration with Google Chrome (formats cljs data structures) 42 | - Figwheel hot code push and hot CSS reload 43 | - A server that can be edited, fixed, restarted with no delays 44 | 45 | We recommend using IntelliJ with Cursive, and these instructions 46 | stress those tools. 47 | 48 | ### The figwheel builds 49 | 50 | There are three builds we recommend for development: 51 | 52 | - dev: Client development build that includes debugging helpers/tools 53 | - cards: A Devcards build, useful for playing with UI components 54 | - test: A build of cljs tests that run in a browser 55 | 56 | We also need to be able to do a production build that elides the extras 57 | in the dev build. 58 | 59 | The critical files for this are organized as follows: 60 | 61 | ``` 62 | resources 63 | ├── config 64 | │   └── defaults.edn ; Web server configuration defaults 65 | └── public 66 | ├── cards.html ; Devcards HTML file 67 | ├── css 68 | │   ├── app.css ; Application CSS 69 | │   ├── edn.css ; Spec rendering CSS 70 | │   └── test.css 71 | ├── index.html ; Main Application HTML 72 | └── test.html ; Spec Running HTML 73 | src 74 | ├── cards 75 | │   └── app_cards 76 | │   ├── cards_ui.cljs ; Devcards entry point (must require all other card ns) 77 | │   └── sample_card.cljs ; Sample Devcard 78 | ├── client 79 | │   └── app 80 | │   ├── core.cljs ; Client creation/initial loading code 81 | │   ├── i18n ; generated i18n code 82 | │   ├── main.cljs ; Production entry point 83 | │   ├── mutations.cljs ; All client mutations to the app state 84 | │   ├── support_viewer.cljs ; A VCR Support viewer 85 | │   └── ui.cljs ; The UI 86 | ├── server 87 | │   └── app 88 | │   ├── api.clj ; The queries and mutations on the server 89 | │   ├── main.clj ; Production web server entry point 90 | │   └── system.clj ; Generation of the web server itself 91 | └── shared ; Location for cljc files 92 | dev 93 | ├── client 94 | │   └── cljs 95 | │   └── user.cljs ; Development mode client entry point 96 | └── server 97 | └── user.clj ; Development mode entry for server 98 | script 99 | └── figwheel.clj ; Script to start various figwheel builds via -Dbuild-id 100 | specs 101 | ├── client 102 | │   └── app 103 | │   ├── all_tests.cljs ; CI (command line) test runner entry point for browser tests 104 | │   ├── sample_spec.cljs ; A specification (client) 105 | │   ├── suite.cljs ; The live spec renderer (browser) for dev mode 106 | │   └── tests_to_run.cljs ; The common list of specs in a require (needed to make CI and live both work) 107 | ├── server 108 | │   └── app 109 | │   └── server_spec.clj ; A sample server spec (run via `lein test-refresh`) 110 | └── shared ; Location for shared spec cljc files 111 | i18n 112 | └── msgs ; Generated i18n template, and user-generated translations 113 | ├── es_MX.mo 114 | ├── es_MX.po 115 | └── messages.pot 116 | ``` 117 | 118 | 119 | ## The Om Basics 120 | 121 | You should read the om-tutorial sections on the 122 | [application database](https://awkay.github.io/om-tutorial/#!/om_tutorial.C_App_Database), 123 | [queries](https://awkay.github.io/om-tutorial/#!/om_tutorial.D_Queries), and 124 | [UI queries](https://awkay.github.io/om-tutorial/#!/om_tutorial.E_UI_Queries_and_State). 125 | 126 | ## Setting up an Untangled Client 127 | 128 | Once you have those three relatively simple topics under your belt, 129 | you're ready to build an untangled client! 130 | 131 | Here are the steps: 132 | 133 | 1. Write some UI using Om `defui` with colocated queries (see `src/client/app/ui.cljs`) 134 | 2. Build an initial state database to go with these queries (see `src/client/app/core.cljs`) 135 | 3. Make a client and mount it on the DOM (see `src/client/app/main.cljs` and `core.cljs`) 136 | 137 | That's it! No need to write a parser, a read function, a reconciler, networking code, etc. All 138 | of that is created for you behind the scenes. You're actually ready to 139 | immediately start working on an interactive application without needing 140 | to write any of the glue code. 141 | 142 | 143 | ## Setting up an Untangled Server 144 | 145 | We have ambitious goals on the server, too: 146 | 147 | - Easily configurable 148 | - Injectable components 149 | - Easy to start/stop/reset (refreshing code) 150 | - All you need to write are the specific handling of application 151 | queries and mutations. 152 | 153 | Here are the steps: 154 | 155 | 1. Create a defaults.edn config file 156 | 2. Make a copy of defaults.edn into an external file for local config 157 | 3. Use make-untangled-server and include the external config filename 158 | 159 | The minimal `defaults.edn` should go in `resources/config/`, and should 160 | contain the port the webserver should bind to: 161 | 162 | ``` 163 | { :port 3000 } 164 | ``` 165 | 166 | Creating the server involves making a server-side request parser and 167 | adding it to a server. 168 | 169 | ### Server API (queries and mutations): 170 | 171 | See `src/server/app/api.clj` 172 | 173 | ### Server Definition 174 | 175 | See `src/server/app/system.clj` 176 | 177 | ### Server Entry Point 178 | 179 | For REPL development mode, we support a `dev/server/user.clj` that can be used to start and reload the server 180 | on demand. 181 | 182 | For production, the main is simple and is in `src/server/app/main.clj`. 183 | 184 | ## Internationalization 185 | 186 | You can get started really fast with the i18n support. Basically, just use 187 | the `tr`, `trf`, and `trc` macros on raw strings. If you want to translate 188 | something in a variable you must make sure that the raw string itself is 189 | somewhere in a `tr` (so it will get extracted). If doing that, there is 190 | `tr-unsafe` (named that because it accepts non-literal strings that 191 | are not guaranteed to be translated). 192 | 193 | -------------------------------------------------------------------------------- /docs/Overview.md: -------------------------------------------------------------------------------- 1 | # Untangled 2 | 3 | ## Quick Tour (Demo) 4 | 5 | Demo of TodoMVC with Datomic server-side integration, internationalized, 6 | VCR viewer, tour of code base and development experience. 7 | 8 | ## The Client 9 | 10 | ### The Database (2 slides) 11 | 12 | - Om standard format (graph database) 13 | - Plain cljs data (maps and vectors) 14 | - Tables are maps keyed by user-defined "object" ID 15 | - Idents used to create links 16 | - Leaves of the tree can be anything (functions should be avoided) 17 | - See online Om Tutorial for more info/exercises 18 | 19 | ### The UI (4 slides) 20 | 21 | - Om standard UI components 22 | - Generates plain React components 23 | - Co-located Queries for data 24 | - Queries compose to root 25 | - Components can have idents, which defines table/ID of data 26 | 27 | ### Mutations (3 slides) 28 | 29 | - Om standard mutations 30 | - All changes to database happen through top-level, abstract, transactions 31 | - Predefined mutate multimethod: simply use `defmethod` 32 | - Optimistic updates and remote interactions under a single abstraction 33 | - Extension for `post-mutate` hook (to trigger common behaviors). Multimethod. 34 | 35 | ### Internationalization (3 slides) 36 | 37 | - GNU gettext-style internationalization support 38 | - `tr` : English string is key 39 | - `trf` : Formatted (plural support, date, time, numerics) 40 | - Formatting based on Yahoo's formatjs 41 | - POT file generated from source code (cljs to js, then standard xgettext) 42 | - Translations edited with standard tools, like PoEdit 43 | - Module support for dynamically loading translations at runtime 44 | 45 | ## The Server (2 slides) 46 | 47 | - Based on Stuart Sierra's component library 48 | - Includes components for: logging, config, static resource handling, 49 | and a web server (currently httpkit) 50 | - Can easily extend/inject your own components 51 | 52 | ### Configuration (1 slide) 53 | 54 | - Configuration via EDN 55 | - Defaults file in project 56 | - Custom-named external file overrides 57 | - Command-line augmentation (e.g. name alt file) 58 | 59 | ### Injectable Server Components (1 slide) 60 | 61 | - Define a component 62 | - Add it to server 63 | - Can automatically inject it in server-side read/mutations 64 | - Can augment Ring handler 65 | 66 | ### Datomic Integration (2 slides) 67 | 68 | - Separate add-on library to create/support Datomic databases 69 | - Migration support 70 | - Seeding support (for testing/development) 71 | - Optional extra schema validation 72 | 73 | ## The Full Stack 74 | 75 | ### Remoting (6 slides) 76 | 77 | - All network plumbing pre-built 78 | - One-at-a-time processing semantics 79 | - Full optimistic update (UI instantly responsive) 80 | - Pluggable network components 81 | - Support for unhappy path (network down, server errors) 82 | - Query attributes namespaced to `ui` are automatically elided 83 | - Complete tempid handling 84 | - Rewritten in state *and* network request queue 85 | - Smart merging: 86 | - Deep merge is the default 87 | - Automatic handling of multi-source merges: A/B ask for different 88 | attributes of the same object...when to stomp vs merge? 89 | 90 | ### Initial Load (1 slide) 91 | 92 | - All loads are explicit 93 | - Initial loads triggered via startup callback 94 | - Queries based on UI or just custom queries 95 | - Post-processing Support 96 | - UI query filtering (e.g. `:without`) 97 | 98 | ### Lazy Loading (4 slides) 99 | 100 | - Lazy loads can be triggered at any time 101 | - Can be field-based if the component has an Ident. Generates the query for you. 102 | - Supports callback post-processing, `:without`, etc. 103 | - Places marker in state at proper location for "spinner" rendering (UI helper) 104 | - Seeing marker requires that you query for `:ui/fetch-state` 105 | 106 | ### Testing (4 slides) 107 | 108 | - Helpers and rendering stacked on clj/cljs test 109 | - Outline-based rendering 110 | - Human readable data diffs and output 111 | - Advanced mocking 112 | - Timeline simulation for testing async behaviors 113 | 114 | #### Protocol Testing (2 slides) 115 | 116 | - Gives end-to-end testing without the integration pain 117 | - Proof of correctness through shared data 118 | 119 | ### Support VCR Viewer (1 slide) 120 | 121 | - Allows an end user to submit a problem report that includes a recording of 122 | their session 123 | - Play forward/backward 124 | - Timestamps of interactions 125 | - Server error recording (with server timestamps) 126 | 127 | ## Dev Tools (2 slides) 128 | 129 | - Click-to-edit components 130 | - Database browser/query tools 131 | 132 | -------------------------------------------------------------------------------- /i18n/msgs/es_MX.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untangled-web/untangled/705220539ff00b1e487ae387def11daad8638206/i18n/msgs/es_MX.mo -------------------------------------------------------------------------------- /i18n/msgs/es_MX.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2016-03-02 16:44-0800\n" 11 | "PO-Revision-Date: 2016-03-02 16:46-0800\n" 12 | "Language-Team: \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "X-Generator: Poedit 1.8.7\n" 17 | "Last-Translator: \n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "Language: es_MX\n" 20 | 21 | #: i18n/app.js:28107 22 | msgid "<===>" 23 | msgstr "" 24 | 25 | #: i18n/app.js:28109 26 | msgid "" 31 | msgstr "" 32 | 33 | #: i18n/app.js:28111 34 | msgid "Frame {f,number} of {end,number} " 35 | msgstr "" 36 | 37 | #: i18n/app.js:28111 38 | msgid "{ts,date,short} {ts,time,long}" 39 | msgstr "" 40 | 41 | #: i18n/app.js:28186 42 | msgid "By {author}" 43 | msgstr "Por {author}" 44 | 45 | #: i18n/app.js:28206 46 | msgid "Show comments" 47 | msgstr "Mostrar comentarios" 48 | -------------------------------------------------------------------------------- /i18n/msgs/messages.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-03-02 16:42-0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: i18n/app.js:28107 21 | msgid "<===>" 22 | msgstr "" 23 | 24 | #: i18n/app.js:28109 25 | msgid "" 30 | msgstr "" 31 | 32 | #: i18n/app.js:28111 33 | msgid "Frame {f,number} of {end,number} " 34 | msgstr "" 35 | 36 | #: i18n/app.js:28111 37 | msgid "{ts,date,short} {ts,time,long}" 38 | msgstr "" 39 | 40 | #: i18n/app.js:28186 41 | msgid "By {author}" 42 | msgstr "" 43 | 44 | #: i18n/app.js:28206 45 | msgid "Show comments" 46 | msgstr "" 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-spec", 3 | "version": "1.0.0", 4 | "description": "Testing", 5 | "main": "index.js", 6 | "directories": { }, 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "karma": "^0.13.19", 10 | "karma-chrome-launcher": "^0.2.2", 11 | "karma-firefox-launcher": "^0.1.7", 12 | "karma-cljs-test": "^0.1.0" 13 | }, 14 | "author": "", 15 | "license": "NAVIS" 16 | } 17 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject untangled/app "0.0.3" 2 | :description "A sample app" 3 | :url "" 4 | :license {:name "MIT" 5 | :url "https://opensource.org/licenses/MIT"} 6 | 7 | :dependencies [[com.datomic/datomic-free "0.9.5206" :exclusions [joda-time]] 8 | [com.taoensso/timbre "4.3.1"] 9 | [commons-codec "1.10"] 10 | [lein-doo "0.1.6" :scope "test"] 11 | [org.clojure/clojure "1.8.0"] 12 | [org.clojure/clojurescript "1.8.51"] 13 | [org.omcljs/om "1.0.0-alpha31" :exclusions [cljsjs/react]] 14 | [navis/untangled-client "0.4.7-SNAPSHOT" :exclusions [cljsjs/react org.omcljs/om]] 15 | [navis/untangled-server "0.4.5"] 16 | [navis/untangled-datomic "0.4.4" :exclusions [com.datomic/datomic-free org.clojure/tools.cli]] 17 | [navis/untangled-spec "0.3.5" :scope "test" :exclusions [cljsjs/react-with-addons]]] 18 | 19 | :plugins [[lein-cljsbuild "1.1.2"] 20 | [lein-doo "0.1.6" :exclusions [org.clojure/tools.reader]] 21 | [navis/untangled-lein-i18n "0.1.2" :exclusions [org.apache.maven.wagon/wagon-provider-api org.codehaus.plexus/plexus-utils org.clojure/tools.cli]] 22 | [lein-environ "1.0.0"] 23 | [com.jakemccrary/lein-test-refresh "0.14.0"]] 24 | 25 | :test-refresh {:report untangled-spec.reporters.terminal/untangled-report 26 | :with-repl true} 27 | 28 | :source-paths ["src/shared" "src/server"] 29 | :test-paths ["specs/shared" "specs/server" "specs"] 30 | :uberjar-name "app.jar" 31 | :jvm-opts ["-server" "-Xmx1024m" "-Xms512m" "-XX:-OmitStackTraceInFastThrow"] 32 | :clean-targets ^{:protect false} ["resources/public/js/specs" "resources/public/js/compiled" "target" "resources/private/js"] 33 | 34 | :doo {:build "automated-tests" 35 | :paths {:karma "node_modules/karma/bin/karma"}} 36 | 37 | :cljsbuild {:builds 38 | [ 39 | {:id "dev" 40 | :source-paths ["src/client" "src/shared" "dev/client"] 41 | :figwheel true 42 | :compiler {:main cljs.user 43 | :asset-path "js/compiled/dev" 44 | :output-to "resources/public/js/compiled/app.js" 45 | :output-dir "resources/public/js/compiled/dev" 46 | :optimizations :none 47 | :parallel-build false 48 | :verbose false 49 | :recompile-dependents true 50 | :source-map-timestamp true}} 51 | 52 | {:id "cards" 53 | :source-paths ["src/cards" "src/client" "src/shared"] 54 | :figwheel {:devcards true} 55 | :compiler {:main app-cards.cards-ui 56 | :asset-path "js/compiled/cards" 57 | :output-to "resources/public/js/compiled/cards.js" 58 | :output-dir "resources/public/js/compiled/cards" 59 | :parallel-build true 60 | :optimizations :none 61 | :recompile-dependents true 62 | :source-map-timestamp true}} 63 | 64 | {:id "test" 65 | :source-paths ["specs/client" "specs/shared" "src/client" "src/shared"] 66 | :figwheel true 67 | :compiler {:main app.suite 68 | :output-to "resources/public/js/specs/specs.js" 69 | :output-dir "resources/public/js/compiled/specs" 70 | :asset-path "js/compiled/specs" 71 | :parallel-build true 72 | :recompile-dependents true 73 | :optimizations :none}} 74 | 75 | {:id "automated-tests" 76 | :source-paths ["specs/client" "specs/shared" "src/client" "src/shared"] 77 | :compiler {:output-to "resources/private/js/unit-tests.js" 78 | :main app.all-tests 79 | :asset-path "js" 80 | :output-dir "resources/private/js" 81 | :optimizations :none 82 | }} 83 | 84 | {:id "production" 85 | :source-paths ["src/client" "src/shared"] 86 | :compiler {:main app.main 87 | :asset-path "js/compiled/prod" 88 | :output-dir "resources/public/js/compiled/prod" 89 | :optimizations :advanced 90 | :modules {:en-US {:output-to "resources/public/js/en-US.js", :entries #{"app.i18n.en-US"}}, 91 | :es-MX {:output-to "resources/public/js/es-MX.js", :entries #{"app.i18n.es-MX"}}, 92 | :main {:output-to "resources/public/js/app.js", :entries #{"app.main"}}} 93 | }} 94 | 95 | {:id "i18n" 96 | :source-paths ["src/client" "src/shared"] 97 | :compiler {:main app.main 98 | :output-to "i18n/app.js" 99 | :output-dir "i18n/out" 100 | :optimizations :whitespace}}]} 101 | 102 | :figwheel {:css-dirs ["resources/public/css"] 103 | :open-file-command "/Users/tonykay/projects/team/scripts/figwheel-intellij.sh"} 104 | 105 | :untangled-i18n {:default-locale "en-US" 106 | :translation-namespace "app.i18n" 107 | :source-folder "src/client" 108 | :target-build "i18n"} 109 | 110 | :profiles {:uberjar {:prep-tasks ["compile" 111 | ["cljsbuild" "once" "production"]] 112 | :omit-source false 113 | :main app.core 114 | :env {:production true} 115 | :aot :all} 116 | 117 | :dev {:dependencies [[binaryage/devtools "0.5.2" :exclusions [environ]] 118 | [figwheel-sidecar "0.5.3-1" :exclusions [ring/ring-core joda-time org.clojure/tools.reader]] 119 | [com.cemerick/piggieback "0.2.1"] 120 | [org.clojure/tools.nrepl "0.2.12"] 121 | [devcards "0.2.1-6" :exclusions [org.omcljs/om]] 122 | [ring/ring-mock "0.2.0"]] 123 | :source-paths ["dev/server" "src/server" "src/shared" "specs/shared" "specs/server"] 124 | :repl-options {:init-ns user 125 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 126 | :env {:dev true}}}) 127 | -------------------------------------------------------------------------------- /resources/config/defaults.edn: -------------------------------------------------------------------------------- 1 | { :port 3000 } 2 | -------------------------------------------------------------------------------- /resources/public/cards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/public/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 12pt; 3 | } 4 | .locale-selector { 5 | position: fixed; 6 | top: 10px; 7 | right: 10px; 8 | } 9 | 10 | .author { 11 | position: relative; 12 | font-size: 8pt; 13 | margin-left: 4px; 14 | top: 4px; 15 | } 16 | .value { 17 | font-style: italic; 18 | } 19 | 20 | .comments { 21 | display:block; 22 | } 23 | 24 | .comment { 25 | display:block; 26 | padding-left: 6px; 27 | padding-bottom: 8px; 28 | } 29 | 30 | .tabs { 31 | list-style: none; 32 | padding-left: 0px; 33 | } 34 | 35 | .tab { 36 | display: inline; 37 | border: 1px solid gray; 38 | padding: 5px; 39 | text-decoration: underline; 40 | background-color: white; 41 | } 42 | 43 | .active-tab { 44 | background-color: lightgray; 45 | } 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /resources/public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /script/figwheel.clj: -------------------------------------------------------------------------------- 1 | (require '[user :refer [start-figwheel]]) 2 | 3 | (start-figwheel) 4 | -------------------------------------------------------------------------------- /specs/client/app/all_tests.cljs: -------------------------------------------------------------------------------- 1 | (ns app.all-tests 2 | (:require 3 | app.tests-to-run 4 | [doo.runner :refer-macros [doo-all-tests]])) 5 | 6 | (doo-all-tests #".*-spec") 7 | -------------------------------------------------------------------------------- /specs/client/app/sample_spec.cljs: -------------------------------------------------------------------------------- 1 | (ns app.sample-spec 2 | (:require 3 | [untangled-spec.core :refer-macros [specification behavior provided assertions component]] 4 | [cljs.test :refer-macros [is]])) 5 | 6 | 7 | (specification "A Sample Specificaation" 8 | (behavior "Has some items" 9 | (assertions 10 | "Math works" 11 | (+ 1 1) => 2 12 | 13 | "Data structures can be compared" 14 | {:a 2} => {:a 2}))) 15 | 16 | -------------------------------------------------------------------------------- /specs/client/app/suite.cljs: -------------------------------------------------------------------------------- 1 | (ns app.suite 2 | (:require 3 | [untangled-spec.reporters.suite :as ts :include-macros true] 4 | app.tests-to-run 5 | [devtools.core :as devtools])) 6 | 7 | (enable-console-print!) 8 | 9 | (devtools/enable-feature! :sanity-hints) 10 | (devtools/install!) 11 | 12 | (ts/deftest-all-suite sample-specs #".*-spec") 13 | 14 | (sample-specs) 15 | -------------------------------------------------------------------------------- /specs/client/app/tests_to_run.cljs: -------------------------------------------------------------------------------- 1 | (ns app.tests-to-run 2 | (:require 3 | app.sample-spec)) 4 | 5 | ;******************************************************************************** 6 | ; IMPORTANT: 7 | ; For cljs tests to work in CI, we want to ensure the namespaces for all tests are included/required. By placing them 8 | ; here (and depending on them in suite.cljs for dev), we ensure that the all-tests namespace (used by CI) loads 9 | ; everything as well. 10 | ;******************************************************************************** 11 | 12 | -------------------------------------------------------------------------------- /specs/server/app/server_spec.clj: -------------------------------------------------------------------------------- 1 | (ns app.server-spec 2 | (:require [untangled-spec.core :refer [specification provided behavior assertions]])) 3 | 4 | (specification "A Sample Specificaation" 5 | (behavior "Has some items" 6 | (assertions 7 | "Server logic is ok" 8 | (if true 2 1) => 2))) 9 | -------------------------------------------------------------------------------- /specs/shared/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untangled-web/untangled/705220539ff00b1e487ae387def11daad8638206/specs/shared/.keep -------------------------------------------------------------------------------- /src/cards/app_cards/cards_ui.cljs: -------------------------------------------------------------------------------- 1 | (ns app-cards.cards-ui 2 | (:require [devcards.core :as dc :include-macros true] 3 | [app-cards.sample-card])) 4 | 5 | (defn start [] 6 | (dc/start-devcard-ui!)) 7 | 8 | (start) 9 | -------------------------------------------------------------------------------- /src/cards/app_cards/sample_card.cljs: -------------------------------------------------------------------------------- 1 | (ns app-cards.sample-card 2 | (:require 3 | [om.dom :as dom] 4 | [devcards.core :refer-macros [defcard dom-node]])) 5 | 6 | (defcard select-control-sample 7 | "A card rendering a select control" 8 | (fn [state _] 9 | (dom/select #js {:value (:selected @state) 10 | :defaultValue "none" 11 | :onChange (fn [evt] (swap! state assoc :selected (-> evt .-target .-value)))} 12 | (dom/option #js {:key "a" :value "none"} "None") 13 | (dom/option #js {:key "b" :value "1"} "One") 14 | (dom/option #js {:key "c" :value "2"} "Two") 15 | (dom/option #js {:key "d" :value "3"} "Three"))) 16 | {:selected "none"} 17 | {:inspect-data true}) 18 | 19 | -------------------------------------------------------------------------------- /src/client/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require 3 | app.mutations 4 | [untangled.client.core :as uc] 5 | [untangled.i18n :refer-macros [tr trf]] 6 | [untangled.client.data-fetch :as df] 7 | [app.ui :as ui] 8 | [om.next :as om])) 9 | 10 | (defonce app (atom (uc/new-untangled-client 11 | ; can pass an atom, which means you hand normalized it already. Untangled ALWAYS normalizes incoming merged data 12 | :initial-state (atom { 13 | :data-item {42 {:text (tr "Nothing loaded...")}} 14 | 15 | :main {:singleton {:id :singleton 16 | :tab/label (tr "Main") 17 | :tab/type :main 18 | :data-items nil}} 19 | 20 | :settings {:singleton {:id :singleton :tab/label (tr "Settings") :tab/type :settings}} 21 | 22 | :current-tab [:main :singleton] ; switch to [:settings :singleton] to change tabs 23 | }) 24 | ; Called right after the app has loaded and mounted on the DOM 25 | :started-callback 26 | (fn [{:keys [reconciler]}] 27 | ; Load a query from the server into app state, eliding any of the :without keywords (recursively) 28 | (df/load-collection reconciler 29 | [{:data-items (om/get-query ui/DataItem)}] 30 | :without #{:comments} 31 | :post-mutation 'post-initial-load))))) 32 | 33 | -------------------------------------------------------------------------------- /src/client/app/i18n/default_locale.cljs: -------------------------------------------------------------------------------- 1 | (ns app.i18n.default-locale (:require app.i18n.en-US [untangled.i18n.core :as i18n])) 2 | 3 | (reset! i18n/*current-locale* "en-US") 4 | 5 | (swap! i18n/*loaded-translations* #(assoc % :en-US app.i18n.en-US/translations)) -------------------------------------------------------------------------------- /src/client/app/i18n/en_US.cljs: -------------------------------------------------------------------------------- 1 | (ns app.i18n.en-US (:require untangled.i18n.core) (:import goog.module.ModuleManager)) 2 | 3 | ;; This file was generated by untangled's i18n leiningen plugin. 4 | 5 | (def translations {}) 6 | 7 | (swap! 8 | untangled.i18n.core/*loaded-translations* 9 | (fn [x] (assoc x "en-US" translations))) 10 | 11 | (if 12 | (exists? js/i18nDevMode) 13 | :noop 14 | (-> goog.module.ModuleManager .getInstance (.setLoaded "en-US"))) -------------------------------------------------------------------------------- /src/client/app/i18n/es_MX.cljs: -------------------------------------------------------------------------------- 1 | (ns app.i18n.es-MX (:require untangled.i18n.core) (:import goog.module.ModuleManager)) 2 | 3 | ;; This file was generated by untangled's i18n leiningen plugin. 4 | 5 | (def 6 | translations 7 | {"|<===>" "", 8 | "|" "", 10 | "|Frame {f,number} of {end,number} " "", 11 | "|{ts,date,short} {ts,time,long}" "", 12 | "|By {author}" "Por {author}", 13 | "|Show comments" "Mostrar comentarios"}) 14 | 15 | (swap! 16 | untangled.i18n.core/*loaded-translations* 17 | (fn [x] (assoc x "es-MX" translations))) 18 | 19 | (if 20 | (exists? js/i18nDevMode) 21 | :noop 22 | (-> goog.module.ModuleManager .getInstance (.setLoaded "es-MX"))) -------------------------------------------------------------------------------- /src/client/app/i18n/locales.cljs: -------------------------------------------------------------------------------- 1 | (ns 2 | app.i18n.locales 3 | (:require 4 | goog.module 5 | goog.module.ModuleLoader 6 | [goog.module.ModuleManager :as module-manager] 7 | [untangled.i18n.core :as i18n] 8 | app.i18n.en-US 9 | app.i18n.es-MX) 10 | (:import goog.module.ModuleManager)) 11 | 12 | (defonce manager (module-manager/getInstance)) 13 | 14 | (defonce modules #js {"en-US" "/en-US.js", "es-MX" "/es-MX.js"}) 15 | 16 | (defonce module-info #js {"en-US" [], "es-MX" []}) 17 | 18 | (defonce ^:export loader (let [loader (goog.module.ModuleLoader.)] (.setLoader manager loader) (.setAllModuleInfo manager module-info) (.setModuleUris manager modules) loader)) 19 | 20 | (defn set-locale [l] (js/console.log (str "LOADING ALTERNATE LOCALE: " l)) (if (exists? js/i18nDevMode) (do (js/console.log (str "LOADED ALTERNATE LOCALE in dev mode: " l)) (reset! i18n/*current-locale* l)) (.execOnLoad manager l (fn after-locale-load [] (js/console.log (str "LOADED ALTERNATE LOCALE: " l)) (reset! i18n/*current-locale* l))))) -------------------------------------------------------------------------------- /src/client/app/main.cljs: -------------------------------------------------------------------------------- 1 | (ns app.main 2 | (:require [app.ui :as ui] 3 | [app.core :as core] 4 | app.i18n.default-locale 5 | app.mutations 6 | [untangled.client.core :as uc])) 7 | 8 | ; PRODUCTION entry point. Not used during development mode. Simply mounts the app on the DOM. 9 | 10 | ; The reset is so we know the mounted app. Mounting an already mounted app will just re-render it. See ALSO dev/client/cljs/user.cljs 11 | (reset! core/app (uc/mount @core/app ui/Root "app")) 12 | -------------------------------------------------------------------------------- /src/client/app/mutations.cljs: -------------------------------------------------------------------------------- 1 | (ns app.mutations 2 | (:require [untangled.client.mutations :as m])) 3 | 4 | ; Untangled comes with some built-in mutations. Extend them like this (good idea to use namespaced symbols): 5 | 6 | (defmethod m/mutate 'nav/change-tab [{:keys [state]} k {:keys [target]}] 7 | {:action (fn [] (swap! state assoc :current-tab [target :singleton]))}) 8 | -------------------------------------------------------------------------------- /src/client/app/support_viewer.cljs: -------------------------------------------------------------------------------- 1 | (ns app.support-viewer 2 | (:require [untangled.client.core :as core] 3 | [untangled.support-viewer :as viewer] 4 | [app.ui :as ui] 5 | [devtools.core :as devtools])) 6 | 7 | (defonce cljs-build-tools 8 | (do (devtools/enable-feature! :sanity-hints) 9 | (devtools.core/install!))) 10 | 11 | ; The support viewed expects an id parameter in the URL to indicate which case to load from the server. You must write 12 | ; server code to persist and fetch these via whatever data store you've chosen. 13 | (viewer/start-untangled-support-viewer "support" ui/Root "app") 14 | -------------------------------------------------------------------------------- /src/client/app/ui.cljs: -------------------------------------------------------------------------------- 1 | (ns app.ui 2 | (:require [om.dom :as dom] 3 | [om.next :as om :refer-macros [defui]] 4 | [untangled.i18n :refer-macros [tr trf]] 5 | yahoo.intl-messageformat-with-locales 6 | [untangled.client.data-fetch :as df])) 7 | 8 | ; Standard Om UI code. 9 | 10 | ; NOTE: Data fetch helpers load-singleton, load-collection, and load-field allow for easy lazy loading based on UI 11 | ; queries that can be triggered via events. These do transact! underneath, and should work find. 12 | 13 | (defui ^:once Comment 14 | static om/IQuery 15 | ; ANY property namespaced to :ui/... will NEVER appear in a server query. They are automatically elided in the plubming 16 | ; for your convenience. This allows you to use things like :ui/visible to store state in the app state without having 17 | ; to use local component state or deal with the server not wanting to see it in queries. 18 | ; See also data-fetch mutation helpers like `df/set-string!` (MEANT ONLY for UI attributes) 19 | (query [this] [:ui/fetch-state :id :author :text]) 20 | static om/Ident 21 | (ident [this props] [:comment/by-id (:id props)]) 22 | Object 23 | (render [this] 24 | (let [{:keys [id author text]} (om/props this)] 25 | (dom/div #js {:className "comment"} 26 | (dom/span #js {:className "value"} text) 27 | ;; i18n is as simple as wrapping strings in tr or trf and leveraging the i18n lein plugin! 28 | (dom/span #js {:className "author"} (trf "By {author}" :author author)))))) 29 | 30 | (def ui-comment (om/factory Comment {:keyfn :id})) 31 | 32 | (defn render-comments [comments] (mapv ui-comment comments)) 33 | 34 | (defui ^:once DataItem 35 | static om/IQuery 36 | (query [this] [:ui/fetch-state :db/id :item/text {:item/comments (om/get-query Comment)}]) 37 | static om/Ident 38 | (ident [this props] [:data-item (:db/id props)]) 39 | Object 40 | (render [this] 41 | (let [{:keys [item/text item/comments]} (om/props this)] 42 | (dom/div nil 43 | (dom/span nil text) 44 | (dom/div #js {:className "comments"} 45 | (when (nil? comments) 46 | ; LAZY LOAD the comments. The original load elided comments. load-field derives the query from the UI, 47 | ; roots it via the component's ident, and places state markers in the app state (so you can render spinners/loading). 48 | ; lazily-loaded (read the source) is an example of how to leverage this. 49 | ; IMPORTANT: The component to load MUST include :ui/fetch-state in order for you to see these state markers. 50 | (dom/button #js {:className "show-button" :onClick #(df/load-field this :item/comments)} (tr "Show comments"))) 51 | (df/lazily-loaded render-comments comments)))))) 52 | 53 | (def ui-data-item (om/factory DataItem {:keyfn :db/id})) 54 | 55 | (defui ^:once SettingsTab 56 | static om/IQuery 57 | (query [this] [:id :tab/type :tab/label]) 58 | Object 59 | (render [this] 60 | (dom/div nil 61 | (dom/h1 nil "Settings") 62 | (dom/p nil "Settings go here...")))) 63 | 64 | (def ui-settings-tab (om/factory SettingsTab)) 65 | 66 | (defn render-data-items [items] (map ui-data-item items)) 67 | 68 | (defui ^:once MainTab 69 | static om/IQuery 70 | (query [this] [:id :tab/type :tab/label {:data-items (om/get-query DataItem)}]) 71 | Object 72 | (render [this] 73 | (let [{:keys [data-items]} (om/props this)] 74 | (dom/div nil 75 | (dom/h1 nil (tr "Main: Data Items")) 76 | (df/lazily-loaded render-data-items data-items))))) 77 | 78 | (def ui-main-tab (om/factory MainTab)) 79 | 80 | (defui ^:once Tab 81 | static om/IQuery 82 | (query [this] {:main (om/get-query MainTab) :settings (om/get-query SettingsTab)}) 83 | static om/Ident 84 | (ident [this props] [(:tab/type props) (:id props)]) 85 | Object 86 | (render [this] 87 | (let [{:keys [id tab/type] :as props} (om/props this)] 88 | (case type 89 | :main (ui-main-tab props) 90 | :settings (ui-settings-tab props) 91 | (dom/div nil "MISSING TAB"))))) 92 | 93 | (def ui-tab (om/factory Tab)) 94 | 95 | (defui ^:once Root 96 | static om/IQuery 97 | (query [this] [:ui/locale :ui/react-key {:current-tab (om/get-query Tab)}]) 98 | Object 99 | (render [this] 100 | (let [{:keys [current-tab ui/locale ui/react-key] :or {ui/react-key "ROOT"} :as props} (om/props this) 101 | tab (:tab/type current-tab)] 102 | (dom/div #js {:key react-key} ; needed for forced re-render to work on locale changes and hot reload 103 | (dom/div nil 104 | (dom/ul #js {:className "tabs"} 105 | (dom/li #js {:className (str "tab" (if (= tab :main) " active-tab"))} (dom/a #js {:onClick #(om/transact! this '[(nav/change-tab {:target :main})])} (tr "Main"))) 106 | (dom/li #js {:className (str "tab" (if (= tab :settings) " active-tab"))} (dom/a #js {:onClick #(om/transact! this '[(nav/change-tab {:target :settings})])} (tr "Settings")))) 107 | (ui-tab current-tab)) 108 | 109 | ;; the build in mutation for setting locale triggers re-renders of translated strings 110 | (dom/select #js {:className "locale-selector" :value locale :onChange (fn [evt] (om/transact! this `[(ui/change-locale {:lang ~(.. evt -target -value)})]))} 111 | (dom/option #js {:value "en-US"} "English") 112 | (dom/option #js {:value "es-MX"} "Español")) 113 | )))) 114 | -------------------------------------------------------------------------------- /src/server/app/api.clj: -------------------------------------------------------------------------------- 1 | (ns app.api 2 | (:require [om.next.server :as om] 3 | [taoensso.timbre :as timbre])) 4 | 5 | (defmulti apimutate om/dispatch) 6 | 7 | ;; your entry point for handling mutations. Standard Om mutate handling. All plumbing is taken care of. UNLIKE Om, if you 8 | ; return :tempids from your :action, they will take effect on the client automatically without post-processing. 9 | (defmethod apimutate :default [e k p] 10 | (timbre/error "Unrecognized mutation " k)) 11 | 12 | ;; your query entry point (feel free to make multimethod). Standard Om fare here. 13 | (defn api-read [{:keys [ast query] :as env} dispatch-key params] 14 | (Thread/sleep 10) 15 | (case dispatch-key 16 | :data-items {:value [{:db/id 1 :item/text "Data Item 1"} 17 | {:db/id 2 :item/text "Data Item 2"}]} 18 | :data-item (let [{:keys [key]} ast] 19 | (if (= (second key) 1) 20 | {:value {:item/comments [{:id 1 :text "Hi there!" :author "Sam"} 21 | {:id 2 :text "Hooray!" :author "Sally"} 22 | {:id 3 :text "La de da!" :author "Mary"}]}} 23 | {:value {:item/comments [{:id 4 :text "Ooops!" :author "Sam"}]}})) 24 | (timbre/error "Unrecognized query for " dispatch-key " : " query))) 25 | -------------------------------------------------------------------------------- /src/server/app/main.clj: -------------------------------------------------------------------------------- 1 | (ns app.main 2 | (:require 3 | [com.stuartsierra.component :as component] 4 | [app.system :as sys] 5 | [untangled.server.core :as c] 6 | [untangled.server.impl.components.config :refer [load-config]]) 7 | (:gen-class)) 8 | 9 | ; Production entry point. 10 | 11 | (defn -main 12 | "Main entry point for the server" 13 | [& args] 14 | (let [system (sys/make-system)] 15 | (component/start system))) 16 | -------------------------------------------------------------------------------- /src/server/app/system.clj: -------------------------------------------------------------------------------- 1 | (ns app.system 2 | (:require 3 | [untangled.server.core :as core] 4 | [app.api :as api] 5 | [om.next.server :as om] 6 | [taoensso.timbre :as timbre])) 7 | 8 | ;; IMPORTANT: Remember to load all multi-method namespaces to ensure all of the methods are defined in your parser! 9 | 10 | (defn logging-mutate [env k params] 11 | (timbre/info "Mutation Request: " k) 12 | (api/apimutate env k params)) 13 | 14 | ; build the server 15 | (defn make-system [] 16 | (core/make-untangled-server 17 | ; where you want to store your override config file 18 | :config-path "/usr/local/etc/app.edn" 19 | ; Standard Om parser 20 | :parser (om/parser {:read api/api-read :mutate logging-mutate}) 21 | ; The keyword names of any components you want auto-injected into the parser env (e.g. databases) 22 | :parser-injections #{} 23 | ; Additional components you want added to the server 24 | :components {})) 25 | -------------------------------------------------------------------------------- /src/shared/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untangled-web/untangled/705220539ff00b1e487ae387def11daad8638206/src/shared/.keep --------------------------------------------------------------------------------