beryl 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +16 -5
- data/Rakefile +1 -27
- data/Rakefile-template +5 -0
- data/app/frontend.rb +4 -0
- data/app/initial_state.rb +5 -0
- data/app/something.rb +12 -0
- data/app/view.rb +8 -0
- data/beryl.gemspec +18 -23
- data/config.ru +2 -2
- data/lib/beryl/Rakefile +28 -0
- data/lib/beryl/backend.rb +44 -0
- data/lib/beryl/deserializer.rb +21 -0
- data/lib/beryl/frontend.rb +26 -0
- data/lib/beryl/runtime.rb +65 -0
- data/lib/beryl/style.css +89 -0
- data/lib/beryl/utils.rb +55 -0
- data/lib/beryl/version.rb +1 -1
- data/lib/beryl/view.rb +35 -0
- data/lib/beryl/virtual_dom.rb +144 -0
- data/lib/beryl/widget.rb +50 -0
- data/lib/{virtual_dom.rb → renderer.rb} +11 -11
- metadata +74 -7
- data/app-Gemfile +0 -6
- data/app/frontend_app.rb +0 -73
- data/lib/app.rb +0 -33
- data/lib/event_loop.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b6146cdf28fe2e973b3d151fe7536034e11a3e1dec1639b7e139509ecd4e298
|
4
|
+
data.tar.gz: 5d7c85d49307cb8c427bf8f2fc593615613d6abc648d6e1dae6a2d081f46abe8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87de7c9e079ca95edf709051289c8c34a6d3f3314c46f9b877fadd5fa4fc199e783f00fd964b6d3fe39db2d48269eec0d3b14708108998fad8eba77dfc431ae5
|
7
|
+
data.tar.gz: 6aa0b0572132a48a1d7f175e386b6bc6b404a0efbbd953ea43d9b861e867d176dfc5effa950cb87b91e757f6c0144e5b7a80551a93616cce585e31c7d9ebe352
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
beryl (0.2.0)
|
5
|
+
|
1
6
|
GEM
|
2
7
|
remote: https://rubygems.org/
|
3
8
|
specs:
|
@@ -5,6 +10,7 @@ GEM
|
|
5
10
|
bowser (1.0.4)
|
6
11
|
opal (>= 0.7.0, < 0.12.0)
|
7
12
|
hike (1.2.3)
|
13
|
+
minitest (5.11.3)
|
8
14
|
opal (0.11.3)
|
9
15
|
ast (>= 2.3.0)
|
10
16
|
hike (~> 1.2)
|
@@ -14,16 +20,21 @@ GEM
|
|
14
20
|
ast (~> 2.2)
|
15
21
|
puma (3.12.0)
|
16
22
|
rack (2.0.5)
|
23
|
+
rake (10.5.0)
|
17
24
|
sourcemap (0.1.1)
|
18
25
|
|
19
26
|
PLATFORMS
|
20
27
|
ruby
|
21
28
|
|
22
29
|
DEPENDENCIES
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
beryl!
|
31
|
+
bowser
|
32
|
+
bundler (~> 1.16)
|
33
|
+
minitest (~> 5.0)
|
34
|
+
opal
|
35
|
+
puma
|
36
|
+
rack
|
37
|
+
rake (~> 10.0)
|
27
38
|
|
28
39
|
BUNDLED WITH
|
29
|
-
1.16.
|
40
|
+
1.16.5
|
data/Rakefile
CHANGED
@@ -1,27 +1 @@
|
|
1
|
-
|
2
|
-
require 'rake/testtask'
|
3
|
-
require 'bowser'
|
4
|
-
require 'bundler/gem_tasks'
|
5
|
-
|
6
|
-
desc 'Build the app to build/app.js'
|
7
|
-
task :build do
|
8
|
-
Opal.append_path 'app'
|
9
|
-
Opal.append_path 'lib'
|
10
|
-
Dir.mkdir('build') unless File.exist?('build')
|
11
|
-
File.binwrite 'build/app.js', Opal::Builder.build('frontend_app').to_s
|
12
|
-
end
|
13
|
-
|
14
|
-
desc 'Build and run the app'
|
15
|
-
task :run do
|
16
|
-
Rake::Task['build'].invoke
|
17
|
-
sh 'bundle exec rackup --port 3000 --host 0.0.0.0'
|
18
|
-
end
|
19
|
-
|
20
|
-
desc 'Test the app'
|
21
|
-
Rake::TestTask.new(:test) do |t|
|
22
|
-
t.libs << 'test'
|
23
|
-
t.libs << 'lib'
|
24
|
-
t.test_files = FileList['test/**/*_test.rb']
|
25
|
-
end
|
26
|
-
|
27
|
-
task :default => :test
|
1
|
+
load './lib/beryl/Rakefile'
|
data/Rakefile-template
ADDED
data/app/frontend.rb
ADDED
data/app/something.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'beryl/widget'
|
2
|
+
|
3
|
+
class Something < Beryl::Widget
|
4
|
+
def render(state)
|
5
|
+
column :fill_width, :fill_height do
|
6
|
+
text 'Bart', height: 100, width: 300
|
7
|
+
text 'Abc', proportional_height: 2
|
8
|
+
text 'Karol', :fill_height
|
9
|
+
text 'Xyz', proportional_height: 3
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/app/view.rb
ADDED
data/beryl.gemspec
CHANGED
@@ -1,38 +1,33 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require 'beryl/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'beryl'
|
8
7
|
spec.version = Beryl::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
8
|
+
spec.authors = ['Bart Blast']
|
9
|
+
spec.email = ['bart@bartblast.com']
|
11
10
|
|
12
11
|
spec.summary = %q{Web framework}
|
13
12
|
spec.description = %q{Web framework}
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
16
|
-
|
17
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
-
# if spec.respond_to?(:metadata)
|
20
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
-
# else
|
22
|
-
# raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
-
# "public gem pushes."
|
24
|
-
# end
|
13
|
+
spec.homepage = 'https://github.com/bartblast/beryl'
|
14
|
+
spec.license = 'MIT'
|
25
15
|
|
26
16
|
# Specify which files should be added to the gem when it is released.
|
27
17
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
18
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
29
19
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
30
20
|
end
|
31
|
-
spec.bindir =
|
21
|
+
spec.bindir = 'exe'
|
32
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
-
spec.require_paths = [
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
34
28
|
|
35
|
-
spec.add_development_dependency
|
36
|
-
spec.add_development_dependency
|
37
|
-
spec.add_development_dependency
|
38
|
-
|
29
|
+
spec.add_development_dependency 'bowser'
|
30
|
+
spec.add_development_dependency 'opal'
|
31
|
+
spec.add_development_dependency 'puma'
|
32
|
+
spec.add_development_dependency 'rack'
|
33
|
+
end
|
data/config.ru
CHANGED
data/lib/beryl/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bowser'
|
2
|
+
require 'opal'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
desc 'Build the app to build/app.js'
|
7
|
+
task :compile do
|
8
|
+
Opal.append_path 'app'
|
9
|
+
Opal.append_path 'lib'
|
10
|
+
Dir.mkdir('build') unless File.exist?('build')
|
11
|
+
File.binwrite 'build/app.js', Opal::Builder.build('frontend').to_s
|
12
|
+
File.binwrite 'build/style.css', File.read("#{File.dirname(__FILE__)}/style.css")
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Build and run the app'
|
16
|
+
task :run do
|
17
|
+
Rake::Task['compile'].invoke
|
18
|
+
sh 'bundle exec rackup --port 3000 --host 0.0.0.0'
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Test the app'
|
22
|
+
Rake::TestTask.new(:test) do |t|
|
23
|
+
t.libs << 'test'
|
24
|
+
t.libs << 'lib'
|
25
|
+
t.test_files = FileList['test/**/*_test.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => :test
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'command_handler'
|
2
|
+
require 'json'
|
3
|
+
require 'serializer'
|
4
|
+
|
5
|
+
module Beryl
|
6
|
+
class Backend
|
7
|
+
def call(env)
|
8
|
+
req = Rack::Request.new(env)
|
9
|
+
case req.path_info
|
10
|
+
when '/command'
|
11
|
+
[200, { 'Content-Type' => 'application/json; charset=utf-8' }, [handle_command(req)]]
|
12
|
+
else
|
13
|
+
[200, { 'Content-Type' => 'text/html; charset=utf-8' }, [response]]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def handle_command(req)
|
20
|
+
json = JSON.parse(req.body.read)
|
21
|
+
result = CommandHandler.new.handle(json['type'].to_sym, json['payload'])
|
22
|
+
Serializer.serialize(result)
|
23
|
+
end
|
24
|
+
|
25
|
+
def hydrate_state
|
26
|
+
Serializer.serialize(eval(File.read('app/initial_state.rb'))).gsub('"', '"')
|
27
|
+
end
|
28
|
+
|
29
|
+
def response
|
30
|
+
<<~HEREDOC
|
31
|
+
<!DOCTYPE html>
|
32
|
+
<html>
|
33
|
+
<head>
|
34
|
+
<script src="build/app.js"></script>
|
35
|
+
<link rel="stylesheet" type="text/css" href="build/style.css">
|
36
|
+
</head>
|
37
|
+
<body>
|
38
|
+
<div id="beryl" data-beryl="#{hydrate_state}" class="bg-color-255-255-255-255 font-color-0-0-0-255 font-size-20 font-open-sanshelveticaverdanasans-serif s e ui s e"></div>
|
39
|
+
</body>
|
40
|
+
</html>
|
41
|
+
HEREDOC
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Beryl
|
4
|
+
module Deserializer
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def deserialize(item, json = false)
|
8
|
+
item = JSON.parse(item) unless json
|
9
|
+
case item['class']
|
10
|
+
when 'Hash'
|
11
|
+
item['value'].each_with_object({}) do |(key, value), result|
|
12
|
+
result[key.to_sym] = deserialize(value, true)
|
13
|
+
end
|
14
|
+
when 'Integer'
|
15
|
+
item['value'].to_i
|
16
|
+
when 'String'
|
17
|
+
item['value']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'opal'
|
2
|
+
require 'native'
|
3
|
+
require 'beryl/deserializer'
|
4
|
+
require 'beryl/runtime'
|
5
|
+
|
6
|
+
module Beryl
|
7
|
+
class Frontend
|
8
|
+
def initialize(view)
|
9
|
+
@view = view
|
10
|
+
end
|
11
|
+
|
12
|
+
def onload(&block)
|
13
|
+
`window.onload = block;`
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
onload do
|
18
|
+
document = Native(`window.document`)
|
19
|
+
root = document.getElementById('beryl')
|
20
|
+
serialized_state = root.getAttribute('data-beryl').gsub('"', '"')
|
21
|
+
state = Beryl::Deserializer.deserialize(serialized_state)
|
22
|
+
Beryl::Runtime.new(root, state, @view).run
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'renderer'
|
2
|
+
require 'task'
|
3
|
+
require 'bowser/http'
|
4
|
+
require 'serializer'
|
5
|
+
|
6
|
+
module Beryl
|
7
|
+
class Runtime
|
8
|
+
def initialize(root, state, view)
|
9
|
+
@messages = []
|
10
|
+
@root = root
|
11
|
+
@state = state
|
12
|
+
@view = view
|
13
|
+
end
|
14
|
+
|
15
|
+
def push(message)
|
16
|
+
@messages << message
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
@view.state = @state
|
21
|
+
virtual_dom = VirtualDOM.new(@view.render)
|
22
|
+
Renderer.new.render(self, virtual_dom.dom.first, @root)
|
23
|
+
end
|
24
|
+
|
25
|
+
def process
|
26
|
+
while @messages.any?
|
27
|
+
message = @messages.shift
|
28
|
+
result = transition(message.first, message.last)
|
29
|
+
@state = result.is_a?(Array) ? result.first : result
|
30
|
+
command = result.is_a?(Array) ? result[1] : nil
|
31
|
+
run_command(result[1], result[2]) if command
|
32
|
+
render
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
process
|
38
|
+
render
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_command(type, payload)
|
42
|
+
Task.new do
|
43
|
+
Bowser::HTTP.fetch('/command', method: :post, data: { type: type, payload: Serializer.serialize(payload) })
|
44
|
+
.then(&:json) # JSONify the response
|
45
|
+
.then { |response| puts response }
|
46
|
+
.catch { |exception| warn exception.message }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def transition(type, payload)
|
51
|
+
case type
|
52
|
+
|
53
|
+
when :IncrementClicked
|
54
|
+
@state.merge(counter: @state[:counter] + 1)
|
55
|
+
|
56
|
+
when :LoadClicked
|
57
|
+
[@state, :FetchData, key_1: 1, key_2: 2]
|
58
|
+
|
59
|
+
when :LoadSuccess
|
60
|
+
@state.merge(content: payload[:data])
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/beryl/style.css
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {.s.r > .s { flex-basis: auto !important; } .s.r > .s.ctr { flex-basis: auto !important; }}
|
2
|
+
|
3
|
+
/* General Input Reset */
|
4
|
+
input[type=range] {
|
5
|
+
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
6
|
+
/* width: 100%; Specific width is required for Firefox. */
|
7
|
+
background: transparent; /* Otherwise white in Chrome */
|
8
|
+
position:absolute;
|
9
|
+
left:0;
|
10
|
+
top:0;
|
11
|
+
z-index:10;
|
12
|
+
width: 100%;
|
13
|
+
outline: dashed 1px;
|
14
|
+
height: 100%;
|
15
|
+
opacity: 0;
|
16
|
+
}
|
17
|
+
|
18
|
+
/* Hide all syling for track */
|
19
|
+
input[type=range]::-moz-range-track {
|
20
|
+
background: transparent;
|
21
|
+
cursor: pointer;
|
22
|
+
}
|
23
|
+
input[type=range]::-ms-track {
|
24
|
+
background: transparent;
|
25
|
+
cursor: pointer;
|
26
|
+
}
|
27
|
+
input[type=range]::-webkit-slider-runnable-track {
|
28
|
+
background: transparent;
|
29
|
+
cursor: pointer;
|
30
|
+
}
|
31
|
+
|
32
|
+
/* Thumbs */
|
33
|
+
input[type=range]::-webkit-slider-thumb {
|
34
|
+
-webkit-appearance: none;
|
35
|
+
opacity: 0.5;
|
36
|
+
width: 80px;
|
37
|
+
height: 80px;
|
38
|
+
background-color: black;
|
39
|
+
border:none;
|
40
|
+
border-radius: 5px;
|
41
|
+
}
|
42
|
+
input[type=range]::-moz-range-thumb {
|
43
|
+
opacity: 0.5;
|
44
|
+
width: 80px;
|
45
|
+
height: 80px;
|
46
|
+
background-color: black;
|
47
|
+
border:none;
|
48
|
+
border-radius: 5px;
|
49
|
+
}
|
50
|
+
input[type=range]::-ms-thumb {
|
51
|
+
opacity: 0.5;
|
52
|
+
width: 80px;
|
53
|
+
height: 80px;
|
54
|
+
background-color: black;
|
55
|
+
border:none;
|
56
|
+
border-radius: 5px;
|
57
|
+
}
|
58
|
+
input[type=range][orient=vertical]{
|
59
|
+
writing-mode: bt-lr; /* IE */
|
60
|
+
-webkit-appearance: slider-vertical; /* WebKit */
|
61
|
+
}
|
62
|
+
|
63
|
+
.explain {
|
64
|
+
border: 6px solid rgb(174, 121, 15) !important;
|
65
|
+
}
|
66
|
+
.explain > .s {
|
67
|
+
border: 4px dashed rgb(0, 151, 167) !important;
|
68
|
+
}
|
69
|
+
|
70
|
+
.ctr {
|
71
|
+
border: none !important;
|
72
|
+
}
|
73
|
+
.explain > .ctr > .s {
|
74
|
+
border: 4px dashed rgb(0, 151, 167) !important;
|
75
|
+
}
|
76
|
+
|
77
|
+
html,body{height:100%;padding:0;margin:0;}.s.e.ic{display:block;}.s:focus{outline:none;}.ui{width:100%;height:auto;min-height:100%;z-index:0;}.ui.s.hf{height:100%;}.ui.s.hf > .hf{height:100%;}.ui > .fr.s{position:fixed;}.s{position:relative;border:none;flex-shrink:0;display:flex;flex-direction:row;flex-basis:auto;resize:none;box-sizing:border-box;margin:0;padding:0;border-width:0;border-style:solid;font-size:inherit;color:inherit;font-family:inherit;line-height:1;font-weight:inherit;text-decoration:none;font-style:inherit;}.s.wrp{flex-wrap:wrap;}.s.notxt{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;}.s.cptr{cursor:pointer;}.s.ctxt{cursor:text;}.s.ppe{pointer-events:none !important;}.s.cpe{pointer-events:auto !important;}.s.clr{opacity:0;}.s.oq{opacity:1;}.s.hvclr:hover{opacity:0;}.s.hvoq:hover{opacity:1;}.s.fcsclr:focus{opacity:0;}.s.fcsoq:focus{opacity:1;}.s.atvclr:active{opacity:0;}.s.atvoq:active{opacity:1;}.s.ts{transition:transform 160ms, opacity 160ms, filter 160ms, background-color 160ms, color 160ms, font-size 160ms;}.s.sb{overflow:auto;flex-shrink:1;}.s.sbx{overflow-x:auto;}.s.sbx.r{flex-shrink:1;}.s.sby{overflow-y:auto;}.s.sby.c{flex-shrink:1;}.s.sby.e{flex-shrink:1;}.s.cp{overflow:hidden;}.s.cpx{overflow-x:hidden;}.s.cpy{overflow-y:hidden;}.s.wc{width:auto;}.s.bn{border-width:0;}.s.bd{border-style:dashed;}.s.bdt{border-style:dotted;}.s.bs{border-style:solid;}.s.t{white-space:pre;display:inline-block;}.s.it{line-height:1.05;}.s.e{display:flex;flex-direction:column;white-space:pre;}.s.e.hbh{z-index:0;}.s.e.hbh > .bh{z-index:-1;}.s.e.sbt > .t.hf{flex-grow:0;}.s.e.sbt > .t.wf{align-self:auto !important;}.s.e > .hc{height:auto;}.s.e > .hf{flex-grow:100000;}.s.e > .wf{width:100%;}.s.e > .wc{align-self:flex-start;}.s.e.ct{justify-content:flex-start;}.s.e > .s.at{margin-bottom:auto !important;margin-top:0 !important;}.s.e.cb{justify-content:flex-end;}.s.e > .s.ab{margin-top:auto !important;margin-bottom:0 !important;}.s.e.cr{align-items:flex-end;}.s.e > .s.ar{align-self:flex-end;}.s.e.cl{align-items:flex-start;}.s.e > .s.al{align-self:flex-start;}.s.e.ccx{align-items:center;}.s.e > .s.cx{align-self:center;}.s.e.ccy > .s{margin-top:auto;margin-bottom:auto;}.s.e > .s.cy{margin-top:auto !important;margin-bottom:auto !important;}.s.r{display:flex;flex-direction:row;}.s.r > .s{flex-basis:0%;}.s.r > .s.we{flex-basis:auto;}.s.r > .hf{align-self:stretch !important;}.s.r > .hfp{align-self:stretch !important;}.s.r > .wf{flex-grow:100000;}.s.r > .ctr{flex-grow:0;flex-basis:auto;align-self:stretch;}.s.r > u:first-of-type.acr{flex-grow:1;}.s.r > s:first-of-type.accx{flex-grow:1;}.s.r > s:first-of-type.accx > .cx{margin-left:auto !important;}.s.r > s:last-of-type.accx{flex-grow:1;}.s.r > s:last-of-type.accx > .cx{margin-right:auto !important;}.s.r > s:only-of-type.accx{flex-grow:1;}.s.r > s:only-of-type.accx > .cy{margin-top:auto !important;margin-bottom:auto !important;}.s.r > s:last-of-type.accx ~ u{flex-grow:0;}.s.r > u:first-of-type.acr ~ s.accx{flex-grow:0;}.s.r.ct{align-items:flex-start;}.s.r > .s.at{align-self:flex-start;}.s.r.cb{align-items:flex-end;}.s.r > .s.ab{align-self:flex-end;}.s.r.cr{justify-content:flex-end;}.s.r.cl{justify-content:flex-start;}.s.r.ccx{justify-content:center;}.s.r.ccy{align-items:center;}.s.r > .s.cy{align-self:center;}.s.r.sev{justify-content:space-between;}.s.c{display:flex;flex-direction:column;}.s.c > .hf{flex-grow:100000;}.s.c > .wf{width:100%;}.s.c > .wfp{width:100%;}.s.c > .wc{align-self:flex-start;}.s.c > u:first-of-type.acb{flex-grow:1;}.s.c > s:first-of-type.accy{flex-grow:1;}.s.c > s:first-of-type.accy > .cy{margin-top:auto !important;margin-bottom:0 !important;}.s.c > s:last-of-type.accy{flex-grow:1;}.s.c > s:last-of-type.accy > .cy{margin-bottom:auto !important;margin-top:0 !important;}.s.c > s:only-of-type.accy{flex-grow:1;}.s.c > s:only-of-type.accy > .cy{margin-top:auto !important;margin-bottom:auto !important;}.s.c > s:last-of-type.accy ~ u{flex-grow:0;}.s.c > u:first-of-type.acb ~ s.accy{flex-grow:0;}.s.c.ct{justify-content:flex-start;}.s.c > .s.at{margin-bottom:auto;}.s.c.cb{justify-content:flex-end;}.s.c > .s.ab{margin-top:auto;}.s.c.cr{align-items:flex-end;}.s.c > .s.ar{align-self:flex-end;}.s.c.cl{align-items:flex-start;}.s.c > .s.al{align-self:flex-start;}.s.c.ccx{align-items:center;}.s.c > .s.cx{align-self:center;}.s.c.ccy{justify-content:center;}.s.c > .ctr{flex-grow:0;flex-basis:auto;width:100%;align-self:stretch !important;}.s.c.sev{justify-content:space-between;}.s.g{display:-ms-grid;}.s.g > .gp > .s{width:100%;}@supports (display:grid) {.s.g{display:grid;
|
78
|
+
}}.s.g > .s.at{justify-content:flex-start;}.s.g > .s.ab{justify-content:flex-end;}.s.g > .s.ar{align-items:flex-end;}.s.g > .s.al{align-items:flex-start;}.s.g > .s.cx{align-items:center;}.s.g > .s.cy{justify-content:center;}.s.pg{display:block;}.s.pg > .s:first-child{margin:0 !important;}.s.pg > .s.al:first-child + .s{margin:0 !important;}.s.pg > .s.ar:first-child + .s{margin:0 !important;}.s.pg > .s.ar{float:right;}.s.pg > .s.ar::after{content:"";display:table;clear:both;}.s.pg > .s.al{float:left;}.s.pg > .s.al::after{content:"";display:table;clear:both;}.s.iml{white-space:pre-wrap;}.s.p{display:block;white-space:normal;}.s.p.hbh{z-index:0;}.s.p.hbh > .bh{z-index:-1;}.s.p > .t{display:inline;white-space:normal;}.s.p > .e{display:inline;white-space:normal;}.s.p > .e.fr{display:flex;}.s.p > .e.bh{display:flex;}.s.p > .e.a{display:flex;}.s.p > .e.b{display:flex;}.s.p > .e.or{display:flex;}.s.p > .e.ol{display:flex;}.s.p > .e > .t{display:inline;white-space:normal;}.s.p > .r{display:inline-flex;}.s.p > .c{display:inline-flex;}.s.p > .g{display:inline-grid;}.s.p > .s.ar{float:right;}.s.p > .s.al{float:left;}.s.hidden{display:none;}.s.a{position:absolute;bottom:100%;left:0;width:100%;z-index:10;margin:0 !important;pointer-events:none;}.s.a > .hf{height:auto;}.s.a > .wf{width:100%;}.s.a > .s{pointer-events:auto;}.s.b{position:absolute;bottom:0;left:0;height:0;width:100%;z-index:10;margin:0 !important;pointer-events:auto;}.s.b > .hf{height:auto;}.s.or{position:absolute;left:100%;top:0;height:100%;margin:0 !important;z-index:10;pointer-events:auto;}.s.ol{position:absolute;right:100%;top:0;height:100%;margin:0 !important;z-index:10;pointer-events:auto;}.s.fr{position:absolute;width:100%;height:100%;left:0;top:0;margin:0 !important;z-index:10;pointer-events:none;}.s.fr > .s{pointer-events:auto;}.s.bh{position:absolute;width:100%;height:100%;left:0;top:0;margin:0 !important;z-index:0;pointer-events:none;}.s.bh > .s{pointer-events:auto;}.s.w1{font-weight:100;}.s.w2{font-weight:200;}.s.w3{font-weight:300;}.s.w4{font-weight:400;}.s.w5{font-weight:500;}.s.w6{font-weight:600;}.s.w7{font-weight:700;}.s.w8{font-weight:800;}.s.w9{font-weight:900;}.s.i{font-style:italic;}.s.sk{text-decoration:line-through;}.s.u{text-decoration:underline;text-decoration-skip-ink:auto;text-decoration-skip:ink;}.s.u.sk{text-decoration:line-through underline;text-decoration-skip-ink:auto;text-decoration-skip:ink;}.s.tun{font-style:normal;}.s.tj{text-align:justify;}.s.tja{text-align:justify-all;}.s.tc{text-align:center;}.s.tr{text-align:right;}.s.tl{text-align:left;}.s.modal{position:fixed;left:0;top:0;width:100%;height:100%;pointer-events:none;}.border-0{border-width:0px;}.border-1{border-width:1px;}.border-2{border-width:2px;}.border-3{border-width:3px;}.border-4{border-width:4px;}.border-5{border-width:5px;}.border-6{border-width:6px;}.font-size-8{font-size:8px;}.font-size-9{font-size:9px;}.font-size-10{font-size:10px;}.font-size-11{font-size:11px;}.font-size-12{font-size:12px;}.font-size-13{font-size:13px;}.font-size-14{font-size:14px;}.font-size-15{font-size:15px;}.font-size-16{font-size:16px;}.font-size-17{font-size:17px;}.font-size-18{font-size:18px;}.font-size-19{font-size:19px;}.font-size-20{font-size:20px;}.font-size-21{font-size:21px;}.font-size-22{font-size:22px;}.font-size-23{font-size:23px;}.font-size-24{font-size:24px;}.font-size-25{font-size:25px;}.font-size-26{font-size:26px;}.font-size-27{font-size:27px;}.font-size-28{font-size:28px;}.font-size-29{font-size:29px;}.font-size-30{font-size:30px;}.font-size-31{font-size:31px;}.font-size-32{font-size:32px;}.p-0{padding:0px;}.p-1{padding:1px;}.p-2{padding:2px;}.p-3{padding:3px;}.p-4{padding:4px;}.p-5{padding:5px;}.p-6{padding:6px;}.p-7{padding:7px;}.p-8{padding:8px;}.p-9{padding:9px;}.p-10{padding:10px;}.p-11{padding:11px;}.p-12{padding:12px;}.p-13{padding:13px;}.p-14{padding:14px;}.p-15{padding:15px;}.p-16{padding:16px;}.p-17{padding:17px;}.p-18{padding:18px;}.p-19{padding:19px;}.p-20{padding:20px;}.p-21{padding:21px;}.p-22{padding:22px;}.p-23{padding:23px;}.p-24{padding:24px;}
|
79
|
+
|
80
|
+
.font-open-sanshelveticaverdanasans-serif{
|
81
|
+
font-family: "Open Sans", "Helvetica", "Verdana", sans-serif;
|
82
|
+
}.font-color-0-0-0-255{
|
83
|
+
color: rgba(0,0,0,1);
|
84
|
+
}.bg-color-255-255-255-255{
|
85
|
+
background-color: rgba(255,255,255,1);
|
86
|
+
}.s:focus .focusable, .s.focusable:focus{
|
87
|
+
box-shadow: 0px 0px 3px 3px rgba(155,203,255,1);
|
88
|
+
outline: none;
|
89
|
+
}
|
data/lib/beryl/utils.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Beryl
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def constantize(camel_cased_word)
|
6
|
+
names = camel_cased_word.split('::'.freeze)
|
7
|
+
|
8
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
9
|
+
Object.const_get(camel_cased_word) if names.empty?
|
10
|
+
|
11
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
12
|
+
names.shift if names.size > 1 && names.first.empty?
|
13
|
+
|
14
|
+
names.inject(Object) do |constant, name|
|
15
|
+
if constant == Object
|
16
|
+
constant.const_get(name)
|
17
|
+
else
|
18
|
+
candidate = constant.const_get(name)
|
19
|
+
next candidate if constant.const_defined?(name, false)
|
20
|
+
next candidate unless Object.const_defined?(name)
|
21
|
+
|
22
|
+
# Go down the ancestors to check if it is owned directly. The check
|
23
|
+
# stops when we reach Object or the end of ancestors tree.
|
24
|
+
constant = constant.ancestors.inject(constant) do |const, ancestor|
|
25
|
+
break const if ancestor == Object
|
26
|
+
break ancestor if ancestor.const_defined?(name, false)
|
27
|
+
const
|
28
|
+
end
|
29
|
+
|
30
|
+
# owner is in Object, so raise
|
31
|
+
constant.const_get(name, false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def deep_symbolize_keys(hash)
|
37
|
+
deep_transform_keys_in_object(hash) { |key| key.to_sym rescue key }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def deep_transform_keys_in_object(object, &block)
|
43
|
+
case object
|
44
|
+
when Hash
|
45
|
+
object.each_with_object({}) do |(key, value), result|
|
46
|
+
result[yield(key)] = deep_transform_keys_in_object(value, &block)
|
47
|
+
end
|
48
|
+
when Array
|
49
|
+
object.map {|e| deep_transform_keys_in_object(e, &block) }
|
50
|
+
else
|
51
|
+
object
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/beryl/version.rb
CHANGED
data/lib/beryl/view.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'beryl/virtual_dom'
|
2
|
+
|
3
|
+
module Beryl
|
4
|
+
class View
|
5
|
+
attr_accessor :state
|
6
|
+
|
7
|
+
def div(props = {}, &children)
|
8
|
+
node('div', props, children ? children.call : [])
|
9
|
+
end
|
10
|
+
|
11
|
+
def input(props = {}, &children)
|
12
|
+
node('input', props, children ? children.call : [])
|
13
|
+
end
|
14
|
+
|
15
|
+
def link(text, props = {}, &children)
|
16
|
+
node('a', props, [text(text)])
|
17
|
+
end
|
18
|
+
|
19
|
+
def node(type, props = {}, children)
|
20
|
+
{
|
21
|
+
type: type,
|
22
|
+
props: props,
|
23
|
+
children: children
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def span(props = {}, &children)
|
28
|
+
node('span', props, children ? children.call : [])
|
29
|
+
end
|
30
|
+
|
31
|
+
def text(value, props = {}, &children)
|
32
|
+
node('text', props.merge(nodeValue: value), children ? children.call : [])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Beryl
|
2
|
+
class VirtualDOM
|
3
|
+
attr_reader :dom
|
4
|
+
|
5
|
+
def initialize(layout)
|
6
|
+
@layout = layout
|
7
|
+
@dom = convert(layout)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def convert(layout)
|
13
|
+
layout.each_with_object([]) do |element, dom|
|
14
|
+
case element[:type]
|
15
|
+
when :column
|
16
|
+
width = width(element[:props])
|
17
|
+
height = height(element[:props])
|
18
|
+
klass = "#{height[:class]} s c #{width[:class]} ct cl"
|
19
|
+
style = "#{width[:style]}#{height[:style]}"
|
20
|
+
props = { class: klass, style: style }
|
21
|
+
dom << node('div', props, element[:children] ? convert(element[:children]) : [])
|
22
|
+
when :row
|
23
|
+
width = width(element[:props])
|
24
|
+
height = height(element[:props])
|
25
|
+
klass = "#{height[:class]} s r #{width[:class]} cl ccy"
|
26
|
+
style = "#{width[:style]}#{height[:style]}"
|
27
|
+
props = { class: klass, style: style }
|
28
|
+
dom << node('div', props, element[:children] ? convert(element[:children]) : [])
|
29
|
+
when :text
|
30
|
+
width = width(element[:props])
|
31
|
+
height = height(element[:props])
|
32
|
+
klass = "#{height[:class]} s e #{width[:class]}"
|
33
|
+
style = "#{width[:style]}#{height[:style]}"
|
34
|
+
props = { class: klass, style: style }
|
35
|
+
dom << node('div', props, [node('text', { nodeValue: element[:value] })])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def height(props)
|
41
|
+
type = height_type(props)
|
42
|
+
{
|
43
|
+
type: type,
|
44
|
+
class: height_class(type),
|
45
|
+
style: height_style(props, type)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def width(props)
|
50
|
+
type = width_type(props)
|
51
|
+
{
|
52
|
+
type: type,
|
53
|
+
class: width_class(type),
|
54
|
+
style: width_style(props, type)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def width_type(props)
|
59
|
+
props = [props] unless props.is_a?(Array)
|
60
|
+
return :content unless props
|
61
|
+
return :fill if props.include?(:fill_width)
|
62
|
+
hash = props.select { |p| p.is_a?(Hash) }.first
|
63
|
+
return :content unless hash
|
64
|
+
return :fixed if hash[:width].is_a?(Integer)
|
65
|
+
return :proportional if hash[:proportional_width].is_a?(Integer)
|
66
|
+
:content
|
67
|
+
end
|
68
|
+
|
69
|
+
def height_type(props)
|
70
|
+
props = [props] unless props.is_a?(Array)
|
71
|
+
return :content unless props
|
72
|
+
return :fill if props.include?(:fill_height)
|
73
|
+
hash = props.select { |p| p.is_a?(Hash) }.first
|
74
|
+
return :content unless hash
|
75
|
+
return :fixed if hash[:height].is_a?(Integer)
|
76
|
+
return :proportional if hash[:proportional_height].is_a?(Integer)
|
77
|
+
:content
|
78
|
+
end
|
79
|
+
|
80
|
+
def width_class(type)
|
81
|
+
case type
|
82
|
+
when :content
|
83
|
+
'wc'
|
84
|
+
when :fill
|
85
|
+
'wf'
|
86
|
+
when :fixed
|
87
|
+
''
|
88
|
+
when :proportional
|
89
|
+
'wfp'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def height_class(type)
|
94
|
+
case type
|
95
|
+
when :content
|
96
|
+
'hc'
|
97
|
+
when :fill
|
98
|
+
'hf'
|
99
|
+
when :fixed
|
100
|
+
''
|
101
|
+
when :proportional
|
102
|
+
'hfp'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def width_style(props, type)
|
107
|
+
case type
|
108
|
+
when :content
|
109
|
+
''
|
110
|
+
when :fill
|
111
|
+
''
|
112
|
+
when :fixed
|
113
|
+
width = props.select { |p| p.is_a?(Hash) }.first[:width]
|
114
|
+
"width: #{width}px;"
|
115
|
+
when :proportional
|
116
|
+
portion = props.select { |p| p.is_a?(Hash) }.first[:proportional_width]
|
117
|
+
"flex-grow: #{100000 * portion};"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def height_style(props, type)
|
122
|
+
case type
|
123
|
+
when :content
|
124
|
+
''
|
125
|
+
when :fill
|
126
|
+
''
|
127
|
+
when :fixed
|
128
|
+
height = props.select { |p| p.is_a?(Hash) }.first[:height]
|
129
|
+
"height: #{height}px;"
|
130
|
+
when :proportional
|
131
|
+
portion = props.select { |p| p.is_a?(Hash) }.first[:proportional_height]
|
132
|
+
"flex-grow: #{100000 * portion};"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def node(type, props = {}, children = [])
|
137
|
+
{
|
138
|
+
type: type,
|
139
|
+
props: props,
|
140
|
+
children: children
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/beryl/widget.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'beryl/utils'
|
2
|
+
|
3
|
+
module Beryl
|
4
|
+
class Widget
|
5
|
+
attr_reader :children
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@children = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(type, *args, &block)
|
12
|
+
element = Widget.new
|
13
|
+
element.instance_eval(&block)
|
14
|
+
{
|
15
|
+
type: type,
|
16
|
+
props: args,
|
17
|
+
children: element.children
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def column(*args, &block)
|
22
|
+
@children << build(:column, *args, &block)
|
23
|
+
@children
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
constantized = Beryl::Utils.constantize(name.to_s)
|
28
|
+
child = args.any? ? constantized.new.render(*args) : constantized.new.render
|
29
|
+
raise SyntaxError.new("Widget #{name} should return only one element (use row or column)") if child.is_a?(Array) && child.size > 1
|
30
|
+
@children += child
|
31
|
+
child
|
32
|
+
rescue NoMethodError
|
33
|
+
raise NameError.new("There is no such widget: #{name}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def row(*args, &block)
|
37
|
+
@children << build(:row, *args, &block)
|
38
|
+
@children
|
39
|
+
end
|
40
|
+
|
41
|
+
def text(string, *props)
|
42
|
+
@children << {
|
43
|
+
type: :text,
|
44
|
+
value: string,
|
45
|
+
props: props
|
46
|
+
}
|
47
|
+
@children
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,27 +1,27 @@
|
|
1
1
|
require 'native'
|
2
|
-
require '
|
2
|
+
require 'beryl/runtime'
|
3
3
|
|
4
|
-
class
|
5
|
-
def render(
|
4
|
+
class Renderer
|
5
|
+
def render(runtime, element, parentDom, replace = true)
|
6
6
|
document = Native(`window.document`)
|
7
7
|
dom = element[:type] == 'text' ? document.createTextNode('') : document.createElement(element[:type])
|
8
8
|
|
9
|
-
add_event_listeners(element, dom,
|
9
|
+
add_event_listeners(element, dom, runtime)
|
10
10
|
set_attributes(element, dom)
|
11
11
|
|
12
12
|
childElements = element[:children] || [];
|
13
|
-
childElements.each { |child| render(
|
13
|
+
childElements.each { |child| render(runtime, child, dom, false) }
|
14
14
|
|
15
15
|
update_dom(parentDom, dom, replace)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
-
def add_event_listeners(element, dom,
|
21
|
-
|
22
|
-
|
20
|
+
def add_event_listeners(element, dom, runtime)
|
21
|
+
listeners = element[:props].select { |key, _value| listener?(key) }
|
22
|
+
listeners.each do |key, value|
|
23
23
|
event_type = key.downcase[2..-1]
|
24
|
-
dom.addEventListener(event_type, lambda {
|
24
|
+
dom.addEventListener(event_type, lambda { runtime.push(value); runtime.process })
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -30,8 +30,8 @@ class VirtualDOM
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def set_attributes(element, dom)
|
33
|
-
|
34
|
-
|
33
|
+
attributes = element[:props].reject { |key, _value| listener?(key) }
|
34
|
+
attributes.each { |key, value| key != :class ? dom[key] = value : dom.className = value }
|
35
35
|
end
|
36
36
|
|
37
37
|
def update_dom(parent_dom, dom, replace)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beryl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bart Blast
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,62 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bowser
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: opal
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: puma
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rack
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
55
111
|
description: Web framework
|
56
112
|
email:
|
57
113
|
- bart@bartblast.com
|
@@ -66,20 +122,31 @@ files:
|
|
66
122
|
- LICENSE.txt
|
67
123
|
- README.md
|
68
124
|
- Rakefile
|
69
|
-
-
|
70
|
-
- app/
|
125
|
+
- Rakefile-template
|
126
|
+
- app/frontend.rb
|
127
|
+
- app/initial_state.rb
|
128
|
+
- app/something.rb
|
129
|
+
- app/view.rb
|
71
130
|
- beryl.gemspec
|
72
131
|
- bin/console
|
73
132
|
- bin/setup
|
74
133
|
- config.ru
|
75
|
-
- lib/app.rb
|
76
134
|
- lib/beryl.rb
|
135
|
+
- lib/beryl/Rakefile
|
136
|
+
- lib/beryl/backend.rb
|
137
|
+
- lib/beryl/deserializer.rb
|
138
|
+
- lib/beryl/frontend.rb
|
139
|
+
- lib/beryl/runtime.rb
|
140
|
+
- lib/beryl/style.css
|
141
|
+
- lib/beryl/utils.rb
|
77
142
|
- lib/beryl/version.rb
|
143
|
+
- lib/beryl/view.rb
|
144
|
+
- lib/beryl/virtual_dom.rb
|
145
|
+
- lib/beryl/widget.rb
|
78
146
|
- lib/command_handler.rb
|
79
|
-
- lib/
|
147
|
+
- lib/renderer.rb
|
80
148
|
- lib/serializer.rb
|
81
149
|
- lib/task.rb
|
82
|
-
- lib/virtual_dom.rb
|
83
150
|
homepage: https://github.com/bartblast/beryl
|
84
151
|
licenses:
|
85
152
|
- MIT
|
data/app-Gemfile
DELETED
data/app/frontend_app.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
require 'opal'
|
2
|
-
require 'native'
|
3
|
-
require 'event_loop'
|
4
|
-
require 'serializer'
|
5
|
-
require 'virtual_dom'
|
6
|
-
|
7
|
-
puts 'Wow, running opal!'
|
8
|
-
|
9
|
-
def div(props = {}, &children)
|
10
|
-
node('div', props, children ? children.call : [])
|
11
|
-
end
|
12
|
-
|
13
|
-
def input(props = {}, &children)
|
14
|
-
node('input', props, children ? children.call : [])
|
15
|
-
end
|
16
|
-
|
17
|
-
def link(text, props = {}, &children)
|
18
|
-
node('a', props, [text(text)])
|
19
|
-
end
|
20
|
-
|
21
|
-
def node(type, props = {}, children)
|
22
|
-
{
|
23
|
-
type: type,
|
24
|
-
props: props,
|
25
|
-
children: children
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
def span(props = {}, &children)
|
30
|
-
node('span', props, children ? children.call : [])
|
31
|
-
end
|
32
|
-
|
33
|
-
def text(value, props = {}, &children)
|
34
|
-
node('text', props.merge(nodeValue: value), children ? children.call : [])
|
35
|
-
end
|
36
|
-
|
37
|
-
def onload(&block)
|
38
|
-
`window.onload = block;`
|
39
|
-
end
|
40
|
-
|
41
|
-
def element(state)
|
42
|
-
div(id: 'container') {[
|
43
|
-
input(value: 'foo', type: 'text'),
|
44
|
-
span() {[
|
45
|
-
text(' Foo ' + state[:counter].to_s + ' ')
|
46
|
-
]},
|
47
|
-
link(' Increment ', onClick: [:IncrementClicked, key_1: 1, key_2: 2]),
|
48
|
-
link(' Load ', onClick: [:LoadClicked, key_1: 1, key_2: 2]),
|
49
|
-
div {[
|
50
|
-
text(state[:content])
|
51
|
-
]}
|
52
|
-
]}
|
53
|
-
end
|
54
|
-
|
55
|
-
class Interval
|
56
|
-
def initialize(time = 0, &block)
|
57
|
-
@interval = `setInterval(function(){#{block.call}}, time)`
|
58
|
-
end
|
59
|
-
|
60
|
-
def stop
|
61
|
-
`clearInterval(#@interval)`
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
onload do
|
66
|
-
document = Native(`window.document`)
|
67
|
-
parentDom = document.getElementById('root')
|
68
|
-
|
69
|
-
state = { counter: 0, content: 'here we will load something' }
|
70
|
-
event_loop = EventLoop.new(parentDom, state)
|
71
|
-
event_loop.process
|
72
|
-
event_loop.render
|
73
|
-
end
|
data/lib/app.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require_relative 'command_handler'
|
3
|
-
require_relative 'serializer'
|
4
|
-
|
5
|
-
class App
|
6
|
-
HTML = <<~HEREDOC
|
7
|
-
<!DOCTYPE html>
|
8
|
-
<html>
|
9
|
-
<head>
|
10
|
-
<script src='build/app.js'></script>
|
11
|
-
</head>
|
12
|
-
<body>
|
13
|
-
<div id="root"></div>
|
14
|
-
</body>
|
15
|
-
</html>
|
16
|
-
HEREDOC
|
17
|
-
|
18
|
-
def call (env)
|
19
|
-
req = Rack::Request.new(env)
|
20
|
-
case req.path_info
|
21
|
-
when '/rock/command'
|
22
|
-
[200, { 'Content-Type' => 'application/json; charset=utf-8' }, [run_command(req)]]
|
23
|
-
else
|
24
|
-
[200, { 'Content-Type' => 'text/html; charset=utf-8' }, [HTML]]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def run_command(req)
|
29
|
-
json = JSON.parse(req.body.read)
|
30
|
-
result = CommandHandler.new.handle(json['type'].to_sym, json['payload'])
|
31
|
-
Serializer.serialize(result)
|
32
|
-
end
|
33
|
-
end
|
data/lib/event_loop.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require 'virtual_dom'
|
2
|
-
require 'task'
|
3
|
-
require 'bowser/http'
|
4
|
-
require 'serializer'
|
5
|
-
|
6
|
-
class EventLoop
|
7
|
-
def initialize(root, state)
|
8
|
-
@messages = []
|
9
|
-
@root = root
|
10
|
-
@state = state
|
11
|
-
end
|
12
|
-
|
13
|
-
def push(message)
|
14
|
-
puts 'adding message...'
|
15
|
-
@messages << message
|
16
|
-
end
|
17
|
-
|
18
|
-
def render
|
19
|
-
VirtualDOM.new.render(self, element(@state), @root)
|
20
|
-
end
|
21
|
-
|
22
|
-
def process
|
23
|
-
while @messages.any?
|
24
|
-
message = @messages.shift
|
25
|
-
result = transition(message.first, message.last)
|
26
|
-
@state = result.is_a?(Array) ? result.first : result
|
27
|
-
command = result.is_a?(Array) ? result[1] : nil
|
28
|
-
run_command(result[1], result[2]) if command
|
29
|
-
render
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def run_command(type, payload)
|
34
|
-
puts 'running command'
|
35
|
-
Task.new do
|
36
|
-
Bowser::HTTP.fetch('/rock/command', method: :post, data: { type: type, payload: Serializer.serialize(payload) })
|
37
|
-
.then(&:json) # JSONify the response
|
38
|
-
.then { |response| puts response }
|
39
|
-
.catch { |exception| warn exception.message }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def transition(type, payload)
|
44
|
-
case type
|
45
|
-
when :IncrementClicked
|
46
|
-
@state.merge(counter: @state[:counter] + 1)
|
47
|
-
when :LoadClicked
|
48
|
-
[@state, :FetchData, key_1: 1, key_2: 2]
|
49
|
-
when :LoadSuccess
|
50
|
-
@state.merge(content: payload[:data])
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|