Skip to content

Commit 6b0e619

Browse files
committed
Init commit
0 parents  commit 6b0e619

22 files changed

+1569
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### SBT template
3+
# Simple Build Tool
4+
# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
5+
6+
dist/*
7+
target/
8+
/project/project/
9+
/.idea/

build.sbt

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
organization := "com.example"
2+
3+
name := "society-events"
4+
5+
version := "0.1.0-SNAPSHOT"
6+
7+
scalaVersion := "2.12.4"
8+
9+
val korolevVersion = "0.8.1"
10+
11+
libraryDependencies ++= Seq(
12+
"org.slf4j" % "slf4j-simple" % "1.7.+",
13+
"com.github.fomkin" %% "korolev-server-blaze" % korolevVersion
14+
)

project/build.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.1.2

project/plugins.sbt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")

src/main/resources/static/main.css

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.todos {
2+
height: 250px;
3+
overflow-y: scroll;
4+
}
5+
6+
.checkbox {
7+
display: inline-block;
8+
width: 10px;
9+
height: 10px;
10+
border: 1px solid green;
11+
border-radius: 10px;
12+
margin-right: 5px;
13+
}
14+
15+
.checkbox__checked {
16+
background-color: green;
17+
}

src/main/scala/SocietyEvents.scala

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import State.globalContext
2+
import korolev.Router.Root
3+
import korolev.state.StateStorage
4+
import korolev.server._
5+
import korolev.blazeServer._
6+
import korolev.execution._
7+
import korolev.state.javaSerialization._
8+
import korolev._
9+
10+
import scala.concurrent.Future
11+
12+
//noinspection TypeAnnotation
13+
object SocietyEvents extends KorolevBlazeServer {
14+
15+
import State.globalContext._
16+
import State.globalContext.symbolDsl._
17+
18+
val storage: StateStorage[Future, State] = StateStorage.default[Future, State](State())
19+
val inputId: globalContext.ElementId = elementId()
20+
21+
22+
private val pageHead = Seq(
23+
'title ("Russian Scala Society Events"),
24+
'link (
25+
'href /= "/main.css",
26+
'rel /= "stylesheet",
27+
'type /= "text/css"
28+
)
29+
)
30+
31+
private val renderer: Render = {
32+
case state =>
33+
'body (
34+
'div ("Super TODO tracker"),
35+
'div (
36+
state.todos.keys map { name =>
37+
'a (
38+
event('click) { access =>
39+
access.transition(_.copy(selectedTab = name))
40+
},
41+
'href /= "/" + name.toLowerCase,
42+
disableHref,
43+
'marginLeft @= 10,
44+
if (name == state.selectedTab) 'strong (name)
45+
else name
46+
)
47+
}
48+
),
49+
'div (
50+
'class /= "todos",
51+
state.todos(state.selectedTab).zipWithIndex map {
52+
case (todo, i) =>
53+
'div (
54+
'div (
55+
'class /= {
56+
if (!todo.done) "checkbox"
57+
else "checkbox checkbox__checked"
58+
},
59+
// Generate transition when clicking checkboxes
60+
event('click) { access =>
61+
access.transition { s =>
62+
val todos = s.todos(s.selectedTab)
63+
val updated = todos.updated(i, todos(i).copy(done = !todo.done))
64+
s.copy(todos = s.todos + (s.selectedTab -> updated))
65+
}
66+
}
67+
),
68+
if (!todo.done) 'span (todo.text)
69+
else 'strike (todo.text)
70+
)
71+
}
72+
),
73+
'form (
74+
// Generate AddTodo action when 'Add' button clicked
75+
event('submit) { access =>
76+
access.property(inputId, 'value) flatMap { value =>
77+
val todo = State.Todo(value, done = false)
78+
access.transition { s =>
79+
s.copy(todos = s.todos + (s.selectedTab -> (s.todos(s.selectedTab) :+ todo)))
80+
}
81+
}
82+
},
83+
'input (
84+
inputId,
85+
'type /= "text",
86+
'placeholder /= "What should be done?"
87+
),
88+
'button ("Add todo")
89+
)
90+
)
91+
}
92+
93+
val service = blazeService[Future, State, Any] from KorolevServiceConfig[Future, State, Any](
94+
stateStorage = storage,
95+
head = pageHead,
96+
render = renderer,
97+
router = { (deviceId, _) =>
98+
Router(
99+
fromState = {
100+
case State(tab, _) =>
101+
Root / tab.toLowerCase
102+
},
103+
toState = {
104+
case (Some(s), Root) =>
105+
val u = s.copy(selectedTab = s.todos.keys.head)
106+
Future.successful(u)
107+
case (Some(s), Root / name) =>
108+
val key = s.todos.keys.find(_.toLowerCase == name)
109+
Future.successful(key.fold(s)(k => s.copy(selectedTab = k)))
110+
case (None, Root) =>
111+
storage.createTopLevelState(deviceId)
112+
case (None, Root / name) =>
113+
storage.createTopLevelState(deviceId) map { s =>
114+
val key = s.todos.keys.find(_.toLowerCase == name)
115+
key.fold(s)(k => s.copy(selectedTab = k))
116+
}
117+
}
118+
)
119+
}
120+
)
121+
}

src/main/scala/State.scala

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import korolev.Context
2+
import korolev.execution._
3+
import scala.concurrent.Future
4+
import korolev.state.javaSerialization._
5+
6+
case class State(
7+
selectedTab: String = "Tab1",
8+
todos: Map[String, Vector[State.Todo]] = Map(
9+
"Tab1" -> State.Todo(5),
10+
"Tab2" -> State.Todo(7),
11+
"Tab3" -> State.Todo(2)
12+
)
13+
)
14+
15+
object State {
16+
val globalContext = Context[Future, State, Any]
17+
case class Todo(text: String, done: Boolean)
18+
object Todo {
19+
def apply(n: Int): Vector[Todo] = (0 to n).toVector map {
20+
i => Todo(s"This is TODO #$i", done = false)
21+
}
22+
}
23+
}

web/.babelrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"presets": ["es2015"],
3+
"compact": false
4+
}

web/.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
node_modules
3+
npm-debug.log
4+
bower_components
5+
dist
6+
*.swp

web/config.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Your project's server will run on localhost:xxxx at this port
2+
PORT: 8000
3+
4+
# Autoprefixer will make sure your CSS works with these browsers
5+
COMPATIBILITY:
6+
- "last 2 versions"
7+
- "ie >= 9"
8+
- "ios >= 7"
9+
10+
# UnCSS will use these settings
11+
UNCSS_OPTIONS:
12+
html:
13+
- "src/**/*.html"
14+
ignore:
15+
- !!js/regexp .foundation-mq
16+
- !!js/regexp ^\.is-.*
17+
18+
# Gulp will reference these paths when it copies files
19+
PATHS:
20+
# Path to dist folder
21+
dist: "dist"
22+
# Paths to static assets that aren't images, CSS, or JavaScript
23+
assets:
24+
- "src/assets/**/*"
25+
- "!src/assets/{img,js,scss}{,/**/*}"
26+
# Paths to Sass libraries, which can then be loaded with @import
27+
sass:
28+
- "node_modules/foundation-sites/scss"
29+
- "node_modules/motion-ui/src"
30+
# Paths to JavaScript entry points for webpack to bundle modules
31+
entries:
32+
- "src/assets/js/app.js"

web/gulpfile.babel.js

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
'use strict';
2+
3+
import plugins from 'gulp-load-plugins';
4+
import yargs from 'yargs';
5+
import browser from 'browser-sync';
6+
import gulp from 'gulp';
7+
import panini from 'panini';
8+
import rimraf from 'rimraf';
9+
import sherpa from 'style-sherpa';
10+
import yaml from 'js-yaml';
11+
import fs from 'fs';
12+
import webpackStream from 'webpack-stream';
13+
import webpack2 from 'webpack';
14+
import named from 'vinyl-named';
15+
16+
// Load all Gulp plugins into one variable
17+
const $ = plugins();
18+
19+
// Check for --production flag
20+
const PRODUCTION = !!(yargs.argv.production);
21+
22+
// Load settings from settings.yml
23+
const { COMPATIBILITY, PORT, UNCSS_OPTIONS, PATHS } = loadConfig();
24+
25+
function loadConfig() {
26+
let ymlFile = fs.readFileSync('config.yml', 'utf8');
27+
return yaml.load(ymlFile);
28+
}
29+
30+
// Build the "dist" folder by running all of the below tasks
31+
gulp.task('build',
32+
gulp.series(clean, gulp.parallel(pages, sass, javascript, images, copy), styleGuide));
33+
34+
// Build the site, run the server, and watch for file changes
35+
gulp.task('default',
36+
gulp.series('build', server, watch));
37+
38+
// Delete the "dist" folder
39+
// This happens every time a build starts
40+
function clean(done) {
41+
rimraf(PATHS.dist, done);
42+
}
43+
44+
// Copy files out of the assets folder
45+
// This task skips over the "img", "js", and "scss" folders, which are parsed separately
46+
function copy() {
47+
return gulp.src(PATHS.assets)
48+
.pipe(gulp.dest(PATHS.dist + '/assets'));
49+
}
50+
51+
// Copy page templates into finished HTML files
52+
function pages() {
53+
return gulp.src('src/pages/**/*.{html,hbs,handlebars}')
54+
.pipe(panini({
55+
root: 'src/pages/',
56+
layouts: 'src/layouts/',
57+
partials: 'src/partials/',
58+
data: 'src/data/',
59+
helpers: 'src/helpers/'
60+
}))
61+
.pipe(gulp.dest(PATHS.dist));
62+
}
63+
64+
// Load updated HTML templates and partials into Panini
65+
function resetPages(done) {
66+
panini.refresh();
67+
done();
68+
}
69+
70+
// Generate a style guide from the Markdown content and HTML template in styleguide/
71+
function styleGuide(done) {
72+
sherpa('src/styleguide/index.md', {
73+
output: PATHS.dist + '/styleguide.html',
74+
template: 'src/styleguide/template.html'
75+
}, done);
76+
}
77+
78+
// Compile Sass into CSS
79+
// In production, the CSS is compressed
80+
function sass() {
81+
return gulp.src('src/assets/scss/app.scss')
82+
.pipe($.sourcemaps.init())
83+
.pipe($.sass({
84+
includePaths: PATHS.sass
85+
})
86+
.on('error', $.sass.logError))
87+
.pipe($.autoprefixer({
88+
browsers: COMPATIBILITY
89+
}))
90+
// Comment in the pipe below to run UnCSS in production
91+
//.pipe($.if(PRODUCTION, $.uncss(UNCSS_OPTIONS)))
92+
.pipe($.if(PRODUCTION, $.cleanCss({ compatibility: 'ie9' })))
93+
.pipe($.if(!PRODUCTION, $.sourcemaps.write()))
94+
.pipe(gulp.dest(PATHS.dist + '/assets/css'))
95+
.pipe(browser.reload({ stream: true }));
96+
}
97+
98+
let webpackConfig = {
99+
module: {
100+
rules: [
101+
{
102+
test: /.js$/,
103+
use: [
104+
{
105+
loader: 'babel-loader'
106+
}
107+
]
108+
}
109+
]
110+
}
111+
}
112+
// Combine JavaScript into one file
113+
// In production, the file is minified
114+
function javascript() {
115+
return gulp.src(PATHS.entries)
116+
.pipe(named())
117+
.pipe($.sourcemaps.init())
118+
.pipe(webpackStream(webpackConfig, webpack2))
119+
.pipe($.if(PRODUCTION, $.uglify()
120+
.on('error', e => { console.log(e); })
121+
))
122+
.pipe($.if(!PRODUCTION, $.sourcemaps.write()))
123+
.pipe(gulp.dest(PATHS.dist + '/assets/js'));
124+
}
125+
126+
// Copy images to the "dist" folder
127+
// In production, the images are compressed
128+
function images() {
129+
return gulp.src('src/assets/img/**/*')
130+
.pipe($.if(PRODUCTION, $.imagemin({
131+
progressive: true
132+
})))
133+
.pipe(gulp.dest(PATHS.dist + '/assets/img'));
134+
}
135+
136+
// Start a server with BrowserSync to preview the site in
137+
function server(done) {
138+
browser.init({
139+
server: PATHS.dist, port: PORT
140+
});
141+
done();
142+
}
143+
144+
// Reload the browser with BrowserSync
145+
function reload(done) {
146+
browser.reload();
147+
done();
148+
}
149+
150+
// Watch for changes to static assets, pages, Sass, and JavaScript
151+
function watch() {
152+
gulp.watch(PATHS.assets, copy);
153+
gulp.watch('src/pages/**/*.html').on('all', gulp.series(pages, browser.reload));
154+
gulp.watch('src/{layouts,partials}/**/*.html').on('all', gulp.series(resetPages, pages, browser.reload));
155+
gulp.watch('src/assets/scss/**/*.scss').on('all', sass);
156+
gulp.watch('src/assets/js/**/*.js').on('all', gulp.series(javascript, browser.reload));
157+
gulp.watch('src/assets/img/**/*').on('all', gulp.series(images, browser.reload));
158+
gulp.watch('src/styleguide/**').on('all', gulp.series(styleGuide, browser.reload));
159+
}

0 commit comments

Comments
 (0)