quince 0.4.0 → 0.5.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: '0039bd9ca9658cfebba4048cc84fdbb095d8f2e656930501528f420c4dfa3bba'
4
- data.tar.gz: a9720e7b11db54113fd56f1e09e43c980893297bbd5d3f46b1cdd0fc8ea2b61e
3
+ metadata.gz: b96b6d822903711930bf3baf921a25c7132761cbc339ff290ee1923aba16f4a6
4
+ data.tar.gz: 9ea1dd9174b8e4f7aa100a83fb29aef865b4426d0889cc41e31b1d13c83cacd6
5
5
  SHA512:
6
- metadata.gz: 6117b88cd23184b2072adae7b3b99ca868197119ffac9344477e6048519c735c7daa498067a4b96a92b60d68ce8fb5e0cc5a40529734c5c01c3b49e58317002d
7
- data.tar.gz: f7952f3eea1cee2ef4153cbe4be1a5c709d777130f7eaa3be3ec0f5fa728da3550b81713766fcea6003673d8a65dd8a92aaae4ee3b1fb599b773463a67c5f05d
6
+ metadata.gz: 59599fdd6fb5b40d606bcc2f70d636f0f214f10e14c424cd95d3b7c1dddef4341368d8569fff5ad5d5ebc77fcd0b865e274229d66b19665bb48ec54c62fca5f7
7
+ data.tar.gz: b6ecdd02de862f7659ac94a3c588687fd2b265e7ceb2ed3cae8893587e5d4e37454a7d9015398baf1e67c82cde6141ec32383093a7799db466c438b3bd52e241
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quince (0.4.0)
4
+ quince (0.5.1)
5
5
  oj (~> 3.13)
6
6
  typed_struct (>= 0.1.4)
7
7
 
@@ -9,7 +9,7 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  diff-lcs (1.4.4)
12
- oj (3.13.7)
12
+ oj (3.13.9)
13
13
  rake (13.0.6)
14
14
  rbs (1.6.2)
15
15
  rspec (3.10.0)
@@ -4,14 +4,14 @@ 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 = {
@@ -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,
@@ -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
@@ -17,8 +17,10 @@ params: Q.f(this),
17
17
  rerender: <%= rerender.to_json %>,
18
18
  <% end %>}),
19
19
  `<%= rerender&.dig(:selector)&.to_s || selector %>`,
20
- <% if mode = rerender&.dig(:mode) %>`<%= mode.to_s %>`<% end %>
20
+ `<%= (mode = rerender&.dig(:mode)) ? mode.to_s : "replace" %>`,
21
+ <%= value.handle_errors.to_json %>,
21
22
  );
23
+ <% unless push_params_state == "null" %>Q.ps(<%= push_params_state %>);<% end %>
22
24
  <% if value.debounce_ms&.positive? %>
23
25
  }, <%= value.debounce_ms %>); window[`<%= fn_name %>`](p)
24
26
  <% end %>
@@ -10,6 +10,8 @@ module Quince
10
10
  if: nil,
11
11
  debugger: false,
12
12
  rerender: nil,
13
+ push_params_state: nil,
14
+ handle_errors: true,
13
15
  }.freeze
14
16
 
15
17
  def callback(method_name, **opts)
@@ -9,7 +9,6 @@ module Quince
9
9
 
10
10
  def Props(**kw)
11
11
  self.const_set "Props", TypedStruct.new(
12
- { default: Quince::Types::Undefined },
13
12
  Quince::Component::PARENT_SELECTOR_ATTR => String,
14
13
  Quince::Component::SELF_SELECTOR => String,
15
14
  **kw,
@@ -17,10 +16,7 @@ module Quince
17
16
  end
18
17
 
19
18
  def State(**kw)
20
- st = kw.empty? ? nil : TypedStruct.new(
21
- { default: Quince::Types::Undefined },
22
- **kw,
23
- )
19
+ st = kw.empty? ? nil : TypedStruct.new(**kw)
24
20
  self.const_set "State", st
25
21
  end
26
22
 
@@ -33,13 +29,13 @@ module Quince
33
29
  route: route,
34
30
  ) do |params|
35
31
  instance = Quince::Serialiser.deserialise(CGI.unescapeHTML(params[:component]))
36
- Quince::Component.class_variable_set :@@params, params
32
+ Quince::Component.class_variable_set :@@params, params[:params]
37
33
  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
34
+ instance.instance_variable_set :@state_container, params[:stateContainer]
35
+ params[:rerender][:method].to_sym
36
+ else
37
+ :render
38
+ end
43
39
  instance.instance_variable_set :@render_with, render_with
44
40
  instance.instance_variable_set :@callback_event, params[:event]
45
41
  if @exposed_actions.member? action
@@ -68,7 +64,11 @@ module Quince
68
64
 
69
65
  def initialize_props(const, id, **props)
70
66
  if const.const_defined?("Props")
71
- const::Props.new(PARENT_SELECTOR_ATTR => id, **props, SELF_SELECTOR => id)
67
+ const::Props.new(
68
+ PARENT_SELECTOR_ATTR => id,
69
+ **props,
70
+ SELF_SELECTOR => id,
71
+ )
72
72
  end
73
73
  end
74
74
  end
@@ -50,11 +50,12 @@ module Quince
50
50
  fn_name = "_Q_#{key}_#{receiver.send(:__id)}"
51
51
  rerender = value.rerender
52
52
  state_container = html_self_selector
53
+ push_params_state = value.push_params_state.to_json
53
54
  code = CALLBACK_ERB_INSTANCE.result(binding)
54
55
  return %Q{#{key}="#{CGI.escape_html(code)}" data-qu-#{key}-state="#{CGI.escapeHTML(internal)}"}
55
56
  when true
56
57
  return key
57
- when false, nil, Quince::Types::Undefined
58
+ when false, nil
58
59
  return nil
59
60
  else
60
61
  raise "prop type not yet implemented #{value}"
@@ -91,14 +92,35 @@ module Quince
91
92
  contents
92
93
  }
93
94
  end
94
- end
95
95
 
96
- Quince::Component.include HtmlTagComponents
97
- end
96
+ ERROR_HANDLING_STYLES = <<~CSS.freeze
97
+ .quince-err-container {
98
+ position: absolute;
99
+ bottom: 32px;
100
+ width: 300px;
101
+ height: 50px;
102
+ background-color: white;
103
+ border-radius: 4px;
104
+ border: 1px solid #bbb;
105
+ z-index: 999;
106
+ box-shadow: 2px 2px 8px rgba(0,0,0,0.3);
107
+ padding: 0 24px;
108
+ left: calc(50vw - 50px);
109
+ }
98
110
 
99
- # tmp hack
100
- class TypedStruct < Struct
101
- def to_json(*args)
102
- to_h.to_json(*args)
111
+ .quince-err-msg {
112
+ font-size: 1.2em;
113
+ text-align: center;
114
+ margin: auto;
115
+ color: chocolate;
116
+ }
117
+ CSS
118
+ private_constant :ERROR_HANDLING_STYLES
119
+
120
+ def error_message_styles
121
+ style(ERROR_HANDLING_STYLES)
122
+ end
103
123
  end
124
+
125
+ Quince::Component.include HtmlTagComponents
104
126
  end
@@ -23,12 +23,30 @@ module Quince
23
23
  Object.send :private, :expose
24
24
  end
25
25
 
26
- def define_constructor(const, constructor_name = const.to_s)
26
+ def define_constructor(const, constructor_name = nil)
27
+ if const.name
28
+ parts = const.name.split("::")
29
+ parent_namespace = Object.const_get(parts[0...-1].join("::")) if parts.length > 1
30
+ constructor_name ||= parts.last
31
+ end
32
+ constructor_name ||= const.to_s
33
+
27
34
  HtmlTagComponents.instance_eval do
28
- define_method(constructor_name) do |*children, **props, &block_children|
29
- new_props = { **props, Quince::Component::PARENT_SELECTOR_ATTR => __id }
35
+ mthd = lambda do |*children, **props, &block_children|
36
+ new_props = {
37
+ **props,
38
+ Quince::Component::PARENT_SELECTOR_ATTR => __id,
39
+ }
30
40
  const.create(*children, **new_props, &block_children)
31
41
  end
42
+
43
+ if parent_namespace
44
+ parent_namespace.instance_exec do
45
+ define_method(constructor_name, &mthd)
46
+ end
47
+ else
48
+ define_method(constructor_name, &mthd)
49
+ end
32
50
  end
33
51
  end
34
52
 
@@ -64,6 +82,7 @@ module Quince
64
82
  var stateContainer = document.querySelector(`#{selector}`);
65
83
  stateContainer.dataset.quOn#{event}State = #{updated_state};
66
84
  JS
85
+ output = output.render if output.is_a?(Component)
67
86
 
68
87
  output += (output.is_a?(String) ? scr : [scr])
69
88
  end
@@ -76,3 +95,10 @@ module Quince
76
95
  end
77
96
  end
78
97
  end
98
+
99
+ ############## TODO #############
100
+ # I think you should be able to know when a component is the first to be called in a render method,
101
+ # so you should be able to attach some props to it behind the scenes. Then any consumers of this
102
+ # state just have to know the selector, so they can read from it before passing it to the back end.
103
+ #
104
+ # Also, the front end needs to be updated such that script tags from the back end are always read
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.0"
4
+ VERSION = "0.5.1"
5
5
  end
data/scripts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const Q = {
2
- c: (endpoint, payload, selector, mode = "replace") => {
2
+ c: (endpoint, payload, selector, mode, handleErrors) => {
3
3
  return fetch(
4
4
  endpoint,
5
5
  {
@@ -9,7 +9,19 @@ const Q = {
9
9
  },
10
10
  body: payload,
11
11
  }
12
- ).then(resp => resp.text()).then(html => {
12
+ ).then(resp => {
13
+ if (resp.status <= 299) {
14
+ return resp.text()
15
+ } else if (resp.status >= 500) {
16
+ throw Q.em["500"];
17
+ } else {
18
+ let msg = Q.em[`${resp.code}`];
19
+
20
+ if (!msg && resp.code >= 400) msg = Q.em.generic;
21
+
22
+ throw msg;
23
+ }
24
+ }).then(html => {
13
25
  const element = document.querySelector(selector);
14
26
  if (!element) {
15
27
  throw `element not found for ${selector}`;
@@ -49,6 +61,20 @@ const Q = {
49
61
  default:
50
62
  throw `mode ${mode} is not valid`;
51
63
  }
64
+ }).catch(err => {
65
+ if (!handleErrors) throw err;
66
+
67
+ let msg;
68
+ if (typeof err === "string") {
69
+ msg = err;
70
+ } else if (err.message) {
71
+ if (err.message.startsWith("NetworkError")) {
72
+ msg = Q.em.network;
73
+ } else {
74
+ msg = err.message;
75
+ }
76
+ } else msg = Q.em.generic;
77
+ Q.e(msg, 2500);
52
78
  })
53
79
  },
54
80
  f: (elem) => {
@@ -72,4 +98,36 @@ const Q = {
72
98
  });
73
99
  };
74
100
  },
101
+ ps: (stateObj) => {
102
+ const base = location.origin + location.pathname;
103
+ const url = new URL(base);
104
+ for (const p in stateObj) {
105
+ url.searchParams.append(p, stateObj[p]);
106
+ };
107
+ window.history.pushState({}, document.title, url);
108
+ },
109
+ e: (msg, durationMs) => {
110
+ const containerClassName = "quince-err-container";
111
+ document.querySelectorAll(`.${containerClassName}`).forEach(e => e.remove());
112
+ const container = document.createElement("div");
113
+ const strong = document.createElement("strong");
114
+ strong.innerText = msg;
115
+ container.className = containerClassName;
116
+ strong.className = "quince-err-msg";
117
+ container.appendChild(strong);
118
+ document.body.insertAdjacentElement("afterbegin", container);
119
+ setTimeout(() => container.remove(), durationMs);
120
+ },
121
+ em: {
122
+ 400: "Bad request",
123
+ 401: "Unauthorised",
124
+ 402: "Payment required",
125
+ 403: "Forbidden",
126
+ 404: "Not found",
127
+ 422: "Unprocessable entity",
128
+ 429: "Too many requests",
129
+ 500: "Internal server error",
130
+ generic: "An error occurred",
131
+ network: "Network error",
132
+ }
75
133
  };
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quince
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.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-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typed_struct