quince 0.4.2 โ†’ 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 896b010bef61ab4914a5cf09ad07c94d2840063788c6637422dfe6544c9bda3c
4
- data.tar.gz: e3c39708bc888257d56ae5463619dca5bc65ace9070537af7a488e5457ffd9a4
3
+ metadata.gz: 8fa04a55360919ddbb5c4cbda37d050d6cceb7f0d18b322db647de52391e2aae
4
+ data.tar.gz: 42576df0afa49fdc03e1627825f6639b1c7ee78df6257e9a19e4bf1e7f70527d
5
5
  SHA512:
6
- metadata.gz: 7693d8919c8bc2f7af200afade98c9300131bf054a9a2614c9c3c80fa0eb90d2c153a092246bb7cf5b23661da2f57fde9038a712970ddecea45938eca4e4bcd5
7
- data.tar.gz: da44b43b26aeeab5fe75579877e6e233e4425bf1befc12685ccdcc39aac445c617e16d2f28d571d1623afd1adc960db52a10918f39fc1922587e38c2aa736147
6
+ metadata.gz: c23ee9c623006fbaf2b27ead13c7fff34dd2527aba7ad618bd4a53d55105608805746ac56e3e7c6b853620801f14f956062005c6126e0c3f2b858112a02a575a
7
+ data.tar.gz: 18d1cca64addec79bef58f47b62006b3ba40ee94d318f74d29e633320aafb2146a9b4c2ff200be56b51177253e889b6e18d7697880ea5c0cd416bc1b42bb736a
data/Gemfile.lock CHANGED
@@ -1,15 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quince (0.4.2)
4
+ quince (0.6.1)
5
5
  oj (~> 3.13)
6
+ rack-contrib (~> 2.3)
7
+ sinatra (~> 2.1)
8
+ sinatra-contrib (~> 2.1)
6
9
  typed_struct (>= 0.1.4)
7
10
 
8
11
  GEM
9
12
  remote: https://rubygems.org/
10
13
  specs:
11
14
  diff-lcs (1.4.4)
12
- oj (3.13.7)
15
+ multi_json (1.15.0)
16
+ mustermann (1.1.1)
17
+ ruby2_keywords (~> 0.0.1)
18
+ oj (3.13.9)
19
+ rack (2.2.3)
20
+ rack-contrib (2.3.0)
21
+ rack (~> 2.0)
22
+ rack-protection (2.1.0)
23
+ rack
13
24
  rake (13.0.6)
14
25
  rbs (1.6.2)
15
26
  rspec (3.10.0)
@@ -25,6 +36,19 @@ GEM
25
36
  diff-lcs (>= 1.2.0, < 2.0)
26
37
  rspec-support (~> 3.10.0)
27
38
  rspec-support (3.10.2)
39
+ ruby2_keywords (0.0.5)
40
+ sinatra (2.1.0)
41
+ mustermann (~> 1.0)
42
+ rack (~> 2.2)
43
+ rack-protection (= 2.1.0)
44
+ tilt (~> 2.0)
45
+ sinatra-contrib (2.1.0)
46
+ multi_json
47
+ mustermann (~> 1.0)
48
+ rack-protection (= 2.1.0)
49
+ sinatra (= 2.1.0)
50
+ tilt (~> 2.0)
51
+ tilt (2.0.10)
28
52
  typed_struct (0.1.4)
29
53
  rbs (~> 1.0)
30
54
 
data/README.md CHANGED
@@ -10,7 +10,8 @@ React, Turbo, Hotwire amongst others
10
10
 
11
11
  ### Current status
12
12
 
13
- Proof of concept, but [working in production](https://quince-rb.herokuapp.com/), and with decent performance despite few optimisations at this stage
13
+ Early, but [working in production](https://quince-rb.herokuapp.com/). Expect more features and
14
+ optimisations to come, but also potential for big changes between versions in the early stages.
14
15
 
15
16
  ### How it works
16
17
 
@@ -22,7 +23,7 @@ Proof of concept, but [working in production](https://quince-rb.herokuapp.com/),
22
23
 
23
24
  ```ruby
24
25
  # app.rb
25
- require "quince_sinatra"
26
+ require "quince"
26
27
 
27
28
  class App < Quince::Component
28
29
  def render
@@ -46,7 +47,7 @@ ruby app.rb
46
47
  ## More complex example
47
48
 
48
49
  ```ruby
49
- require 'quince_sinatra'
50
+ require 'quince'
50
51
 
51
52
  class App < Quince::Component
52
53
  def render
@@ -114,49 +115,23 @@ expose App, at: "/"
114
115
 
115
116
  ### Why?
116
117
 
117
- - Lightweight ๐Ÿชถ
118
- - Very few dependencies
119
- - Just a couple hundred lines of core logic
120
- - Fewer than 30 lines (unminified) of JavaScript in the front end
121
- - Plug and play into multiple back ends ๐Ÿ”Œ
122
118
  - Components > templates ๐Ÿงฉ
123
- - Write html-like elements, but with strong typo resistence
124
- - no special syntax or compilation required
119
+ - Lightweight ๐Ÿชถ
125
120
  - Shallow learning curve if you are already familiar with React ๐Ÿ“ˆ
126
- - Just worry about your core business logic and how the app looks ๐Ÿงช
127
- - No need to worry about
128
- - routes
129
- - controllers
130
- - front end -> back end communication/APIs/Data transfer
131
- - front end -> back end code sharing
132
- - Quince handles these for you
133
- - No node_modules ๐Ÿ“ฆ
134
- - No yarn/npm
135
- - Minimise bundle size concerns
136
- - Manage your dependencies just using bundler & rubygems
137
- - Make use of other pre-built Quince components via rubygems
121
+ - Focus on your core business logic, not routes/APIs/data transfer/code sharing ๐Ÿงช
122
+ - No compilation/node_modules/yarn/js bundle size concerns - just bundler ๐Ÿ“ฆ
138
123
  - Get full use of Ruby's rich and comprehensive standard library ๐Ÿ’Ž
139
124
  - Take advantage of Ruby's ability to wrap native libraries (eg. gems using C) โšก๏ธ
140
- - Fully server-rendered responses ๐Ÿ“ก
141
- - Single source of truth for your app's code (no code-sharing needed)
142
- - Better SEO out the box
143
- - Know exactly what your user is seeing
144
- - Tracking a user's activity on the front end has become a big deal, especially in heavily front-end driven apps/SPAs, in order to be able to see how a user is actually using the app (ie. to track how the state has been changing on the front end)
145
- - This normally requires cookies and a premium third party service
146
- - But if everything a user sees is generated server-side, it would be easy to reconstruct a user's journey and their state changes
125
+ - Fully server-rendered responses - single source of truth ๐Ÿ“ก
126
+ - Easy to recreate/rehydrate a pages state (almost nothing is stored in memory from JavaScript - all
127
+ the state is stored with the HTML document's markup)
147
128
 
148
129
  ## Installation
149
130
 
150
- Quince itself is framework agnostic, so you should use an adaptor which plugs it into an existing framework for handling basic server needs
151
-
152
- ### Install it via adapters
153
-
154
- - [Sinatra](https://github.com/johansenja/quince_sinatra)
155
-
156
- Pick one, and add it to your application's Gemfile, eg:
131
+ Add this to your application's Gemfile:
157
132
 
158
133
  ```ruby
159
- gem 'quince_sinatra'
134
+ gem 'quince'
160
135
  ```
161
136
 
162
137
  And then execute:
@@ -165,7 +140,7 @@ And then execute:
165
140
 
166
141
  Or install it yourself as:
167
142
 
168
- $ gem install quince_sinatra
143
+ $ gem install quince
169
144
 
170
145
 
171
146
  ## Usage notes
@@ -4,19 +4,19 @@ require_relative "types"
4
4
 
5
5
  module Quince
6
6
  module HtmlTagComponents
7
- referrer_policy = Rbs("'' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' | Quince::Types::Undefined")
7
+ referrer_policy = Rbs("'' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' | nil")
8
8
  form_method = Rbs(
9
- '"get" | "post" | "GET" | "POST" | :GET | :POST | :get | :post | Quince::Types::Undefined'
9
+ '"get" | "post" | "GET" | "POST" | :GET | :POST | :get | :post | nil'
10
10
  )
11
11
  t = Quince::Types
12
12
  opt_string_sym = Rbs("#{t::OptionalString} | Symbol")
13
13
  opt_bool = t::OptionalBoolean
14
- opt_callback = Rbs("Quince::Callback::Interface | Quince::Types::Undefined")
14
+ opt_callback = Rbs("Quince::Callback::Interface | nil")
15
15
  value = opt_string_sym # for now
16
16
 
17
17
  ATTRIBUTES_BY_ELEMENT = {
18
18
  "A" => {
19
- # download: t::Any,
19
+ download: opt_string_sym,
20
20
  href: opt_string_sym,
21
21
  hreflang: opt_string_sym,
22
22
  media: opt_string_sym,
@@ -58,7 +58,7 @@ module Quince
58
58
  formnovalidate: opt_bool,
59
59
  formtarget: opt_string_sym,
60
60
  name: opt_string_sym,
61
- type: Rbs("'submit' | 'reset' | 'button' | Quince::Types::Undefined"),
61
+ type: Rbs("'submit' | 'reset' | 'button' | nil"),
62
62
  value: value,
63
63
  }.freeze,
64
64
  "Canvas" => {
@@ -128,7 +128,7 @@ module Quince
128
128
  allowfullscreen: opt_bool,
129
129
  allowtransparency: opt_bool,
130
130
  height: opt_string_sym,
131
- loading: Rbs('"eager" | "lazy" | Quince::Types::Undefined'),
131
+ loading: Rbs('"eager" | "lazy" | nil'),
132
132
  name: opt_string_sym,
133
133
  referrerpolicy: referrer_policy,
134
134
  sandbox: opt_string_sym,
@@ -192,7 +192,7 @@ module Quince
192
192
  "Ol" => {
193
193
  reversed: opt_bool,
194
194
  start: opt_string_sym,
195
- type: Rbs("'1' | 'a' | 'A' | 'i' | 'I' | Quince::Types::Undefined"),
195
+ type: Rbs("'1' | 'a' | 'A' | 'i' | 'I' | nil"),
196
196
  }.freeze,
197
197
  "Optgroup" => {
198
198
  disabled: opt_bool,
@@ -270,7 +270,7 @@ module Quince
270
270
  }.freeze,
271
271
  "Tbody" => {}.freeze,
272
272
  "Td" => {
273
- align: Rbs('"left" | "center" | "right" | "justify" | "char" | Quince::Types::Undefined'),
273
+ align: Rbs('"left" | "center" | "right" | "justify" | "char" | nil'),
274
274
  colspan: opt_string_sym,
275
275
  headers: opt_string_sym,
276
276
  rowspan: opt_string_sym,
@@ -278,7 +278,7 @@ module Quince
278
278
  abbr: opt_string_sym,
279
279
  height: opt_string_sym,
280
280
  width: opt_string_sym,
281
- valign: Rbs('"top" | "middle" | "bottom" | "baseline" | Quince::Types::Undefined'),
281
+ valign: Rbs('"top" | "middle" | "bottom" | "baseline" | nil'),
282
282
  }.freeze,
283
283
  "Textarea" => {
284
284
  autocomplete: opt_string_sym,
@@ -300,7 +300,7 @@ module Quince
300
300
  }.freeze,
301
301
  "Tfoot" => {}.freeze,
302
302
  "Th" => {
303
- align: Rbs('"left" | "center" | "right" | "justify" | "char" | Quince::Types::Undefined'),
303
+ align: Rbs('"left" | "center" | "right" | "justify" | "char" | nil'),
304
304
  colspan: opt_string_sym,
305
305
  headers: opt_string_sym,
306
306
  rowspan: opt_string_sym,
@@ -337,7 +337,7 @@ module Quince
337
337
  "Area" => {
338
338
  alt: opt_string_sym,
339
339
  coords: opt_string_sym,
340
- # download: t::Any,
340
+ download: opt_string_sym,
341
341
  href: opt_string_sym,
342
342
  hreflang: opt_string_sym,
343
343
  media: opt_string_sym,
@@ -364,10 +364,10 @@ module Quince
364
364
  "Hr" => {}.freeze,
365
365
  "Img" => {
366
366
  alt: opt_string_sym,
367
- crossorigin: Rbs('"anonymous" | "use-credentials" | "" | Quince::Types::Undefined'),
368
- decoding: Rbs('"async" | "auto" | "sync" | Quince::Types::Undefined'),
367
+ crossorigin: Rbs('"anonymous" | "use-credentials" | "" | nil'),
368
+ decoding: Rbs('"async" | "auto" | "sync" | nil'),
369
369
  height: Rbs("#{opt_string_sym} | Integer"),
370
- loading: Rbs('"eager" | "lazy" | Quince::Types::Undefined'),
370
+ loading: Rbs('"eager" | "lazy" | nil'),
371
371
  referrerpolicy: referrer_policy,
372
372
  sizes: opt_string_sym,
373
373
  src: opt_string_sym,
@@ -479,5 +479,3 @@ module Quince
479
479
  }.freeze
480
480
  end
481
481
  end
482
-
483
- Undefined = Quince::Types::Undefined
@@ -12,6 +12,7 @@ module Quince
12
12
  rerender: nil,
13
13
  push_params_state: nil,
14
14
  handle_errors: true,
15
+ download: false,
15
16
  }.freeze
16
17
 
17
18
  def callback(method_name, **opts)
@@ -29,7 +30,7 @@ module Quince
29
30
  attr_reader(
30
31
  :receiver,
31
32
  :method_name,
32
- *ComponentHelpers::DEFAULT_CALLBACK_OPTIONS.keys,
33
+ *ComponentHelpers::DEFAULT_CALLBACK_OPTIONS.keys,
33
34
  )
34
35
  end
35
36
 
@@ -1,3 +1,4 @@
1
+ require "forwardable"
1
2
  require_relative "callback"
2
3
 
3
4
  module Quince
@@ -8,8 +9,7 @@ module Quince
8
9
  end
9
10
 
10
11
  def Props(**kw)
11
- self.const_set "Props", TypedStruct.new(
12
- { default: Quince::Types::Undefined },
12
+ self.const_set "Props", Quince::Config.props_struct_type.new(
13
13
  Quince::Component::PARENT_SELECTOR_ATTR => String,
14
14
  Quince::Component::SELF_SELECTOR => String,
15
15
  **kw,
@@ -17,10 +17,7 @@ module Quince
17
17
  end
18
18
 
19
19
  def State(**kw)
20
- st = kw.empty? ? nil : TypedStruct.new(
21
- { default: Quince::Types::Undefined },
22
- **kw,
23
- )
20
+ st = kw.empty? ? nil : Quince::Config.state_struct_type.new(**kw)
24
21
  self.const_set "State", st
25
22
  end
26
23
 
@@ -28,18 +25,20 @@ module Quince
28
25
  @exposed_actions ||= Set.new
29
26
  @exposed_actions.add action
30
27
  route = "/api/#{self.name}/#{action}"
31
- Quince.middleware.create_route_handler(
28
+ Quince::SinatraApp.create_route_handler(
32
29
  verb: method,
33
30
  route: route,
34
- ) do |params|
31
+ ) do |bind|
32
+ Thread.current[:request_binding] = bind
33
+ params = bind.receiver.params
34
+ Thread.current[:params] = params[:params] || {}
35
35
  instance = Quince::Serialiser.deserialise(CGI.unescapeHTML(params[:component]))
36
- Quince::Component.class_variable_set :@@params, params
37
- render_with = if params[:rerender]
38
- instance.instance_variable_set :@state_container, params[:stateContainer]
39
- params[:rerender][:method].to_sym
40
- else
41
- :render
42
- end
36
+ if params[:rerender]
37
+ instance.instance_variable_set :@state_container, params[:stateContainer]
38
+ render_with = params[:rerender][:method].to_sym
39
+ else
40
+ render_with = :render
41
+ end
43
42
  instance.instance_variable_set :@render_with, render_with
44
43
  instance.instance_variable_set :@callback_event, params[:event]
45
44
  if @exposed_actions.member? action
@@ -68,16 +67,18 @@ module Quince
68
67
 
69
68
  def initialize_props(const, id, **props)
70
69
  if const.const_defined?("Props")
71
- const::Props.new(PARENT_SELECTOR_ATTR => id, **props, SELF_SELECTOR => id)
70
+ const::Props.new(
71
+ PARENT_SELECTOR_ATTR => id,
72
+ **props,
73
+ SELF_SELECTOR => id,
74
+ )
72
75
  end
73
76
  end
74
77
  end
75
78
 
79
+ extend Forwardable
76
80
  include Callback::ComponentHelpers
77
81
 
78
- # set default
79
- @@params = {}
80
-
81
82
  attr_reader :props, :state, :children
82
83
 
83
84
  def render
@@ -91,9 +92,16 @@ module Quince
91
92
  end
92
93
 
93
94
  def params
94
- @@params
95
+ Thread.current[:params]
96
+ end
97
+
98
+ def request_context
99
+ Thread.current[:request_binding].receiver
95
100
  end
96
101
 
102
+ def_delegators :request_context,
103
+ :attachment, :request, :response, :redirect, :halt, :session, :cache_control, :send_file, :to, :status, :headers, :body
104
+
97
105
  private
98
106
 
99
107
  attr_reader :__id
@@ -0,0 +1,39 @@
1
+ module Quince
2
+ class Config
3
+ class << self
4
+ attr_reader :props_struct_type, :state_struct_type
5
+
6
+ def base
7
+ props typed: ENV["RACK_ENV"] != "production"
8
+ state typed: ENV["RACK_ENV"] != "production"
9
+ sinatra_config do
10
+ configure :development do
11
+ if Object.const_defined? "Sinatra::Reloader"
12
+ register Sinatra::Reloader
13
+ dont_reload __FILE__
14
+ also_reload $0
15
+ end
16
+ end
17
+ enable :logging
18
+ use Rack::JSONBodyParser
19
+ use Rack::Deflater
20
+ set :public_folder, File.join(File.dirname(File.expand_path($0)), "public")
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def props(typed:)
27
+ @props_struct_type = typed ? Quince::TypedStruct : Quince::Struct
28
+ end
29
+
30
+ def state(typed:)
31
+ @state_struct_type = typed ? Quince::TypedStruct : Quince::Struct
32
+ end
33
+
34
+ def sinatra_config(&block)
35
+ Quince::SinatraApp.instance_exec &block
36
+ end
37
+ end
38
+ end
39
+ end
@@ -55,7 +55,7 @@ module Quince
55
55
  return %Q{#{key}="#{CGI.escape_html(code)}" data-qu-#{key}-state="#{CGI.escapeHTML(internal)}"}
56
56
  when true
57
57
  return key
58
- when false, nil, Quince::Types::Undefined
58
+ when false, nil
59
59
  return nil
60
60
  else
61
61
  raise "prop type not yet implemented #{value}"
@@ -124,10 +124,3 @@ module Quince
124
124
 
125
125
  Quince::Component.include HtmlTagComponents
126
126
  end
127
-
128
- # tmp hack
129
- class TypedStruct < Struct
130
- def to_json(*args)
131
- to_h.to_json(*args)
132
- end
133
- end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ module Quince
3
+ class SinatraApp < Sinatra::Base
4
+ class << self
5
+ def create_route_handler(verb:, route:, &blck)
6
+ meth = case verb
7
+ when :POST, :post
8
+ :post
9
+ when :GET, :get
10
+ :get
11
+ else
12
+ raise "invalid verb"
13
+ end
14
+
15
+ public_send meth, route do
16
+ Quince.to_html(blck.call(binding))
17
+ ensure
18
+ Thread.current[:request_binding] = nil
19
+ Thread.current[:params] = nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def expose(component, at:)
27
+ Quince::SinatraApp.get(at) do
28
+ Thread.current[:request_binding] = binding
29
+ Thread.current[:params] = binding.receiver.params
30
+ comp = component.instance_of?(Class) ? component.create : component
31
+ comp.instance_variable_set :@render_with, :render
32
+ Quince.to_html(comp)
33
+ ensure
34
+ Thread.current[:request_binding] = nil
35
+ Thread.current[:params] = nil
36
+ end
37
+ end
38
+
39
+ at_exit do
40
+ if $!.nil? || ($!.is_a?(SystemExit) && $!.success?)
41
+ if Object.const_defined? "Sinatra::Reloader"
42
+ app_dir = Pathname(File.expand_path($0)).dirname.to_s
43
+ $LOADED_FEATURES.each do |f|
44
+ next unless f.start_with? app_dir
45
+
46
+ Quince::SinatraApp.also_reload f
47
+ end
48
+ end
49
+
50
+ Quince::SinatraApp.run!
51
+ end
52
+ end
@@ -1,34 +1,29 @@
1
1
  module Quince
2
2
  class << self
3
- attr_reader :middleware
4
- attr_accessor :underlying_app
5
-
6
- def optional_string
7
- @optional_string ||= Rbs("String?")
8
- end
9
-
10
- def middleware=(middleware)
11
- @middleware = middleware
12
- Object.define_method(:expose) do |component, at:|
13
- Quince.middleware.create_route_handler(
14
- verb: :GET,
15
- route: at,
16
- ) do |params|
17
- component = component.create if component.instance_of? Class
18
- Quince::Component.class_variable_set :@@params, params
19
- component.instance_variable_set :@render_with, :render
20
- component
21
- end
3
+ def define_constructor(const, constructor_name = nil)
4
+ if const.name
5
+ parts = const.name.split("::")
6
+ parent_namespace = Object.const_get(parts[0...-1].join("::")) if parts.length > 1
7
+ constructor_name ||= parts.last
22
8
  end
23
- Object.send :private, :expose
24
- end
9
+ constructor_name ||= const.to_s
25
10
 
26
- def define_constructor(const, constructor_name = const.to_s)
27
11
  HtmlTagComponents.instance_eval do
28
- define_method(constructor_name) do |*children, **props, &block_children|
29
- new_props = { **props, Quince::Component::PARENT_SELECTOR_ATTR => __id }
12
+ mthd = lambda do |*children, **props, &block_children|
13
+ new_props = {
14
+ **props,
15
+ Quince::Component::PARENT_SELECTOR_ATTR => __id,
16
+ }
30
17
  const.create(*children, **new_props, &block_children)
31
18
  end
19
+
20
+ if parent_namespace
21
+ parent_namespace.instance_exec do
22
+ define_method(constructor_name, &mthd)
23
+ end
24
+ else
25
+ define_method(constructor_name, &mthd)
26
+ end
32
27
  end
33
28
  end
34
29
 
@@ -64,6 +59,7 @@ module Quince
64
59
  var stateContainer = document.querySelector(`#{selector}`);
65
60
  stateContainer.dataset.quOn#{event}State = #{updated_state};
66
61
  JS
62
+ output = output.render if output.is_a?(Component)
67
63
 
68
64
  output += (output.is_a?(String) ? scr : [scr])
69
65
  end
@@ -76,3 +72,10 @@ module Quince
76
72
  end
77
73
  end
78
74
  end
75
+
76
+ ############## TODO #############
77
+ # I think you should be able to know when a component is the first to be called in a render method,
78
+ # so you should be able to attach some props to it behind the scenes. Then any consumers of this
79
+ # state just have to know the selector, so they can read from it before passing it to the back end.
80
+ #
81
+ # Also, the front end needs to be updated such that script tags from the back end are always read
@@ -0,0 +1,4 @@
1
+ module Quince
2
+ class TypedStruct < ::TypedStruct
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Quince
2
+ class Struct < ::Struct
3
+ def self.new(**attrs)
4
+ super *attrs.keys, keyword_init: true
5
+ end
6
+ end
7
+ end
data/lib/quince/types.rb CHANGED
@@ -1,13 +1,8 @@
1
1
  module Quince
2
2
  module Types
3
- class Base; end
4
-
5
3
  # precompiled helper types
6
- OptionalString = Rbs("String | Quince::Types::Undefined").freeze
7
- OptionalBoolean = Rbs("true | false | Quince::Types::Undefined").freeze
8
-
9
- # no functional value for now, other than constants
10
- Undefined = Class.new(Base).new.freeze
11
- Any = Class.new(Base).new.freeze
4
+ OptionalString = Rbs("String?").freeze
5
+ OptionalBoolean = Rbs("true | false | nil").freeze
6
+ Any = Rbs("untyped").freeze
12
7
  end
13
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quince
4
- VERSION = "0.4.2"
4
+ VERSION = "0.6.1"
5
5
  end
data/lib/quince.rb CHANGED
@@ -1,6 +1,34 @@
1
+ ENV["RACK_ENV"] ||= "development"
2
+
3
+ require "sinatra/base"
4
+ require "sinatra/reloader" if ENV["RACK_ENV"] == "development"
5
+ require "rack/contrib"
6
+
1
7
  require "securerandom"
2
8
  require "typed_struct"
3
9
  require "cgi"
10
+
11
+ require_relative "quince/config"
12
+ require_relative "quince/struct/typed"
13
+ require_relative "quince/struct/untyped"
14
+ require_relative "quince/sinatra"
15
+
16
+ module Quince
17
+ def self.load_config!
18
+ Quince::Config.base
19
+ app_dir = File.dirname(File.expand_path($0))
20
+ conf = Pathname(File.join(app_dir, "config.rb"))
21
+ require conf.to_s if conf.exist?
22
+
23
+ rack_env = ENV["RACK_ENV"]
24
+ if Quince::Config.respond_to? rack_env
25
+ Quince::Config.send rack_env
26
+ end
27
+ end
28
+ end
29
+
30
+ Quince.load_config!
31
+
4
32
  require_relative "quince/singleton_methods"
5
33
  require_relative "quince/component"
6
34
  require_relative "quince/html_tag_components"
data/quince.gemspec CHANGED
@@ -29,6 +29,9 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ["lib"]
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
+ spec.add_dependency "sinatra", "~> 2.1"
33
+ spec.add_dependency "sinatra-contrib", "~> 2.1"
34
+ spec.add_dependency "rack-contrib", "~> 2.3"
32
35
  spec.add_dependency "typed_struct", ">= 0.1.4"
33
36
  spec.add_dependency "oj", "~> 3.13"
34
37
 
data/scripts.js CHANGED
@@ -11,7 +11,12 @@ const Q = {
11
11
  }
12
12
  ).then(resp => {
13
13
  if (resp.status <= 299) {
14
- return resp.text()
14
+ const cd = resp.headers.get("Content-Disposition");
15
+ if (cd && cd.trim().startsWith("attachment")) {
16
+ return resp.blob();
17
+ } else {
18
+ return resp.text();
19
+ }
15
20
  } else if (resp.status >= 500) {
16
21
  throw Q.em["500"];
17
22
  } else {
@@ -21,45 +26,46 @@ const Q = {
21
26
 
22
27
  throw msg;
23
28
  }
24
- }).then(html => {
25
- const element = document.querySelector(selector);
26
- if (!element) {
27
- throw `element not found for ${selector}`;
28
- }
29
-
30
- switch (mode) {
31
- case "append_diff":
32
- const tmpElem = document.createElement(element.nodeName);
33
- tmpElem.innerHTML = html;
34
- const newNodes = Array.from(tmpElem.childNodes);
35
- const script = newNodes.pop();
36
- const existingChildren = element.childNodes;
37
- // This comparison doesn't currently work because of each node's unique id (data-quid).
38
- // maybe it would be possible to use regex replace to on the raw html, but it could also
39
- // be overkill
40
- // let c = 0;
41
- // for (; c < existingChildren.length; c++) {
42
- // if (existingChildren[c].isEqualNode(newNodes[c]))
43
- // continue;
44
- // else
45
- // break;
46
- // }
47
- // for the time being, we can just assume that we can just take the extra items
48
- let c = existingChildren.length;
49
- for (const node of newNodes.slice(c)) {
50
- element.appendChild(node);
29
+ }).then(data => {
30
+ switch (true) {
31
+ case data instanceof Blob:
32
+ const url = URL.createObjectURL(data);
33
+ const a = document.createElement('a');
34
+ a.href = url;
35
+ a.download = "download";
36
+ document.body.appendChild(a);
37
+ a.click();
38
+ a.remove();
39
+ break;
40
+ default: // html
41
+ const element = document.querySelector(selector);
42
+ if (!element) {
43
+ throw `element not found for ${selector}`;
51
44
  }
52
45
 
53
- const newScript = document.createElement("script");
54
- newScript.dataset.quid = script.dataset.quid;
55
- newScript.innerHTML = script.innerHTML;
56
- document.head.appendChild(newScript);
57
- break;
58
- case "replace":
59
- element.outerHTML = html;
60
- break;
61
- default:
62
- throw `mode ${mode} is not valid`;
46
+ switch (mode) {
47
+ case "append_diff":
48
+ const tmpElem = document.createElement(element.nodeName);
49
+ tmpElem.innerHTML = data;
50
+ const newNodes = Array.from(tmpElem.childNodes);
51
+ const script = newNodes.pop();
52
+ const existingChildren = element.childNodes;
53
+ let c = existingChildren.length;
54
+ for (const node of newNodes.slice(c)) {
55
+ element.appendChild(node);
56
+ }
57
+
58
+ const newScript = document.createElement("script");
59
+ newScript.dataset.quid = script.dataset.quid;
60
+ newScript.innerHTML = script.innerHTML;
61
+ document.head.appendChild(newScript);
62
+ break;
63
+ case "replace":
64
+ element.outerHTML = data;
65
+ break;
66
+ default:
67
+ throw `mode ${mode} is not valid`;
68
+ }
63
69
  }
64
70
  }).catch(err => {
65
71
  if (!handleErrors) throw err;
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quince
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Johansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-24 00:00:00.000000000 Z
11
+ date: 2021-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra-contrib
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-contrib
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
13
55
  - !ruby/object:Gem::Dependency
14
56
  name: typed_struct
15
57
  requirement: !ruby/object:Gem::Requirement
@@ -60,9 +102,13 @@ files:
60
102
  - lib/quince/callback.js.erb
61
103
  - lib/quince/callback.rb
62
104
  - lib/quince/component.rb
105
+ - lib/quince/config.rb
63
106
  - lib/quince/html_tag_components.rb
64
107
  - lib/quince/serialiser.rb
108
+ - lib/quince/sinatra.rb
65
109
  - lib/quince/singleton_methods.rb
110
+ - lib/quince/struct/typed.rb
111
+ - lib/quince/struct/untyped.rb
66
112
  - lib/quince/types.rb
67
113
  - lib/quince/version.rb
68
114
  - quince.gemspec