initial commit

This commit is contained in:
Gered 2016-12-03 14:13:54 -05:00
commit 2b7bb08034
21 changed files with 1258 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Gered King
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

182
README.md Normal file
View file

@ -0,0 +1,182 @@
# electroncljs
A Leiningen template for [Electron](http://electron.atom.io/)
applications written using ClojureScript, Figwheel and Reagent.
This template is based on [descjop](https://github.com/karad/lein_template_descjop).
## Usage
```
$ lein new electroncljs <your-project-name>
```
Then,
```
$ cd <your-project-name>/
$ lein npm install
```
This will download all the required NPM dependencies necessary.
Once complete you can get to work on your app.
## Getting Started With Electron App Development
I **strongly** recommend first reading the [Quick Start Guide](http://electron.atom.io/docs/tutorial/quick-start/)
on the Electron website before continuing further! At least up until it
starts talking about running/packaging your app, as that part is
different here.
## Project Layout
#### `/app`
The main Electron app directory. Everything in here is what will be
available to your app when it is running and will be included with your
app when packaged for distribution.
You should place your app resources here such as images, CSS, other JS
scripts, etc.
`index.html` is what will be loaded first when your application starts.
`package.json` is used by Electron to specify details about your
application and most importantly what the startup script is. You
probably won't need to change this other then updating the application
name and version as needed.
#### `/src`
Common source directory that will be available to both the Electron
main process code and the renderer process code. You can also think of
this as where you'd put your `.cljc` files if this were a typical
Clojure/ClojureScript web app.
#### `/src_front`
Source root for the Electron renderer process code. This is likely
where the vast majority of your application code will go.
#### `/src_main`
Source root for the Electron main process code.
#### `/env`
Additional source roots for `/src`, `/src_front`, and `/src_main` which
are pulled in for either development or production builds.
There is also an application config map located under
`/env/[profile]/src/[your-project-name]/config.cljs`
## Building
To build both the code for the main and renderer process:
```
$ lein build
```
Or to build each separately:
```
$ lein build-main
$ lein build-front
```
JS artifacts will be output to `/app/js`.
To clean up all build artifacts:
```
$ lein clean
```
## Running
After building, simply do:
```
$ lein run
```
And Electron should start up with your app running.
## Figwheel
IMPORTANT: You should start up Figwheel **before** Electron is running!
```
$ lein figwheel
```
And then in another terminal:
```
$ lein run
```
By default `lein figwheel` will run for the Electron renderer process
code which is probably what you will want the majority of the time.
You can connect to the Figwheel REPL on port 7888. Once connected if
you run:
```
(cljs-repl)
```
You are now set up to work with your renderer process application code.
Note that you can also use Figwheel for the Electron main process, but
do keep in mind that each time you make a code change Electron will
restart to make sure _all_ potential main process code changes are
reloaded.
```
$ lein figwheel main
```
You can change this reload behaviour if you need to by editing the
function `on-figwheel-reload!` located under
`/env/dev/src_main/[your-project-name]/main/init.cljs`. If you do change
this, just remember that certain code changes won't take effect until
Electron relaunches.
## Packaging for Release
Out of the box, you can do:
```
$ lein package
```
Which will prepare an Electron release for your app under `/releases`.
Note that with the default `project.clj` configuration, this will only
build a release for the current platform/architecture.
You can add additional platform/architectures and change other
packaging properties by editing the `package` alias in `project.clj`.
For example, to build for all platforms (Win/Linux/Mac):
```
["with-profile" "prod" "shell" "./node_modules/.bin/electron-packager"
"app" :project/name
~(str "--version=" electron-version)
"--asar"
"--out=releases"
"--overwrite"
"--platform=all"]
```
Building Windows releases under Mac/Linux is supported but you will
need to install Wine.
Packaging is performed via [electron-packager](https://www.npmjs.com/package/electron-packager).
## License
Distributed under the the MIT License. See LICENSE for more details.

6
project.clj Normal file
View file

@ -0,0 +1,6 @@
(defproject electroncljs/lein-template "0.1"
:description "Leiningen template for creating Electron apps with ClojureScript"
:url "https://github.com/gered/electroncljs"
:license {:name "MIT License"
:url "http://opensource.org/licenses/MIT"}
:eval-in-leiningen true)

View file

@ -0,0 +1,13 @@
.DS_Store
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
/node_modules
/releases
/*.log

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) {{year}} YOUR-NAME-HERE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,12 @@
# {{name}}
A ClojureScript Electron application designed to ... well, that part is
up to you.
## Usage
FIXME
## License
Distributed under the the MIT License. See LICENSE for more details.

View file

@ -0,0 +1,558 @@
@charset "utf-8";
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea;
/* background: #eaeaea url('bg.png'); */
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
button,
input[type="checkbox"] {
outline: none;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
/* filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); */
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}

View file

@ -0,0 +1,15 @@
{{=<% %>=}}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><%name%></title>
<link rel="stylesheet" href="css/todos.css">
</head>
<body>
<div id="app">
<h1>Reagent has not loaded yet.</h1>
</div>
<script src="js/front.js"></script>
</body>
</html>

View file

@ -0,0 +1,6 @@
{{=<% %>=}}
{
"name": "<%name%>",
"version": "0.1.0",
"main": "js/main.js"
}

View file

@ -0,0 +1,8 @@
(ns {{root-ns}}.config)
(def config
{:env :dev
:dev-tools {:auto-open? false
:position "bottom"}
:browser-window {:width 1024
:height 768}})

View file

@ -0,0 +1,2 @@
(ns user
(:use figwheel-sidecar.repl-api))

View file

@ -0,0 +1,11 @@
(ns {{root-ns}}.init
(:require
[{{root-ns}}.core :as core]))
(enable-console-print!)
(defn start-front!
[]
(core/init!))
(start-front!)

View file

@ -0,0 +1,19 @@
(ns {{root-ns}}.main.init
(:require
[cljs.nodejs :as nodejs]
[{{root-ns}}.main.core :as core]))
(nodejs/enable-util-print!)
(def on-figwheel-reload!
(fn []
(println "Figwheel initiated application reload.")
(.relaunch core/app)
(.exit core/app 0)))
(def start-electron!
(fn []
(reset! core/window nil)
(core/init!)))
(set! *main-cli-fn* start-electron!)

View file

@ -0,0 +1,6 @@
(ns {{root-ns}}.config)
(def config
{:env :prod
:browser-window {:width 1024
:height 768}})

View file

@ -0,0 +1,11 @@
(ns {{root-ns}}.init
(:require
[{{root-ns}}.core :as core]))
(enable-console-print!)
(defn start-front!
[]
(core/init!))
(start-front!)

View file

@ -0,0 +1,13 @@
(ns {{root-ns}}.main.init
(:require
[cljs.nodejs :as nodejs]
[{{root-ns}}.main.core :as core]))
(nodejs/enable-util-print!)
(def start-electron!
(fn []
(reset! core/window nil)
(core/init!)))
(set! *main-cli-fn* start-electron!)

View file

@ -0,0 +1,107 @@
(def electron-version "1.4.10")
(def figwheel-version "0.5.8")
(defproject {{name}} "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "MIT License"
:url "http://opensource.org/licenses/MIT"}
:dependencies [[cljsjs/bootstrap "3.3.6-1"]
[figwheel ~figwheel-version]
[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.293"]
[org.webjars/bootstrap "3.3.6"]
[reagent "0.6.0"]
[ring/ring-core "1.5.0"]]
:plugins [[lein-cljsbuild "1.1.4"]
[lein-externs "0.1.6"]
[lein-figwheel ~figwheel-version]
[lein-npm "0.6.2"]
[lein-shell "0.5.0"]]
:npm {:devDependencies [[electron-prebuilt ~electron-version]
[electron-packager "^8.3.0"]
[closurecompiler-externs "^1.0.4"]
[ws "^1.1.1"]]}
:source-paths ["src"]
:clean-targets ^{:protect false} [:target-path
"releases"
[:cljsbuild :builds :main :compiler :output-to]
[:cljsbuild :builds :main :compiler :output-dir]
[:cljsbuild :builds :front :compiler :output-to]
[:cljsbuild :builds :front :compiler :output-dir]
"app/js/externs_main.js"
"app/js/externs_front.js"]
:figwheel {:nrepl-port 7888
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]
:builds-to-start [:front]}
:cljsbuild {:builds
{:main
{:source-paths ["src" "src_main"]
:figwheel {:on-jsload {{root-ns}}.main.init/on-figwheel-reload!}
:compiler {:main {{root-ns}}.main.init
:output-to "app/js/main.js"
:output-dir "app/js/out_main"
:asset-path "app/js/out_main"
:externs ["app/js/externs_main.js"]
:target :nodejs
:optimizations :none}}
:front
{:source-paths ["src" "src_front"]
:figwheel {:on-jsload {{root-ns}}.init/start-front!}
:compiler {:main {{root-ns}}.init
:output-to "app/js/front.js"
:output-dir "app/js/out_front"
:asset-path "js/out_front"
:externs ["app/js/externs_front.js"]
:optimizations :none}}}}
:profiles {:dev {:source-paths ["env/dev/src"]
:dependencies [[com.cemerick/piggieback "0.2.1"]
[figwheel-sidecar ~figwheel-version]]
:cljsbuild {:builds
{:main {:source-paths ["env/dev/src" "env/dev/src_main"]}
:front {:source-paths ["env/dev/src" "env/dev/src_front"]}}}}
:prod {:source-paths ["env/prod/src"]
:cljsbuild {:builds
{:main {:source-paths ["env/prod/src" "env/prod/src_main"]
:compiler ^:replace
{:output-to "app/js/main.js"
:externs ["app/js/externs_main.js"]
:optimizations :advanced
:target :nodejs
:pretty-print false}}
:front {:source-paths ["env/prod/src" "env/prod/src_front"]
:compiler ^:replace
{:output-to "app/js/front.js"
:externs ["app/js/externs_front.js"]
:optimizations :advanced
:pretty-print false}}}}}}
:aliases {"build-main" ["do"
["externs" "main" "app/js/externs_main.js"]
["cljsbuild" "once" "main"]]
"build-front" ["do"
["externs" "front" "app/js/externs_front.js"]
["cljsbuild" "once" "front"]]
"build" ["do" "build-main" "build-front"]
"run" ["do"
["shell" "./node_modules/.bin/electron" "app"]]
"package" ["do"
"clean"
["with-profile" "prod" "build"]
["with-profile" "prod" "shell" "./node_modules/.bin/electron-packager"
"app" :project/name
~(str "--version=" electron-version)
"--asar"
"--out=releases"
"--overwrite"]]}
)

View file

@ -0,0 +1,117 @@
(ns {{root-ns}}.core
(:require
[reagent.core :as r]))
(defonce todos (r/atom (sorted-map)))
(defonce counter (r/atom 0))
(defn add-todo
[text]
(let [id (swap! counter inc)]
(swap! todos assoc id {:id id :title text :done false})))
(defn toggle [id] (swap! todos update-in [id :done] not))
(defn save [id title] (swap! todos assoc-in [id :title] title))
(defn delete [id] (swap! todos dissoc id))
(defn mmap [m f a] (->> m (f a) (into (empty m))))
(defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v)))
(defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done])))
(defonce init (do
(add-todo "Rename Cloact to Reagent")
(add-todo "Add undo demo")
(add-todo "Make all rendering async")
(add-todo "Allow any arguments to component functions")
(complete-all true)))
(defn todo-input
[{:keys [title on-save on-stop]}]
(let [val (r/atom title)
stop #(do (reset! val "")
(if on-stop (on-stop)))
save #(let [v (-> @val str clojure.string/trim)]
(if-not (empty? v) (on-save v))
(stop))]
(fn [{:keys [id class placeholder]}]
[:input {:type "text" :value @val
:id id :class class :placeholder placeholder
:on-blur save
:on-change #(reset! val (-> % .-target .-value))
:on-key-down #(case (.-which %)
13 (save)
27 (stop)
nil)}])))
(def todo-edit (with-meta todo-input
{:component-did-mount #(.focus (r/dom-node %))}))
(defn todo-stats
[{:keys [filt active done]}]
(let [props-for (fn [name]
{:class (if (= name @filt) "selected")
:on-click #(reset! filt name)})]
[:div
[:span#todo-count
[:strong active] " " (case active 1 "item" "items") " left"]
[:ul#filters
[:li [:a (props-for :all) "All"]]
[:li [:a (props-for :active) "Active"]]
[:li [:a (props-for :done) "Completed"]]]
(when (pos? done)
[:button#clear-completed {:on-click clear-done}
"Clear completed " done])]))
(defn todo-item
[]
(let [editing (r/atom false)]
(fn [{:keys [id done title]}]
[:li {:class (str (if done "completed ")
(if @editing "editing"))}
[:div.view
[:input.toggle {:type "checkbox" :checked done
:on-change #(toggle id)}]
[:label {:on-double-click #(reset! editing true)} title]
[:button.destroy {:on-click #(delete id)}]]
(when @editing
[todo-edit {:class "edit" :title title
:on-save #(save id %)
:on-stop #(reset! editing false)}])])))
(defn todo-app
[props]
(let [filt (r/atom :all)]
(fn []
(let [items (vals @todos)
done (->> items (filter :done) count)
active (- (count items) done)]
[:div
[:section#todoapp
[:header#header
[:h1 "todos"]
[todo-input {:id "new-todo"
:placeholder "What needs to be done?"
:on-save add-todo}]]
(when (-> items count pos?)
[:div
[:section#main
[:input#toggle-all {:type "checkbox" :checked (zero? active)
:on-change #(complete-all (pos? active))}]
[:label {:for "toggle-all"} "Mark all as complete"]
[:ul#todo-list
(for [todo (filter (case @filt
:active (complement :done)
:done :done
:all identity) items)]
^{:key (:id todo)} [todo-item todo])]]
[:footer#footer
[todo-stats {:active active :done done :filt filt}]]])]
[:footer#info
[:p "Double-click to edit a todo"]]]))))
(defn init!
[]
(r/render
[todo-app]
(.getElementById js/document "app")))

View file

@ -0,0 +1,54 @@
(ns {{root-ns}}.main.core
(:require
[cljs.nodejs :as nodejs]
[{{root-ns}}.config :refer [config]]))
(def path (nodejs/require "path"))
(def electron (nodejs/require "electron"))
(def app (.-app electron))
(def BrowserWindow (.-BrowserWindow electron))
(defonce window (atom nil))
(defn on-window-closed
[]
(reset! window nil))
(defn on-window-ready-to-show
[]
(.show @window))
(defn on-process-error
[error]
(println "ERROR:" error))
(defn on-app-window-all-closed
[]
(.quit app))
(defn on-app-quit
[e exit-code]
(println "Main process quitting. Exit code:" exit-code))
(defn on-app-ready
[launch-info]
(let [window-settings (merge
{:show false}
(:browser-window config))]
(println "App ready.")
(reset! window (BrowserWindow. (clj->js window-settings)))
(.loadURL @window (str "file://" (.getAppPath app) "/index.html"))
(.on @window "ready-to-show" on-window-ready-to-show)
(.on @window "closed" on-window-closed)
(if (get-in config [:dev-tools :auto-open?])
(.openDevTools (.-webContents @window)
(clj->js {:mode (get-in config [:dev-tools :position] "right")})))))
(defn init!
[]
(println "Main process started, app is starting up.")
(.on nodejs/process "error" on-process-error)
(.on app "window-all-closed" on-app-window-all-closed)
(.on app "quit" on-app-quit)
(.on app "ready" on-app-ready))

View file

@ -0,0 +1,66 @@
(ns leiningen.new.electroncljs
(:import
[java.util Calendar GregorianCalendar])
(:require
[leiningen.core.main :as main]
[leiningen.new.templates :as t]))
(def render (t/renderer "electroncljs"))
(defn electroncljs
[name]
(let [data {:name name
:sanitized (t/sanitize name)
:root-ns (t/sanitize-ns name)
:root-ns-path (t/name-to-path name)
:year (.get (GregorianCalendar.) (Calendar/YEAR))}]
(main/info "Creating a new ClojureScript Electron project \"" name "\" ...")
(t/->files
data
"app/js"
"app/img"
["app/css/todos.css" (render "app/css/todos.css")]
["app/index.html" (render "app/index.html" data)]
["app/package.json" (render "app/package.json" data)]
["env/dev/src/{{root-ns-path}}/config.cljs" (render "env/dev/src/root_ns/config.cljs" data)]
["env/dev/src/user.clj" (render "env/dev/src/user.clj" data)]
["env/dev/src_front/{{root-ns-path}}/init.cljs" (render "env/dev/src_front/root_ns/init.cljs" data)]
["env/dev/src_main/{{root-ns-path}}/main/init.cljs" (render "env/dev/src_main/root_ns/main/init.cljs" data)]
["env/prod/src/{{root-ns-path}}/config.cljs" (render "env/prod/src/root_ns/config.cljs" data)]
["env/prod/src_front/{{root-ns-path}}/init.cljs" (render "env/prod/src_front/root_ns/init.cljs" data)]
["env/prod/src_main/{{root-ns-path}}/main/init.cljs" (render "env/prod/src_main/root_ns/main/init.cljs" data)]
"src/{{root-ns-path}}"
["src_front/{{root-ns-path}}/core.cljs" (render "src_front/root_ns/core.cljs" data)]
["src_main/{{root-ns-path}}/main/core.cljs" (render "src_main/root_ns/main/core.cljs" data)]
[".gitignore" (render ".gitignore")]
["LICENSE" (render "LICENSE" data)]
["README.md" (render "README.md" data)]
["project.clj" (render "project.clj" data)])
(main/info "------------------------------------------------------------------------")
(main/info "Your new Electron project is ready! To get started:")
(main/info "")
(main/info " $ cd" name)
(main/info " $ lein npm install")
(main/info "")
(main/info "You app's code is located in a few places:\n")
(main/info " /src - code common to both main and renderer (front)")
(main/info " /src_front - code for renderer process (UI)")
(main/info " /src_main - code for main process (backend)")
(main/info "")
(main/info "Build profile-specific sources are located under /env")
(main/info "Application code config map at:")
(main/info (str " /env/dev/src/" (:root-ns-path data) "/config.cljs"))
(main/info (str " /env/prod/src/" (:root-ns-path data) "/config.cljs"))
(main/info "")
(main/info "Electron app resources (HTML, CSS, JS, etc) are located under /app")
(main/info "")
(main/info "Build: $ lein build")
(main/info "Run: $ lein run")
(main/info "Rebuild: $ lein clean && lein build")
(main/info "Package: $ lein package")
(main/info "Figwheel: (do in order!)")
(main/info " (terminal #1) $ lein figwheel")
(main/info " (terminal #2) $ lein run")
(main/info "(Figwheel REPL running on port 7888, run (cljs-repl) after connecting)")
(main/info "------------------------------------------------------------------------")))