├── .bowerrc ├── .gitignore ├── .midje.clj ├── LICENSE ├── Procfile ├── README.md ├── bower.json ├── build-vendor ├── .gitignore ├── main.css ├── main.js └── package.json ├── code_of_conduct.md ├── env ├── dev │ ├── clj │ │ └── fractalify │ │ │ ├── dev.clj │ │ │ └── user.clj │ └── cljs │ │ └── fractalify │ │ └── main.cljs ├── prod │ ├── clj │ │ └── fractalify │ │ │ └── prod.clj │ └── cljs │ │ └── fractalify │ │ └── main.cljs └── test │ ├── js │ ├── polyfill.js │ └── unit-test.js │ └── unit-test.html ├── fractalify.iml ├── npm-debug.log ├── project.clj ├── resources ├── index.html ├── log4j.properties ├── mail-templates │ ├── forgot-password.html │ └── join.html └── public │ ├── css │ └── style.css │ ├── img │ ├── favicon.ico │ └── fractalify.svg │ └── vendor │ ├── mdi │ ├── .bower.json │ ├── bower.json │ ├── css │ │ ├── materialdesignicons.css │ │ ├── materialdesignicons.css.map │ │ ├── materialdesignicons.min.css │ │ └── materialdesignicons.min.css.map │ ├── fonts │ │ ├── materialdesignicons-webfont.eot │ │ ├── materialdesignicons-webfont.svg │ │ ├── materialdesignicons-webfont.ttf │ │ ├── materialdesignicons-webfont.woff │ │ └── materialdesignicons-webfont.woff2 │ ├── license.txt │ ├── package.json │ ├── preview.html │ └── scss │ │ ├── _core.scss │ │ ├── _extras.scss │ │ ├── _icons.scss │ │ ├── _path.scss │ │ ├── _variables.scss │ │ └── materialdesignicons.scss │ ├── vendor.js │ └── vendor.min.js ├── sql-transactor.properties ├── src ├── clj │ ├── fractalify │ │ ├── api │ │ │ ├── api.clj │ │ │ ├── fractals │ │ │ │ ├── fractals_db.clj │ │ │ │ ├── fractals_generator.clj │ │ │ │ └── resources.clj │ │ │ ├── main │ │ │ │ └── resources.clj │ │ │ └── users │ │ │ │ ├── resources.clj │ │ │ │ ├── users_db.clj │ │ │ │ └── users_generator.clj │ │ ├── config.clj │ │ ├── figwheel.clj │ │ ├── img_cloud │ │ │ ├── cloudinary.clj │ │ │ ├── img_cloud.clj │ │ │ ├── mock_img_cloud.clj │ │ │ └── protocols.clj │ │ ├── less_watcher.clj │ │ ├── mailers │ │ │ ├── mailer.clj │ │ │ ├── mock_mail_sender.clj │ │ │ ├── protocols.clj │ │ │ └── sendgrid.clj │ │ ├── middlewares.clj │ │ ├── mongodb.clj │ │ ├── readers.clj │ │ ├── router.clj │ │ ├── system.clj │ │ └── tracer_macros.clj │ └── material_ui │ │ └── macros.clj ├── cljc │ └── fractalify │ │ ├── fractals │ │ ├── api_routes.cljc │ │ └── schemas.cljc │ │ ├── main │ │ ├── api_routes.cljc │ │ └── schemas.cljc │ │ ├── users │ │ ├── api_routes.cljc │ │ └── schemas.cljc │ │ ├── utils.cljc │ │ └── workers │ │ └── schemas.cljc ├── cljs │ ├── cljsjs │ │ └── react.cljs │ ├── fractalify │ │ ├── api.cljs │ │ ├── components │ │ │ ├── api_wrap.cljs │ │ │ ├── color_picker.cljs │ │ │ ├── dialog.cljs │ │ │ ├── dialog_action.cljs │ │ │ ├── form.cljs │ │ │ ├── form_input.cljs │ │ │ ├── form_select.cljs │ │ │ ├── gravatar.cljs │ │ │ ├── icon_button_remove.cljs │ │ │ ├── icon_text_button.cljs │ │ │ ├── paper_content.cljs │ │ │ ├── paper_panel.cljs │ │ │ ├── responsive_panel.cljs │ │ │ ├── snackbar.cljs │ │ │ ├── social.cljs │ │ │ ├── tab_anchor.cljs │ │ │ └── text_field.cljs │ │ ├── core.cljs │ │ ├── db.cljs │ │ ├── fractals │ │ │ ├── components │ │ │ │ ├── canvas.cljs │ │ │ │ ├── canvas_controls.cljs │ │ │ │ ├── comments.cljs │ │ │ │ ├── control_text.cljs │ │ │ │ ├── fractal_card_list.cljs │ │ │ │ ├── fractal_create.cljs │ │ │ │ ├── fractal_detail.cljs │ │ │ │ ├── fractal_hints.cljs │ │ │ │ ├── fractal_page_layout.cljs │ │ │ │ ├── fractals_sidebar.cljs │ │ │ │ ├── l_system_operations │ │ │ │ │ ├── cmd.cljs │ │ │ │ │ ├── remove_btn.cljs │ │ │ │ │ ├── rule.cljs │ │ │ │ │ └── tab.cljs │ │ │ │ ├── sidebar_pagination.cljs │ │ │ │ └── star_count.cljs │ │ │ ├── db.cljs │ │ │ ├── handlers.cljs │ │ │ ├── lib │ │ │ │ ├── l_systems.cljs │ │ │ │ └── renderer.cljs │ │ │ ├── routes.cljs │ │ │ └── subs.cljs │ │ ├── ga.cljs │ │ ├── handler_utils.cljs │ │ ├── main │ │ │ ├── components │ │ │ │ ├── about.cljs │ │ │ │ ├── contact.cljs │ │ │ │ ├── footer.cljs │ │ │ │ ├── header.cljs │ │ │ │ ├── home.cljs │ │ │ │ └── sidenav.cljs │ │ │ ├── db.cljs │ │ │ ├── handlers.cljs │ │ │ ├── routes.cljs │ │ │ ├── subs.cljs │ │ │ └── view.cljs │ │ ├── middleware.cljs │ │ ├── router.cljs │ │ ├── styles.cljs │ │ ├── tracer.cljs │ │ ├── users │ │ │ ├── components │ │ │ │ ├── change_pass.cljs │ │ │ │ ├── edit_profile.cljs │ │ │ │ ├── forgot_pass.cljs │ │ │ │ ├── login_join.cljs │ │ │ │ ├── reset_pass.cljs │ │ │ │ └── user_detail.cljs │ │ │ ├── db.cljs │ │ │ ├── handlers.cljs │ │ │ ├── routes.cljs │ │ │ └── subs.cljs │ │ └── validators.cljs │ ├── material_ui │ │ └── core.cljs │ └── workers │ │ ├── core.cljs │ │ └── turtle.cljs ├── cljs�fractalify │ └── fractals │ │ └── handlers.cljs ├── externs.js └── less │ └── style.less ├── system.properties └── test ├── clj └── fractalify │ ├── api.clj │ └── api │ ├── fractals_tests.clj │ ├── main_tests.clj │ └── users_tests.clj └── cljs └── fractalify ├── core-test.cljs └── test-runner.cljs /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "resources/public/vendor/" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | .idea -------------------------------------------------------------------------------- /.midje.clj: -------------------------------------------------------------------------------- 1 | (change-defaults :check-after-creation false) -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/fractalify.jar clojure.main -m fractalify.prod 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fractalify 2 | ##### Not just a clojure example app 3 | ## 4 | 5 | [fractalify.com] is a entertainment and educational webapp for creating & sharing fractal images made via so called [L-systems][1]. 6 | 7 | Originally, I started this app as a little app for practicing lovely language [Clojure][2] & [Clojurescript][3], but over time it somehow became fully usable and nice webapp :) 8 | 9 | Main reason I share this code is that I spent fair amount of time finding out how various libraries work together, so every Clojure beginner can take a quick look into this code if he struggles using same libraries. 10 | 11 | ##### List of notable libraries used in this project: 12 | # 13 | ##### *Clojure:* 14 | * [Ring](https://github.com/ring-clojure/ring) 15 | * [Liberator](http://clojure-liberator.github.io/liberator/) - Awesome stuff for creating REST API 16 | * [HTTP Kit](https://github.com/http-kit/http-kit) - Used as HTTP server 17 | * [Component](https://github.com/stuartsierra/component) - Dependency Injection for Clojure, lovely! 18 | * [Monger](http://clojuremongodb.info/) - Superb Clojure MongoDB client 19 | * [Friend](https://github.com/cemerick/friend) - Authentication library for Clojure/Ring 20 | * [Midje](https://github.com/marick/Midje) - Perfect test framework, used for REST API tests 21 | * [cloudinary_java](https://github.com/cloudinary/cloudinary_java) Java library used to upload images to [Cloudinary](http://cloudinary.com/) 22 | 23 | ##### *Clojurescript*: 24 | * [re-frame](https://github.com/Day8/re-frame) - Reagent Framework for SPAs (so simple, yet so powerful!) 25 | * [Reagent](http://reagent-project.github.io/) - Minimalistic React interface 26 | * [material-ui](http://material-ui.com/#/) - Material Design components for React. See my [library](https://github.com/madvas/cljs-react-material-ui) for simpler adding material-ui your project. Not used here. 27 | * [pushy](https://github.com/kibu-australia/pushy) - HTML5 pushState 28 | * [monet](https://github.com/rm-hull/monet) - JS Canvas interop 29 | * [Figwheel](https://github.com/bhauman/lein-figwheel) Hot loads for cljs 30 | * [cljs-ajax](https://github.com/JulianBirch/cljs-ajax) 31 | * WebWorkers - I haven't used any library for this, because none worked for me. It was quite challenging, you can see the code how I eventually managed to get it working. 32 | 33 | 34 | ##### *Both CLJ & CLJS:* 35 | * [Schema](https://github.com/Prismatic/schema) - Data validation (this is gold!) 36 | * [Plumbing](https://github.com/Prismatic/plumbing) - Utility functions (very useful) 37 | * [Specter](https://github.com/nathanmarz/specter) - Advanced manipulating lists & maps 38 | * [bidi](https://github.com/juxt/bidi) - Server & Client side routing 39 | * [Transit Format](https://github.com/cognitect/transit-format) - Format used between client & server 40 | 41 | Infinite thanks to creators or Clojure and creators of all these amazing libraries! 42 | Feel free to use code in any way or if you know how to improve it, please let me know. 43 | 44 | Enjoy! 45 | [@matuslestan](https://twitter.com/matuslestan) 46 | 47 | [1]: https://en.wikipedia.org/wiki/L-system 48 | [2]: https://github.com/clojure/clojure 49 | [3]: https://github.com/clojure/clojurescript 50 | [fractalify.com]: http://fractalify.com/ 51 | 52 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fractalify", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/madvas/fractalify", 5 | "authors": [ 6 | "madvas " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "resources/public/vendor/", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "mdi": "~1.1.70" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /build-vendor/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /build-vendor/main.css: -------------------------------------------------------------------------------- 1 | @import url("./node_modules/react-colors-picker/assets/index.css"); 2 | /*@import url("./node_modules/mdi/css/materialdesignicons.css");*/ 3 | -------------------------------------------------------------------------------- /build-vendor/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var React = require('react'), 4 | injectTapEventPlugin = require("react-tap-event-plugin"), 5 | materialUI = require('material-ui'), 6 | reactSocial = require('react-social'), 7 | colorPicker = require('react-colors-picker'); 8 | 9 | require('./main.css'); 10 | 11 | //Needed for React Developer Tools 12 | window.React = React; 13 | window.MaterialUI = materialUI; 14 | window.ColorPicker = colorPicker; 15 | window.ReactSocial = reactSocial; 16 | 17 | //Needed for onTouchTap 18 | //Can go away when react 1.0 release 19 | //Check this repo: 20 | //https://github.com/zilverline/react-tap-event-plugin 21 | injectTapEventPlugin(); 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /build-vendor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-mui", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "js:dev": "browserify -t browserify-css main.js > ../resources/public/vendor/vendor.js", 8 | "js:min": "NODE_ENV=production browserify -t browserify-css main.js | uglifyjs -cm -o ../resources/public/vendor/vendor.min.js", 9 | "start": "npm run js:dev && npm run js:min" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "browserify": "^9.0.3", 15 | "clean-css": "^3.1.4", 16 | "less": "^2.4.0", 17 | "material-ui": "=0.10.4", 18 | "mdi": "^1.2.65", 19 | "react": "^0.13.3", 20 | "react-colors-picker": "=2.0.1", 21 | "react-social": "^0.1.8", 22 | "react-tap-event-plugin": "^0.1.7", 23 | "reactify": "^1.0.0", 24 | "uglify-js": "^2.4.16" 25 | }, 26 | "devDependencies": { 27 | "browserify-css": "^0.6.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect 4 | all people who contribute through reporting issues, posting feature 5 | requests, updating documentation, submitting pull requests or patches, 6 | and other activities. 7 | 8 | We are committed to making participation in this project a 9 | harassment-free experience for everyone, regardless of level of 10 | experience, gender, gender identity and expression, sexual 11 | orientation, disability, personal appearance, body size, race, age, or 12 | religion. 13 | 14 | Examples of unacceptable behavior by participants include the use of 15 | sexual language or imagery, derogatory comments or personal attacks, 16 | trolling, public or private harassment, insults, or other 17 | unprofessional conduct. 18 | 19 | Project maintainers have the right and responsibility to remove, edit, 20 | or reject comments, commits, code, wiki edits, issues, and other 21 | contributions that are not aligned to this Code of Conduct. Project 22 | maintainers who do not follow the Code of Conduct may be removed from 23 | the project team. 24 | 25 | Instances of abusive, harassing, or otherwise unacceptable behavior 26 | may be reported by opening an issue or contacting one or more of the 27 | project maintainers. 28 | 29 | This Code of Conduct is adapted from the 30 | [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, 31 | available at 32 | [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 33 | -------------------------------------------------------------------------------- /env/dev/clj/fractalify/dev.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [clojure.pprint :refer (pprint)] 7 | [clojure.reflect :refer (reflect)] 8 | [clojure.repl :refer (apropos dir doc find-doc pst source)] 9 | [clojure.tools.namespace.repl :as tools-repl] 10 | [com.stuartsierra.component :as c] 11 | [fractalify.system :as sys] 12 | [clojure.java.io :as io] 13 | [fractalify.utils :as u] 14 | [fractalify.config :as cnf] 15 | [modular.bidi :as mb] 16 | [schema.core :as s] 17 | [com.rpl.specter :as e])) 18 | 19 | (defn browser-repl [] 20 | (let [repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001)] 21 | (piggieback/cljs-repl :repl-env repl-env))) 22 | 23 | (def system nil) 24 | (s/set-fn-validation! true) 25 | 26 | (defn new-dev-system 27 | "Create a development system" 28 | [] 29 | (let [config (cnf/config) 30 | s-map (-> (sys/new-system-map config) 31 | (sys/dev-system-map config))] 32 | (-> s-map 33 | (c/system-using 34 | (merge (sys/new-dependency-map) 35 | (sys/dev-dependency-map))) 36 | ))) 37 | 38 | (defn init [init-system] 39 | "Constructs the current development system." 40 | [] 41 | (alter-var-root #'system (constantly init-system))) 42 | 43 | (defn start 44 | "Starts the current development system." 45 | [] 46 | (alter-var-root 47 | #'system 48 | c/start 49 | )) 50 | 51 | (defn stop 52 | "Shuts down and destroys the current development system." 53 | [] 54 | (alter-var-root #'system 55 | (fn [s] (when s (c/stop s))))) 56 | 57 | (defn go 58 | "Initializes the current development system and starts it running." 59 | [] 60 | (println "Go called") 61 | (init (new-dev-system)) 62 | (start) 63 | :ok) 64 | 65 | (defn repl-refresh [] 66 | (tools-repl/refresh :after 'fractalify.dev/go)) 67 | 68 | (defn reset [] 69 | (stop) 70 | (repl-refresh)) 71 | 72 | ;; REPL Convenience helpers 73 | 74 | (defn routes [] 75 | (-> system :router :router)) 76 | 77 | (defn match-route [path] 78 | (bidi.bidi/match-route (routes) path)) 79 | 80 | (defn path-for [path & args] 81 | (apply mb/path-for 82 | (-> system :router) path args)) 83 | 84 | -------------------------------------------------------------------------------- /env/dev/clj/fractalify/user.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.user) 2 | 3 | ;; This is an old trick from Pedestal. When system.clj doesn't compile, 4 | ;; it can prevent the REPL from starting, which makes debugging very 5 | ;; difficult. This extra step ensures the REPL starts, no matter what. 6 | 7 | (defn dev 8 | [] 9 | (require 'fractalify.dev) 10 | (in-ns 'fractalify.dev)) 11 | 12 | (defn go 13 | [] 14 | (println "Don't you mean (dev) ?")) 15 | -------------------------------------------------------------------------------- /env/dev/cljs/fractalify/main.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main 2 | (:require [fractalify.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (figwheel/watch-and-reload 8 | :websocket-url "ws://localhost:3449/figwheel-ws" 9 | :jsload-callback (fn [] 10 | (core/init))) 11 | 12 | ;(weasel/connect "ws://localhost:9001" :verbose true :print #{:repl :console}) 13 | 14 | (core/init) 15 | -------------------------------------------------------------------------------- /env/prod/clj/fractalify/prod.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.prod 2 | (:require [fractalify.system :as sys] 3 | [com.stuartsierra.component :as c]) 4 | (:gen-class)) 5 | 6 | (defn -main [& args] 7 | (println "Starting system...") 8 | (let [system (sys/new-production-system)] 9 | (c/start system) 10 | (println "System started"))) 11 | 12 | -------------------------------------------------------------------------------- /env/prod/cljs/fractalify/main.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main 2 | (:require [fractalify.core :as core])) 3 | 4 | (core/init) 5 | -------------------------------------------------------------------------------- /env/test/js/polyfill.js: -------------------------------------------------------------------------------- 1 | if (!Function.prototype.bind) { 2 | Function.prototype.bind = function(oThis) { 3 | if (typeof this !== 'function') { 4 | // closest thing possible to the ECMAScript 5 5 | // internal IsCallable function 6 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 7 | } 8 | 9 | var aArgs = Array.prototype.slice.call(arguments, 1), 10 | fToBind = this, 11 | fNOP = function() {}, 12 | fBound = function() { 13 | return fToBind.apply(this instanceof fNOP && oThis 14 | ? this 15 | : oThis, 16 | aArgs.concat(Array.prototype.slice.call(arguments))); 17 | }; 18 | 19 | fNOP.prototype = this.prototype; 20 | fBound.prototype = new fNOP(); 21 | 22 | return fBound; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /env/test/js/unit-test.js: -------------------------------------------------------------------------------- 1 | var page = require('webpage').create(); 2 | var url; 3 | 4 | if (phantom.args) { 5 | url = phantom.args[0]; 6 | } else { 7 | url = require('system').args[1]; 8 | } 9 | 10 | page.onConsoleMessage = function (message) { 11 | console.log(message); 12 | }; 13 | 14 | function exit(code) { 15 | setTimeout(function(){ phantom.exit(code); }, 0); 16 | phantom.onError = function(){}; 17 | } 18 | 19 | console.log("Loading URL: " + url); 20 | 21 | page.open(url, function (status) { 22 | if (status != "success") { 23 | console.log('Failed to open ' + url); 24 | phantom.exit(1); 25 | } 26 | 27 | console.log("Running test."); 28 | 29 | var result = page.evaluate(function() { 30 | return fractalify.test_runner.runner(); 31 | }); 32 | 33 | if (result != 0) { 34 | console.log("*** Test failed! ***"); 35 | exit(1); 36 | } 37 | else { 38 | console.log("Test succeeded."); 39 | exit(0); 40 | } 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /env/test/unit-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'node', '/usr/local/bin/npm', 'install' ] 3 | 2 info using npm@2.7.5 4 | 3 info using node@v0.12.2 5 | 4 verbose node symlink /usr/local/bin/node 6 | 5 error install Couldn't read dependencies 7 | 6 verbose stack Error: ENOENT, open '/Users/matus/www/fractalify/package.json' 8 | 6 verbose stack at Error (native) 9 | 7 verbose cwd /Users/matus/www/fractalify 10 | 8 error Darwin 14.4.0 11 | 9 error argv "node" "/usr/local/bin/npm" "install" 12 | 10 error node v0.12.2 13 | 11 error npm v2.7.5 14 | 12 error path /Users/matus/www/fractalify/package.json 15 | 13 error code ENOPACKAGEJSON 16 | 14 error errno -2 17 | 15 error package.json ENOENT, open '/Users/matus/www/fractalify/package.json' 18 | 15 error package.json This is most likely not a problem with npm itself. 19 | 15 error package.json npm can't find a package.json file in your current directory. 20 | 16 verbose exit [ -2, true ] 21 | -------------------------------------------------------------------------------- /resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | fractalify 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 37 | 38 | -------------------------------------------------------------------------------- /resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Based on the example properties given at http://logging.apache.org/log4j/1.2/manual.html 2 | # Set root logger level to DEBUG and its only appender to A1. 3 | log4j.rootLogger=DEBUG, A1 4 | 5 | # A1 is set to be a ConsoleAppender. 6 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 7 | 8 | # A1 uses PatternLayout. 9 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.A1.layout.ConversionPattern= %-5p %c - %m%n 11 | 12 | log4j.logger.httpclient.wire.header=WARN 13 | log4j.logger.httpclient.wire.content=WARN 14 | log4j.logger.org.apache.commons.httpclient=WARN 15 | log4j.logger.org.apache.http=WARN 16 | log4j.logger.org.apache.http.wire=WARN -------------------------------------------------------------------------------- /resources/mail-templates/forgot-password.html: -------------------------------------------------------------------------------- 1 | Hi {{username}}! 2 | 3 | Your password on {{domain}} has been reset. Please visit this link to reset your password: 4 | {{protocol}}{{domain}}/reset-password/{{username}}/{{token}} 5 | 6 | Thank you, enjoy! 7 | -------------------------------------------------------------------------------- /resources/mail-templates/join.html: -------------------------------------------------------------------------------- 1 | Welcome to fractalify -------------------------------------------------------------------------------- /resources/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvas/fractalify/20193e6ff7293af19eb90ed2f40d3f928d9f4ccc/resources/public/img/favicon.ico -------------------------------------------------------------------------------- /resources/public/img/fractalify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | fractalify 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/public/vendor/mdi/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdi", 3 | "version": "1.2.65", 4 | "main": [ 5 | "css/materialdesignicons.css", 6 | "fonts/*", 7 | "css/*", 8 | "scss/*", 9 | "package.json", 10 | "preview.html" 11 | ], 12 | "homepage": "http://materialdesignicons.com", 13 | "authors": [ 14 | { 15 | "name": "Austin Andrews", 16 | "homepage": "http://templarian.com" 17 | }, 18 | { 19 | "name": "Google", 20 | "homepage": "http://www.google.com/design" 21 | } 22 | ], 23 | "license": [ 24 | "OFL-1.1", 25 | "MIT" 26 | ], 27 | "ignore": [ 28 | "*.md", 29 | "*.json" 30 | ], 31 | "keywords": [ 32 | "material", 33 | "design", 34 | "icons", 35 | "webfont" 36 | ], 37 | "_release": "1.2.65", 38 | "_resolution": { 39 | "type": "version", 40 | "tag": "v1.2.65", 41 | "commit": "26c91cdce488fe42876178e37c07e9965356984a" 42 | }, 43 | "_source": "git://github.com/Templarian/MaterialDesign-Webfont.git", 44 | "_target": "~1.2.65", 45 | "_originalSource": "mdi", 46 | "_direct": true 47 | } -------------------------------------------------------------------------------- /resources/public/vendor/mdi/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdi", 3 | "version": "1.2.65", 4 | "main": [ 5 | "css/materialdesignicons.css", 6 | "fonts/*", 7 | "css/*", 8 | "scss/*", 9 | "package.json", 10 | "preview.html" 11 | ], 12 | "homepage": "http://materialdesignicons.com", 13 | "authors": [ 14 | { "name": "Austin Andrews", "homepage": "http://templarian.com" }, 15 | { "name": "Google", "homepage": "http://www.google.com/design" } 16 | ], 17 | "license": ["OFL-1.1", "MIT"], 18 | "ignore": [ 19 | "*.md", 20 | "*.json" 21 | ], 22 | "keywords": [ 23 | "material", 24 | "design", 25 | "icons", 26 | "webfont" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /resources/public/vendor/mdi/fonts/materialdesignicons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvas/fractalify/20193e6ff7293af19eb90ed2f40d3f928d9f4ccc/resources/public/vendor/mdi/fonts/materialdesignicons-webfont.eot -------------------------------------------------------------------------------- /resources/public/vendor/mdi/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvas/fractalify/20193e6ff7293af19eb90ed2f40d3f928d9f4ccc/resources/public/vendor/mdi/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /resources/public/vendor/mdi/fonts/materialdesignicons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvas/fractalify/20193e6ff7293af19eb90ed2f40d3f928d9f4ccc/resources/public/vendor/mdi/fonts/materialdesignicons-webfont.woff -------------------------------------------------------------------------------- /resources/public/vendor/mdi/fonts/materialdesignicons-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvas/fractalify/20193e6ff7293af19eb90ed2f40d3f928d9f4ccc/resources/public/vendor/mdi/fonts/materialdesignicons-webfont.woff2 -------------------------------------------------------------------------------- /resources/public/vendor/mdi/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), 2 | with Reserved Font Name Material Design Icons. 3 | Copyright (c) 2014, Google (http://www.google.com/design/) 4 | uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE 5 | 6 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 7 | This license is copied below, and is also available with a FAQ at: 8 | http://scripts.sil.org/OFL 9 | 10 | 11 | ----------------------------------------------------------- 12 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 13 | ----------------------------------------------------------- 14 | 15 | PREAMBLE 16 | The goals of the Open Font License (OFL) are to stimulate worldwide 17 | development of collaborative font projects, to support the font creation 18 | efforts of academic and linguistic communities, and to provide a free and 19 | open framework in which fonts may be shared and improved in partnership 20 | with others. 21 | 22 | The OFL allows the licensed fonts to be used, studied, modified and 23 | redistributed freely as long as they are not sold by themselves. The 24 | fonts, including any derivative works, can be bundled, embedded, 25 | redistributed and/or sold with any software provided that any reserved 26 | names are not used by derivative works. The fonts and derivatives, 27 | however, cannot be released under any other type of license. The 28 | requirement for fonts to remain under this license does not apply 29 | to any document created using the fonts or their derivatives. 30 | 31 | DEFINITIONS 32 | "Font Software" refers to the set of files released by the Copyright 33 | Holder(s) under this license and clearly marked as such. This may 34 | include source files, build scripts and documentation. 35 | 36 | "Reserved Font Name" refers to any names specified as such after the 37 | copyright statement(s). 38 | 39 | "Original Version" refers to the collection of Font Software components as 40 | distributed by the Copyright Holder(s). 41 | 42 | "Modified Version" refers to any derivative made by adding to, deleting, 43 | or substituting -- in part or in whole -- any of the components of the 44 | Original Version, by changing formats or by porting the Font Software to a 45 | new environment. 46 | 47 | "Author" refers to any designer, engineer, programmer, technical 48 | writer or other person who contributed to the Font Software. 49 | 50 | PERMISSION & CONDITIONS 51 | Permission is hereby granted, free of charge, to any person obtaining 52 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 53 | redistribute, and sell modified and unmodified copies of the Font 54 | Software, subject to the following conditions: 55 | 56 | 1) Neither the Font Software nor any of its individual components, 57 | in Original or Modified Versions, may be sold by itself. 58 | 59 | 2) Original or Modified Versions of the Font Software may be bundled, 60 | redistributed and/or sold with any software, provided that each copy 61 | contains the above copyright notice and this license. These can be 62 | included either as stand-alone text files, human-readable headers or 63 | in the appropriate machine-readable metadata fields within text or 64 | binary files as long as those fields can be easily viewed by the user. 65 | 66 | 3) No Modified Version of the Font Software may use the Reserved Font 67 | Name(s) unless explicit written permission is granted by the corresponding 68 | Copyright Holder. This restriction only applies to the primary font name as 69 | presented to the users. 70 | 71 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 72 | Software shall not be used to promote, endorse or advertise any 73 | Modified Version, except to acknowledge the contribution(s) of the 74 | Copyright Holder(s) and the Author(s) or with their explicit written 75 | permission. 76 | 77 | 5) The Font Software, modified or unmodified, in part or in whole, 78 | must be distributed entirely under this license, and must not be 79 | distributed under any other license. The requirement for fonts to 80 | remain under this license does not apply to any document created 81 | using the Font Software. 82 | 83 | TERMINATION 84 | This license becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 91 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 96 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /resources/public/vendor/mdi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdi", 3 | "version": "1.2.65", 4 | "description": "Dist for Material Design Webfont. This includes the Stock and Community icons in a single webfont collection.", 5 | "main": "preview.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Templarian/MaterialDesign-Webfont.git" 12 | }, 13 | "keywords": [ 14 | "material", 15 | "design", 16 | "icons", 17 | "webfont" 18 | ], 19 | "author": { 20 | "name": "Austin Andrews", 21 | "web": "http://twitter.com/templarian" 22 | }, 23 | "licenses": [ 24 | { 25 | "type": "OFL-1.1", 26 | "url": "http://scripts.sil.org/OFL" 27 | }, 28 | { 29 | "type": "MIT", 30 | "url": "http://opensource.org/licenses/mit-license.html" 31 | } 32 | ], 33 | "bugs": { 34 | "url": "https://github.com/Templarian/MaterialDesign/issues" 35 | }, 36 | "homepage": "http://materialdesignicons.com" 37 | } 38 | -------------------------------------------------------------------------------- /resources/public/vendor/mdi/scss/_core.scss: -------------------------------------------------------------------------------- 1 | .#{$mdi-css-prefix} { 2 | display: inline-block; 3 | font: normal normal normal #{$mdi-font-size-base}/1 MaterialDesignIcons; // shortening font declaration 4 | font-size: inherit; // can't have font-size inherit on line above, so need to override 5 | text-rendering: auto; // optimizelegibility throws things off #1094 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 9 | } -------------------------------------------------------------------------------- /resources/public/vendor/mdi/scss/_extras.scss: -------------------------------------------------------------------------------- 1 | .#{$mdi-css-prefix + '-18px'} { font-size: 18px; } 2 | .#{$mdi-css-prefix + '-24px'} { font-size: 24px; } 3 | .#{$mdi-css-prefix + '-36px'} { font-size: 36px; } 4 | .#{$mdi-css-prefix + '-48px'} { font-size: 48px; } 5 | .#{$mdi-css-prefix}-dark { color: rgba(0, 0, 0, 0.54); } 6 | .#{$mdi-css-prefix}-dark.mdi-inactive { color: rgba(0, 0, 0, 0.26); } 7 | .#{$mdi-css-prefix}-light { color: rgba(255, 255, 255, 1); } 8 | .#{$mdi-css-prefix}-light.mdi-inactive { color: rgba(255, 255, 255, 0.3); } -------------------------------------------------------------------------------- /resources/public/vendor/mdi/scss/_path.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'MaterialDesignIcons'; 3 | src: url('#{$mdi-font-path}/materialdesignicons-webfont.eot?v=#{$mdi-version}'); 4 | src: url('#{$mdi-font-path}/materialdesignicons-webfont.eot?#iefix&v=#{$mdi-version}') format('embedded-opentype'), 5 | url('#{$mdi-font-path}/materialdesignicons-webfont.woff2?v=#{$mdi-version}') format('woff2'), 6 | url('#{$mdi-font-path}/materialdesignicons-webfont.woff?v=#{$mdi-version}') format('woff'), 7 | url('#{$mdi-font-path}/materialdesignicons-webfont.ttf?v=#{$mdi-version}') format('truetype'), 8 | url('#{$mdi-font-path}/materialdesignicons-webfont.svg?v=#{$mdi-version}#materialdesigniconsregular') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | -------------------------------------------------------------------------------- /resources/public/vendor/mdi/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $mdi-font-path: "../fonts" !default; 2 | $mdi-font-size-base: 24px !default; 3 | $mdi-css-prefix: mdi !default; 4 | $mdi-version: "1.2.64" !default; -------------------------------------------------------------------------------- /resources/public/vendor/mdi/scss/materialdesignicons.scss: -------------------------------------------------------------------------------- 1 | /* MaterialDesignIcons.com */ 2 | @import "variables"; 3 | @import "path"; 4 | @import "core"; 5 | @import "icons"; 6 | @import "extras"; -------------------------------------------------------------------------------- /sql-transactor.properties: -------------------------------------------------------------------------------- 1 | ################################################################ 2 | 3 | protocol=sql 4 | host=localhost 5 | port=4334 6 | 7 | 8 | 9 | ################################################################ 10 | # See http://docs.datomic.com/storage.html 11 | 12 | license-key=G4HKoIF5atZs/XpjrsTsUJuwRdtmIee/iGL0YfEuBQ9uGqx5VA\ 13 | 3Es5rK2Uw6ftp2LvV8rtPPBLtTqjo/+6FWjJ+x/+AHbgw+8pEM51+DqYNnd7Vv\ 14 | H/6pCF8ug5FPHP8DOJVvRIk5c4mfrczeOyftZG3PJR38+MbMJSRGVd6ovkrqHW\ 15 | Wg/9Bk8zClX8KFCz60Kd5fhjeblPfP6+jpPOu4aV04Yxt6koKFz0vk091p3UV9\ 16 | Rog2uef26lnhRDCCqyKssNRcdFgiCQW2zdJVxjcWVB1T4/2WEzcvVf2vIqg0fC\ 17 | CRqbdT0EQju2mafx4UGK84ImSr2xehToFCNNI6wjVIxg== 18 | 19 | 20 | 21 | ################################################################ 22 | # See http://docs.datomic.com/storage.html 23 | 24 | sql-url=jdbc:postgresql://ec2-54-204-3-200.compute-1.amazonaws.com:5432/d6v7sp5mnmdppt 25 | sql-user=mzoycjylbthpou 26 | sql-password=5oEebeL5KSy5uqRmyYgWCYJvu_ 27 | 28 | # The Postgres driver is included with Datomic. For other SQL 29 | # databases, you will need to install the driver on the 30 | # transactor classpath, by copying the file into lib/, 31 | # and place the driver on your peer's classpath. 32 | sql-driver-class=org.postgresql.Driver 33 | 34 | # Driver specified params, as semicolon-separated pairs. 35 | # Optional 36 | #sql-driver-params=ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory 37 | sql-driver-params=ssl=true;sslfactory=org.postgresql.ssl.NonValidatingFactory 38 | 39 | # The query used to validate JDBC connection. 40 | # Optional 41 | # sql-validation-query=select 1 42 | 43 | 44 | 45 | ################################################################ 46 | # See http://docs.datomic.com/capacity.html 47 | 48 | 49 | # Recommended settings for -Xmx4g production usage. 50 | # memory-index-threshold=32m 51 | # memory-index-max=512m 52 | # object-cache-max=1g 53 | 54 | # Recommended settings for -Xmx1g usage, e.g. dev laptops. 55 | memory-index-threshold=32m 56 | memory-index-max=256m 57 | object-cache-max=128m 58 | 59 | 60 | 61 | ## OPTIONAL #################################################### 62 | 63 | 64 | # Set to false to disable SSL between the peers and the transactor. 65 | # Default: true 66 | # encrypt-channel=true 67 | 68 | # Data directory is used for dev: and free: storage, and 69 | # as a temporary directory for all storages. 70 | # data-dir=data 71 | 72 | # Transactor will log here, see bin/logback.xml to configure logging. 73 | # log-dir=log 74 | 75 | # Transactor will write process pid here on startup 76 | # pid-file=transactor.pid 77 | 78 | 79 | 80 | ## OPTIONAL #################################################### 81 | # See http://docs.datomic.com/storage.html 82 | # Memcached configuration. 83 | 84 | # memcached=host:port,host:port,... 85 | # memcached-username=datomic 86 | # memcached-password=datomic 87 | 88 | 89 | 90 | ## OPTIONAL #################################################### 91 | # See http://docs.datomic.com/capacity.html 92 | 93 | 94 | # Soft limit on the number of concurrent writes to storage. 95 | # Default: 4, Miniumum: 2 96 | # write-concurrency=4 97 | 98 | # Soft limit on the number of concurrent reads to storage. 99 | # Default: 2 times write-concurrency, Miniumum: 2 100 | # read-concurrency=8 101 | 102 | 103 | 104 | ## OPTIONAL #################################################### 105 | # See http://docs.datomic.com/aws.html 106 | # Optional settings for rotating logs to S3 107 | # (Can be auto-generated by bin/datomic ensure-transactor.) 108 | 109 | # aws-s3-log-bucket-id= 110 | 111 | 112 | 113 | ## OPTIONAL #################################################### 114 | # See http://docs.datomic.com/aws.html 115 | # Optional settings for Cloudwatch metrics. 116 | # (Can be auto-generated by bin/datomic ensure-transactor.) 117 | 118 | # aws-cloudwatch-region= 119 | 120 | # Pick a unique name to distinguish transactor metrics from different systems. 121 | # aws-cloudwatch-dimension-value=your-system-name 122 | 123 | 124 | 125 | ## OPTIONAL #################################################### 126 | # See http://docs.datomic.com/ha.html 127 | 128 | 129 | # # The transactor will write a heartbeat into storage on this interval. 130 | # A standby transactor will take over if it sees the heartbeat go 131 | # unwritten for 2x this interval. If your transactor load leads to 132 | # long gc pauses, you can increase this number to prevent the standby 133 | # transactor from unnecessarily taking over during a long gc pause. 134 | # Default: 5000, Miniumum: 5000 135 | # heartbeat-interval-msec=5000 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/clj/fractalify/api/api.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.api 2 | (:require [schema.core :as s] 3 | [cemerick.friend :as frd] 4 | [fractalify.utils :as u] 5 | [plumbing.core :as p] 6 | [clojure.set :as set] 7 | [monger.collection :as mc] 8 | [monger.operators :as mop] 9 | [io.clojure.liberator-transit :as lt] 10 | [liberator.representation :as lr] 11 | [cognitect.transit :as transit]) 12 | (:import [org.bson.types ObjectId] 13 | (org.joda.time ReadableInstant))) 14 | 15 | (defn malformed-params? [schema params] 16 | (fn [_] (s/check schema 17 | (try 18 | (u/coerce-str params schema) 19 | (catch Exception e 20 | #_(println "except" (u/coerce-str params schema)) 21 | params))))) 22 | 23 | (def joda-time-writer 24 | (transit/write-handler 25 | (constantly "m") 26 | (fn [v] (-> ^ReadableInstant v .getMillis)) 27 | (fn [v] (-> ^ReadableInstant v .getMillis .toString)))) 28 | 29 | (defn as-transit-response 30 | ([] (as-transit-response lr/as-response)) 31 | ([f] 32 | (lt/as-response 33 | {:handlers {org.joda.time.DateTime joda-time-writer} 34 | :allow-json-verbose? false} 35 | (fn [res ctx] 36 | (f (when (u/http-status-ok (:status ctx)) res) ctx))))) 37 | 38 | (def base-resource 39 | {:available-media-types ["application/transit+json"] 40 | :as-response (as-transit-response)}) 41 | 42 | (def base-put 43 | (merge base-resource {:allowed-methods [:put]})) 44 | 45 | (def base-delete 46 | (merge base-resource {:allowed-methods [:delete]})) 47 | 48 | (def base-post 49 | (merge base-resource {:allowed-methods [:post]})) 50 | 51 | (defn admin? [& _] 52 | (p/when-letk [[roles] (frd/current-authentication)] 53 | (contains? (set roles) :admin))) 54 | 55 | (defn _id->id [x] 56 | (-> x 57 | (set/rename-keys {:_id :id}) 58 | (update :id str))) 59 | 60 | (s/defn id->_id 61 | [x :- (s/cond-pre {s/Keyword s/Any} (s/pred sequential?))] 62 | (cond-> x 63 | (map? x) (set/rename-keys {:id :_id}) 64 | (sequential? x) (-> set (disj :id) (conj :_id) seq))) 65 | 66 | (defn schema->fields [schema] 67 | (-> schema u/schema-keys id->_id)) 68 | 69 | (defn db->cljs [m schema] 70 | (when m 71 | (-> m 72 | _id->id 73 | (u/coerce-json schema)))) 74 | 75 | (s/defn insert-and-return 76 | ([db coll document schema] 77 | (insert-and-return db coll document schema (u/partial-right db->cljs schema))) 78 | ([db coll document schema transformation] 79 | (-> (mc/insert-and-return db coll (merge 80 | {:_id (ObjectId.)} 81 | document)) 82 | _id->id 83 | (u/select-schema-keys schema) 84 | transformation))) 85 | 86 | (defprotocol RouteResource 87 | (route->resource [route-resource])) 88 | 89 | (defn populate 90 | "Populates the given docs sequence by looking up the 'foreign key' as an :_id in `foreign-coll`. 91 | `foreign-path` can be either a single key or a sequence of keys (as in get-in) 92 | Assumes the foreign keys are ObjectIds or coercable to objectIds. 93 | Returns a seq of the docs where the foreign keys have been updated to be the foreign documents, in the same order. 94 | " 95 | [db foreign-coll foreign-path foreign-key foreign-schema docs] 96 | (let [foreign-path (u/ensure-seq foreign-path) 97 | foreign-keys (->> (u/ensure-seq docs) (map #(get-in % foreign-path)) (filter some?) set) 98 | foreign-docs (->> (mc/find-maps db foreign-coll 99 | {foreign-key {mop/$in foreign-keys}} 100 | (schema->fields foreign-schema)) 101 | (reduce (fn [m fd] 102 | (assoc m (fd foreign-key) 103 | (db->cljs fd foreign-schema))) {}))] 104 | (let [res (->> (u/ensure-seq docs) (map #(update-in % foreign-path foreign-docs)))] 105 | (if (sequential? docs) 106 | res 107 | (first res))))) 108 | -------------------------------------------------------------------------------- /src/clj/fractalify/api/fractals/fractals_generator.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.fractals.fractals-generator 2 | (:require [com.stuartsierra.component :as c] 3 | [fractalify.utils :as u] 4 | [plumbing.core :as p] 5 | [monger.collection :as mc] 6 | [fractalify.api.fractals.fractals-db :as fdb] 7 | [clojure.test.check.generators :as gen] 8 | [clj-time.core :as m] 9 | [schema.core :as s] 10 | [fractalify.fractals.schemas :as fch])) 11 | 12 | (declare gen-fractal) 13 | (def total-items-generated 30) 14 | 15 | (defrecord FractalsGenerator [] 16 | c/Lifecycle 17 | (start [this] 18 | (p/letk [[db] (:db-server this) 19 | [users] (:users-generator this)] 20 | (mc/remove db fdb/coll) 21 | (mc/remove db fdb/coll-comments) 22 | (assoc this 23 | :fractals 24 | (doall 25 | (take total-items-generated 26 | (repeatedly 27 | #(fdb/fractal-insert-and-return db (gen-fractal) 28 | {:username "admin"}))))))) 29 | 30 | (stop [this] 31 | (dissoc this :fractals)) 32 | ) 33 | 34 | (def new-fractals-generator ->FractalsGenerator) 35 | 36 | (s/defn gen-fractal [] 37 | (merge {:title (u/gen-sentence 7 1 3) 38 | :desc (u/gen-sentence 10 3 10) 39 | :src "http://res.cloudinary.com/hcjlrkjcu/image/upload/v1442987648/dragon-curve.png"} 40 | fch/dragon-curve)) -------------------------------------------------------------------------------- /src/clj/fractalify/api/fractals/resources.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.fractals.resources 2 | (:require 3 | [bidi.bidi :as b] 4 | [liberator.core :refer [defresource]] 5 | [fractalify.utils :as u] 6 | [fractalify.api.fractals.fractals-db :as fdb] 7 | [schema.core :as s] 8 | [fractalify.fractals.schemas :as fch] 9 | [plumbing.core :as p] 10 | [cemerick.friend :as frd] 11 | [fractalify.api.api :as a] 12 | [fractalify.fractals.api-routes :as far] 13 | [fractalify.img-cloud.img-cloud :as ic]) 14 | (:import (org.bson.types ObjectId))) 15 | 16 | (defn fractal-exists-fn [db params] 17 | (fn [& _] 18 | (when-let [fractal (fdb/fractal-get-by-id db (:id params))] 19 | {::fractal fractal}))) 20 | 21 | (p/defnk mine? [author] 22 | (u/eq-in-key? :username author (frd/current-authentication))) 23 | 24 | (defresource 25 | fractal [{:keys [db params]}] 26 | a/base-resource 27 | :exists? (fractal-exists-fn db params) 28 | :handle-ok 29 | (s/fn :- (s/maybe fch/PublishedFractal) [ctx] 30 | (::fractal ctx))) 31 | 32 | (defresource 33 | fractal-put [{:keys [db params img-cloud]}] 34 | a/base-put 35 | :malformed? (a/malformed-params? fch/PutFractalForm params) 36 | :authorized? (constantly (frd/current-authentication)) 37 | :put! 38 | (fn [_] 39 | (let [fractal (assoc params :_id (ObjectId.)) 40 | url (get (ic/upload img-cloud (str (:_id fractal)) (:data-url fractal)) "url") 41 | src (if url url (:data-url fractal)) 42 | fractal (-> fractal 43 | (assoc :src src) 44 | (dissoc :data-url))] 45 | {::fractal (fdb/fractal-insert-and-return db fractal (frd/current-authentication))})) 46 | :handle-created 47 | (s/fn :- fch/PublishedFractal [ctx] 48 | (::fractal ctx))) 49 | 50 | (defresource 51 | fractal-delete [{:keys [db params img-cloud]}] 52 | a/base-delete 53 | :authorized? 54 | (fn [_] 55 | (when-let [fractal (fdb/fractal-get-by-id db (:id params))] 56 | (let [allowed (or (a/admin?) (mine? fractal))] 57 | [allowed {::fractal fractal}]))) 58 | :delete! 59 | (fn [ctx] 60 | (let [id (get-in ctx [::fractal :id])] 61 | (fdb/fractal-delete-by-id db id) 62 | (ic/delete img-cloud id)))) 63 | 64 | (defresource 65 | fractals [{:keys [db params]}] 66 | a/base-resource 67 | :malformed? (a/malformed-params? fch/FractalListForm params) 68 | :handle-ok 69 | (s/fn :- fch/PublishedFractalsList [_] 70 | {:total-items (fdb/fractal-count 71 | db (when-let [username (:username params)] 72 | {:author username})) 73 | :items (fdb/get-fractals db params)})) 74 | 75 | (defn star-fractal [db params unstar?] 76 | (fdb/star-fractal db (:id params) (frd/current-authentication) unstar?)) 77 | 78 | (defresource 79 | fractal-star [{:keys [db params]}] 80 | a/base-resource 81 | :allowed-methods [:delete :post] 82 | :authorized? (constantly (frd/current-authentication)) 83 | :can-post-to-missing? false 84 | :exists? (fractal-exists-fn db params) 85 | :post! 86 | (fn [_] 87 | (star-fractal db params false)) 88 | :delete! 89 | (fn [_] 90 | (star-fractal db params true))) 91 | 92 | (defresource 93 | fractal-comments [{:keys [db params]}] 94 | a/base-resource 95 | :handle-ok 96 | (fn [_] 97 | (let [comments (fdb/get-comments db (:id params))] 98 | {:total-items (count comments) ; because currenlty without paging 99 | :items comments}))) 100 | 101 | (defresource 102 | fractal-comment-post [{:keys [db params]}] 103 | a/base-post 104 | :malformed? (a/malformed-params? (merge fch/FractalIdField fch/CommentForm) params) 105 | :authorized? (constantly (frd/current-authentication)) 106 | :can-post-to-missing? false 107 | :exists? (fractal-exists-fn db params) 108 | :post! 109 | (fn [_] 110 | (let [[fractal comment] (u/split-map params :id)] 111 | {::comment (fdb/comment-insert-and-return 112 | db comment (:id fractal) 113 | (frd/current-authentication))})) 114 | :handle-created 115 | (s/fn :- fch/Comment [ctx] 116 | (::comment ctx))) 117 | 118 | (defresource 119 | fractal-comment-delete [{:keys [db params]}] 120 | a/base-delete 121 | :authorized? 122 | (fn [_] 123 | (let [comment (fdb/get-comment-by-id db (:comment-id params))] 124 | (or (a/admin?) (mine? comment)))) 125 | :delete! 126 | (fn [_] 127 | (fdb/comment-delete-by-id db (:comment-id params)))) 128 | 129 | (def routes->resources 130 | {:fractal fractal 131 | :fractal-add fractal-put 132 | :fractal-remove fractal-delete 133 | :fractals fractals 134 | :fractal-comments fractal-comments 135 | :fractal-comment-add fractal-comment-post 136 | :fractal-comment-remove fractal-comment-delete 137 | :fractal-star fractal-star}) 138 | 139 | (defrecord FractalRoutes [] 140 | b/RouteProvider 141 | (routes [_] 142 | (far/get-routes true)) 143 | 144 | a/RouteResource 145 | (route->resource [_] 146 | routes->resources)) 147 | 148 | (defn new-fractal-routes [] 149 | (->FractalRoutes)) 150 | -------------------------------------------------------------------------------- /src/clj/fractalify/api/main/resources.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.main.resources 2 | (:require 3 | [net.cgrand.enlive-html :refer [deftemplate]] 4 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 5 | [bidi.bidi :refer (path-for RouteProvider)] 6 | [liberator.core :as l :refer [defresource]] 7 | [fractalify.utils :as u] 8 | [clojure.string :as str] 9 | [clojure.java.io :as io] 10 | [fractalify.api.api :as a] 11 | [fractalify.main.api-routes :as mar] 12 | [io.clojure.liberator-transit] 13 | [ring.middleware.resource :refer [wrap-resource]] 14 | [ring.middleware.content-type :refer [wrap-content-type]] 15 | [ring.middleware.not-modified :refer [wrap-not-modified]] 16 | [fractalify.main.schemas :as mch] 17 | [fractalify.mailers.mailer :as mm])) 18 | 19 | (def inject-devmode-html 20 | (comp 21 | (set-attr :class "is-dev") 22 | (prepend (html [:script {:type "text/javascript" :src "/public/js/out/goog/base.js"}])) 23 | (append (html [:script {:type "text/javascript"} "goog.require('fractalify.main')"])))) 24 | 25 | (def static-dir "resources") 26 | 27 | (deftemplate 28 | page (io/resource "index.html") [] 29 | [:body] (if u/is-dev? inject-devmode-html identity)) 30 | 31 | (def page-html 32 | (str/join "" (page))) 33 | 34 | (defresource 35 | main [_] 36 | :available-media-types ["text/html" "text/plain"] 37 | :handle-ok page-html) 38 | 39 | (defresource 40 | contact [{:keys [params mailer site]}] 41 | a/base-post 42 | :malformed? (a/malformed-params? mch/ContactForm params) 43 | :post-redirect? false 44 | :post! 45 | (fn [_] 46 | (mm/send-email 47 | mailer 48 | {:subject (:subject params) 49 | :text (str "From: " (:email params) "\n" (:text params)) 50 | :to (:contact-email site)}))) 51 | 52 | (defn static [_] 53 | (-> {} 54 | (wrap-resource "") 55 | (wrap-content-type) 56 | (wrap-not-modified))) 57 | 58 | (def routes->resources 59 | {:static static 60 | :contact contact 61 | :main main}) 62 | 63 | (defrecord MainRoutes [] 64 | RouteProvider 65 | (routes [_] 66 | (mar/get-routes)) 67 | 68 | a/RouteResource 69 | (route->resource [_] 70 | routes->resources)) 71 | 72 | (defn new-main-routes [] 73 | (->MainRoutes)) 74 | -------------------------------------------------------------------------------- /src/clj/fractalify/api/users/users_db.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.users.users-db 2 | (:require 3 | [monger.collection :as mc] 4 | [com.stuartsierra.component :as c] 5 | [plumbing.core :as p] 6 | [fractalify.utils :as u] 7 | [monger.json] 8 | [monger.joda-time] 9 | [clj-time.core :as t] 10 | [schema.core :as s] 11 | [clojurewerkz.scrypt.core :as sc] 12 | [monger.operators :refer :all] 13 | [clj-time.core :as m] 14 | [fractalify.users.schemas :as uch] 15 | [fractalify.api.api :as api] 16 | [digest]) 17 | (:import (org.bson.types ObjectId))) 18 | 19 | (def coll "users") 20 | 21 | (defrecord UsersDb [] 22 | c/Lifecycle 23 | (start [this] 24 | (p/letk [[db] (:db-server this)] 25 | (when-not (mc/exists? db coll) 26 | (mc/create db coll {})) 27 | (mc/ensure-index db coll (array-map :username 1) {:unique true}) 28 | (assoc this :coll-name coll))) 29 | 30 | (stop [this] 31 | (dissoc this :coll-name))) 32 | 33 | (defn new-users-db [] 34 | (map->UsersDb {})) 35 | 36 | (defn gen-password [password] 37 | (let [salt (u/gen-str 16) 38 | pass-hash (u/hash-pass salt password)] 39 | {:salt salt :password pass-hash})) 40 | 41 | (defn get-user 42 | ([db where] 43 | (get-user db where uch/UserDb)) 44 | ([db where schema] 45 | (-> (mc/find-one-as-map db coll where (api/schema->fields schema)) 46 | (api/db->cljs schema)))) 47 | 48 | (s/defn user-insert-and-return 49 | ([db user] (user-insert-and-return db user uch/UserOther)) 50 | ([db user schema] 51 | (api/insert-and-return db coll (merge user 52 | {:created (t/now) 53 | :roles [:user] 54 | :gravatar (digest/md5 (:email user))} 55 | (gen-password (:password user))) 56 | schema))) 57 | 58 | (defn update-user [db where what] 59 | (let [fields (if-let [email (:email what)] 60 | (assoc what :gravatar (digest/md5 email)) 61 | what)] 62 | (mc/update db coll where {$set fields}))) 63 | 64 | (defn get-user-by-acc 65 | ([db username schema] 66 | (get-user-by-acc db username username schema)) 67 | ([db username email schema] 68 | (get-user db {$or [{:username username} 69 | {:email email}]} schema))) 70 | 71 | (s/defn verify-credentials :- (s/maybe uch/UserSession) 72 | [db {:keys [username password]}] 73 | (let [user (get-user-by-acc db username uch/UserDb) 74 | submitted-pass password] 75 | (when user 76 | (p/letk [[password salt] user] 77 | (when (sc/verify (str salt submitted-pass) password) 78 | (u/select-req-keys user uch/UserSession)))))) 79 | 80 | (s/defn create-reset-token :- s/Str 81 | [db user-id] 82 | (let [token (u/gen-str 20) 83 | expire-date (m/plus (m/now) (m/weeks 1))] 84 | (mc/update-by-id db coll (ObjectId. user-id) {$set 85 | {:reset-password-token token 86 | :reset-password-expire expire-date}}) 87 | token)) 88 | 89 | (defn get-user-by-reset-token 90 | [db username token] 91 | (get-user db {$and [{:username username} 92 | {:reset-password-token token} 93 | {:reset-password-expire {$gte (m/now)}}]} uch/UserMe)) 94 | 95 | (defn set-new-password 96 | [db username new-pass] 97 | (update-user db {:username username} (merge (gen-password new-pass) 98 | {:reset-password-token nil 99 | :reset-password-expire nil}))) 100 | 101 | (defn add-admin-role [db user-id] 102 | (mc/update-by-id db coll (ObjectId. user-id) {$addToSet 103 | {:roles :admin}})) -------------------------------------------------------------------------------- /src/clj/fractalify/api/users/users_generator.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.users.users-generator 2 | (:require [com.stuartsierra.component :as c] 3 | [fractalify.utils :as u] 4 | [clojure.test.check.generators :as gen] 5 | [plumbing.core :as p] 6 | [monger.collection :as mc] 7 | [fractalify.api.users.users-db :as udb])) 8 | 9 | (defn gen-user 10 | ([] (gen-user nil)) 11 | ([user] 12 | (merge 13 | {:username (u/gen-str 6) 14 | :email (u/gen-email) 15 | :password (u/gen-str 6) 16 | :bio (u/gen-sentence 10 3 10)} 17 | user))) 18 | 19 | (def admin 20 | {:username "admin" 21 | :email "matus.lestan@gmail.com" 22 | :password "111111"}) 23 | 24 | (def some-user 25 | {:username "matus" 26 | :email "somemai@gmail.com" 27 | :password "111111"}) 28 | 29 | (def some-other-user 30 | {:username "someotheruser" 31 | :email "someotheruser@email.com" 32 | :password "111111"}) 33 | 34 | (defrecord UsersGenerator [] 35 | c/Lifecycle 36 | (start [this] 37 | (p/letk [[db] (:db-server this)] 38 | (mc/remove db udb/coll) 39 | (p/when-letk [[id] (udb/user-insert-and-return db (gen-user admin))] 40 | (udb/add-admin-role db id)) 41 | (doseq [user [some-user some-other-user]] 42 | (udb/user-insert-and-return db (gen-user user))) 43 | (assoc this :users 44 | (doall 45 | (take 10 (repeatedly #(udb/user-insert-and-return db (gen-user)))))))) 46 | 47 | (stop [this] 48 | (dissoc this :users))) 49 | 50 | (def new-users-generator ->UsersGenerator) 51 | -------------------------------------------------------------------------------- /src/clj/fractalify/config.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.config 2 | (:require [fractalify.utils :as u])) 3 | 4 | (defn config [] 5 | {:site {:domain "fractalify.com" 6 | :protocol "http://" 7 | :contact-email (System/getenv "CONTACT_EMAIL")} 8 | :db-server {:uri (System/getenv "MONGOLAB_URI") 9 | :host "127.0.0.1" 10 | :db-name "fractalify-dev" 11 | :port 27017} 12 | :http-listener {:port (u/s->int (or (System/getenv "PORT") 10555))} 13 | :figwheel {} 14 | :mailer {:default-from "fractalify.com"} 15 | :sendgrid-mail-sender {:auth {:api-user (System/getenv "MAILER_USER") 16 | :api-key (System/getenv "MAILER_PASS")}} 17 | :cloudinary {:url (System/getenv "CLOUDINARY_URL")}}) 18 | -------------------------------------------------------------------------------- /src/clj/fractalify/figwheel.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.figwheel 2 | (:require 3 | [com.stuartsierra.component :as c] 4 | [figwheel-sidecar.core :as fig] 5 | [figwheel-sidecar.auto-builder :as fig-auto] 6 | [plumbing.core :as p] 7 | [clojurescript-build.auto :as auto] 8 | [fractalify.utils :as u])) 9 | 10 | 11 | (def build-cfg {:builds [{:id "dev" 12 | :source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] 13 | :compiler {:output-to "resources/public/js/app.js" 14 | :output-dir "resources/public/js/out" 15 | :source-map true 16 | :optimizations :none 17 | :source-map-timestamp true 18 | :preamble ["react/react.min.js"]}}]}) 19 | 20 | (def fig-cfg {:css-dirs ["resources/public/css"] 21 | :server-port 3449}) 22 | 23 | (defrecord Figwheel [] 24 | c/Lifecycle 25 | (start [this] 26 | (-> this 27 | (assoc :figwheel (fig/start-server fig-cfg)) 28 | (#(assoc % :builder (fig-auto/autobuild* 29 | (assoc build-cfg :figwheel-server (:figwheel %))))))) 30 | 31 | (stop [this] 32 | (p/when-letk [[figwheel builder] this] 33 | (fig/stop-server figwheel) 34 | (auto/stop-autobuild! builder)) 35 | (dissoc this :figwheel :builder))) 36 | 37 | (def new-figwheel map->Figwheel) 38 | 39 | -------------------------------------------------------------------------------- /src/clj/fractalify/img_cloud/cloudinary.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.img-cloud.cloudinary 2 | (:require 3 | [com.stuartsierra.component :as c] 4 | [schema.core :as s] 5 | [fractalify.utils :as u] 6 | [fractalify.img-cloud.protocols :as icp]) 7 | (:import 8 | (com.cloudinary Cloudinary Transformation))) 9 | 10 | (s/defschema CloudinaryConfig 11 | {:url s/Str}) 12 | 13 | (defrecord CloudinaryComponent [url] 14 | c/Lifecycle 15 | (start [this] 16 | (assoc this :cloudinary (new Cloudinary url))) 17 | 18 | (stop [this] 19 | (dissoc this :cloudinary)) 20 | 21 | icp/ImgCloud 22 | (upload [img-cloud filename src] 23 | (.. (:cloudinary img-cloud) (uploader) (upload src (hash-map "public_id" filename)))) 24 | 25 | (delete [img-cloud filename] 26 | (.. (:cloudinary img-cloud) (uploader) (destroy filename (hash-map)))) 27 | 28 | (thumb-url [img-cloud filename width height] 29 | (let [trans (.. (Transformation.) (width width) (height height) (crop "scale"))] 30 | (.. (:cloudinary img-cloud) (url) (transformation trans) (generate filename))))) 31 | 32 | (defn new-cloudinary [config] 33 | (->> config 34 | (s/validate CloudinaryConfig) 35 | (map->CloudinaryComponent))) -------------------------------------------------------------------------------- /src/clj/fractalify/img_cloud/img_cloud.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.img-cloud.img-cloud 2 | (:require [fractalify.img-cloud.protocols :as icp] 3 | [schema.core :as s])) 4 | 5 | (s/defn upload 6 | [img-cloud :- (s/protocol icp/ImgCloud) 7 | filename :- s/Str 8 | src :- s/Str] 9 | (icp/upload img-cloud filename src)) 10 | 11 | (s/defn delete 12 | [img-cloud :- (s/protocol icp/ImgCloud) 13 | filename :- s/Str] 14 | (icp/delete img-cloud filename)) 15 | 16 | (s/defn thumb-url 17 | [img-cloud :- (s/protocol icp/ImgCloud) 18 | filename :- s/Str 19 | width :- s/Int 20 | height :- s/Int] 21 | (icp/thumb-url img-cloud filename width height)) -------------------------------------------------------------------------------- /src/clj/fractalify/img_cloud/mock_img_cloud.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.img-cloud.mock-img-cloud 2 | (:require [fractalify.img-cloud.protocols :as icp] 3 | [com.stuartsierra.component :as c] 4 | [schema.core :as s])) 5 | 6 | (defrecord MockImgCloud [] 7 | c/Lifecycle 8 | (start [this] 9 | (assoc this :img-cloud {})) 10 | 11 | (stop [this] 12 | (dissoc this :img-cloud)) 13 | 14 | icp/ImgCloud 15 | (upload [_ filename src] 16 | {"url" filename}) 17 | 18 | (delete [_ filename] 19 | {"status" "ok"}) 20 | 21 | (thumb-url [_ filename width height] 22 | (str "thumb_" filename))) 23 | 24 | (def new-mock-img-cloud ->MockImgCloud) 25 | -------------------------------------------------------------------------------- /src/clj/fractalify/img_cloud/protocols.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.img-cloud.protocols) 2 | 3 | (defprotocol ImgCloud 4 | (upload [_ filename src]) 5 | (delete [_ filename]) 6 | (thumb-url [_ filename width height])) -------------------------------------------------------------------------------- /src/clj/fractalify/less_watcher.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.less-watcher 2 | (:require 3 | [com.stuartsierra.component :as c] 4 | [me.raynes.conch.low-level :as sh])) 5 | 6 | (defrecord LessWatcher [] 7 | c/Lifecycle 8 | (start [this] 9 | this 10 | #_ (assoc this :proc (sh/proc "lein" "less" "auto"))) 11 | 12 | (stop [this] 13 | #_ (sh/destroy (:proc this)) 14 | (dissoc this :proc))) 15 | 16 | (def new-less-watcher ->LessWatcher) 17 | 18 | -------------------------------------------------------------------------------- /src/clj/fractalify/mailers/mailer.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.mailers.mailer 2 | (:require 3 | [com.stuartsierra.component :as c] 4 | [schema.core :as s] 5 | [clojure.java.io :as io] 6 | [fractalify.utils :as u] 7 | [selmer.parser :as sel] 8 | [plumbing.core :as p] 9 | [fractalify.mailers.protocols :as mp] 10 | [fractalify.users.schemas :as uch])) 11 | 12 | (def tpl-dir "mail-templates/") 13 | 14 | (def tpl-files 15 | (u/map-values (partial str tpl-dir) 16 | {:forgot-password "forgot-password.html" 17 | :join "join.html"})) 18 | 19 | (s/defschema MailerConfig 20 | {:default-from s/Str 21 | :site {s/Keyword s/Any}}) 22 | 23 | (defrecord Mailer [default-from site] 24 | c/Lifecycle 25 | (start [this] 26 | (->> tpl-files 27 | (u/map-values io/resource) 28 | (u/map-values slurp) 29 | (assoc this :templates))) 30 | 31 | (stop [this] 32 | (dissoc this :templates))) 33 | 34 | (defn new-mailer [config site] 35 | (->> config 36 | (merge {:site site}) 37 | (s/validate MailerConfig) 38 | map->Mailer)) 39 | 40 | (s/defschema EmailMessage 41 | {:to uch/Email 42 | :from s/Str 43 | :subject s/Str 44 | (s/optional-key :html) s/Str 45 | s/Keyword s/Str}) 46 | 47 | (s/defn send-email-template 48 | [mailer 49 | template :- s/Keyword 50 | template-vals :- {s/Keyword s/Any} 51 | email :- {s/Keyword s/Str}] 52 | (p/letk [[templates :- {s/Keyword s/Str} 53 | default-from site mail-sender] mailer 54 | body (-> templates 55 | template 56 | (sel/render (merge site template-vals)))] 57 | (s/validate (s/protocol mp/MailSender) mail-sender) 58 | (->> email 59 | (merge {:from default-from :html body}) 60 | (s/validate EmailMessage) 61 | (mp/send-email mail-sender)))) 62 | 63 | 64 | (s/defn send-email 65 | [mailer 66 | email :- {s/Keyword s/Str}] 67 | (p/letk [[default-from mail-sender] mailer] 68 | (s/validate (s/protocol mp/MailSender) mail-sender) 69 | (->> email 70 | (merge {:from default-from}) 71 | (s/validate EmailMessage) 72 | (mp/send-email mail-sender)))) 73 | 74 | -------------------------------------------------------------------------------- /src/clj/fractalify/mailers/mock_mail_sender.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.mailers.mock-mail-sender 2 | (:require 3 | [fractalify.mailers.protocols :as mp])) 4 | 5 | (defrecord MockMailSender [] 6 | mp/MailSender 7 | (send-email [_ email] 8 | email)) 9 | 10 | (def new-mock-mail-sender ->MockMailSender) 11 | -------------------------------------------------------------------------------- /src/clj/fractalify/mailers/protocols.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.mailers.protocols) 2 | 3 | (defprotocol MailSender 4 | (send-email [_ email])) -------------------------------------------------------------------------------- /src/clj/fractalify/mailers/sendgrid.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.mailers.sendgrid 2 | (:require 3 | [schema.core :as s] 4 | [fractalify.utils :as u] 5 | [plumbing.core :as p] 6 | [sendgrid-clj.core :as sg] 7 | [camel-snake-kebab.core :as csk] 8 | [fractalify.mailers.protocols :as mp])) 9 | 10 | (s/defschema SendgridConfig 11 | {:auth {:api-user s/Str 12 | :api-key s/Str}}) 13 | 14 | (defrecord SendgridMailSender [auth] 15 | mp/MailSender 16 | (send-email 17 | [this email] 18 | (p/letk [[auth] this] 19 | (sg/send-email (u/map-keys csk/->snake_case auth) email) 20 | true))) 21 | 22 | (defn new-sendgrid-mail-sender [config] 23 | (->> config 24 | (s/validate SendgridConfig) 25 | map->SendgridMailSender)) 26 | -------------------------------------------------------------------------------- /src/clj/fractalify/middlewares.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.middlewares 2 | (:require 3 | [com.stuartsierra.component :as c] 4 | [ring.middleware.reload :as reload] 5 | [clojure.pprint :refer [pprint]] 6 | [plumbing.core :as p] 7 | [ring.middleware.session :as session] 8 | [ring.middleware.params :as params] 9 | [ring.middleware.nested-params :as nested-params] 10 | [ring.middleware.defaults :as defaults] 11 | [cemerick.drawbridge :as drawbridge] 12 | [ring.middleware.keyword-params :as keyword-params] 13 | [ring.middleware.basic-authentication :refer [wrap-basic-authentication]] 14 | [ring.middleware.format :refer [wrap-restful-format]] 15 | [fractalify.utils :as u] 16 | [liberator.dev :as ld] 17 | [cemerick.friend :as frd] 18 | [ring.middleware.conditional :as mc])) 19 | 20 | 21 | (defn authenticated? [name pass] 22 | (= [name pass] [(System/getenv "AUTH_USER") (System/getenv "AUTH_PASS")])) 23 | 24 | (defn debug-handler [handler] 25 | (fn [req] 26 | (pprint req) 27 | (handler req))) 28 | 29 | (def drawbridge-handler 30 | (-> (drawbridge/ring-handler) 31 | (keyword-params/wrap-keyword-params) 32 | (nested-params/wrap-nested-params) 33 | (params/wrap-params) 34 | (session/wrap-session))) 35 | 36 | (def ring-defaults (-> defaults/site-defaults 37 | (u/dissoc-in [:security :anti-forgery]))) 38 | 39 | (def repl-url "/repl") 40 | (def api-url "/api") 41 | 42 | (defn get-middlewares [handler] 43 | (-> handler 44 | (frd/authenticate nil) 45 | (p/?> u/is-dev? (ld/wrap-trace :header :ui)) 46 | (p/?> u/is-dev? reload/wrap-reload) 47 | (mc/if-url-starts-with 48 | api-url 49 | #(wrap-restful-format % :formats [:transit-json])) 50 | 51 | (mc/if-url-starts-with 52 | repl-url 53 | (constantly (wrap-basic-authentication drawbridge-handler authenticated?))) 54 | 55 | (mc/if-url-doesnt-start-with 56 | repl-url 57 | #(defaults/wrap-defaults % ring-defaults)))) 58 | 59 | (defrecord Middlewares [] 60 | c/Lifecycle 61 | (start [this] 62 | (assoc this :middlewares get-middlewares)) 63 | 64 | (stop [this] 65 | (dissoc this :middlewares))) 66 | 67 | (defn new-middlewares [] 68 | (map->Middlewares {})) -------------------------------------------------------------------------------- /src/clj/fractalify/mongodb.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.mongodb 2 | (:require 3 | [monger.core :as m] 4 | [monger.json] 5 | [monger.joda-time] 6 | [com.stuartsierra.component :as c] 7 | [schema.core :as s] 8 | [fractalify.utils :as u])) 9 | 10 | (s/defschema MongoConfig 11 | {:uri (s/maybe s/Str) 12 | (s/optional-key :host) s/Str 13 | (s/optional-key :port) s/Int 14 | (s/optional-key :db-name) s/Str}) 15 | 16 | (defrecord MongoDb [uri host port db-name] 17 | c/Lifecycle 18 | (start [this] 19 | (merge this 20 | (if uri 21 | (m/connect-via-uri uri) 22 | (let [conn (m/connect {:host host :port port})] 23 | {:conn conn :db (m/get-db conn db-name)})))) 24 | 25 | (stop [this] 26 | (when-let [conn (:conn this)] 27 | (m/disconnect conn)) 28 | (dissoc this :conn :db))) 29 | 30 | (s/defn new-mongodb [config :- MongoConfig] 31 | (map->MongoDb config)) 32 | -------------------------------------------------------------------------------- /src/clj/fractalify/readers.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.readers 2 | (:require [clj-time.format :as tf])) 3 | 4 | (defn object-reader [[symbol _ value]] 5 | (when (= (name symbol) "org.joda.time.DateTime") 6 | (tf/parse value))) 7 | 8 | (defn get-readers [] 9 | {'object #'fractalify.readers/object-reader}) 10 | -------------------------------------------------------------------------------- /src/clj/fractalify/router.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.router 2 | (:require 3 | [schema.core :as s] 4 | [modular.ring :as mr] 5 | [com.stuartsierra.component :as c] 6 | [bidi.bidi :as b] 7 | [bidi.ring :as br] 8 | [plumbing.core :as p] 9 | [fractalify.api.main.resources] 10 | [fractalify.api.api :as a] 11 | [fractalify.utils :as u])) 12 | 13 | (s/defn match-route->resource 14 | [route :- s/Keyword 15 | route-providers :- [(s/protocol a/RouteResource)]] 16 | (let [routes-map (->> route-providers 17 | (map a/route->resource) 18 | (reduce merge {}))] 19 | (route routes-map))) 20 | 21 | (defn satisfies-route? [x] 22 | (and (satisfies? b/RouteProvider x) 23 | (satisfies? a/RouteResource x))) 24 | 25 | (defn get-route-providers [router] 26 | (->> router 27 | (map val) 28 | (filter satisfies-route?))) 29 | 30 | (s/defn dispatch-route 31 | [router db mailer img-cloud site matched-route :- s/Keyword] 32 | (let [resource (match-route->resource matched-route (get-route-providers router))] 33 | (fn [res] 34 | ((resource {:db db 35 | :params (:params res) 36 | :mailer mailer 37 | :img-cloud img-cloud 38 | :site site}) res)))) 39 | 40 | (s/defn as-request-handler 41 | "Convert a RouteProvider component into Ring handler." 42 | [service :- (s/protocol b/RouteProvider) handler-fn] 43 | (some-fn 44 | (br/make-handler 45 | (cond 46 | (satisfies? b/RouteProvider service) 47 | (b/routes service)) handler-fn))) 48 | 49 | (defrecord Router [site] 50 | c/Lifecycle 51 | (start [this] 52 | (assoc this 53 | :router ["" (->> (get-route-providers this) 54 | (map b/routes) 55 | (remove nil?) 56 | vec)])) 57 | (stop [this] 58 | (dissoc this :router)) 59 | 60 | b/RouteProvider 61 | (routes [this] (:router this)) 62 | 63 | mr/WebRequestHandler 64 | (request-handler [this] 65 | (p/letk [[db] (:db-server this) 66 | [mailer img-cloud] this 67 | [middlewares] (:middlewares this)] 68 | (middlewares (as-request-handler this (partial dispatch-route this db mailer img-cloud site)))))) 69 | 70 | (defn new-router [site] 71 | (Router. site)) -------------------------------------------------------------------------------- /src/clj/fractalify/system.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.system 2 | "Components and their dependency relationships" 3 | (:refer-clojure :exclude (read)) 4 | (:require 5 | [fractalify.readers] 6 | [com.stuartsierra.component :as c] 7 | [fractalify.config :as cfg] 8 | [fractalify.api.main.resources :as mr] 9 | [fractalify.api.users.resources :as ur] 10 | [fractalify.api.fractals.resources :as fr] 11 | [fractalify.figwheel :as fig] 12 | [modular.http-kit :as mh] 13 | [fractalify.utils :as u] 14 | [fractalify.router :as t] 15 | [fractalify.less-watcher :as lw] 16 | [fractalify.mongodb :as mongo] 17 | [fractalify.api.users.users-db :as udb] 18 | [fractalify.api.fractals.fractals-db :as fdb] 19 | [fractalify.api.users.users-generator :as ug] 20 | [fractalify.api.fractals.fractals-generator :as fg] 21 | [fractalify.middlewares :as mw] 22 | [fractalify.mailers.sendgrid :as sgm] 23 | [fractalify.mailers.mailer :as mm] 24 | [fractalify.img-cloud.cloudinary :as cloudinary])) 25 | 26 | 27 | (defn http-listener-components 28 | [system config] 29 | (assoc system :http-listener (mh/map->Webserver (:http-listener config)))) 30 | 31 | (defn router-components 32 | [system config] 33 | (assoc system :router (t/new-router (:site config)) 34 | :main-routes (mr/new-main-routes) 35 | :user-routes (ur/new-user-routes) 36 | :fractal-routes (fr/new-fractal-routes) 37 | :middlewares (mw/new-middlewares))) 38 | 39 | 40 | (defn fig-component [system config] 41 | (assoc system :figwheel (fig/new-figwheel (:figwheel config)))) 42 | 43 | (defn mailer-components [system config] 44 | (-> system 45 | (assoc :mailer (mm/new-mailer (:mailer config) (:site config))) 46 | (assoc :mail-sender (sgm/new-sendgrid-mail-sender (:sendgrid-mail-sender config))))) 47 | 48 | (defn less-component [system config] 49 | (assoc system :less-watcher (lw/new-less-watcher))) 50 | 51 | (defn db-components [system config] 52 | (-> system 53 | (assoc :db-server (mongo/new-mongodb (:db-server config))) 54 | (assoc :users-db (udb/new-users-db)) 55 | (assoc :fractals-db (fdb/new-fractals-db)))) 56 | 57 | (defn generator-components [system config] 58 | (-> system 59 | (assoc :users-generator (ug/new-users-generator)) 60 | (assoc :fractals-generator (fg/new-fractals-generator)))) 61 | 62 | (defn img-cloud-component [system config] 63 | (assoc system :img-cloud (cloudinary/new-cloudinary (:cloudinary config)))) 64 | 65 | (defn new-system-map 66 | [config] 67 | (apply c/system-map 68 | (apply concat 69 | (-> {} 70 | (http-listener-components config) 71 | (router-components config) 72 | (db-components config) 73 | (mailer-components config) 74 | (img-cloud-component config))))) 75 | 76 | 77 | (defn dev-system-map [system-map config] 78 | (-> system-map 79 | (fig-component config) 80 | (less-component config) 81 | (generator-components config))) 82 | 83 | (defn new-dependency-map [] 84 | {:http-listener [:router] 85 | :router [:db-server :fractal-routes :user-routes :main-routes :middlewares :mailer :img-cloud] 86 | :users-db [:db-server] 87 | :fractals-db [:db-server] 88 | :mailer [:mail-sender]}) 89 | 90 | 91 | (def db-generators-dependencies 92 | {:users-generator [:db-server :users-db] 93 | :fractals-generator [:db-server :fractals-db :users-generator]}) 94 | 95 | (defn dev-dependency-map [] 96 | db-generators-dependencies) 97 | 98 | (defn new-production-system 99 | "Create the production system" 100 | ([] (new-production-system {})) 101 | ([opts] 102 | (-> (new-system-map (merge (cfg/config) opts)) 103 | (c/system-using (new-dependency-map))))) 104 | -------------------------------------------------------------------------------- /src/clj/material_ui/macros.clj: -------------------------------------------------------------------------------- 1 | (ns material-ui.macros 2 | (:require [clojure.string :as str])) 3 | 4 | (def material-tags 5 | '[AppBar 6 | AppCanvas 7 | Avatar 8 | FlatButton 9 | RaisedButton 10 | FloatingActionButton 11 | Card 12 | CardHeader 13 | CardMedia 14 | CardTitle 15 | CardActions 16 | CardText 17 | DatePicker 18 | Dialog 19 | DropDownMenu 20 | DropDownIcon 21 | FontIcon 22 | IconButton 23 | IconMenu 24 | MenuItem 25 | LeftNav 26 | List 27 | ListItem 28 | ListDivider 29 | Menu 30 | MenuItem 31 | Paper 32 | LinearProgress 33 | CircularProgress 34 | RefreshIndicator 35 | Slider 36 | Checkbox 37 | Snackbar 38 | Table 39 | Tabs 40 | Tab 41 | TextField 42 | SelectField 43 | TimePicker 44 | Toolbar 45 | ToolbarGroup 46 | ToolbarSeparator]) 47 | 48 | (defn kebab-case 49 | "Converts CamelCase / camelCase to kebab-case" 50 | [s] 51 | (str/join "-" (map str/lower-case (re-seq #"\w[a-z]+" s)))) 52 | 53 | (defn material-ui-react-import [tname] 54 | `(def ~(symbol (kebab-case (str tname))) (reagent.core/adapt-react-class (aget js/MaterialUI ~(name tname))))) 55 | 56 | (defmacro export-material-ui-react-classes [] 57 | `(do ~@(map material-ui-react-import material-tags))) 58 | 59 | (macroexpand '(export-material-ui-react-classes)) ; I paste result of this into material-ui.core in cljs 60 | -------------------------------------------------------------------------------- /src/cljc/fractalify/fractals/api_routes.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.api-routes) 2 | 3 | (defn get-routes 4 | ([] (get-routes false)) 5 | ([with-methods?] 6 | ["/api/fractals" {["/" :id] [["/star" :fractal-star] 7 | ["/comments" [[["/" :comment-id] (if with-methods? 8 | {:delete :fractal-comment-remove} 9 | :fractal-comment)] 10 | ["" (if with-methods? 11 | {:post :fractal-comment-add 12 | :get :fractal-comments} 13 | :fractal-comments)]]] 14 | 15 | ["" (if with-methods? 16 | {:delete :fractal-remove 17 | :get :fractal} 18 | :fractal)]] 19 | 20 | "" (if with-methods? 21 | {:put :fractal-add 22 | :get :fractals} 23 | :fractals)}])) 24 | -------------------------------------------------------------------------------- /src/cljc/fractalify/fractals/schemas.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.schemas 2 | (:require [schema.core :as s] 3 | [fractalify.workers.schemas :as wch] 4 | [fractalify.users.schemas :as uch] 5 | [fractalify.main.schemas :as mch] 6 | [fractalify.utils :as u] 7 | [clojure.set :as set])) 8 | 9 | (def o s/optional-key) 10 | (s/defschema hex-color? (partial re-matches #"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")) 11 | (def FractalTitle s/Str) 12 | (def FractalDesc s/Str) 13 | (def CommentText s/Str) 14 | 15 | (s/defschema Color 16 | (wch/with-coerce [(s/one (s/pred hex-color?) "hex-color") 17 | (s/one s/Num "alpha")] 18 | ["#000" 0])) 19 | 20 | (s/defschema Base64Png (s/pred (partial re-matches 21 | (re-pattern (str "^" u/base64-png-prefix ".*"))))) 22 | 23 | (s/defschema operation-type (s/enum :cmds :rules)) 24 | 25 | (do 26 | #?@(:cljs [(s/defschema CanvasElement (s/pred (partial instance? js/HTMLCanvasElement))) 27 | (s/defschema CanvasContext (s/pred (partial instance? js/CanvasRenderingContext2D)))])) 28 | 29 | (s/defschema FractalIdField 30 | {:id s/Str}) 31 | 32 | (s/defschema FractalPublishForm 33 | {:title FractalTitle 34 | :desc FractalDesc}) 35 | 36 | (s/defschema Canvas 37 | {:color Color 38 | :bg-color Color 39 | :line-width s/Num 40 | :size s/Num 41 | (o :lines) wch/Lines}) 42 | 43 | (s/defschema RenderableCanvas 44 | (-> Canvas 45 | (set/rename-keys {(o :lines) :lines}) 46 | (merge mch/FormErros))) 47 | 48 | (s/defschema PutFractalForm 49 | (merge 50 | FractalPublishForm 51 | {:l-system wch/LSystem 52 | :canvas (dissoc Canvas (o :lines)) 53 | :data-url Base64Png})) 54 | 55 | (s/defschema FractalOrderTypes (s/enum :star-count :created)) 56 | 57 | (s/defschema FractalListForm 58 | {(o :page) s/Int 59 | (o :limit) s/Int 60 | (o :sort) FractalOrderTypes 61 | (o :sort-dir) s/Int 62 | (o :username) s/Str}) 63 | 64 | (s/defschema CommentForm {:text CommentText}) 65 | 66 | (s/defschema Comment 67 | {:id s/Str 68 | :text CommentText 69 | :author uch/UserOther 70 | :fractal s/Str 71 | :created mch/Date}) 72 | 73 | (s/defschema CommentList (mch/list-response Comment)) 74 | 75 | (s/defschema PublishedFractal 76 | {:id s/Str 77 | :title FractalTitle 78 | :desc FractalDesc 79 | :l-system wch/LSystem 80 | :canvas Canvas 81 | :src s/Str 82 | :author (s/cond-pre uch/UserOther s/Str) 83 | :star-count s/Int 84 | :starred-by-me s/Bool 85 | :created mch/Date 86 | (o :comments) (s/maybe CommentList)}) 87 | 88 | (s/defschema PublishedFractalsList (mch/list-response PublishedFractal)) 89 | 90 | (s/defschema FractalsForms 91 | {:info FractalPublishForm 92 | :l-system wch/LSystem 93 | :canvas Canvas 94 | :comment CommentForm 95 | :sidebar FractalListForm}) 96 | 97 | (s/defschema FractalsSchema 98 | {:forms FractalsForms 99 | (o :fractal-detail) PublishedFractal 100 | (o :fractals-sidebar) PublishedFractalsList 101 | (o :fractals-user) PublishedFractalsList 102 | (o :fractals-home) {(o :star-count) PublishedFractalsList 103 | (o :created) PublishedFractalsList} 104 | (o :l-system-generating) s/Bool 105 | (o :turtle-worker) #?(:cljs (s/maybe (s/pred (partial instance? js/Worker))) 106 | :clj s/Any)}) 107 | (s/defschema OriginChange 108 | {:origin {s/Keyword s/Num}}) 109 | 110 | (def dragon-curve 111 | {:l-system {:rules {1 ["X" "X+YF"] 112 | 2 ["Y" "FX-Y"]} 113 | :angle 90 114 | :start "FX" 115 | :iterations 12 116 | :line-length 6 117 | :origin {:x 500 :y 400} 118 | :start-angle 90 119 | :cmds {1 ["F" :forward] 120 | 2 ["+" :left] 121 | 3 ["-" :right] 122 | 4 ["[" :push] 123 | 5 ["]" :pop]}} 124 | :canvas {:bg-color ["#FFF" 100] 125 | :size 700 126 | :color ["#00bcd4" 100] 127 | :line-width 3}}) -------------------------------------------------------------------------------- /src/cljc/fractalify/main/api_routes.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.api-routes) 2 | 3 | (defn get-routes [] 4 | ["/" [["public/" [[true :static]]] 5 | ["api/contact" :contact] 6 | [true :main]]]) -------------------------------------------------------------------------------- /src/cljc/fractalify/main/schemas.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.schemas 2 | (:require [schema.core :as s] 3 | #?@(:clj [ 4 | [clj-time.core :as m]] 5 | :cljs [[cljs-time.core :as m] 6 | [cljs.core]]))) 7 | 8 | (def o s/optional-key) 9 | 10 | (s/defschema DbPath [(s/cond-pre s/Keyword s/Int)]) 11 | 12 | (s/defschema QueryParams {s/Keyword s/Any}) 13 | 14 | (s/defschema FormErros 15 | {(o :errors) {s/Keyword s/Any}}) 16 | 17 | (s/defschema Date #?(:clj (s/pred #(or (satisfies? m/DateTimeProtocol %) 18 | (instance? java.util.Date %))) 19 | :cljs (s/pred #(instance? js/Date %)))) 20 | 21 | (s/defschema ApiSendOpts 22 | {:api-route s/Keyword 23 | (o :route-params) {s/Keyword s/Any} 24 | :params {s/Keyword s/Any} 25 | :handler s/Any 26 | :method s/Keyword 27 | :error-handler s/Any 28 | (o :error-undo?) s/Bool}) 29 | 30 | 31 | (s/defschema ContactForm 32 | {:email s/Str 33 | :text s/Str 34 | :subject s/Str}) 35 | 36 | (s/defschema MainForms 37 | {:contact ContactForm}) 38 | 39 | (s/defschema MainSchema 40 | {:forms MainForms}) 41 | 42 | (defn list-response [item-type] 43 | {:items [item-type] 44 | :total-items s/Int}) -------------------------------------------------------------------------------- /src/cljc/fractalify/users/api_routes.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.api-routes) 2 | 3 | (defn get-routes [] 4 | ["/api/" {"users" {["/" :username] [["/reset-pass" :reset-password] 5 | ["/change-pass" :change-password] 6 | ["/edit-profile" :edit-profile] 7 | ["" :user]]} 8 | "auth/" [["logged-user" :logged-user] 9 | ["login" :login] 10 | ["logout" :logout] 11 | ["join" :join] 12 | ["forgot-pass" :forgot-password]]}]) 13 | -------------------------------------------------------------------------------- /src/cljc/fractalify/users/schemas.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.schemas 2 | (:require [schema.core :as s] 3 | [fractalify.main.schemas :as mch] 4 | [fractalify.utils :as u])) 5 | 6 | (s/defschema o s/optional-key) 7 | 8 | (s/defschema Email (s/pred (partial re-matches #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"))) 9 | (def Username s/Str) 10 | (def Password s/Str) 11 | (def UserBio s/Str) 12 | 13 | (s/defschema UsernameField 14 | {:username Username}) 15 | 16 | (s/defschema UserRoles [(s/enum :admin :user)]) 17 | 18 | (s/defschema UserDb 19 | {:id s/Str 20 | :username Username 21 | :roles UserRoles 22 | :created mch/Date 23 | :gravatar s/Str 24 | :salt s/Str 25 | :password s/Str 26 | :email Email 27 | :bio UserBio 28 | (o :reset-password-expire) (s/maybe mch/Date) 29 | (o :reset-password-token) (s/maybe s/Str)}) 30 | 31 | (s/defschema UserId (u/select-key UserDb :id)) 32 | 33 | (s/defschema UserSession 34 | (select-keys UserDb [:id :username :roles])) 35 | 36 | (s/defschema UserMe 37 | (dissoc UserDb 38 | :salt 39 | :password 40 | (o :reset-password-expire) 41 | (o :reset-password-token))) 42 | 43 | (s/defschema UserOther (dissoc UserMe :email)) 44 | 45 | (s/defschema LoginForm 46 | {:username Username 47 | :password Password}) 48 | 49 | (s/defschema JoinForm 50 | {:username Username 51 | :email s/Str 52 | :password Password 53 | :confirm-pass Password 54 | :bio UserBio}) 55 | 56 | (s/defschema ForgotPassForm 57 | {:email s/Str}) 58 | 59 | (s/defschema ResetPassForm 60 | {:username Username 61 | :token s/Str 62 | :new-pass Password}) 63 | 64 | (s/defschema ChangePassForm 65 | {:current-pass Password 66 | :new-pass Password 67 | :confirm-new-pass Password}) 68 | 69 | (s/defschema EditProfileForm 70 | {:email s/Str 71 | :bio UserBio}) 72 | 73 | (s/defschema UserForms 74 | {:login LoginForm 75 | :join JoinForm 76 | :forgot-password ForgotPassForm 77 | :change-password ChangePassForm 78 | :reset-password ResetPassForm 79 | :edit-profile EditProfileForm}) 80 | 81 | (s/defschema UsersSchema 82 | {(o :logged-user) (s/maybe UserMe) 83 | :forms UserForms 84 | (o :user-detail) (s/maybe 85 | (s/conditional (complement (partial s/check UserOther)) UserOther :else UserMe))}) 86 | -------------------------------------------------------------------------------- /src/cljc/fractalify/workers/schemas.cljc: -------------------------------------------------------------------------------- 1 | (ns fractalify.workers.schemas 2 | (:require [schema.core :as s :include-macros true] 3 | [schema.coerce :as coerce])) 4 | 5 | (s/defrecord DefaultCoerceSchema 6 | [schema :- (s/protocol s/Schema) default-value] 7 | s/Schema 8 | (spec [this] (s/spec schema)) 9 | 10 | (explain [this] (cons 'default-coerce schema))) 11 | 12 | (defn with-coerce [schema default-value] 13 | (DefaultCoerceSchema. schema default-value)) 14 | 15 | (def o s/optional-key) 16 | 17 | (def coords {:x s/Num :y s/Num}) 18 | (def Line [(s/one coords "line-from") 19 | (s/one coords "line-to")]) 20 | (def Lines [(s/maybe Line)]) 21 | 22 | (def Rule [(s/one s/Str "rule-variable") 23 | (s/one s/Str "rule-product")]) 24 | 25 | (def Cmd [(s/one s/Str "cmd-variable") 26 | (s/one s/Keyword "cmd-action")]) 27 | 28 | (def LSystem 29 | {(o :rules) {s/Int Rule} 30 | :start s/Str 31 | :angle s/Num 32 | :iterations (with-coerce (s/pred pos?) 1) 33 | :line-length s/Num 34 | :start-angle s/Num 35 | :origin (with-coerce coords {:x 0 :y 0}) 36 | (o :cmds) {s/Int Cmd}}) 37 | 38 | (def Turtle 39 | {:position coords 40 | :angle s/Num 41 | :stack (s/pred list?) 42 | :lines Lines}) 43 | 44 | (defn keyword->string [k] 45 | (name k)) 46 | 47 | (defn keyword->int [k] 48 | (int (keyword->string k))) 49 | 50 | (def +keyword-coercion+ 51 | (merge 52 | {s/Str keyword->string 53 | s/Int keyword->int})) 54 | 55 | (defn l-system-coercion-matcher [schema] 56 | (or 57 | (+keyword-coercion+ schema) 58 | (coerce/+string-coercions+ schema) 59 | (coerce/keyword-enum-matcher schema) 60 | (coerce/set-matcher schema))) 61 | -------------------------------------------------------------------------------- /src/cljs/cljsjs/react.cljs: -------------------------------------------------------------------------------- 1 | (ns cljsjs.react) -------------------------------------------------------------------------------- /src/cljs/fractalify/api.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.api 2 | (:require [fractalify.utils :as u] 3 | [schema.core :as s :include-macros true] 4 | [fractalify.main.schemas :as ch] 5 | [ajax.core :refer [GET POST PUT DELETE]] 6 | [fractalify.users.api-routes :as uar] 7 | [fractalify.main.api-routes :as mar] 8 | [fractalify.fractals.api-routes :as far] 9 | [bidi.bidi :as b] 10 | [plumbing.core :as p] 11 | [fractalify.main.schemas :as mch])) 12 | 13 | (def all-routes 14 | ["" [(mar/get-routes) (uar/get-routes) (far/get-routes)]]) 15 | 16 | (s/defn path-for :- s/Str 17 | [route :- s/Keyword 18 | route-params :- {s/Keyword s/Any}] 19 | (apply b/path-for all-routes route (flatten (seq route-params)))) 20 | 21 | (def default-opts 22 | {:headers {:Accept ["application/transit+json"]} 23 | :format :transit}) 24 | 25 | (s/defn fetch! 26 | [api-route :- s/Keyword 27 | query-params :- ch/QueryParams 28 | route-param-names :- [s/Keyword] 29 | opts] 30 | (let [url (path-for api-route (select-keys query-params route-param-names))] 31 | (GET url (merge default-opts 32 | opts 33 | {:params (-> query-params clj->js js->clj)})))) 34 | 35 | 36 | (s/defn send! [opts :- mch/ApiSendOpts] 37 | (p/letk [[api-route {route-params {}}] opts 38 | url (path-for api-route route-params)] 39 | (let [f (condp = (:method opts) 40 | :put PUT 41 | :post POST 42 | :delete DELETE)] 43 | (f url (merge default-opts 44 | opts 45 | {:format :transit 46 | :response-format :transit 47 | :headers {:Accept ["application/transit+json"]}}))))) 48 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/api_wrap.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.api-wrap 2 | (:require [reagent.core :as r] 3 | [re-frame.core :as f] 4 | [fractalify.main.schemas :as mch] 5 | [schema.core :as s :include-macros true] 6 | [reagent.impl.util :as ru] 7 | [fractalify.utils :as u] 8 | [plumbing.core :as p])) 9 | 10 | 11 | (defn ^:private api-wrap 12 | [api-route path value-sub route-param-names force-reload] 13 | (let [val (f/subscribe (u/ensure-seq value-sub)) 14 | loading? (f/subscribe [:loading? path]) 15 | dispatch #(f/dispatch 16 | [:api-fetch 17 | {:api-route api-route 18 | :path path 19 | :query-params % 20 | :route-param-names route-param-names 21 | :force-reload force-reload}])] 22 | (r/create-class 23 | {:component-will-mount 24 | (fn [this] 25 | (dispatch (r/props this))) 26 | :component-will-receive-props 27 | (fn [_ new-argv] 28 | (dispatch (ru/extract-props new-argv))) 29 | :reagent-render 30 | (fn [_ child] 31 | (conj child @val @loading?))}))) 32 | 33 | (def ApiWrapConfig 34 | {:api-route s/Keyword 35 | :path mch/DbPath 36 | :value-sub (s/cond-pre s/Keyword [s/Keyword]) 37 | :query-params-sub (s/cond-pre s/Keyword [s/Keyword]) 38 | (s/optional-key :route-param-names) [s/Keyword] 39 | (s/optional-key :force-reload) s/Bool}) 40 | 41 | (s/defn ^:private api-query-params-wrap [config :- ApiWrapConfig] 42 | (p/letk [[api-route path value-sub query-params-sub {route-param-names []} {force-reload nil}] config 43 | query-params (f/subscribe (u/ensure-seq query-params-sub)) 44 | f (partial api-wrap api-route path value-sub route-param-names force-reload)] 45 | (fn [child] 46 | [f @query-params child]))) 47 | 48 | (s/defn create-api-wrap [config :- ApiWrapConfig] 49 | (partial api-query-params-wrap config)) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/color_picker.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.color-picker 2 | (:require-macros [cljs.core.async.macros :refer [go]]) 3 | (:require [reagent.core :as r] 4 | [schema.core :as s :include-macros true] 5 | [re-frame.core :as f] 6 | [fractalify.utils :as u] 7 | [cljs.core.async :refer [chan >! (js->clj x) 17 | (select-keys ["color" "alpha"]) 18 | vals vec)) 19 | 20 | (defn on-change [path val] 21 | (f/dispatch (conj path val))) 22 | 23 | (s/defn color-picker 24 | [color :- fch/Color 25 | path :- ch/DbPath 26 | props] 27 | (let [debounced-change (u/debounce #(on-change path %) (:debounce props))] 28 | (fn [color] 29 | [react-color-picker 30 | (let [[hex-color alpha] color] 31 | (merge 32 | {:default-color hex-color 33 | :alpha alpha 34 | :animation "slide-up" 35 | :trigger (r/as-element 36 | [ui/icon-button 37 | (merge 38 | {:icon-class-name "mdi mdi-eyedropper-variant" 39 | :class-name "colorpicker-trigger-btn" 40 | :icon-style {:color (ui/color :grey700)} 41 | :tooltip "Choose Color" 42 | :tooltip-position "top-center"} 43 | (:trigger-props props))]) 44 | :on-change #(-> % parse-val debounced-change)} 45 | 46 | props))]))) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/dialog.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.dialog 2 | (:require [reagent.core :as r] 3 | [material-ui.core :as ui] 4 | [re-frame.core :as f])) 5 | 6 | (def ^:dynamic *dialog-parent* (atom)) 7 | (def ^:dynamic *queued-dialog* (atom)) 8 | 9 | (defn- get-dialog-ref [] 10 | (aget @*dialog-parent* "refs" "dialog")) 11 | 12 | (defn show-dialog! [] 13 | (if @*dialog-parent* 14 | (.show (get-dialog-ref)) 15 | (reset! *queued-dialog* true))) 16 | 17 | (defn hide-dialog! [] 18 | (.dismiss (get-dialog-ref))) 19 | 20 | (add-watch *dialog-parent* :dialog 21 | (fn [_ _ old-state _] 22 | (when (and (nil? old-state) @*queued-dialog*) 23 | (show-dialog!) 24 | (remove-watch *dialog-parent* :dialog)))) 25 | 26 | (defn dialog [] 27 | (r/create-class 28 | {:component-did-mount (fn [this] 29 | (reset! *dialog-parent* this)) 30 | :reagent-render 31 | (let [props (f/subscribe [:dialog-props])] 32 | (fn [] 33 | [ui/dialog (merge {:ref "dialog"} 34 | @props) 35 | (:content @props)]))})) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/dialog_action.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.dialog-action 2 | (:require [material-ui.core :as ui] 3 | [reagent.core :as r])) 4 | 5 | (defn dialog-action [form] 6 | (r/as-element 7 | [ui/mui-theme-wrap 8 | form])) 9 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/form.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.form 2 | (:require [fractalify.main.schemas :as ch] 3 | [schema.core :as s :include-macros true] 4 | [re-frame.core :as f] 5 | [fractalify.utils :as u])) 6 | 7 | (s/defn form 8 | [module :- s/Keyword 9 | form-name :- s/Keyword 10 | contents] 11 | (let [form-vals (f/subscribe [:form-data module form-name]) 12 | form-errs (f/subscribe [:form-errors module form-name])] 13 | (fn [] 14 | [:form {:on-submit (constantly false) :novalidate true} 15 | [contents @form-vals (not (empty? @form-errs))]]))) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/form_input.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.form-input 2 | (:require-macros [fractalify.tracer-macros :refer [trace-views]]) 3 | (:require 4 | [schema.core :as s :include-macros true] 5 | [fractalify.components.text-field :as text-field] 6 | [fractalify.utils :as u] 7 | [fractalify.validators :as v])) 8 | 9 | (s/defn text 10 | [value floating-label-text path props] 11 | [text-field/text-field 12 | value 13 | floating-label-text 14 | (into [:set-form-item] path) 15 | (into [:form-error] path) 16 | props]) 17 | 18 | (defn number [& args] 19 | (u/with-default-props 20 | text 21 | {:type "number"} 22 | args)) 23 | 24 | (defn password [& args] 25 | (u/with-default-props 26 | text 27 | {:type "password" 28 | :required true 29 | :validators [v/password]} 30 | args)) 31 | 32 | (defn email [& args] 33 | (u/with-default-props 34 | text 35 | {:required true 36 | :validators [v/email]} 37 | args)) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/form_select.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.form-select 2 | (:require 3 | [material-ui.core :as ui] 4 | [re-frame.core :as f] 5 | [schema.core :as s :include-macros true] 6 | [fractalify.utils :as u] 7 | [plumbing.core :as p] 8 | [clojure.walk :as w] 9 | [fractalify.main.schemas :as ch] 10 | [reagent.core :as r] 11 | [com.rpl.specter :as e])) 12 | 13 | (def default-val-member :payload) 14 | (def default-display-member :text) 15 | 16 | (def style {:width "100%"}) 17 | (def MenuItems [{(s/cond-pre s/Str s/Keyword s/Num) s/Any}]) 18 | 19 | (s/defn parse-val [evt menu-items :- MenuItems val-member] 20 | "Hack to retrieve value types other than string, because e.target.value 21 | always returns string (no keywords)" 22 | (let [val (u/e-val evt)] 23 | (-> menu-items 24 | (->> (e/select [e/ALL val-member])) 25 | (#(zipmap % (u/range-count %))) 26 | w/stringify-keys 27 | (#(nth menu-items (% val))) 28 | (get val-member)))) 29 | 30 | (s/defn get-member-names [props] 31 | (p/letk [[{value-member default-val-member}] props 32 | [{display-member default-display-member}] props] 33 | [value-member display-member])) 34 | 35 | (def Value (s/maybe s/Any #_ (s/cond-pre s/Str s/Keyword s/Num))) 36 | 37 | (s/defn form-select 38 | ([_ 39 | floating-label-text :- s/Str 40 | path :- ch/DbPath 41 | props :- {s/Keyword s/Any}] 42 | (let [val-member (first (get-member-names props))] 43 | (s/fn [value :- Value] 44 | [ui/select-field 45 | (r/merge-props 46 | {:value-member default-val-member 47 | :display-member default-display-member 48 | :floating-label-text floating-label-text 49 | :value value 50 | :style style 51 | :on-change #(f/dispatch 52 | (u/concat-vec 53 | [:set-form-item] 54 | path 55 | [(parse-val % (:menu-items props) val-member)]))} 56 | props)])))) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/gravatar.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.gravatar 2 | (:require [fractalify.utils :as u])) 3 | 4 | (defn gravatar [md5 size] 5 | [:img {:src (u/gravatar-url md5 size)}]) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/icon_button_remove.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.icon-button-remove 2 | (:require [material-ui.core :as ui] 3 | [reagent.core :as r])) 4 | 5 | (defn icon-button-remove [props] 6 | [ui/icon-button 7 | (r/merge-props 8 | {:icon-class-name "mdi mdi-close-circle-outline" 9 | :icon-style {:color (ui/palette-color :accent1-color)}} props)]) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/icon_text_button.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.icon-text-button 2 | (:require [material-ui.core :as ui] 3 | [reagent.core :as r])) 4 | 5 | (def icon-style 6 | {:height "100%" 7 | :display :inline-block 8 | :vertical-align :middle 9 | :float :left 10 | :padding-left "12px" ; 11 | :line-height "36px"}) 12 | 13 | (defn icon-text-button [props] 14 | (let [btn (if (:raised props) ui/raised-button ui/flat-button)] 15 | [btn 16 | (r/merge-props {} props) 17 | [ui/font-icon 18 | {:style (merge 19 | icon-style 20 | (:icon-style props) 21 | (when (or (:primary props) (:secondary props)) 22 | {:color "#FFF"})) 23 | :class-name (or (:icon-class-name props) "")}]])) 24 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/paper_content.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.paper-content 2 | (:require [fractalify.styles :as y] 3 | [material-ui.core :as ui])) 4 | 5 | (defn paper-content [& children] 6 | [ui/paper 7 | {:style y/pad-20} 8 | (into [:div.row] children)]) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/paper_panel.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.paper-panel 2 | (:require [fractalify.components.responsive-panel :as responsive-panel] 3 | [fractalify.components.paper-content :as paper-content])) 4 | 5 | (defn paper-panel [& children] 6 | [responsive-panel/responsive-panel 7 | (into [] (concat [paper-content/paper-content] children))]) 8 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/responsive_panel.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.responsive-panel) 2 | 3 | (defn responsive-panel [children] 4 | [:div.row.middle-xs.center-xs 5 | [:div.col-xs-12.col-sm-8.col-md-6.col-lg-4 6 | children]]) 7 | 8 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/snackbar.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.snackbar 2 | (:require [reagent.core :as r] 3 | [material-ui.core :as ui] 4 | [re-frame.core :as f] 5 | [fractalify.utils :as u])) 6 | 7 | (def ^:dynamic *snackbar-parent* (atom)) 8 | (def ^:dynamic *queued-snackbar* (atom)) 9 | 10 | (defn- get-snackbar-ref [] 11 | (aget @*snackbar-parent* "refs" "snackbar")) 12 | 13 | (defn show-snackbar! [] 14 | (if @*snackbar-parent* 15 | (.show (get-snackbar-ref)) 16 | (reset! *queued-snackbar* true))) 17 | 18 | (add-watch *snackbar-parent* :snackbar 19 | (fn [_ _ old-state _] 20 | (when (and (nil? old-state) @*queued-snackbar*) 21 | (show-snackbar!) 22 | (remove-watch *snackbar-parent* :snackbar)))) 23 | 24 | (defn snackbar [] 25 | (r/create-class 26 | {:component-did-mount (fn [this] 27 | (reset! *snackbar-parent* this)) 28 | :reagent-render 29 | (let [props (f/subscribe [:snackbar-props])] 30 | (fn [] 31 | [ui/snackbar (merge {:ref "snackbar" 32 | :autoHideDuration 10000 33 | :message ""} 34 | @props)]))})) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/social.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.social 2 | (:require [reagent.core :as r])) 3 | 4 | 5 | (defn create-class [class-name] 6 | (r/adapt-react-class (aget js/ReactSocial class-name))) 7 | 8 | (def fb-button (create-class "FacebookButton")) 9 | (def twitter-button (create-class "TwitterButton")) 10 | (def pinterest-button (create-class "PinterestButton")) 11 | (def vk-button (create-class "VKontakteButton")) 12 | -------------------------------------------------------------------------------- /src/cljs/fractalify/components/tab_anchor.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.tab-anchor 2 | (:require 3 | [reagent.core :as r] 4 | [material-ui.core :as ui] 5 | [fractalify.styles :as y])) 6 | 7 | (defn tab-anchor [props children] 8 | ^{:key (:href props)} 9 | [ui/tab 10 | {:label (r/as-element [:a {:href (:href props) 11 | :style y/tab-anchor 12 | :class "no-dec"} 13 | (:label props)])} 14 | children]) -------------------------------------------------------------------------------- /src/cljs/fractalify/components/text_field.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.components.text-field 2 | (:require-macros [fractalify.tracer-macros :refer [trace-views]] 3 | [cljs.core.async.macros :refer [go]]) 4 | (:require 5 | [reagent.core :as r] 6 | [fractalify.tracer] 7 | [material-ui.core :as ui] 8 | [re-frame.core :as f] 9 | [schema.core :as s :include-macros true] 10 | [fractalify.utils :as u] 11 | [fractalify.validators :as v] 12 | [fractalify.styles :as y] 13 | [plumbing.core :as p] 14 | [cljs.core.async :refer [chan >! (u/e-val evt) 27 | (p/?> (= type "number") 28 | u/parse-float))) 29 | 30 | (defn- dirty? [this] 31 | (:dirty? (r/state this))) 32 | 33 | (defn- set-dirty! [this value] 34 | (r/set-state this {:dirty? value})) 35 | 36 | (defn- get-error [this] 37 | (:error (r/state this))) 38 | 39 | (defn- set-error! [this value err-path] 40 | (f/dispatch (conj err-path value)) 41 | (r/set-state this {:error value})) 42 | 43 | (defn on-change [dispatch val] 44 | (f/dispatch (conj dispatch val))) 45 | 46 | (s/defschema Value (s/maybe (s/cond-pre s/Str s/Num))) 47 | 48 | (defn validate [val props] 49 | (let [validators (u/concat-vec (when (:required props) [v/required]) 50 | (:validators props)) 51 | validator (apply u/or-fn validators)] 52 | (validator val))) 53 | 54 | (s/defn text-field 55 | ([value floating-label-text props] 56 | (text-field value floating-label-text nil nil props)) 57 | ([value 58 | floating-label-text :- s/Str 59 | path :- (s/maybe ch/DbPath) 60 | err-path :- (s/maybe ch/DbPath) 61 | props :- {s/Keyword s/Any}] 62 | (let [debounced-change (u/debounce #(on-change path %) (:debounce props)) 63 | this (r/current-component)] 64 | (set-error! this (validate value props) err-path) 65 | (s/fn [value :- Value _ _ _ props] 66 | [ui/text-field 67 | (merge 68 | {:default-value value 69 | :floating-label-text floating-label-text 70 | :errorText (when (dirty? this) (get-error this)) 71 | :style style 72 | :underline-style underline-style 73 | :error-style error-style} 74 | (when path 75 | {:on-change (fn [evt] 76 | (let [val (parse-val evt (:type props)) 77 | error (validate val props)] 78 | (set-dirty! this true) 79 | (set-error! this error err-path) 80 | (when-not (and (:stop-dispatch-on-error props) error) 81 | (debounced-change val))))}) 82 | props)])))) -------------------------------------------------------------------------------- /src/cljs/fractalify/core.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.core 2 | (:require [reagent.core :as reagent] 3 | [re-frame.core :as f] 4 | [fractalify.router :as router] 5 | [fractalify.main.handlers] 6 | [fractalify.main.subs] 7 | [fractalify.main.routes] 8 | [fractalify.main.view] 9 | [fractalify.users.handlers] 10 | [fractalify.users.subs] 11 | [fractalify.users.routes] 12 | [fractalify.fractals.handlers] 13 | [fractalify.fractals.subs] 14 | [fractalify.fractals.routes] 15 | [schema.core :as s] 16 | [cljsjs.google-analytics])) 17 | 18 | (enable-console-print!) 19 | 20 | (js/ga "create" (str "UA-21191392-" (if goog.DEBUG "4" "9")) "auto") 21 | 22 | ;(set! (.-DEBUG js/goog) false) 23 | (s/set-fn-validation! goog.DEBUG) 24 | 25 | 26 | (defn ^:export mount-root [] 27 | (reagent/render [fractalify.main.view/main-view] 28 | (.getElementById js/document "app"))) 29 | 30 | (fractalify.users.routes/define-routes!) 31 | (fractalify.fractals.routes/define-routes!) 32 | (fractalify.main.routes/define-routes!) 33 | (router/start!) 34 | (f/dispatch-sync [:initialize]) 35 | 36 | (defn ^:export init [] 37 | (mount-root)) 38 | -------------------------------------------------------------------------------- /src/cljs/fractalify/db.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.db 2 | (:require [schema.core :as s :include-macros true] 3 | [fractalify.main.db :as mdb] 4 | [fractalify.users.db :as udb] 5 | [fractalify.fractals.db :as fdb] 6 | [com.rpl.specter :as e] 7 | [fractalify.main.schemas :as mch] 8 | [fractalify.users.schemas :as uch] 9 | [fractalify.fractals.schemas :as fch])) 10 | 11 | (enable-console-print!) 12 | (def o s/optional-key) 13 | 14 | (def db-schema-base 15 | {(o :active-panel) s/Keyword 16 | 17 | :main mch/MainSchema 18 | :users uch/UsersSchema 19 | :fractals fch/FractalsSchema 20 | 21 | (o :dialog-props) {s/Keyword s/Any} 22 | (o :snackbar-props) {:message s/Str 23 | (o :action) s/Str 24 | (o :autoHideDuration) s/Int 25 | (o :onActionTouchTap) s/Any} 26 | (o :route-params) (s/maybe {s/Keyword s/Any}) 27 | (o :queries) {[s/Keyword] {(o :query-params) {s/Keyword s/Any} 28 | (o :loading?) s/Bool 29 | (o :error) s/Any}}}) 30 | 31 | (defn assoc-form-errors [db-schema] 32 | (reduce #(e/transform [%2 :forms e/ALL e/LAST] 33 | (partial merge mch/FormErros) %1) db-schema [:main :users :fractals])) 34 | 35 | (def db-schema 36 | (-> db-schema-base 37 | assoc-form-errors)) 38 | 39 | (defn valid? [db] 40 | (s/validate db-schema db)) 41 | 42 | (def default-db 43 | {:main mdb/default-db 44 | :users udb/default-db 45 | :fractals fdb/default-db}) 46 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/canvas.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.canvas 2 | (:require [re-frame.core :as f] 3 | [reagent.core :as r] 4 | [fractalify.styles :as y])) 5 | 6 | (defn canvas-el [] 7 | (let [canvas (f/subscribe [:canvas])] 8 | (r/create-class 9 | {:component-will-update 10 | (fn [this] 11 | (f/dispatch [:canvas-change (r/dom-node this) @canvas])) 12 | :reagent-render 13 | (fn [] 14 | [:canvas 15 | {:width (:size @canvas) 16 | :height (:size @canvas) 17 | :style y/canvas-style}])}))) 18 | 19 | (defn- dispatch-l-system [this l-system-new] 20 | (let [l-system-old (:l-system (r/state this))] 21 | (f/dispatch [:l-system-change l-system-new l-system-old]) 22 | (r/set-state this {:l-system l-system-new}))) 23 | 24 | (defn l-system [] 25 | (let [l-system (f/subscribe [:l-system-new])] 26 | (r/create-class 27 | {:component-did-mount 28 | (fn [this] 29 | (dispatch-l-system this @l-system)) 30 | :component-will-update 31 | (fn [this] 32 | (dispatch-l-system this @l-system)) 33 | :reagent-render 34 | (fn [] 35 | @l-system 36 | [:div])}))) 37 | 38 | (defn canvas [] 39 | [:div 40 | [canvas-el] 41 | [l-system]]) 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/canvas_controls.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.canvas-controls 2 | (:require [re-frame.core :as f] 3 | [fractalify.utils :as u] 4 | [fractalify.fractals.components.control-text :as control-text] 5 | [material-ui.core :as ui] 6 | [fractalify.styles :as y] 7 | [fractalify.fractals.components.l-system-operations.tab :as tab] 8 | [fractalify.components.color-picker :as color-picker] 9 | [schema.core :as s :include-macros true] 10 | [fractalify.main.schemas :as ch] 11 | [plumbing.core :as p] 12 | [fractalify.fractals.schemas :as fch] 13 | [fractalify.fractals.components.l-system-operations.rule :as rule] 14 | [fractalify.fractals.components.l-system-operations.cmd :as cmd] 15 | [fractalify.fractals.components.fractal-hints :as hints] 16 | [fractalify.validators :as v])) 17 | 18 | (defn preview-shield [fractal] 19 | [:div 20 | [:div {:style y/fractal-controls-shield}] 21 | [:div.row.center-xs.middle-xs {:style y/fork-btn-wrap} 22 | [ui/raised-button 23 | {:label "Fork this" 24 | :primary true 25 | :on-touch-tap #(f/dispatch [:fractal-fork fractal])}]]]) 26 | 27 | (defn color-picker-btn [key color tooltip icon path] 28 | ^{:key key} 29 | [:div.col-xs-4.col-md-2 30 | [color-picker/color-picker color 31 | [:set-form-item :fractals :canvas path] 32 | {:debounce 300 33 | :trigger-props {:tooltip tooltip 34 | :icon-class-name (str "mdi mdi-" icon)}}]]) 35 | 36 | (def max-canvas-size 1000) 37 | 38 | (s/defn canvas-controls [fractal-sub preview?] 39 | (let [fractal (f/subscribe fractal-sub)] 40 | (fn [] 41 | (when @fractal 42 | (p/letk 43 | [[l-system canvas] @fractal 44 | [[:origin x y] start start-angle iterations angle line-length rules cmds] l-system 45 | [size line-width color bg-color] canvas] 46 | 47 | [ui/paper 48 | [:div.row.mar-0.pos-rel.pad-ver-10 49 | (when preview? [preview-shield @fractal]) 50 | [hints/what-is-this-hint] 51 | 52 | [:div.col-xs-6.col-sm-6.col-md-4 53 | [control-text/control-number x "Start X" [:l-system :origin :x]]] 54 | [:div.col-xs-6.col-sm-6.col-md-4 55 | [control-text/control-number y "Start Y" [:l-system :origin :y]]] 56 | #_ [:div.col-xs-6.col-sm-6.col-md-4 57 | [control-text/control-number size "Canvas Size" [:canvas :size] 58 | {:validators [(v/less-eq max-canvas-size 59 | (str "Maximum canvas size is " max-canvas-size 60 | ". Try smaller line length"))] 61 | :stop-dispatch-on-error true}]] 62 | [:div.col-xs-6.col-sm-6.col-md-4 63 | [control-text/control-number start-angle "Start Angle" [:l-system :start-angle]]] 64 | [:div.col-xs-6.col-sm-6.col-md-4 65 | [control-text/control-number iterations "Iterations" [:l-system :iterations]]] 66 | [:div.col-xs-6.col-sm-6.col-md-4 67 | [control-text/control-number angle "Rotation Angle" [:l-system :angle]]] 68 | [:div.col-xs-6.col-sm-6.col-md-4 69 | [control-text/control-number line-length "Line Length" [:l-system :line-length]]] 70 | [:div.col-xs-6.col-sm-6.col-md-4 71 | [control-text/control-number line-width "Line Width" [:canvas :line-width]]] 72 | [:div.col-xs-6.col-sm-6.col-md-4 73 | [control-text/control-text start "Start" [:l-system :start]]] 74 | 75 | [:div.row.col-xs-6.center-xs.middle-xs.start-md 76 | (color-picker-btn 1 color "Choose Line Color" "brush" :color) 77 | (color-picker-btn 2 bg-color "Choose Background Color" "format-paint" :bg-color)] 78 | [:div.col-xs-12.pad-0 79 | [ui/tabs {:style y/pad-bot-15} 80 | ^{:key 1} (tab/tab rules :rules rule/rule {:label "Rules"}) 81 | ^{:key 2} (tab/tab cmds :cmds cmd/cmd {:label "Actions"})]]]]))))) 82 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/comments.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.comments 2 | (:require [re-frame.core :as f] 3 | [material-ui.core :as ui] 4 | [fractalify.styles :as y] 5 | [fractalify.utils :as u] 6 | [plumbing.core :as p] 7 | [reagent.core :as r] 8 | [fractalify.router :as t] 9 | [cljs-time.core :as m] 10 | [fractalify.components.icon-button-remove :as icon-button-remove] 11 | [fractalify.components.api-wrap :as api-wrap] 12 | [fractalify.components.form-input :as form-input] 13 | [fractalify.components.form :as form])) 14 | 15 | 16 | (def comments-api-wrap 17 | (api-wrap/create-api-wrap 18 | {:api-route :fractal-comments 19 | :path [:fractals :fractal-detail :comments] 20 | :value-sub :fractal-comments 21 | :query-params-sub :route-params 22 | :route-param-names [:id]})) 23 | 24 | (defn add-comment [_] 25 | (fn [logged-user fractal] 26 | (when logged-user 27 | [form/form :fractals :comment 28 | (fn [vals has-err?] 29 | [:div.row.pad-hor-10.mar-top-10.end-xs 30 | [:div.col-xs-12 31 | [form-input/text (:text vals) "Enter your comment" [:fractals :comment :text] 32 | {:required true 33 | :multi-line true}]] 34 | [:div.col-xs-3 35 | [ui/flat-button 36 | {:label "Send" 37 | :on-touch-tap #(f/dispatch [:fractal-comment-add (:id fractal)]) 38 | :disabled has-err?}]]])]))) 39 | 40 | (defn comment-list [logged-user fractal comments-list loading?] 41 | (let [comments (:items comments-list)] 42 | [ui/list 43 | {:style y/mar-top-10 44 | :subheader "Comments"} 45 | (cond 46 | loading? [ui/linear-progress {:mode "indeterminate"}] 47 | (u/empty-seq? comments) [ui/list-item {:disabled true} "No comments were added yet"] 48 | :else 49 | (doall 50 | (for [comment comments] 51 | (p/letk [[id text created [:author gravatar username]] comment] 52 | ^{:key id} 53 | [ui/list-item 54 | {:left-avatar (r/as-element [:a {:href (t/url :user-detail :username username)} 55 | [ui/avatar {:src (u/gravatar-url gravatar 50)}]]) 56 | :disabled true 57 | :right-icon-button (when (= (:username logged-user) username) 58 | (r/as-element (icon-button-remove/icon-button-remove 59 | {:on-touch-tap 60 | #(f/dispatch 61 | [:fractal-comment-remove 62 | (:id fractal) 63 | (:id comment)])}))) 64 | :secondary-text (u/time-ago created)} 65 | [:p text]]))))])) 66 | 67 | (defn comments [] 68 | (let [logged-user (f/subscribe [:logged-user])] 69 | (fn [fractal] 70 | [ui/paper {:style y/comments-wrap} 71 | [add-comment @logged-user fractal] 72 | [comments-api-wrap 73 | [comment-list @logged-user fractal]]]))) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/control_text.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.control-text 2 | (:require [fractalify.components.form-input :as form-input] 3 | [fractalify.utils :as u])) 4 | 5 | (def controls-debounce 700) 6 | 7 | (defn control-input [f value floating-label-text path props] 8 | [f value floating-label-text 9 | (into [:fractals] path) 10 | (merge {:debounce controls-debounce} props)]) 11 | 12 | (defn control-text [& args] 13 | (apply control-input form-input/text args)) 14 | 15 | (defn control-number [& args] 16 | (apply control-input form-input/number args)) 17 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractal_card_list.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractal-card-list 2 | (:require [fractalify.utils :as u] 3 | [fractalify.fractals.components.star-count :as star-count] 4 | [fractalify.styles :as y] 5 | [material-ui.core :as ui] 6 | [fractalify.router :as t] 7 | [plumbing.core :as p] 8 | [schema.core :as s :include-macros true] 9 | [fractalify.fractals.schemas :as fch] 10 | [reagent.core :as r])) 11 | 12 | (def url (partial t/url :fractal-detail :id)) 13 | 14 | (defn frac-link [id children] 15 | [:a.block.default-color.no-dec.text-left 16 | {:href (t/url :fractal-detail :id id)} 17 | children]) 18 | 19 | (s/defn fractal-card-list [fractals :- (s/maybe fch/PublishedFractalsList) props] 20 | [:div (r/merge-props {:class "row"} (:container-props props)) 21 | (when fractals 22 | (doall 23 | (for [fractal (:items fractals)] 24 | (p/letk [[id src title star-count created] fractal] 25 | ^{:key id} 26 | [:div 27 | (r/merge-props {:class "col-xs-6 col-sm-4 col-md-3 col-lg-3 mar-bot-20"} props) 28 | [ui/card 29 | [frac-link id 30 | [ui/card-media 31 | [:img {:src src}]]] 32 | [frac-link id 33 | [ui/card-text {:style (merge y/pad-hor-10 y/pad-ver-5)} 34 | [:div.row.mar-bot-5 35 | [:div.col-xs-12 title]] 36 | [:div.row.between-xs.middle-xs 37 | [:div.col-xs-6 38 | [star-count/star-count star-count]] 39 | [:h5.col-xs-6.text-right 40 | (u/time-ago created)]]]] 41 | (when-let [actions (:actions props)] 42 | [ui/card-actions 43 | (conj actions fractal)])]]))))]) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractal_create.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractal-create 2 | (:require [re-frame.core :as f] 3 | [reagent.core :as r] 4 | [material-ui.core :as ui] 5 | [fractalify.fractals.components.canvas :as canvas] 6 | [fractalify.styles :as y] 7 | [fractalify.fractals.components.canvas-controls :as canvas-controls] 8 | [fractalify.utils :as u] 9 | [fractalify.components.form-input :as form-input] 10 | [fractalify.validators :as v] 11 | [fractalify.components.dialog-action :as dialog-action] 12 | [fractalify.fractals.components.fractal-page-layout :as fractal-page-layout] 13 | [fractalify.components.form :as form])) 14 | 15 | (def title-maxlen 50) 16 | (def desc-maxlen 140) 17 | 18 | (defn centered-refresh-indicator [visible] 19 | [:div {:style y/canvas-refresh-indicator-wrap} 20 | [ui/refresh-indicator 21 | {:status "loading" 22 | :left 0 23 | :top 0 24 | :style (y/canvas-refresh-indicator visible)}] 25 | [ui/raised-button 26 | {:label "Cancel" 27 | :style (y/canvas-refresh-cancel-btn visible) 28 | :on-touch-tap #(f/dispatch [:cancel-turtle-worker])}]]) 29 | 30 | 31 | (defn publish-confirm-btn [] 32 | (dialog-action/dialog-action 33 | (let [form-errors (f/subscribe [:form-errors :fractals :info])] 34 | (fn [] 35 | [ui/flat-button {:label "Publish" 36 | :ref "publish" 37 | :primary true 38 | :disabled (not (empty? @form-errors)) 39 | :onTouchTap #(f/dispatch [:fractal-publish])}])))) 40 | 41 | (def publish-form 42 | [form/form :fractals :info 43 | (fn [vals] 44 | (let [{:keys [title desc]} vals] 45 | [:div.row 46 | [:div.col-xs-12 47 | [form-input/text title "Title" [:fractals :info :title] 48 | {:required true 49 | :validators [(v/length 0 title-maxlen)]}]] 50 | [:div.col-xs-12 51 | [form-input/text desc "Description" [:fractals :info :desc] 52 | {:multi-line true 53 | :validators [(v/length 0 desc-maxlen)]}]]]))]) 54 | 55 | (defn publish-dialog-props [] 56 | {:title "Publish Fractal" 57 | :action-focus "publish" 58 | :content publish-form 59 | :actions [{:text "Cancel"} (publish-confirm-btn)]}) 60 | 61 | (defn canvas-section [] 62 | (let [l-system-generating (f/subscribe [:l-system-generating])] 63 | (fn [] 64 | [:div 65 | [centered-refresh-indicator @l-system-generating] 66 | [canvas/canvas]]))) 67 | 68 | (defn btns-section [] 69 | [:div.row.end-xs 70 | [ui/raised-button 71 | {:label "Publish" 72 | :primary true 73 | :on-touch-tap #(f/dispatch [:show-dialog (publish-dialog-props)])}]]) 74 | 75 | (defn sidebar-section [] 76 | [canvas-controls/canvas-controls [:fractal-new]]) 77 | 78 | (defn fractal-create [] 79 | [fractal-page-layout/fractal-page-layout 80 | [canvas-section] 81 | [btns-section] 82 | [sidebar-section]]) 83 | 84 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractal_detail.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractal-detail 2 | (:require [fractalify.fractals.components.canvas-controls :as canvas-controls] 3 | [fractalify.fractals.components.fractal-page-layout :as fractal-page-layout] 4 | [re-frame.core :as f] 5 | [plumbing.core :as p] 6 | [fractalify.utils :as u] 7 | [fractalify.styles :as y] 8 | [fractalify.fractals.schemas :as ch] 9 | [schema.core :as s :include-macros true] 10 | [material-ui.core :as ui] 11 | [fractalify.router :as t] 12 | [fractalify.fractals.components.comments :as comments] 13 | [fractalify.components.api-wrap :as api-wrap] 14 | [fractalify.fractals.components.fractals-sidebar :as fractals-sidebar] 15 | [fractalify.components.social :as soc])) 16 | 17 | (s/set-fn-validation! true) 18 | 19 | (defn canvas-section [_] 20 | (p/fnk [src] :- ch/PublishedFractal 21 | [:img {:src src 22 | :style (merge y/w-100 23 | {:min-height (:height y/canvas-size)})}])) 24 | 25 | (defn fractal-url [id] 26 | (str "http://fractalify.com" (t/url :fractal-detail :id id))) 27 | 28 | (defn share-btn [& _] 29 | (fn [btn id icon-class color props] 30 | [btn 31 | (merge 32 | {:url (fractal-url id) 33 | :element "span" 34 | :style (y/share-btn-style color) 35 | :class-name (str "mdi " icon-class)} props)])) 36 | 37 | (defn btn-section [_] 38 | (p/fnk 39 | [id title {desc ""} star-count starred-by-me created src 40 | [:author gravatar username]] :- ch/PublishedFractal 41 | [ui/paper {:style y/paper-block} 42 | [:div.row.between-xs.middle-xs 43 | [:div.col-xs 44 | [:h1.mar-bot-10 title] 45 | [:div.row.center-xs 46 | [:a.col-xs-2.mar-bot-10.default-color 47 | {:href (t/url :user-detail :username username)} 48 | [ui/avatar {:src (u/gravatar-url gravatar 50)}] 49 | [:h6.mar-top-5 username]] 50 | [:div.col-xs.text-left 51 | [:h5.mar-bot-5 (u/time-ago created)] 52 | [:h3 desc]]]] 53 | [:div.row.middle-xs.col-xs-4 54 | [:div.col-xs-6.text-right 55 | [:h1 star-count]] 56 | [:div.col-xs-6 57 | [:div 58 | [ui/floating-action-button 59 | {:icon-class-name (str "mdi mdi-star" (when-not starred-by-me "-outline")) 60 | :background-color (ui/color :amber700) 61 | :on-touch-tap #(f/dispatch [:fractal-toggle-star id])}]]]]] 62 | [:div.row.mar-lef-0 63 | [share-btn soc/fb-button id "mdi-facebook-box" (ui/color :indigo500) 64 | {:message title}] 65 | [share-btn soc/twitter-button id "mdi-twitter-box" (ui/color :cyan500) 66 | {:message title}] 67 | [share-btn soc/pinterest-button id "mdi-pinterest-box" (ui/color :red500) 68 | {:media src}]]])) 69 | 70 | (defn sidebar-section [] 71 | (fn [] 72 | [ui/tabs 73 | [ui/tab 74 | {:label "Other Fractals"} 75 | [fractals-sidebar/fractals-sidebar]] 76 | [ui/tab 77 | {:label "Fractal Settings"} 78 | [canvas-controls/canvas-controls [:fractal-detail] true]]])) 79 | 80 | 81 | (defn fractal-detail-content [_] 82 | (fn [fractal] 83 | [fractal-page-layout/fractal-page-layout 84 | (when-not (empty? fractal) [canvas-section fractal]) 85 | (when-not (empty? fractal) 86 | [:div 87 | [btn-section fractal] 88 | [comments/comments fractal]]) 89 | [sidebar-section]])) 90 | 91 | (def fractal-api-wrap 92 | (api-wrap/create-api-wrap 93 | {:api-route :fractal 94 | :path [:fractals :fractal-detail] 95 | :value-sub :fractal-detail 96 | :query-params-sub :route-params 97 | :route-param-names [:id]})) 98 | 99 | (defn fractal-detail [] 100 | [fractal-api-wrap 101 | [fractal-detail-content]]) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractal_hints.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractal-hints 2 | (:require [material-ui.core :as ui])) 3 | 4 | 5 | (defn what-is-this-hint [] 6 | [:div.col-xs-12.middle-xs.start-xs 7 | [:a.row.middle-xs.pad-lef-10.no-dec 8 | {:href "https://en.wikipedia.org/wiki/L-system" 9 | :target "_blank"} 10 | [:h5.mar-rig-5 "What is this?"] 11 | [ui/font-icon 12 | {:class-name "mdi mdi-help-circle" 13 | :style {:font-size "1.2em"} 14 | :color (ui/color :grey600)}]]]) 15 | 16 | (defn chars-hint [] 17 | [:h5.col-xs-12.mar-top-10 18 | "Note: Be aware of character differences like minus vs. hyphen"]) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractal_page_layout.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractal-page-layout 2 | (:require [fractalify.styles :as y] 3 | [material-ui.core :as ui])) 4 | 5 | (defn fractal-page-layout [canvas-section btns-section sidebar-section] 6 | [:div.row 7 | [:div.col-xs-12.col-sm-7.col-md-6.col-lg-5.col-lg-offset-1.relative 8 | [ui/paper {:style y/canvas-paper-wrap} 9 | canvas-section] 10 | btns-section] 11 | [:div.col-xs-12.col-sm-5.col-md-offset-1.col-md-5.col-lg-4.col-lg-offset-1 12 | sidebar-section]]) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/fractals_sidebar.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.fractals-sidebar 2 | (:require [material-ui.core :as ui] 3 | [fractalify.styles :as y] 4 | [fractalify.components.api-wrap :as api-wrap] 5 | [plumbing.core :as p] 6 | [reagent.core :as r] 7 | [fractalify.utils :as u] 8 | [fractalify.router :as t] 9 | [re-frame.core :as f] 10 | [fractalify.components.form-select :as form-select] 11 | [fractalify.fractals.components.sidebar-pagination :as sidebar-pagination] 12 | [fractalify.components.form :as form] 13 | [fractalify.fractals.components.star-count :as star-count])) 14 | 15 | (def fractals-api-wrap 16 | (api-wrap/create-api-wrap 17 | {:api-route :fractals 18 | :path [:fractals :fractals-sidebar] 19 | :value-sub :fractals-sidebar 20 | :query-params-sub :fractals-sidebar-query-params 21 | :force-reload true})) 22 | 23 | (def sort-items 24 | [{:payload :star-count :text "Top Rated"} 25 | {:payload :created :text "Most Recent"}]) 26 | 27 | (defn list-order-select [] 28 | [form/form :fractals :sidebar 29 | (fn [vals] 30 | [form-select/form-select (:sort vals) "Order by" [:fractals :sidebar :sort] 31 | {:menu-items sort-items}])]) 32 | 33 | (defn fractal-list [] 34 | (fn [fractals loading?] 35 | [:div 36 | [ui/list {:style y/pad-0} 37 | (cond 38 | loading? [ui/linear-progress {:mode "indeterminate"}] 39 | (u/empty-seq? fractals) [ui/list-item {:disabled true} "No fractals found"] 40 | :else 41 | (doall 42 | (for [fractal (:items fractals)] 43 | (p/letk 44 | [[id title desc star-count src] fractal] 45 | ^{:key id} 46 | [:div 47 | [ui/list-item 48 | {:on-touch-tap #(f/dispatch [:fractals-sidebar-select fractal])} 49 | [:div.row.middle-xs 50 | [:div.col-xs-2 51 | [ui/avatar {:src src 52 | :style {:border-radius 0} 53 | :size 60}]] 54 | [:div.col-xs-9.mar-lef-5 55 | [:div.col-xs-12 title] 56 | [:h6.col-xs-12.mar-top-5 (u/ellipsis desc 20)]] 57 | [:div.col-xs-1.row.center-xs 58 | [star-count/star-count star-count]]]] 59 | [ui/list-divider]]))))] 60 | [sidebar-pagination/sidebar-pagination fractals loading?]])) 61 | 62 | (defn fractals-sidebar [] 63 | (fn [] 64 | [ui/paper {:style y/sidebar-wrap} 65 | [list-order-select] 66 | [fractals-api-wrap 67 | [fractal-list]]])) 68 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/l_system_operations/cmd.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.l-system-operations.cmd 2 | (:require [fractalify.utils :as u] 3 | [schema.core :as s :include-macros true] 4 | [fractalify.components.form-select :as form-select] 5 | [fractalify.fractals.components.l-system-operations.remove-btn :as remove-btn] 6 | [fractalify.fractals.components.control-text :as control-text] 7 | [fractalify.workers.schemas :as ch])) 8 | 9 | (def all-cmds 10 | [{:payload :forward :text "Forward"} 11 | {:payload :left :text "Rotate Left"} 12 | {:payload :right :text "Rotate Right"} 13 | {:payload :push :text "Push Position"} 14 | {:payload :pop :text "Pop Position"} 15 | {:payload :default :text "No Action"}]) 16 | 17 | (s/defn cmd 18 | [k :- s/Int 19 | cmd-item :- ch/Cmd] 20 | (let [[var action-val] cmd-item] 21 | [:div.row 22 | [:div.col-xs-3 23 | [control-text/control-text var "Variable" [:l-system :cmds k 0]]] 24 | [:div.col-xs-8 25 | [form-select/form-select action-val "Action" [:fractals :l-system :cmds k 1] 26 | {:menu-items all-cmds}]] 27 | [remove-btn/remove-btn :cmds k]])) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/l_system_operations/remove_btn.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.l-system-operations.remove-btn 2 | (:require [re-frame.core :as f] 3 | [material-ui.core :as ui] 4 | [schema.core :as s :include-macros true] 5 | [fractalify.fractals.schemas :as ch] 6 | [fractalify.components.icon-button-remove :as icon-button-remove])) 7 | 8 | (s/defn remove-btn 9 | [type :- ch/operation-type 10 | key] 11 | [:div.col-xs-1.row.middle-xs 12 | [icon-button-remove/icon-button-remove 13 | {:on-touch-tap #(f/dispatch [:dissoc-l-system-operation type key])}]]) 14 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/l_system_operations/rule.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.l-system-operations.rule 2 | (:require [re-frame.core :as f] 3 | [fractalify.fractals.components.control-text :as control-text] 4 | [material-ui.core :as ui] 5 | [fractalify.utils :as u] 6 | [fractalify.fractals.components.l-system-operations.remove-btn :as remove-btn] 7 | [schema.core :as s :include-macros true] 8 | [fractalify.workers.schemas :as ch])) 9 | 10 | (s/defn rule 11 | [k :- s/Int 12 | rule-item :- ch/Rule] 13 | (let [[var-name rule-val] rule-item] 14 | (fn [] 15 | [:div.row 16 | [:div.col-xs-3 17 | [control-text/control-text var-name "Variable" [:l-system :rules k 0]]] 18 | [:div.col-xs-8 19 | [control-text/control-text rule-val "Rule" [:l-system :rules k 1]]] 20 | [remove-btn/remove-btn :rules k]]))) 21 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/l_system_operations/tab.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.l-system-operations.tab 2 | (:require [re-frame.core :as f] 3 | [material-ui.core :as ui] 4 | [schema.core :as s :include-macros true] 5 | [fractalify.fractals.schemas :as ch] 6 | [fractalify.utils :as u] 7 | [fractalify.fractals.components.fractal-hints :as hints])) 8 | 9 | (s/defn operations [items component] 10 | [:div.row.col-xs-12 11 | (for [item items] 12 | (let [k (key item) 13 | v (val item)] 14 | ^{:key k} 15 | [:div.col-xs-12 16 | [component k v]]))]) 17 | 18 | (s/defn tab 19 | [items 20 | type :- ch/operation-type 21 | component 22 | props] 23 | [ui/tab props 24 | [hints/chars-hint] 25 | [operations items component] 26 | [:div.row.center-xs 27 | [ui/icon-button 28 | {:icon-class-name "mdi mdi-plus-circle-outline" 29 | :icon-style {:color (ui/palette-color :primary1-color)} 30 | :on-touch-tap #(f/dispatch [:assoc-l-system-operation type])}]]]) 31 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/sidebar_pagination.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.sidebar-pagination 2 | (:require [material-ui.core :as ui] 3 | [plumbing.core :as p] 4 | [re-frame.core :as f] 5 | [fractalify.utils :as u])) 6 | 7 | (def pagination-items 8 | [["skip-previous" "First page" #(identity 1)] 9 | ["chevron-double-left" "Back 10 pages" #(max (- %2 10) 1)] 10 | ["chevron-left" "Previous page" #(max (dec %2) 1)] 11 | ["chevron-right" "Next page" #(min (inc %2) %1)] 12 | ["chevron-double-right" "Forward 10 pages" #(min (+ %2 10) %1)] 13 | ["skip-next" "Last Page" #(identity %1)]]) 14 | 15 | (def change-page 16 | (u/debounce 17 | (fn [page] 18 | (f/dispatch [:set-form-item :fractals :sidebar :page page])) 19 | 600 true)) 20 | 21 | (defn sidebar-pagination [_] 22 | (let [query-params (f/subscribe [:fractals-sidebar-query-params])] 23 | (fn [fractals _] 24 | (when fractals 25 | (p/letk [[page limit] @query-params 26 | [total-items] fractals 27 | total-pages (u/ceil (/ total-items limit)) 28 | page-buffer (atom page)] 29 | [:div 30 | [:div.row.around-xs.middle-xs 31 | (for [[icon tooltip f] pagination-items] 32 | ^{:key icon} 33 | [ui/icon-button 34 | {:icon-class-name (str "mdi mdi-" icon) 35 | :icon-style {:color (ui/color :grey700)} 36 | :tooltip tooltip 37 | :on-touch-tap #(change-page 38 | (reset! page-buffer (f total-pages @page-buffer)))}])] 39 | [:div.row.center-xs 40 | (str page "/" total-pages)]]))))) 41 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/components/star_count.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.components.star-count 2 | (:require [material-ui.core :as ui])) 3 | 4 | (defn star-count [_] 5 | (fn [count props] 6 | [:div props 7 | [ui/font-icon 8 | {:style {:font-size "1.2em"} 9 | :class-name "mdi mdi-star"}] 10 | [:span count]])) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/db.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.db 2 | (:require [fractalify.utils :as u] 3 | [fractalify.fractals.schemas :as fch])) 4 | 5 | (def default-db 6 | {:forms (merge 7 | (u/coerce-forms-with-defaults fch/FractalsForms) 8 | {:sidebar {:page 1 9 | :sort :star-count 10 | :sort-dir -1 11 | :limit 10}} 12 | fch/dragon-curve)}) 13 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/lib/l_systems.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.lib.l-systems 2 | (:require [schema.core :as s :include-macros true] 3 | [fractalify.workers.schemas :as ch] 4 | [plumbing.core :as p])) 5 | 6 | (s/defn apply-rules :- s/Str 7 | [rules :- {s/Str s/Str} 8 | pattern :- s/Str] 9 | (apply str 10 | (replace rules pattern))) 11 | 12 | (s/defn l-system :- s/Str 13 | [l-system :- ch/LSystem] 14 | (p/letk [[start {rules {}} iterations] l-system] 15 | (nth 16 | (iterate 17 | (partial apply-rules (into {} (vals rules))) start) 18 | iterations))) 19 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/lib/renderer.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.lib.renderer 2 | (:require [schema.core :as s :include-macros true] 3 | [monet.canvas :as c] 4 | [fractalify.styles :as y] 5 | [fractalify.workers.schemas :as turtle-schemas] 6 | [fractalify.fractals.schemas :as ch] 7 | [fractalify.utils :as u] 8 | [plumbing.core :as p])) 9 | 10 | (def ^:dynamic *ctx* (atom nil)) 11 | (def ^:dynamic *canvas* (atom nil)) 12 | 13 | (defn init! [canvas-dom] 14 | (reset! *ctx* (c/get-context canvas-dom "2d")) 15 | (reset! *canvas* canvas-dom)) 16 | 17 | (defn clear-canvas [ctx w h] 18 | (c/clear-rect ctx {:x 0 :y 0 :w w :h h})) 19 | 20 | (s/defn draw-bg 21 | [ctx 22 | canvas-dom 23 | [hex-color alpha] :- ch/Color] 24 | (-> ctx (c/fill-style hex-color) 25 | (c/alpha (/ alpha 100)) 26 | (c/fill-rect {:x 0 :y 0 :w (aget canvas-dom "width") :h (aget canvas-dom "height")}))) 27 | 28 | (s/defn render-lines :- ch/CanvasContext 29 | [ctx :- ch/CanvasContext 30 | lines :- turtle-schemas/Lines] 31 | (doseq [line lines] 32 | (let [[from to] line] 33 | (-> ctx 34 | (c/move-to (:x from) (:y from)) 35 | (c/line-to (:x to) (:y to))))) 36 | ctx) 37 | 38 | (s/defn get-data-url :- ch/Base64Png [] 39 | (.toDataURL (aget @*ctx* "canvas"))) 40 | 41 | (s/defn render! 42 | [canvas-dom :- ch/CanvasElement 43 | canvas :- ch/RenderableCanvas] 44 | (when (or (nil? @*ctx*) (not= canvas-dom (aget @*ctx* "canvas"))) 45 | (init! canvas-dom)) 46 | (let [[stroke-color stroke-alpha] (:color canvas)] 47 | (p/letk [[bg-color line-width lines] canvas] 48 | (-> @*ctx* 49 | (draw-bg canvas-dom ["#FFF" 100]) 50 | (draw-bg canvas-dom bg-color) 51 | (c/stroke-style stroke-color) 52 | (c/alpha (/ stroke-alpha 100)) 53 | (c/stroke-width line-width) 54 | c/begin-path 55 | (render-lines lines) 56 | c/stroke)))) -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/routes.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.routes 2 | (:require [fractalify.router :as t] 3 | [fractalify.fractals.components.fractal-create :as fractal-create] 4 | [fractalify.fractals.components.fractal-detail :as fractal-detail])) 5 | 6 | 7 | (defn define-routes! [] 8 | (t/add-routes! {"fractals" 9 | {"/" 10 | {"create" :fractal-create 11 | [:id] :fractal-detail}}}) 12 | 13 | (defmethod t/panels :fractal-create [] [fractal-create/fractal-create]) 14 | (defmethod t/panels :fractal-detail [] [fractal-detail/fractal-detail])) 15 | -------------------------------------------------------------------------------- /src/cljs/fractalify/fractals/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.fractals.subs 2 | (:require-macros [reagent.ratom :refer [reaction]] 3 | [fractalify.tracer-macros :refer [trace-subs]]) 4 | (:require [fractalify.tracer] 5 | [re-frame.core :as f] 6 | [fractalify.utils :as u] 7 | [fractalify.handler-utils :as d] 8 | [fractalify.router :as t] 9 | [schema.core :as s :include-macros true] 10 | [fractalify.fractals.schemas :as fch])) 11 | 12 | 13 | (f/register-sub 14 | :l-system-new 15 | (fn [db _] 16 | (reaction (get-in @db [:fractals :forms :l-system])))) 17 | 18 | (f/register-sub 19 | :l-system-generating 20 | (fn [db _] 21 | (reaction (get-in @db [:fractals :l-system-generating])))) 22 | 23 | (f/register-sub 24 | :canvas 25 | (fn [db _] 26 | (reaction (get-in @db [:fractals :forms :canvas])))) 27 | 28 | (f/register-sub 29 | :fractal-new 30 | (fn [db _] 31 | (reaction (-> @db 32 | (get-in [:fractals :forms]) 33 | (select-keys [:canvas :l-system]))))) 34 | 35 | (f/register-sub 36 | :fractal-detail 37 | (fn [db _] 38 | (reaction (get-in @db [:fractals :fractal-detail])))) 39 | 40 | (f/register-sub 41 | :fractal-comments 42 | (fn [db _] 43 | (reaction (get-in @db [:fractals :fractal-detail :comments])))) 44 | 45 | (f/register-sub 46 | :fractals-home 47 | (s/fn [db [sub type :- fch/FractalOrderTypes]] 48 | (reaction (get-in @db [:fractals :fractals-home type])))) 49 | 50 | (f/register-sub 51 | :fractals-home-query-params 52 | (s/fn [db [sub type :- fch/FractalOrderTypes]] 53 | (reaction {:sort type :sort-dir -1 :limit 10}))) 54 | 55 | (f/register-sub 56 | :fractals-sidebar 57 | (fn [db _] 58 | (reaction (get-in @db [:fractals :fractals-sidebar])))) 59 | 60 | (f/register-sub 61 | :fractals-sidebar-query-params 62 | (fn [db _] 63 | (reaction (get-in @db [:fractals :forms :sidebar])))) 64 | 65 | (f/register-sub 66 | :fractals-user 67 | (fn [db _] 68 | (reaction (get-in @db [:fractals :fractals-user])))) 69 | 70 | (f/register-sub 71 | :fractals-user-query-params 72 | (s/fn [db] 73 | (let [route-params (f/subscribe [:route-params])] 74 | (reaction (merge {:limit 1000 75 | :sort :created 76 | :sort-dir -1 77 | :username (:username @route-params)} 78 | (u/select-key @db :username)))))) 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/cljs/fractalify/ga.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.ga 2 | (:require [fractalify.router :as t] 3 | [fractalify.utils :as u] 4 | [schema.core :as s :include-macros true])) 5 | 6 | (defn send-page-view [route route-params] 7 | (let [url (apply t/url route (flatten (vec route-params)))] 8 | (js/ga "send" "pageview" url))) 9 | 10 | 11 | (s/defn send-event 12 | ([c a] (send-event c a nil)) 13 | ([c a l] (send-event c a l nil)) 14 | ([category :- (s/cond-pre s/Str s/Keyword) 15 | action :- (s/cond-pre s/Str s/Keyword) 16 | label :- (s/maybe s/Str) 17 | value :- (s/maybe s/Num)] 18 | (js/ga "send" 19 | (clj->js 20 | (merge {:hitType "event" 21 | :eventCategory category 22 | :eventAction action} 23 | (when label {:eventLabel label}) 24 | (when value {:eventValue value}))))) 25 | ) -------------------------------------------------------------------------------- /src/cljs/fractalify/handler_utils.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.handler-utils 2 | (:require [fractalify.utils :as u] 3 | [plumbing.core :as p] 4 | [cljs.core] 5 | [schema.core :as s :include-macros true] 6 | [fractalify.main.schemas :as ch] 7 | [fractalify.router :as t] 8 | [re-frame.core :as f] 9 | [fractalify.ga :as ga] 10 | [com.rpl.specter :as e])) 11 | 12 | (defn logged-user [db] 13 | (get-in db [:users :logged-user])) 14 | 15 | (defn logged-username [db] 16 | (:username (logged-user db))) 17 | 18 | (defn get-form-data [db module form] 19 | (-> db 20 | (get-in [module :forms form]) 21 | (dissoc :errors))) 22 | 23 | (defn clear-text-form [db module form] 24 | (e/transform [module :forms form e/ALL e/LAST] (constantly "") db)) 25 | 26 | (s/defn path-query-params :- (s/maybe ch/QueryParams) 27 | [db path :- ch/DbPath] 28 | (:query-params (get-in db [:queries path]))) 29 | 30 | (s/defn assoc-path-query-params 31 | [db 32 | path :- ch/DbPath 33 | query-params :- ch/QueryParams] 34 | (assoc-in db [:queries path] {:query-params query-params})) 35 | 36 | (s/defn assoc-query-loading [db path :- ch/DbPath v :- s/Bool] 37 | (assoc-in db [:queries path :loading?] v)) 38 | 39 | (s/defn query-loading? :- (s/maybe s/Bool) 40 | [db path :- ch/DbPath] 41 | (get-in db [:queries path :loading?])) 42 | 43 | (s/defn assoc-with-query-params 44 | ([db 45 | path :- ch/DbPath 46 | val 47 | query-params :- ch/QueryParams] 48 | (-> db 49 | (assoc-in path val) 50 | (assoc-path-query-params path query-params)))) 51 | 52 | (defn show-snackbar [snackbar-text] 53 | (f/dispatch [:show-snackbar snackbar-text])) 54 | 55 | (defn snack-n-go! [snackbar-text route] ; :) 56 | (show-snackbar snackbar-text) 57 | (t/go! route)) 58 | 59 | (s/defn create-dispatch [key :- s/Keyword] 60 | #(f/dispatch (into [key] %&))) 61 | 62 | (s/defn create-handler 63 | ([x] (create-handler x identity)) 64 | ([x default-handler] (cond (keyword? x) (create-dispatch x) 65 | (u/function? x) x 66 | :else default-handler))) 67 | 68 | (defn default-send-err-handler [undo? err] 69 | (when undo? 70 | (f/dispatch [:undo])) 71 | (let [status (:status err) 72 | text (condp = status 73 | 401 "Seems like you're not authorized to do this. Maybe try to login" 74 | "Oops, something went awfully wrong :(")] 75 | (ga/send-event :server-error :send (str err) status) 76 | (f/dispatch [:show-snackbar text]))) 77 | 78 | (defn create-send-error-handler 79 | ([undo? x] 80 | (if (map? x) 81 | (create-handler 82 | (p/fnk [status] 83 | (if-let [snack-text (x status)] 84 | (show-snackbar snack-text) 85 | (default-send-err-handler undo? "")))) 86 | (create-handler x (partial default-send-err-handler undo?))))) 87 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/about.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.about 2 | (:require [fractalify.router :as t] 3 | [fractalify.components.paper-panel :as paper-panel])) 4 | 5 | (defn- a [label href] 6 | [:a {:href href :target "_blank"} 7 | label]) 8 | 9 | (defn about [] 10 | [paper-panel/paper-panel 11 | [:div.col-xs-12 12 | [:h1 "About Us"]] 13 | [:div.col-xs-12.text-left.pad-top-20.line-height-bigger 14 | "Fractalify is a entertainment and educational webapp for creating & sharing fractal images 15 | via so called " [a "L-systems" "https://en.wikipedia.org/wiki/L-system"] "." 16 | [:p.mar-top-10 "Source code for fractalify is fully open-source and can be found on " 17 | [a "Github" "https://github.com/madvas/fractalify"] "."] 18 | [:p "The webapp is written purely in " [a "Clojure & Clojurescript" "http://clojure.org/"] 19 | " and can be freely used for any purpose."] 20 | [:p.mar-top-20 21 | "Thank you and enjoy your stay here!" 22 | [:p "Creator of Fractalify, " [a "madvas" "https://github.com/madvas"] " or " 23 | [a "@matuslestan" "https://twitter.com/matuslestan"]]]]]) -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/contact.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.contact 2 | (:require [fractalify.components.form-input :as form-input] 3 | [fractalify.components.paper-content :as paper-content] 4 | [material-ui.core :as ui] 5 | [re-frame.core :as f] 6 | [fractalify.components.form :as form] 7 | [fractalify.components.paper-panel :as paper-panel])) 8 | 9 | (defn contact [] 10 | (let [logged-user (f/subscribe [:logged-user])] 11 | (when-let [email (:email @logged-user)] 12 | (f/dispatch-sync [:set-form-item :main :contact :email email])) 13 | [form/form :main :contact 14 | (fn [vals has-err?] 15 | (let [{:keys [email subject text]} vals] 16 | [paper-panel/paper-panel 17 | [:h1.w-100.text-center "Contact Us"] 18 | 19 | [form-input/email email "Email" [:main :contact :email] 20 | {:name :email}] 21 | 22 | [form-input/text subject "Subject" [:main :contact :subject] 23 | {:required true}] 24 | 25 | [form-input/text text "Message" [:main :contact :text] 26 | {:required true 27 | :multi-line true}] 28 | 29 | [:div.row.col-xs-12.mar-top-20 30 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 31 | [ui/flat-button {:label "Send" 32 | :primary true 33 | :type :submit 34 | :disabled has-err? 35 | :onTouchTap #(f/dispatch [:contact])}]]]]))])) -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/footer.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.footer 2 | (:require 3 | [re-frame.core :as f] 4 | [reagent.core :as r] 5 | [material-ui.core :as ui] 6 | [fractalify.router :as t] 7 | [fractalify.styles :as y])) 8 | 9 | (def social-btn-style 10 | {:position :absolute 11 | :bottom 10 12 | :right 30}) 13 | 14 | (defn footer [] 15 | [:footer.row.middle-xs.center-xs.relative 16 | [:a {:href (t/url :about)} "About"] 17 | [:a.mar-lef-10 {:href (t/url :contact)} "Contact"] 18 | [:a.mar-lef-10 {:href "http://disapainted.com" :target "_blank"} "disapainted.com"] 19 | [:div {:style social-btn-style} 20 | [:a {:class "twitter-share-button" 21 | :href "https://twitter.com/intent/tweet?hashtag=fractalify&url=http://fractalify.com"} "Tweet"]] 22 | [:div {:style (merge social-btn-style 23 | {:right (+ (:right social-btn-style) 60) 24 | :width 80})} 25 | [:iframe {:src "https://ghbtns.com/github-btn.html?user=madvas&repo=fractalify&type=star&count=true" 26 | :frame-border 0 27 | :scrolling 0 28 | :width "80px" 29 | :height "20px"}]]]) 30 | 31 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/header.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.header 2 | (:require 3 | [re-frame.core :as f] 4 | [reagent.core :as r] 5 | [material-ui.core :as ui] 6 | [fractalify.main.components.sidenav :as sidenav] 7 | [fractalify.router :as t] 8 | [fractalify.styles :as y] 9 | [fractalify.utils :as u])) 10 | 11 | (defn- right-button 12 | ([label href] (right-button label href {})) 13 | ([label href props] (right-button label href props ui/flat-button)) 14 | ([label href props btn-fn] 15 | [btn-fn (r/merge-props 16 | {:label label 17 | :linkButton true 18 | :href href 19 | :style y/header-btn} props)])) 20 | 21 | (defn header [] 22 | (let [user (f/subscribe [:logged-user])] 23 | (fn [] 24 | (let [right-btn (if @user 25 | [:div.row.middle-xs.pad-top-5 26 | [:div.col-xs.text-center 27 | (right-button "Create" 28 | (t/url :fractal-create) 29 | {:primary true} 30 | ui/raised-button)] 31 | [:div.col-xs 32 | (right-button (u/ellipsis (:username @user) 10) 33 | (t/url :user-detail :username (:username @user)))]] 34 | (right-button "Login / Join" 35 | (t/url :login)))] 36 | [ui/app-bar {:title (r/as-element 37 | [:a {:href (t/url :home) :class "no-dec"} 38 | [:img {:src "/public/img/fractalify.svg" 39 | :style y/logo}]]) 40 | :iconElementRight (r/as-element right-btn) 41 | :showMenuIconButton (not (empty? @user)) 42 | :onLeftIconButtonTouchTap #(sidenav/toggle-sidenav!)}])))) 43 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/home.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.home 2 | (:require [fractalify.router :as t] 3 | [material-ui.core :as ui] 4 | [fractalify.components.api-wrap :as api-wrap] 5 | [schema.core :as s :include-macros true] 6 | [fractalify.fractals.schemas :as fch] 7 | [plumbing.core :as p] 8 | [fractalify.utils :as u] 9 | [fractalify.styles :as y] 10 | [fractalify.fractals.components.fractal-card-list :as fractal-card-list])) 11 | 12 | (s/defn create-home-api [type :- fch/FractalOrderTypes] 13 | (api-wrap/create-api-wrap 14 | {:api-route :fractals 15 | :path [:fractals :fractals-home type] 16 | :value-sub [:fractals-home type] 17 | :query-params-sub [:fractals-home-query-params type] 18 | :force-reload true})) 19 | 20 | (def best-api-wrap (create-home-api :star-count)) 21 | (def recent-api-wrap (create-home-api :created)) 22 | 23 | (defn fractals-list [_] 24 | (fn [title fractals] 25 | [:div.row.center-xs.mar-bot-20 26 | [:h1.col-xs-12 title] 27 | [:hr.col-xs-10.mar-bot-20] 28 | [fractal-card-list/fractal-card-list fractals {:class "col-lg-2p5" 29 | :container-props {:class "center-xs"}}]])) 30 | 31 | (defn home [] 32 | [:div.pad-20 33 | [best-api-wrap 34 | [fractals-list "Top Rated"]] 35 | [recent-api-wrap 36 | [fractals-list "Most Recent"]]]) -------------------------------------------------------------------------------- /src/cljs/fractalify/main/components/sidenav.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.components.sidenav 2 | (:require 3 | [re-frame.core :as f] 4 | [reagent.core :as r] 5 | [material-ui.core :as ui] 6 | [fractalify.utils :as u] 7 | [fractalify.router :as t])) 8 | 9 | (def ^:dynamic *sidenav-parent* (atom)) 10 | 11 | (defn- get-sidenav-ref [] 12 | (aget @*sidenav-parent* "refs" "sidenav")) 13 | 14 | (defn toggle-sidenav! [] 15 | (when @*sidenav-parent* 16 | (.toggle (get-sidenav-ref)))) 17 | 18 | (defn close-sidenav! [] 19 | (when @*sidenav-parent* 20 | (.close (get-sidenav-ref)))) 21 | 22 | (defn sidenav [] 23 | (let [logged-user (f/subscribe [:logged-user])] 24 | (r/create-class 25 | {:component-did-mount 26 | (fn [this] 27 | (reset! *sidenav-parent* this)) 28 | :reagent-render 29 | (fn [] 30 | [ui/left-nav 31 | {:ref "sidenav" 32 | :docked false 33 | :on-change (fn [_ _ menu-item] 34 | (when (= "Logout" (:text (js->clj menu-item :keywordize-keys true))) 35 | (f/dispatch [:logout]))) 36 | :menuItems [{:text (:username @logged-user) 37 | :type ui/MENU-ITEM-SUBHEADER} 38 | {:text "Create Fractal" 39 | :payload (t/url :fractal-create) 40 | :type ui/MENU-ITEM-LINK} 41 | {:text "Change Password" 42 | :payload (t/url :change-password) 43 | :type ui/MENU-ITEM-LINK} 44 | {:text "Edit profile" 45 | :payload (t/url :edit-profile) 46 | :type ui/MENU-ITEM-LINK} 47 | {:text "Logout"}]}])}))) 48 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/db.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.db 2 | (:require [fractalify.utils :as u] 3 | [fractalify.main.schemas :as mch])) 4 | 5 | (def default-db 6 | {:forms (u/coerce-forms-with-defaults mch/MainForms)}) -------------------------------------------------------------------------------- /src/cljs/fractalify/main/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.handlers 2 | (:require-macros [fractalify.tracer-macros :refer [trace-handlers]] 3 | [clairvoyant.core :refer [trace-forms]]) 4 | (:require [fractalify.db :as db] 5 | [fractalify.middleware :as m] 6 | [fractalify.utils :as u] 7 | [fractalify.components.snackbar :as snackbar] 8 | [fractalify.router :as t] 9 | [fractalify.main.components.sidenav :as sidenav] 10 | [fractalify.tracer :refer [tracer]] 11 | [fractalify.components.dialog :as dialog] 12 | [fractalify.api :as api] 13 | [re-frame.core :as f] 14 | [fractalify.handler-utils :as d] 15 | [schema.core :as s :include-macros true] 16 | [plumbing.core :as p] 17 | [fractalify.ga :as ga])) 18 | 19 | (defn api-send! [params] 20 | (-> params 21 | (update :handler d/create-handler) 22 | (update :error-handler (partial d/create-send-error-handler (:error-undo? params))) 23 | api/send!)) 24 | 25 | (f/register-handler 26 | :initialize 27 | m/standard-no-debug 28 | (fn [_] 29 | (f/dispatch [:api-fetch 30 | {:api-route :logged-user 31 | :path [:users :logged-user]}]) 32 | db/default-db)) 33 | 34 | (f/register-handler 35 | :set-active-panel 36 | m/standard-middlewares 37 | (fn [db [active-panel route-params]] 38 | (sidenav/close-sidenav!) 39 | (ga/send-page-view active-panel route-params) 40 | (assoc db :active-panel active-panel 41 | :route-params route-params))) 42 | 43 | (f/register-handler 44 | :set-form-item 45 | m/standard-middlewares 46 | (fn [db [module & params]] 47 | (let [value (last params) 48 | path (vec (butlast params))] 49 | (assoc-in db (into [module :forms] path) value)))) 50 | 51 | (f/register-handler 52 | :set-form 53 | m/standard-middlewares 54 | (fn [db [module form value merge?]] 55 | (update-in db [module :forms form] #(if merge? (merge % value) value)))) 56 | 57 | (f/register-handler 58 | :dissoc-form-item 59 | m/standard-middlewares 60 | (fn [db [module & path]] 61 | (u/dissoc-in db (into [module :forms] path)))) 62 | 63 | (f/register-handler 64 | :form-error 65 | m/standard-no-debug 66 | ;m/standard-middlewares 67 | (fn [db [module form-name & params]] 68 | (let [value (last params) 69 | item-path (into [] (butlast params)) 70 | path (into [module :forms form-name :errors] item-path)] 71 | (if value 72 | (assoc-in db path value) 73 | (u/dissoc-in db path))))) 74 | 75 | (f/register-handler 76 | :show-snackbar 77 | m/standard-no-debug 78 | (fn [db [msg & snackbar-props]] 79 | (snackbar/show-snackbar!) 80 | (assoc db :snackbar-props 81 | (assoc snackbar-props :message msg)))) 82 | 83 | (f/register-handler 84 | :show-dialog 85 | m/standard-no-debug 86 | (fn [db [dialog-props]] 87 | (let [db (assoc db :dialog-props dialog-props)] 88 | (dialog/show-dialog!) 89 | db))) 90 | 91 | (f/register-handler 92 | :api-fetch 93 | m/standard-middlewares 94 | (fn [db [opts]] 95 | (p/letk [[api-route 96 | path 97 | {query-params {}} 98 | {route-param-names []} 99 | {force-reload false} 100 | {handler #(f/dispatch [:default-fetch-resp path query-params %])} 101 | {error-handler :default-fetch-resp-err} 102 | ] opts] 103 | (if (or force-reload 104 | (not= (d/path-query-params db path) query-params)) 105 | (do (api/fetch! api-route query-params route-param-names 106 | {:handler (d/create-handler handler) 107 | :error-handler (d/create-handler error-handler)}) 108 | (d/assoc-query-loading db path true)) 109 | db)))) 110 | 111 | (f/register-handler 112 | :default-fetch-resp 113 | m/standard-middlewares 114 | (fn [db [path query-params val]] 115 | (-> db 116 | (assoc-in path val) 117 | (d/assoc-path-query-params path query-params)))) 118 | 119 | (f/register-handler 120 | :default-fetch-resp-err 121 | m/standard-middlewares 122 | (fn [db [err]] 123 | (if (= 404 (:status err)) 124 | (f/dispatch [:show-snackbar "Sorry, but this page was not found..."]) 125 | (u/merror "Error while fetching " err)) 126 | (ga/send-event :server-error :fetch (str err) (:status err)) 127 | db)) 128 | 129 | (f/register-handler 130 | :api-put 131 | m/standard-middlewares 132 | (fn [db [opts]] 133 | (api-send! (merge {:method :put} opts)) 134 | db)) 135 | 136 | (f/register-handler 137 | :api-post 138 | m/standard-middlewares 139 | (fn [db [opts]] 140 | (api-send! (merge {:method :post :params {}} opts)) 141 | db)) 142 | 143 | (f/register-handler 144 | :api-delete 145 | m/standard-middlewares 146 | (fn [db [opts]] 147 | (api-send! (merge {:method :delete :params {}} opts)) 148 | db)) 149 | 150 | (f/register-handler 151 | :contact 152 | m/standard-middlewares 153 | (fn [db _] 154 | (let [form-data (d/get-form-data db :main :contact)] 155 | (f/dispatch [:api-post 156 | {:api-route :contact 157 | :params form-data 158 | :handler :contact-resp}]) 159 | (ga/send-event :main :contact (:email form-data)) 160 | db))) 161 | 162 | (f/register-handler 163 | :contact-resp 164 | m/standard-middlewares 165 | (fn [db _] 166 | (d/snack-n-go! "You message has been sent. Thank you for contacting us." :home) 167 | (d/clear-text-form db :main :contact))) 168 | 169 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/routes.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.routes 2 | (:require [re-frame.core :as r] 3 | [fractalify.router :as t] 4 | [fractalify.main.components.home :as home] 5 | [fractalify.main.components.about :as about] 6 | [fractalify.main.components.contact :as contact])) 7 | 8 | 9 | (defn define-routes! [] 10 | 11 | (t/add-routes! {"" :home 12 | "about" :about 13 | "contact" :contact}) 14 | 15 | (defmethod t/panels :home [] [home/home]) 16 | (defmethod t/panels :about [] [about/about]) 17 | (defmethod t/panels :contact [] [contact/contact]) 18 | (defmethod t/panels :default [] [:h1 "Page not found"])) 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.subs 2 | (:require-macros [reagent.ratom :refer [reaction]] 3 | [fractalify.tracer-macros :refer [trace-subs]]) 4 | (:require [fractalify.main.handlers :as h] 5 | [clairvoyant.core :refer-macros [trace-forms]] 6 | [fractalify.tracer :refer [tracer]] 7 | [fractalify.utils :as u] 8 | [fractalify.handler-utils :as d] 9 | [re-frame.core :as f] 10 | [schema.core :as s :include-macros true] 11 | [fractalify.main.schemas :as ch])) 12 | 13 | (f/register-sub 14 | :active-panel 15 | (fn [db _] 16 | (reaction (:active-panel @db)))) 17 | 18 | (f/register-sub 19 | :route-params 20 | (fn [db _] 21 | (reaction (:route-params @db)))) 22 | 23 | (f/register-sub 24 | :snackbar-props 25 | (fn [db _] 26 | (reaction (:snackbar-props @db)))) 27 | 28 | (f/register-sub 29 | :dialog-props 30 | (fn [db _] 31 | (reaction (:dialog-props @db)))) 32 | 33 | (f/register-sub 34 | :form-errors 35 | (fn [db [_ module form]] 36 | (reaction (get-in @db [module :forms form :errors])))) 37 | 38 | (f/register-sub 39 | :form-item 40 | (fn [db [_ module & path]] 41 | (reaction 42 | (if-let [key (:key (last path))] 43 | key 44 | (get-in @db (into [module :forms] path)))))) 45 | 46 | (f/register-sub 47 | :form-data 48 | (fn [db [_ module form]] 49 | (reaction (d/get-form-data @db module form)))) 50 | 51 | (f/register-sub 52 | :loading? 53 | (s/fn [db [sub path :- ch/QueryParams]] 54 | (reaction (d/query-loading? @db path)))) 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/cljs/fractalify/main/view.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.main.view 2 | (:require [re-frame.core :as f] 3 | [fractalify.router :as t] 4 | [fractalify.main.components.header :as header] 5 | [fractalify.main.components.sidenav :as sidenav] 6 | [fractalify.components.snackbar :as snackbar] 7 | [fractalify.main.components.footer :as footer] 8 | [fractalify.components.dialog :as dialog] 9 | [material-ui.core :as ui] 10 | [fractalify.styles :as y])) 11 | 12 | (defn main-layout [] 13 | (let [active-panel (f/subscribe [:active-panel])] 14 | (fn [] 15 | [ui/app-canvas 16 | [header/header] 17 | [sidenav/sidenav] 18 | [snackbar/snackbar] 19 | [dialog/dialog] 20 | [:div {:style y/main-body} 21 | (t/panels @active-panel)] 22 | [footer/footer]]))) 23 | 24 | (defn main-view [] 25 | (ui/set-palette! 26 | {:canvasColor "#F2F2F2"}) 27 | 28 | [ui/mui-theme-wrap 29 | [main-layout]]) 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/cljs/fractalify/middleware.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.middleware 2 | (:require [fractalify.db :as db] 3 | [re-frame.core :as r :refer [after trim-v debug undoable]] 4 | [fractalify.utils :as u])) 5 | 6 | (def enabled? 7 | ;true 8 | false 9 | ) 10 | 11 | (def standard-middlewares [(when ^boolean (and goog.DEBUG enabled?) debug) 12 | trim-v 13 | (when ^boolean goog.DEBUG (after db/valid?))]) 14 | 15 | (def standard-no-debug (rest standard-middlewares)) -------------------------------------------------------------------------------- /src/cljs/fractalify/router.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.router 2 | (:require [clojure.set :refer [rename-keys]] 3 | [bidi.bidi :as b] 4 | [pushy.core :as pu] 5 | [re-frame.core :as f] 6 | [fractalify.utils :as u] 7 | [plumbing.core :as p])) 8 | 9 | (defmulti panels identity) 10 | 11 | (def ^:dynamic *routes* (atom ["/" {}])) 12 | (def ^:dynamic *history* (atom)) 13 | 14 | (declare go!) 15 | (declare swap-route!) 16 | 17 | (defn- parse-url [url] 18 | (b/match-route @*routes* url)) 19 | 20 | (p/defnk dispatch-route [handler {tag nil} {route-params nil}] 21 | (let [active-panel (if (keyword? handler) handler tag)] 22 | (f/dispatch [:set-active-panel active-panel route-params]))) 23 | 24 | (defn add-routes! [routes] 25 | (swap! *routes* (fn [[root current] new] 26 | [root (merge current new)]) 27 | routes)) 28 | 29 | (defn url [& args] 30 | (apply b/path-for @*routes* args)) 31 | 32 | (defn start! [] 33 | (reset! *history* (pu/pushy dispatch-route parse-url)) 34 | (pu/start! @*history*)) 35 | 36 | (defn go! [& route] 37 | (pu/set-token! @*history* (apply url route))) 38 | 39 | (defn replace! [& route] 40 | (pu/replace-token! @*history* (apply url route))) 41 | -------------------------------------------------------------------------------- /src/cljs/fractalify/tracer.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.tracer 2 | (:require 3 | [re-frame-tracer.core :as t] 4 | [clairvoyant.core :as c :refer [ITraceEnter ITraceError ITraceExit]] 5 | [fractalify.utils :as u])) 6 | 7 | (def always-trace 8 | ;true 9 | false 10 | ) 11 | 12 | (def trace? #(or (.-TRACE js/window) always-trace)) 13 | 14 | (defn tracer 15 | [& args] 16 | (let [tr (apply t/tracer args)] 17 | (reify 18 | ITraceEnter 19 | (-trace-enter 20 | [_ trace-data] 21 | (when (trace?) 22 | (c/trace-enter tr trace-data))) 23 | 24 | ITraceExit 25 | (-trace-exit 26 | [_ trace-data] 27 | (when (trace?) 28 | (c/trace-exit tr trace-data))) 29 | 30 | ITraceError 31 | (-trace-error 32 | [_ trace-data] 33 | (when (trace?) 34 | (c/trace-error tr trace-data)))))) 35 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/change_pass.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.change-pass 2 | (:require [material-ui.core :as ui] 3 | [fractalify.validators :as v] 4 | [fractalify.components.paper-panel :as paper-panel] 5 | [re-frame.core :as f] 6 | [fractalify.components.form :as form] 7 | [fractalify.components.form-input :as form-input])) 8 | 9 | (defn change-pass [] 10 | [form/form :users :change-password 11 | (fn [vals has-err?] 12 | (let [{:keys [current-pass new-pass confirm-new-pass]} vals] 13 | [paper-panel/paper-panel 14 | [:div.col-xs-12 15 | [:h1 "Change Password"]] 16 | [:div.col-xs-12 17 | [form-input/password current-pass "Current password" 18 | [:users :change-password :current-pass]] 19 | [form-input/password new-pass "New password" 20 | [:users :change-password :new-pass]] 21 | [form-input/password confirm-new-pass "Confirm new password" 22 | [:users :change-password :confirm-new-pass] 23 | {:validators [(v/passwords-match new-pass)]}] 24 | [:div.row.col-xs-12.mar-top-20 25 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 26 | [ui/flat-button {:label "Save" 27 | :primary true 28 | :disabled has-err? 29 | :onTouchTap #(f/dispatch [:change-password])}]]]]]))]) 30 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/edit_profile.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.edit-profile 2 | (:require [material-ui.core :as ui] 3 | [fractalify.validators :as v] 4 | [fractalify.components.paper-panel :as paper-panel] 5 | [re-frame.core :as f] 6 | [fractalify.components.text-field :as text-field] 7 | [fractalify.components.form-input :as form-input] 8 | [fractalify.components.form :as form])) 9 | 10 | (def user-bio-maxlen 140) 11 | 12 | (defn edit-profile [] 13 | (let [user (f/subscribe [:logged-user])] 14 | (f/dispatch-sync [:set-form :users :edit-profile (select-keys @user [:email :bio])]) 15 | (fn [] 16 | [form/form :users :edit-profile 17 | (fn [vals has-err?] 18 | (let [{:keys [email bio]} vals] 19 | [paper-panel/paper-panel 20 | [:div.col-xs-12 21 | [:h1 "Edit your profile"]] 22 | [:div.col-xs-12 23 | [text-field/text-field (:username @user) "Username" nil nil 24 | {:disabled true}]] 25 | [:div.col-xs-12 26 | [form-input/email email "Email" [:users :edit-profile :email]]] 27 | [:div.col-xs-12 28 | [form-input/text bio "Bio" [:users :edit-profile :bio] 29 | {:multi-line true 30 | :validators [(v/length 0 user-bio-maxlen)]}]] 31 | [:div.col-xs-12 32 | [:div.row.col-xs-12.mar-top-20 33 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 34 | [ui/flat-button {:label "Save" 35 | :primary true 36 | :disabled has-err? 37 | :on-touch-tap #(f/dispatch [:edit-profile])}]]]]]))]))) 38 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/forgot_pass.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.forgot-pass 2 | (:require [fractalify.components.paper-panel :as paper-panel] 3 | [fractalify.validators :as v] 4 | [fractalify.components.form-input :as form-input] 5 | [re-frame.core :as f] 6 | [material-ui.core :as ui] 7 | [fractalify.components.form :as form])) 8 | 9 | 10 | (defn forgot-pass [] 11 | [form/form :users :forgot-password 12 | (fn [vals has-err?] 13 | [paper-panel/paper-panel 14 | [:div.col-xs-12 15 | [:h1 "Restore Password"]] 16 | [:div.col-xs-12 17 | [form-input/email (:email vals) "Your Email" [:users :forgot-password :email]] 18 | [:div.row.col-xs-12.mar-top-20 19 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 20 | [ui/flat-button {:label "Send" 21 | :primary true 22 | :disabled has-err? 23 | :onTouchTap #(f/dispatch [:forgot-password])}]]]]])]) 24 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/login_join.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.login-join 2 | (:require [re-frame.core :as f] 3 | [material-ui.core :as ui] 4 | [fractalify.styles :as y] 5 | [fractalify.router :as t] 6 | [fractalify.components.form-input :as form-input] 7 | [schema.core :as s :include-macros true] 8 | [fractalify.components.tab-anchor :as tab-anchor] 9 | [fractalify.validators :as v] 10 | [fractalify.components.responsive-panel :as responsive-panel] 11 | [fractalify.components.paper-content :as paper-content] 12 | [fractalify.components.form :as form] 13 | [plumbing.core :as p] 14 | [fractalify.utils :as u])) 15 | 16 | (defn login-tab [] 17 | [form/form :users :login 18 | (fn [vals has-err?] 19 | (let [{:keys [username password]} vals] 20 | [paper-content/paper-content 21 | [:div.col-xs-12 22 | [form-input/text username "Username or email" [:users :login :username] 23 | {:required true 24 | :name :username}]] 25 | 26 | [:div.col-xs-12 27 | [form-input/password password "Password" [:users :login :password] 28 | {:name :password}]] 29 | 30 | [:div.row.col-xs-12.mar-top-20 31 | [:div.col-xs-6 32 | [ui/flat-button {:label "Forgot password?" 33 | :linkButton true 34 | :href (t/url :forgot-password) 35 | :tab-index -1 36 | :style y/text-center}]] 37 | [:div.col-xs-6 38 | [ui/flat-button {:label "Login" 39 | :primary true 40 | :type :submit 41 | :disabled has-err? 42 | :onTouchTap #(f/dispatch [:login])}]]]]))]) 43 | 44 | (defn join-tab [] 45 | [form/form :users :join 46 | (fn [vals has-err?] 47 | (let [{:keys [username email password confirm-pass]} vals] 48 | [paper-content/paper-content 49 | [:div.col-xs-12 50 | [form-input/text username "Username" [:users :join :username] 51 | {:required true 52 | :validators [(v/length 3)] 53 | :name :username}]] 54 | 55 | [:div.col-xs-12 56 | [form-input/email email "Email" [:users :join :email] 57 | {:name :email}]] 58 | 59 | [:div.col-xs-12 60 | [form-input/password password "Password" [:users :join :password] 61 | {:name :password}]] 62 | 63 | [:div.col-xs-12 64 | [form-input/password confirm-pass "Confirm Password" [:users :join :confirm-pass] 65 | {:validators [(v/passwords-match password)]}]] 66 | 67 | [:div.row.col-xs-12.mar-top-20 68 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 69 | [ui/flat-button {:label "Create Account" 70 | :primary true 71 | :type :submit 72 | :disabled has-err? 73 | :onTouchTap #(f/dispatch [:join])}]]]]))]) 74 | 75 | (s/defn login-join [type :- (s/enum :login :join)] 76 | [responsive-panel/responsive-panel 77 | [ui/tabs {:initialSelectedIndex (if (= type :login) 0 1)} 78 | (tab-anchor/tab-anchor {:label "Login" :href (t/url :login)} [login-tab]) 79 | (tab-anchor/tab-anchor {:label "Join" :href (t/url :join)} [join-tab])]]) 80 | 81 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/reset_pass.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.reset-pass 2 | (:require [material-ui.core :as ui] 3 | [fractalify.components.paper-panel :as paper-panel] 4 | [re-frame.core :as f] 5 | [fractalify.components.form-input :as form-input] 6 | [fractalify.utils :as u] 7 | [fractalify.validators :as v] 8 | [fractalify.components.form :as form])) 9 | 10 | (defn reset-pass [] 11 | (let [route-params (f/subscribe [:route-params])] 12 | (f/dispatch-sync [:set-form :users :reset-password 13 | (select-keys @route-params [:username :token]) true]) 14 | (fn [] 15 | [form/form :users :reset-password 16 | (fn [vals has-err?] 17 | (let [{:keys [new-pass]} vals] 18 | [paper-panel/paper-panel 19 | [:div.col-xs-12 20 | [:h1 "Reset Password"]] 21 | [:div.col-xs-12 22 | [form-input/password new-pass "New password" 23 | [:users :reset-password :new-pass]] 24 | [:div.row.col-xs-12.mar-top-20 25 | [:div.col-xs-12.col-sm-6.col-sm-offset-6 26 | [ui/flat-button {:label "Save" 27 | :primary true 28 | :disabled has-err? 29 | :onTouchTap #(f/dispatch [:reset-password])}]]]]]))]))) 30 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/components/user_detail.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.components.user-detail 2 | (:require [fractalify.components.api-wrap :as api-wrap] 3 | [plumbing.core :as p] 4 | [schema.core :as s :include-macros true] 5 | [material-ui.core :as ui] 6 | [fractalify.components.gravatar :as gravatar] 7 | [fractalify.fractals.schemas :as fch] 8 | [fractalify.utils :as u] 9 | [fractalify.styles :as y] 10 | [fractalify.router :as t] 11 | [fractalify.fractals.components.fractal-card-list :as fractal-card-list] 12 | [re-frame.core :as f] 13 | [reagent.core :as r])) 14 | 15 | (def user-api-wrap 16 | (api-wrap/create-api-wrap 17 | {:api-route :user 18 | :path [:users :user-detail] 19 | :value-sub :user-detail 20 | :query-params-sub :route-params 21 | :route-param-names [:username]})) 22 | 23 | (def user-fractals-api-wrap 24 | (api-wrap/create-api-wrap 25 | {:api-route :fractals 26 | :path [:fractals :fractals-user] 27 | :value-sub :fractals-user 28 | :query-params-sub :fractals-user-query-params 29 | :force-reload true})) 30 | 31 | (def gravatar-size 300) 32 | 33 | (defn user-card [] 34 | (s/fn [user] 35 | [:div.row.center-xs.col-xs-12.col-lg-3.top-xs.text-left 36 | (when user 37 | (p/letk [[username gravatar bio] user] 38 | [ui/card {:style {:max-width gravatar-size}} 39 | [ui/card-media 40 | [gravatar/gravatar gravatar gravatar-size]] 41 | [ui/card-title [:h2 username]] 42 | [ui/card-text [:div bio]]]))])) 43 | 44 | (defn create-delete-dialog [fractal] 45 | {:title "Are you sure?" 46 | :action-focus "cancel" 47 | :content [:div (str "Once you delete " (:title fractal) ", it's gone forever!")] 48 | :actions [{:text "Cancel" :ref "cancel"} 49 | {:text "Delete" :onTouchTap #(f/dispatch [:fractal-remove fractal])}]}) 50 | 51 | (defn remove-fractal-btn [] 52 | (s/fn [fractal :- fch/PublishedFractal] 53 | [:div.col-xs-12.mar-0 54 | [ui/flat-button 55 | {:label "Delete" 56 | :primary true 57 | :on-touch-tap #(f/dispatch [:show-dialog (create-delete-dialog fractal)])}]])) 58 | 59 | (defn user-fractals [] 60 | (let [my-user-detail? (f/subscribe [:my-user-detail?])] 61 | (s/fn [fractals :- (s/maybe fch/PublishedFractalsList)] 62 | [:div.row.col-xs-12.col-lg-9.top-xs 63 | [:div.row.col-xs-12.text-left.pad-hor-20.center-xs 64 | [:h1.col-xs-12.pad-top-10 (str "Fractals (" (:total-items fractals) ")")] 65 | [:hr.col-xs-12]] 66 | (if (u/empty-seq? (:items fractals)) 67 | [:h3.col-xs-12 "This user haven't created anything yet"] 68 | [:div.row.col-xs-12.text-left.pad-hor-20.center-xs 69 | [fractal-card-list/fractal-card-list fractals 70 | (when @my-user-detail? {:actions [remove-fractal-btn]})]])]))) 71 | 72 | 73 | (defn user-detail [] 74 | [:div.row.center-xs 75 | [user-api-wrap 76 | [user-card]] 77 | [user-fractals-api-wrap 78 | [user-fractals]]]) 79 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/db.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.db 2 | (:require [fractalify.utils :as u] 3 | [fractalify.users.schemas :as uch])) 4 | 5 | 6 | (def default-db 7 | {:forms (merge 8 | (u/coerce-forms-with-defaults uch/UserForms) 9 | (when goog.DEBUG {:login {:username "admin" :password "111111"} 10 | :join {:username "newuser" 11 | :email "some@email.com" 12 | :password "111111" 13 | :confirm-pass "111111" 14 | :bio ""} 15 | :forgot-password {:email "matus.lestan@gmail.com"} 16 | :change-password {:current-pass "111111" 17 | :new-pass "111111" 18 | :confirm-new-pass "111111"}}))}) 19 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.handlers 2 | (:require-macros [fractalify.tracer-macros :refer [trace-handlers]]) 3 | (:require [fractalify.middleware :as m] 4 | [re-frame.core :as r] 5 | [fractalify.handler-utils :as d] 6 | [re-frame.core :as f] 7 | [fractalify.router :as t] 8 | [fractalify.tracer] 9 | [fractalify.utils :as u] 10 | [fractalify.ga :as ga])) 11 | 12 | (defn login-user [db user] 13 | (t/go! :home) 14 | (assoc-in db [:users :logged-user] user)) 15 | 16 | 17 | (r/register-handler 18 | :login 19 | m/standard-middlewares 20 | (fn [db _] 21 | (f/dispatch 22 | [:api-post 23 | {:api-route :login 24 | :params (d/get-form-data db :users :login) 25 | :handler :login-resp 26 | :error-handler {403 "Sorry, these are wrong credentials"}}]) 27 | db)) 28 | 29 | (r/register-handler 30 | :login-resp 31 | m/standard-middlewares 32 | (fn [db [user]] 33 | (ga/send-event :users :login (:username user)) 34 | (login-user db user))) 35 | 36 | (r/register-handler 37 | :join 38 | m/standard-middlewares 39 | (fn [db _] 40 | (f/dispatch 41 | [:api-put 42 | {:api-route :join 43 | :params (d/get-form-data db :users :join) 44 | :handler (fn [user] 45 | (d/show-snackbar "Welcome to fractalify, enjoy your stay here :)") 46 | (ga/send-event :users :join (:username user)) 47 | (f/dispatch [:login-resp user])) 48 | :error-handler {409 "Sorry, this account already exists. Please choose other"}}]) 49 | db)) 50 | 51 | (r/register-handler 52 | :logout 53 | [m/standard-middlewares (f/undoable "logout")] 54 | (fn [db _] 55 | (f/dispatch [:api-post 56 | {:api-route :logout 57 | :error-undo? true}]) 58 | (t/go! :home) 59 | (ga/send-event :users :logout (d/logged-username db)) 60 | (u/dissoc-in db [:users :logged-user]))) 61 | 62 | (r/register-handler 63 | :forgot-password 64 | [m/standard-middlewares (r/undoable "forgot-password")] 65 | (fn [db _] 66 | (let [form-data (d/get-form-data db :users :forgot-password)] 67 | (f/dispatch [:api-post 68 | {:api-route :forgot-password 69 | :params form-data 70 | :handler #(d/snack-n-go! "Password was reset. Please check your email." :home) 71 | :error-undo? true}]) 72 | (ga/send-event :users "forgot-password" (:email form-data)) 73 | (assoc-in db [:users :forms :forgot-password :email] "")))) 74 | 75 | (r/register-handler 76 | :edit-profile 77 | [m/standard-middlewares (f/undoable "edit-profile")] 78 | (fn [db _] 79 | (let [profile (d/get-form-data db :users :edit-profile)] 80 | (f/dispatch [:api-post 81 | {:api-route :edit-profile 82 | :route-params (u/select-key (d/logged-user db) :username) 83 | :params profile 84 | :handler #(d/show-snackbar "Your profile was successfully saved.") 85 | :error-undo? true}]) 86 | (ga/send-event :users :edit-profile (d/logged-username db)) 87 | (update-in db [:users :logged-user] (u/partial-right merge profile))))) 88 | 89 | 90 | (r/register-handler 91 | :change-password 92 | m/standard-middlewares 93 | (fn [db _] 94 | (f/dispatch [:api-post 95 | {:api-route :change-password 96 | :route-params (u/select-key (d/logged-user db) :username) 97 | :params (d/get-form-data db :users :change-password) 98 | :handler #(d/show-snackbar "Your password has been successfully changed.")}]) 99 | (ga/send-event :users :change-password (d/logged-username db)) 100 | db)) 101 | 102 | (r/register-handler 103 | :reset-password 104 | m/standard-middlewares 105 | (fn [db _] 106 | (let [form-data (d/get-form-data db :users :reset-password)] 107 | (f/dispatch [:api-post 108 | {:api-route :reset-password 109 | :params form-data 110 | :route-params (u/select-key form-data :username) 111 | :handler #(d/snack-n-go! "Your password has been reset. You can login now" :login) 112 | :error-handler {401 "Sorry, your token is invalid or expired"}}]) 113 | (ga/send-event :users :reset-password (:username form-data))) 114 | db)) 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/routes.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.routes 2 | (:require [re-frame.core :as r] 3 | [fractalify.router :as t] 4 | [fractalify.users.components.login-join :as login-join] 5 | [fractalify.users.components.forgot-pass :as forgot-pass] 6 | [fractalify.users.components.change-pass :as change-pass] 7 | [fractalify.users.components.edit-profile :as edit-profile] 8 | [fractalify.users.components.user-detail :as user-detail] 9 | [fractalify.users.components.reset-pass :as reset-pass])) 10 | 11 | (defn define-routes! [] 12 | 13 | (t/add-routes! {"login" :login 14 | "join" :join 15 | "logout" :logout 16 | "forgot-password" :forgot-password 17 | ["reset-password/" :username "/" :token] :reset-password 18 | "change-password" :change-password 19 | "edit-profile" :edit-profile 20 | ["users/" :username] :user-detail}) 21 | 22 | (defmethod t/panels :login [] (login-join/login-join :login)) 23 | (defmethod t/panels :join [] (login-join/login-join :join)) 24 | (defmethod t/panels :forgot-password [] [forgot-pass/forgot-pass]) 25 | (defmethod t/panels :change-password [] [change-pass/change-pass]) 26 | (defmethod t/panels :reset-password [] [reset-pass/reset-pass]) 27 | (defmethod t/panels :edit-profile [] [edit-profile/edit-profile]) 28 | (defmethod t/panels :user-detail [] [user-detail/user-detail])) 29 | -------------------------------------------------------------------------------- /src/cljs/fractalify/users/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.users.subs 2 | (:require-macros [reagent.ratom :refer [reaction]] 3 | [fractalify.tracer-macros :refer [trace-subs]]) 4 | (:require [re-frame.core :as f] 5 | [fractalify.tracer :refer [tracer]])) 6 | 7 | (f/register-sub 8 | :logged-user 9 | (fn [db _] 10 | (reaction (get-in @db [:users :logged-user])))) 11 | 12 | (f/register-sub 13 | :username 14 | (fn [_] 15 | (let [user (f/subscribe [:logged-user])] 16 | (reaction (:username @user))))) 17 | 18 | (f/register-sub 19 | :user-detail 20 | (fn [db _] 21 | (reaction (get-in @db [:users :user-detail])))) 22 | 23 | (f/register-sub 24 | :users-form-errors 25 | (fn [_ path & args] 26 | (let [errors (apply f/subscribe (into [:form-errors :users] path) args)] 27 | (reaction @errors)))) 28 | 29 | (f/register-sub 30 | :my-user-detail? 31 | (fn [db _] 32 | (let [logged-user (f/subscribe [:logged-user])] 33 | (reaction (= (:username (get-in @db [:users :user-detail])) 34 | (:username @logged-user)))))) -------------------------------------------------------------------------------- /src/cljs/fractalify/validators.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.validators 2 | (:require [fractalify.utils :as u] 3 | [schema.core :as s :include-macros true])) 4 | 5 | (defn required [val] 6 | (when (empty? val) "This field is required")) 7 | 8 | (defn alphanumeric [val] 9 | (when-not (re-matches #"[a-zA-Z]\w*" val) "Only alphanumeric characters are allowed")) 10 | 11 | (defn passwords-match [first] 12 | (fn [second] 13 | (when (not= first second) "Passwords must match"))) 14 | 15 | (defn email [val] 16 | (when-not (re-matches #"\S+@\S+.\S+" 17 | val) "This is invalid email address")) 18 | 19 | (defn length 20 | ([min] 21 | (fn [val] 22 | (when (> min (count val)) (str "Please enter at least " min " characters")))) 23 | ([min max] 24 | (fn [val] 25 | (let [c (count val)] 26 | (when (or (> min c) 27 | (< max c)) (if (= min 0) 28 | (str "Please enter string up to " max " characters") 29 | (str "Please enter string between " min " and " max " characters"))))))) 30 | 31 | (s/defn less-eq 32 | ([m] (less-eq m (str "Maximum is " m))) 33 | ([max-val text] 34 | (fn [val] 35 | (when (< max-val val) 36 | text)))) 37 | 38 | (def password (length 6)) -------------------------------------------------------------------------------- /src/cljs/material_ui/core.cljs: -------------------------------------------------------------------------------- 1 | (ns material-ui.core 2 | (:refer-clojure :exclude [list]) 3 | (:require [reagent.core :as r] 4 | [camel-snake-kebab.core :as c])) 5 | 6 | (do (def app-bar (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "AppBar"))) (def app-canvas (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "AppCanvas"))) (def avatar (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Avatar"))) (def flat-button (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "FlatButton"))) (def raised-button (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "RaisedButton"))) (def floating-action-button (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "FloatingActionButton"))) (def card (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Card"))) (def card-header (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CardHeader"))) (def card-media (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CardMedia"))) (def card-title (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CardTitle"))) (def card-actions (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CardActions"))) (def card-text (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CardText"))) (def date-picker (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "DatePicker"))) (def dialog (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Dialog"))) (def drop-down-menu (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "DropDownMenu"))) (def drop-down-icon (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "DropDownIcon"))) (def font-icon (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "FontIcon"))) (def icon-button (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "IconButton"))) (def icon-menu (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "IconMenu"))) (def menu-item (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "MenuItem"))) (def left-nav (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "LeftNav"))) (def list (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "List"))) (def list-item (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "ListItem"))) (def list-divider (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "ListDivider"))) (def menu (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Menu"))) (def paper (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Paper"))) (def linear-progress (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "LinearProgress"))) (def circular-progress (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "CircularProgress"))) (def refresh-indicator (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "RefreshIndicator"))) (def slider (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Slider"))) (def checkbox (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Checkbox"))) (def snackbar (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Snackbar"))) (def table (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Table"))) (def tabs (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Tabs"))) (def tab (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Tab"))) (def text-field (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "TextField"))) (def select-field (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "SelectField"))) (def time-picker (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "TimePicker"))) (def toolbar (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "Toolbar"))) (def toolbar-group (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "ToolbarGroup"))) (def toolbar-separator (reagent.core/adapt-react-class (clojure.core/aget js/MaterialUI "ToolbarSeparator")))) 7 | 8 | (def ThemeManager (new js/MaterialUI.Styles.ThemeManager)) 9 | 10 | (def menu-item-types (aget js/MaterialUI "MenuItem" "Types")) 11 | (def MENU-ITEM-LINK (aget menu-item-types "LINK")) 12 | (def MENU-ITEM-SUBHEADER (aget menu-item-types "SUBHEADER")) 13 | 14 | (defn get-current-theme [] (js->clj (.getCurrentTheme ThemeManager))) 15 | (defn set-theme! [theme] 16 | (.setTheme ThemeManager (clj->js theme))) 17 | 18 | (defn set-palette! [pal] 19 | (.setPalette ThemeManager (clj->js pal))) 20 | 21 | (defn get-palette [] 22 | (js->clj (aget ThemeManager "palette") :keywordize-keys true)) 23 | 24 | (defn set-component-themes! [overrides] 25 | (.setComponentThemes ThemeManager (clj->js overrides))) 26 | 27 | (defn palette-color [color-type] 28 | ((get-palette) (c/->camelCase color-type))) 29 | 30 | (defn set-spacing! [spacing] 31 | (.setSpacing ThemeManager (clj->js spacing))) 32 | 33 | (def DARK-THEME (js->clj (aget ThemeManager "types" "DARK"))) 34 | (def LIGHT-THEME (js->clj (aget ThemeManager "types" "LIGHT"))) 35 | 36 | (defn color [color-key] 37 | (aget js/MaterialUI "Styles" "Colors" (c/->camelCase (name color-key)))) 38 | 39 | (defn mui-theme-wrap [form] 40 | (r/create-class 41 | {:child-context-types 42 | #js {:muiTheme (aget js/React "PropTypes" "object")} 43 | :get-child-context 44 | (fn [_] 45 | (clj->js {:muiTheme (get-current-theme)})) 46 | :reagent-render 47 | (fn [] 48 | form)})) 49 | -------------------------------------------------------------------------------- /src/cljs/workers/core.cljs: -------------------------------------------------------------------------------- 1 | (ns workers.core 2 | (:import goog.events.EventHandler) 3 | (:require [goog.events :as e] 4 | [goog.events.EventType :as t])) 5 | 6 | (defn evt-data [evt] 7 | (js->clj (aget (.-event_ evt) "data") :keywordize-keys true)) 8 | 9 | (defn on-message 10 | ([f] (on-message f js/self)) 11 | ([f worker] 12 | (e/listen worker t/MESSAGE #(f (evt-data %))))) 13 | 14 | (def msg-clb 15 | (fn [f e] 16 | (f (evt-data e)) 17 | (.removeEventListener "message" msg-clb))) 18 | 19 | (defn on-message-once 20 | ([f] (on-message f js/self)) 21 | ([f worker] 22 | (let [event-handler (new goog.events.EventHandler)] 23 | (.listenOnce event-handler worker t/MESSAGE #(f (evt-data %)))))) 24 | 25 | (defn post-message 26 | ([data] (post-message data js/self)) 27 | ([data worker] (.postMessage worker (clj->js data)))) 28 | 29 | (defn p [& args] 30 | "Like print, but returns last arg. For debugging purposes" 31 | (apply println args) 32 | (last args)) -------------------------------------------------------------------------------- /src/cljs/workers/turtle.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.workers.turtle 2 | (:require [schema.core :as s :include-macros true] 3 | [workers.core :as w] 4 | [fractalify.workers.schemas :as ch] 5 | [plumbing.core :as p] 6 | [schema.coerce :as coerce])) 7 | 8 | (enable-console-print!) 9 | (s/set-fn-validation! false) 10 | 11 | (s/defn round 12 | [d :- s/Num 13 | precision :- s/Int] 14 | (let [factor (Math/pow 10 precision)] 15 | (/ (Math/round (* d factor)) factor))) 16 | 17 | (def deg (/ (.-PI js/Math) 180)) 18 | 19 | (defmulti command identity) 20 | 21 | (s/defn move-coord :- s/Num 22 | [angle :- s/Num 23 | length :- s/Num 24 | type :- (s/enum :x :y) 25 | coord :- s/Num] 26 | (let [f (if (= type :x) Math/sin Math/cos)] 27 | (-> (f (* angle deg)) 28 | (* length) 29 | (+ coord) 30 | (round 3)))) 31 | 32 | (s/defn turn 33 | [turtle :- ch/Turtle 34 | angle :- s/Num 35 | direction :- (s/=> s/Num s/Num)] 36 | (update turtle :angle #(direction % angle))) 37 | 38 | (s/defn exec-cmd :- ch/Turtle 39 | [l-system :- ch/LSystem 40 | cmd-map :- {s/Str s/Keyword} 41 | turtle :- ch/Turtle 42 | cmd :- s/Str] 43 | (command (cmd-map cmd) turtle l-system)) 44 | 45 | (s/defn gen-lines-coords :- ch/Lines 46 | [l-system :- ch/LSystem 47 | result-cmds :- s/Str] 48 | (p/letk [[origin start-angle] l-system 49 | turtle {:position origin 50 | :angle start-angle 51 | :stack '() 52 | :lines []} 53 | cmd-map (into {} (vals (:cmds l-system))) 54 | exec-fn (partial exec-cmd l-system cmd-map)] 55 | (:lines (reduce exec-fn turtle result-cmds)))) 56 | 57 | 58 | (s/defn update-turtle-lines :- ch/Turtle 59 | [turtle :- ch/Turtle 60 | old-pos :- (:position ch/Turtle)] 61 | (update turtle :lines 62 | #(conj % [old-pos (:position turtle)]))) 63 | 64 | (s/defmethod 65 | command :forward :- ch/Turtle 66 | [_ turtle :- ch/Turtle 67 | l-system :- ch/LSystem] 68 | (p/letk [[angle position] turtle 69 | [line-length] l-system 70 | move-fn (partial move-coord angle line-length)] 71 | (-> turtle 72 | (update-in [:position :x] (partial move-fn :x)) 73 | (update-in [:position :y] (partial move-fn :y)) 74 | (update-turtle-lines position)))) 75 | 76 | (s/defmethod 77 | command :left :- ch/Turtle 78 | [_ turtle :- ch/Turtle 79 | l-system :- ch/LSystem] 80 | (turn turtle (:angle l-system) +)) 81 | 82 | (s/defmethod 83 | command :right :- ch/Turtle 84 | [_ turtle :- ch/Turtle 85 | l-system :- ch/LSystem] 86 | (turn turtle (:angle l-system) -)) 87 | 88 | (s/defmethod 89 | command :push :- ch/Turtle 90 | [_ turtle :- ch/Turtle 91 | l-system :- ch/LSystem] 92 | (update turtle :stack #(cons (select-keys turtle [:position :angle]) %))) 93 | 94 | (s/defmethod 95 | command :pop :- ch/Turtle 96 | [_ turtle :- ch/Turtle 97 | l-system :- ch/LSystem] 98 | (-> turtle 99 | (merge turtle (first (:stack turtle))) 100 | (update :stack rest))) 101 | 102 | (s/defmethod 103 | command :default :- ch/Turtle 104 | [_ turtle :- ch/Turtle 105 | l-system :- ch/LSystem] 106 | turtle) 107 | 108 | (def l-system-coercer (coerce/coercer ch/LSystem ch/l-system-coercion-matcher)) 109 | 110 | (w/on-message (fn [[l-system-data result-cmds]] 111 | (let [l-system (l-system-coercer l-system-data)] 112 | (w/post-message (gen-lines-coords l-system result-cmds))))) -------------------------------------------------------------------------------- /src/externs.js: -------------------------------------------------------------------------------- 1 | var MaterialUI = { 2 | Styles : { 3 | ThemeManager : { 4 | getCurrentTheme : function() {}, 5 | setPalette : function() {}, 6 | setTheme : function() {}, 7 | setComponentThemes : function() {}, 8 | setSpacing : function() {} 9 | } 10 | }, 11 | Dialog : { 12 | show : function() {}, 13 | dismiss : function() {} 14 | }, 15 | Snackbar : { 16 | show : function() {}, 17 | dismiss : function() {} 18 | }, 19 | LeftNav : { 20 | close : function() {}, 21 | toggle : function() {} 22 | }, 23 | DatePicker : { 24 | getDate : function() {}, 25 | setDate : function() {}, 26 | openDialog : function() {}, 27 | focus : function() {} 28 | }, 29 | TimePicker : { 30 | getTime : function() {}, 31 | setTime : function() {}, 32 | formatTime : function() {} 33 | } 34 | } 35 | 36 | var React = { 37 | PropTypes : { 38 | object : {} 39 | } 40 | } 41 | 42 | var Worker = { 43 | terminate : function() {} 44 | } -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /test/clj/fractalify/api.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api 2 | (:refer-clojure :exclude [get]) 3 | (:require [fractalify.dev :as dev] 4 | [com.stuartsierra.component :as c] 5 | [fractalify.system :as sys] 6 | [fractalify.mailers.mock-mail-sender :as mms] 7 | [fractalify.img-cloud.mock-img-cloud :as mic] 8 | [fractalify.config :as cfg] 9 | [clj-http.client :as client] 10 | [plumbing.core :as p] 11 | [schema.core :as s] 12 | [fractalify.utils :as u]) 13 | (:use midje.sweet)) 14 | 15 | (def server-port 10556) 16 | (def server-url (str "http://localhost:" server-port)) 17 | (def ^:dynamic *cookies* (clj-http.cookies/cookie-store)) 18 | 19 | (defn init-test-system [] 20 | (let [deps (merge sys/db-generators-dependencies (sys/new-dependency-map))] 21 | (-> (sys/new-system-map (merge (cfg/config) {:http-listener {:port server-port}})) 22 | (assoc :mail-sender (mms/new-mock-mail-sender)) 23 | (assoc :img-cloud (mic/new-mock-img-cloud)) 24 | (sys/generator-components nil) 25 | (c/system-using deps) 26 | (dev/init)))) 27 | 28 | (def stop-system dev/stop) 29 | (def start-system dev/start) 30 | 31 | (defn clear-cookies [] 32 | (alter-var-root #'*cookies* (constantly (clj-http.cookies/cookie-store)))) 33 | 34 | (defn request [path opts] 35 | (client/request (merge {:url (str server-url path) 36 | #_"http://requestb.in/xsd0u1xs" 37 | :headers {"Accept" "application/transit+json"} 38 | :content-type :application/transit+json 39 | :force-redirects true 40 | :throw-exceptions false 41 | :as :transit+json 42 | :cookie-store *cookies*} 43 | opts))) 44 | 45 | (defn get 46 | ([path] (get path {})) 47 | ([path opts] 48 | (request path (merge {:method :get} opts)))) 49 | 50 | (defn post 51 | ([path body] (post path body {})) 52 | ([path body opts] 53 | (request path (merge {:method :post 54 | :form-params body} opts)))) 55 | 56 | (defn put 57 | ([path body] (put path body {})) 58 | ([path body opts] 59 | (request path (merge {:method :put 60 | :form-params body} opts)))) 61 | 62 | (defn delete 63 | ([path] (delete path {})) 64 | ([path opts] 65 | (request path (merge {:method :delete} opts)))) 66 | 67 | (defn status-checker [expected-status] 68 | (p/fnk [status & _] 69 | (= status expected-status))) 70 | 71 | (defn response-schema [schema] 72 | (p/fnk [body & _] 73 | (s/validate schema body))) 74 | 75 | (defn resp-has-map? [m] 76 | (p/fnk resp-has-map [body & _] 77 | (u/submap? m body))) 78 | 79 | (def get-list-items (p/fn-> :body :items)) 80 | 81 | (defn list-resp-has-map? [m] 82 | (fn resp-has-map [res] 83 | (some #(u/submap? m %) (get-list-items res)))) 84 | 85 | (defn list-resp-items-total? [n] 86 | (p/fnk [[:body total-items] & _] 87 | (= n total-items))) 88 | 89 | (defn list-resp-items-count? [n] 90 | (fn [res] 91 | (= n (count (get-list-items res))))) 92 | 93 | (def status-ok (status-checker 200)) 94 | (def created (status-checker 201)) 95 | (def no-content (status-checker 204)) 96 | (def bad-request (status-checker 400)) 97 | (def unauthorized (status-checker 401)) 98 | (def forbidden (status-checker 403)) 99 | (def not-found (status-checker 404)) 100 | (def conflict (status-checker 409)) -------------------------------------------------------------------------------- /test/clj/fractalify/api/main_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.main-tests 2 | (:require 3 | [fractalify.api :as a] 4 | [fractalify.api.users.users-generator :as ug] 5 | [fractalify.utils :as u] 6 | [plumbing.core :as p] 7 | [bidi.bidi :as b] 8 | [schema.core :as s] 9 | [fractalify.main.api-routes :as mar]) 10 | (:use midje.sweet)) 11 | 12 | (def path-for (partial b/path-for (mar/get-routes))) 13 | 14 | (def some-contact-form 15 | {:email "some@email.com" 16 | :text "Hello there" 17 | :subject "Some subject"}) 18 | 19 | (defn send-contact [contact-form] 20 | (a/post (path-for :contact) contact-form)) 21 | 22 | (a/init-test-system) 23 | (with-state-changes 24 | [(before :facts (a/start-system)) 25 | (after :facts (a/stop-system))] 26 | 27 | (fact "it sends contact form" 28 | (send-contact some-contact-form) => a/created) 29 | 30 | (fact "it doesnt send invalid contact form" 31 | (send-contact (dissoc some-contact-form :text)) => a/bad-request) 32 | 33 | ) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/clj/fractalify/api/users_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fractalify.api.users-tests 2 | (:require 3 | [fractalify.api :as a] 4 | [fractalify.api.users.users-generator :as ug] 5 | [fractalify.users.schemas :as uch] 6 | [fractalify.utils :as u] 7 | [plumbing.core :as p] 8 | [bidi.bidi :as b] 9 | [fractalify.users.api-routes :as uar] 10 | [schema.core :as s]) 11 | (:use midje.sweet)) 12 | 13 | (def admin-login (select-keys ug/admin [:username :password])) 14 | (def some-user-login (select-keys ug/some-user [:username :password])) 15 | (def some-other-user-login (select-keys ug/some-other-user [:username :password])) 16 | 17 | (def new-user 18 | {:username "someuser" 19 | :email "some@gmail.com" 20 | :password "111111" 21 | :confirm-pass "111111" 22 | :bio ""}) 23 | 24 | (def new-user-created? 25 | (every-checker a/created 26 | (a/response-schema uch/UserMe) 27 | (a/resp-has-map? (u/select-key new-user :username)))) 28 | 29 | (def path-for (partial b/path-for (uar/get-routes))) 30 | 31 | (defn put-new-user 32 | ([] (put-new-user new-user)) 33 | ([user] 34 | (a/put (path-for :join) user))) 35 | 36 | (defn login [user] 37 | (a/post (path-for :login) user)) 38 | 39 | (defn user-response? 40 | ([user] (user-response? user uch/UserMe)) 41 | ([user schema] 42 | (every-checker (a/response-schema schema) 43 | (a/resp-has-map? (u/select-key user :username))))) 44 | 45 | (defn forgot-pass [user] 46 | (a/post (path-for :forgot-password) 47 | (u/select-key user :email))) 48 | 49 | (def logged-user #(a/get (path-for :logged-user))) 50 | 51 | (defn user-path-for [route user] 52 | (path-for route :username (:username user))) 53 | 54 | (defn get-user [user] 55 | (a/get (user-path-for :user user))) 56 | 57 | (def new-profile 58 | {:email "new-email@email.com" 59 | :bio "new bio"}) 60 | 61 | (def logout (partial a/post (path-for :logout) {})) 62 | 63 | (defn edit-profle [user] 64 | (a/post (user-path-for :edit-profile user) new-profile)) 65 | 66 | (defn change-pass [user] 67 | (a/post (user-path-for :change-password user) 68 | {:current-pass (:password user) 69 | :new-pass "somepass" 70 | :confirm-new-pass "somepass"})) 71 | 72 | (a/init-test-system) 73 | (with-state-changes 74 | [(before :facts (a/start-system)) 75 | (after :facts (a/stop-system))] 76 | 77 | (fact "it doesn't login with bad credentials" 78 | (login {}) => a/forbidden 79 | (login {:username "baduser" :password "badpass"}) => a/forbidden) 80 | 81 | (fact "it logs in with good credentials" 82 | (login admin-login) => (user-response? ug/admin)) 83 | 84 | (fact "it gets logged user" 85 | (login some-user-login) => (user-response? ug/some-user) 86 | ;(logged-user) => (user-response? ug/some-user) 87 | ) 88 | 89 | (fact "it doesnt create invalid new user" 90 | (put-new-user (dissoc new-user :email)) => a/bad-request) 91 | 92 | (fact "it creates and logs in new user" 93 | (put-new-user) => new-user-created? 94 | ;(logged-user) => (user-response? new-user) 95 | ) 96 | 97 | (fact "it disallows to create same user twice" 98 | (put-new-user) => new-user-created? 99 | (put-new-user) => a/conflict) 100 | 101 | (fact "it sends new pass in case of forgetting" 102 | (forgot-pass ug/some-user) => a/created) 103 | 104 | (fact "it returns not found when reseting unexisting account" 105 | (forgot-pass {:email "non-existent@email.com"}) => a/not-found) 106 | 107 | (fact "it loads some user" 108 | (get-user ug/some-user) => (user-response? ug/some-user uch/UserOther)) 109 | 110 | (fact "it returns 404 for non existent user" 111 | (get-user {:username "non-existent"}) => a/not-found) 112 | 113 | (fact "it disallows to edit profile to unauthenticated" 114 | (edit-profle ug/some-user) => a/unauthorized) 115 | 116 | (fact "it edits profile to user" 117 | (login ug/some-user) => (user-response? ug/some-user) 118 | (edit-profle ug/some-user) => a/created 119 | (get-user ug/some-user) => (a/resp-has-map? new-profile)) 120 | 121 | (fact "it allows admin to edit some user's profile" 122 | (login ug/admin) => (user-response? ug/admin) 123 | (edit-profle ug/some-user) => a/created 124 | (get-user ug/some-user) => (a/resp-has-map? new-profile)) 125 | 126 | (fact "it disallows to change password to unauthenticated" 127 | (change-pass ug/some-user) => a/unauthorized) 128 | 129 | (fact "it changes password to user" 130 | (login ug/some-user) => (user-response? ug/some-user) 131 | (change-pass ug/some-user) => a/created) 132 | 133 | (fact "it allows admin to change some user's password" 134 | (login ug/admin) => (user-response? ug/admin) 135 | (change-pass ug/some-user) => a/created) 136 | 137 | (fact "it logs out user" 138 | (login ug/admin) => (user-response? ug/admin) 139 | (logout) => a/status-ok) 140 | ) 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /test/cljs/fractalify/core-test.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.core-test 2 | (:require-macros [cljs.test :refer (is deftest testing)]) 3 | (:require [cljs.test])) 4 | 5 | (deftest example-passing-test 6 | (is (= 1 1))) 7 | -------------------------------------------------------------------------------- /test/cljs/fractalify/test-runner.cljs: -------------------------------------------------------------------------------- 1 | (ns fractalify.test-runner 2 | (:require 3 | [cljs.test :refer-macros [run-tests]] 4 | [fractalify.core-test])) 5 | 6 | (enable-console-print!) 7 | 8 | (defn runner [] 9 | (if (cljs.test/successful? 10 | (run-tests 11 | 'fractalify.core-test)) 12 | 0 13 | 1)) 14 | --------------------------------------------------------------------------------