beryl 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b009e4e353f9efecf2cb768b1e71126f5367edaf534559c0c6ef1b9d02312d1
4
- data.tar.gz: 4e0fa2a870b6bec3f12f5b5d5e6b840571fff39e3181e8965f0c48e6d7032f4d
3
+ metadata.gz: 5b6146cdf28fe2e973b3d151fe7536034e11a3e1dec1639b7e139509ecd4e298
4
+ data.tar.gz: 5d7c85d49307cb8c427bf8f2fc593615613d6abc648d6e1dae6a2d081f46abe8
5
5
  SHA512:
6
- metadata.gz: a3834be4e596b8260b5a8d246b97e58ecfb771c74a8d0b5c6c49e8598a8cd9a5d483b7f1a5341e41a3238678c756f0ddbc23b1ae7cde8ea8af312102e214c60f
7
- data.tar.gz: 3c0ca7a52f50ba852be97f031e89cd80df1ccd056ac8e4af4b9cbcff71c86efb32fe28a8d8c5980cf2f04514f4c1db343893e3ec9d15413eb2328a44ad21fd9b
6
+ metadata.gz: 87de7c9e079ca95edf709051289c8c34a6d3f3314c46f9b877fadd5fa4fc199e783f00fd964b6d3fe39db2d48269eec0d3b14708108998fad8eba77dfc431ae5
7
+ data.tar.gz: 6aa0b0572132a48a1d7f175e386b6bc6b404a0efbbd953ea43d9b861e867d176dfc5effa950cb87b91e757f6c0144e5b7a80551a93616cce585e31c7d9ebe352
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ source "https://rubygems.org"
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in beryl.gemspec
6
- gemspec
6
+ gemspec
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
- bowser!
24
- opal!
25
- puma!
26
- rack!
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.4
40
+ 1.16.5
data/Rakefile CHANGED
@@ -1,27 +1 @@
1
- require 'opal'
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
@@ -0,0 +1,5 @@
1
+ require 'beryl'
2
+
3
+ spec = Gem::Specification.find_by_name 'beryl'
4
+ rakefile = "#{spec.gem_dir}/lib/beryl/Rakefile"
5
+ load rakefile
data/app/frontend.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'beryl/frontend'
2
+ require 'view'
3
+
4
+ Beryl::Frontend.new(View.new).run
@@ -0,0 +1,5 @@
1
+ {
2
+ content: 'here we will load something',
3
+ counter: 0,
4
+ route: nil
5
+ }
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
@@ -0,0 +1,8 @@
1
+ require 'beryl/view'
2
+ require 'something'
3
+
4
+ class View < Beryl::View
5
+ def render
6
+ Something.new.render(state)
7
+ end
8
+ end
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 "beryl/version"
3
+ require 'beryl/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "beryl"
6
+ spec.name = 'beryl'
8
7
  spec.version = Beryl::VERSION
9
- spec.authors = ["Bart Blast"]
10
- spec.email = ["bart@bartblast.com"]
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 = "https://github.com/bartblast/beryl"
15
- spec.license = "MIT"
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 = "exe"
21
+ spec.bindir = 'exe'
32
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
- spec.require_paths = ["lib"]
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 "bundler", "~> 1.16"
36
- spec.add_development_dependency "rake", "~> 10.0"
37
- spec.add_development_dependency "minitest", "~> 5.0"
38
- end
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
@@ -1,5 +1,5 @@
1
+ require 'beryl/backend'
1
2
  require 'rack'
2
- require_relative 'lib/app'
3
3
 
4
4
  use Rack::Static, :urls => ['/build']
5
- run App.new
5
+ run Beryl::Backend.new
@@ -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('"', '&quot;')
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('&quot;', '"')
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
@@ -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
+ }
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Beryl
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
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
@@ -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 'event_loop'
2
+ require 'beryl/runtime'
3
3
 
4
- class VirtualDOM
5
- def render(event_loop, element, parentDom, replace = true)
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, event_loop)
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(event_loop, child, dom, false) }
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, event_loop)
21
- listener_props = element[:props].select { |key, _value| listener?(key) }
22
- listener_props.each do |key, value|
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 { event_loop.push(value); event_loop.process })
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
- attribute_props = element[:props].reject { |key, _value| listener?(key) }
34
- attribute_props.each { |key, value| dom[key] = value }
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.1.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-23 00:00:00.000000000 Z
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
- - app-Gemfile
70
- - app/frontend_app.rb
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/event_loop.rb
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
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org' do
2
- gem 'bowser'
3
- gem 'opal'
4
- gem 'puma'
5
- gem 'rack'
6
- end
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