Module: Scarpe::Components::Calzini
- Extended by:
- Calzini
- Includes:
- Base64
- Defined in:
- lib/scarpe/components/calzini.rb,
lib/scarpe/components/calzini/misc.rb,
lib/scarpe/components/calzini/para.rb,
lib/scarpe/components/calzini/alert.rb,
lib/scarpe/components/calzini/slots.rb,
lib/scarpe/components/calzini/border.rb,
lib/scarpe/components/calzini/button.rb,
lib/scarpe/components/calzini/art_drawables.rb
Overview
The Calzini module expects to be included by a class defining the following methods:
* html_id - the HTML ID for the specific rendered DOM object
* handler_js_code(event_name) - the JS handler code for this DOM object and event name
* (optional) shoes_styles - the Shoes styles for this object, unless overridden in render()
Constant Summary collapse
- HTML =
Scarpe::Components::HTML
- SPACING_DIRECTIONS =
[:left, :right, :top, :bottom]
Instance Method Summary collapse
- #alert_element(props) ⇒ Object
- #arc_element(props, &block) ⇒ Object
- #border_element(props) ⇒ Object
- #button_element(props) ⇒ Object
- #check_element(props) ⇒ Object
- #contains_number?(str) ⇒ Boolean
- #contains_only_numbers?(string) ⇒ Boolean
- #degrees_to_radians(degrees) ⇒ Object
- #dimensions_length(value) ⇒ Object
- #documentroot_element(props, &block) ⇒ Object
- #drawable_style(props) ⇒ Object
- #edit_box_element(props) ⇒ Object
- #edit_line_element(props) ⇒ Object
-
#empty_page_element ⇒ String
Return HTML for an empty page element, to be filled with HTML renderings of the DOM tree.
- #first_color_of(*colors) ⇒ Object
- #flow_element(props, &block) ⇒ Object
- #image_element(props) ⇒ Object
- #line_element(props) ⇒ Object
- #list_box_element(props) ⇒ Object
- #oval_element(props, &block) ⇒ Object
- #para_element(props, &block) ⇒ Object
- #progress_element(props) ⇒ Object
- #radians_to_degrees(radians) ⇒ Object
- #radio_element(props) ⇒ Object
- #rect_element(props) ⇒ Object
-
#render(drawable_name, properties = shoes_styles, &block) ⇒ String
Render the Shoes drawable of type ‘drawable_name` with the given properties to HTML and return it.
-
#rgb_to_hex(color) ⇒ Object
Convert an [r, g, b, a] array to an HTML hex color code Arrays support alpha.
- #slot_element(props, &block) ⇒ Object
-
#spacing_styles_for_attr(attr, props, styles, with_options: true) ⇒ Object
We extract the appropriate margin and padding from the margin and padding properties.
- #stack_element(props, &block) ⇒ Object
- #star_element(props, &block) ⇒ Object
-
#text_drawable_element(prop_array) ⇒ Object
The text element is used to render the equivalent of Shoes cText, which includes em, strong, span, link and so on.
- #text_size(sz) ⇒ Object
- #video_element(props) ⇒ Object
Methods included from Base64
#encode_file_to_base64, #mime_type_for_filename, #valid_url?
Instance Method Details
#alert_element(props) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 |
# File 'lib/scarpe/components/calzini/alert.rb', line 4 def alert_element(props) event = props["event_name"] || "click" onclick = handler_js_code(event) HTML.render do |h| h.div(id: html_id, style: (props)) do h.div(style: alert_modal_style) do h.div(style: {}) { props["text"] } h.(style: {}, onclick: onclick) { "OK" } end end end end |
#arc_element(props, &block) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 |
# File 'lib/scarpe/components/calzini/art_drawables.rb', line 4 def arc_element(props, &block) dc = props["draw_context"] || {} rotate = dc["rotate"] HTML.render do |h| h.div(id: html_id, style: arc_style(props)) do h.svg(width: props["width"], height: props["height"]) do h.path(d: arc_path(props), transform: "rotate(#{rotate}, #{props["width"] / 2}, #{props["height"] / 2})") end block.call(h) if block_given? end end end |
#border_element(props) ⇒ Object
2 3 4 5 6 |
# File 'lib/scarpe/components/calzini/border.rb', line 2 def border_element(props) HTML.render do |h| h.div(id: html_id, style: style(props)) end end |
#button_element(props) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/scarpe/components/calzini/button.rb', line 4 def (props) HTML.render do |h| = { id: html_id, onclick: handler_js_code("click"), onmouseover: handler_js_code("hover"), style: (props), class: props["html_class"], title: props["tooltip"], }.compact h.(**) do props["text"] end end end |
#check_element(props) ⇒ Object
4 5 6 7 8 9 10 11 12 13 |
# File 'lib/scarpe/components/calzini/misc.rb', line 4 def check_element(props) HTML.render do |h| h.input type: :checkbox, id: html_id, onclick: handler_js_code("click"), value: props["text"], checked: props["checked"], style: drawable_style(props) end end |
#contains_number?(str) ⇒ Boolean
100 101 102 |
# File 'lib/scarpe/components/calzini/para.rb', line 100 def contains_number?(str) !!(str =~ /\d/) end |
#contains_only_numbers?(string) ⇒ Boolean
103 104 105 |
# File 'lib/scarpe/components/calzini/para.rb', line 103 def contains_only_numbers?(string) /^\d+\z/ =~ string end |
#degrees_to_radians(degrees) ⇒ Object
229 230 231 |
# File 'lib/scarpe/components/calzini.rb', line 229 def degrees_to_radians(degrees) degrees * Math::PI / 180 end |
#dimensions_length(value) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/scarpe/components/calzini.rb', line 97 def dimensions_length(value) case value when Integer if value < 0 "calc(100% - #{value.abs}px)" else "#{value}px" end when Float "#{value * 100}%" else value end end |
#documentroot_element(props, &block) ⇒ Object
28 29 30 |
# File 'lib/scarpe/components/calzini/slots.rb', line 28 def documentroot_element(props, &block) flow_element(props, &block) end |
#drawable_style(props) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/scarpe/components/calzini.rb', line 112 def drawable_style(props) styles = {} if props["hidden"] styles[:display] = "none" end # Do we need to set CSS positioning here, especially if displace is set? Position: relative maybe? # We need some Shoes3 screenshots and HTML-based tests here... if props["top"] || props["left"] styles[:position] = "absolute" end styles[:top] = dimensions_length(props["top"]) if props["top"] styles[:left] = dimensions_length(props["left"]) if props["left"] styles[:width] = dimensions_length(props["width"]) if props["width"] styles[:height] = dimensions_length(props["height"]) if props["height"] styles[:"margin-left"] = dimensions_length(props["margin_left"]) if props["margin_left"] styles[:"margin-right"] = dimensions_length(props["margin_right"]) if props["margin_right"] styles[:"margin-top"] = dimensions_length(props["margin_top"]) if props["margin_top"] styles[:"margin-bottom"] = dimensions_length(props["margin_bottom"]) if props["margin_bottom"] styles = spacing_styles_for_attr("padding", props, styles) styles end |
#edit_box_element(props) ⇒ Object
15 16 17 18 19 20 21 |
# File 'lib/scarpe/components/calzini/misc.rb', line 15 def edit_box_element(props) oninput = handler_js_code("change", "this.value") HTML.render do |h| h.textarea(id: html_id, oninput: oninput,onmouseover: handler_js_code("hover"), style: edit_box_style(props),title: props["tooltip"]) { props["text"] } end end |
#edit_line_element(props) ⇒ Object
23 24 25 26 27 28 29 |
# File 'lib/scarpe/components/calzini/misc.rb', line 23 def edit_line_element(props) oninput = handler_js_code("change", "this.value") HTML.render do |h| h.input(id: html_id, oninput: oninput, onmouseover: handler_js_code("hover"), value: props["text"], style: edit_line_style(props),title: props["tooltip"]) end end |
#empty_page_element ⇒ String
Return HTML for an empty page element, to be filled with HTML renderings of the DOM tree.
The wrapper-wvroot element is where Scarpe will fill in the DOM element.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/scarpe/components/calzini.rb', line 56 def empty_page_element <<~HTML <html> <head id='head-wvroot'> <style id='style-wvroot'> /** Style resets **/ body { font-family: arial, Helvetica, sans-serif; margin: 0; height: 100%; overflow: hidden; } p { margin: 0; } #wrapper-wvroot { height: 100%; width: 100%; } </style> </head> <body id='body-wvroot'> <div id='wrapper-wvroot'></div> </body> </html> HTML end |
#first_color_of(*colors) ⇒ Object
192 193 194 195 196 |
# File 'lib/scarpe/components/calzini.rb', line 192 def first_color_of(*colors) colors.compact! colors.select! { |c| c != "" } rgb_to_hex(colors[0]) end |
#flow_element(props, &block) ⇒ Object
12 13 14 15 16 17 18 |
# File 'lib/scarpe/components/calzini/slots.rb', line 12 def flow_element(props, &block) HTML.render do |h| h.div((props["html_attributes"] || {}).merge(id: html_id, style: flow_style(props))) do h.div(style: { height: "100%", width: "100%", position: "relative" }, &block) end end end |
#image_element(props) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/scarpe/components/calzini/misc.rb', line 31 def image_element(props) style = drawable_style(props) if props["click"] HTML.render do |h| h.a(id: html_id, href: props["click"]) { h.img(id: html_id, src: props["url"], style:) } end else HTML.render do |h| h.img(id: html_id, src: props["url"], style:) end end end |
#line_element(props) ⇒ Object
38 39 40 41 42 43 44 45 46 |
# File 'lib/scarpe/components/calzini/art_drawables.rb', line 38 def line_element(props) HTML.render do |h| h.div(id: html_id, style: line_div_style(props)) do h.svg(width: props["x2"], height: props["y2"]) do h.line(x1: props["left"], y1: props["top"], x2: props["x2"], y2: props["y2"], style: line_svg_style(props)) end end end end |
#list_box_element(props) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/scarpe/components/calzini/misc.rb', line 45 def list_box_element(props) onchange = handler_js_code("change", "this.options[this.selectedIndex].value") # Is this useful at all? Is it overridden below completely? option_attrs = { value: nil, selected: false } HTML.render do |h| h.select(id: html_id, onchange:, style: list_box_style(props)) do (props["items"] || []).each do |item| option_attrs = { value: item, } if item == props["choose"] option_attrs[:selected] = "true" end h.option(**option_attrs) do item end end end end end |
#oval_element(props, &block) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/scarpe/components/calzini/art_drawables.rb', line 62 def oval_element(props, &block) dc = props["draw_context"] || {} fill = first_color_of(props["fill"], dc["fill"], "black") stroke = first_color_of(props["stroke"], dc["stroke"], "black") strokewidth = props["strokewidth"] || dc["strokewidth"] || "2" radius = props["radius"] width = radius * 2 height = props["height"] || radius * 2 # If there's a height, it's an oval, if not, circle center = props["center"] || false HTML.render do |h| h.div(id: html_id, style: oval_style(props)) do h.svg(width: width, height: height, style: "fill:#{fill};") do h.ellipse( cx: center ? radius : 0, cy: center ? height / 2 : 0, rx: width ? width / 2 : radius, ry: height ? height / 2 : radius, style: "stroke:#{stroke};stroke-width:#{strokewidth};", ) end block.call(h) if block_given? end end end |
#para_element(props, &block) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/scarpe/components/calzini/para.rb', line 4 def para_element(props, &block) # Align requires an extra wrapping div. # Stacking strikethrough with underline requires multiple elements. # We handle this by making strikethrough part of the main element, # but using an extra wrapping element for underline. tag = props["tag"] || "p" para_styles, extra_styles = para_style(props) HTML.render do |h| if extra_styles.empty? h.send(tag, id: html_id, style: para_styles, &block) else h.div(id: html_id, style: extra_styles.merge(width: "100%")) do h.send(tag, style: para_styles, &block) end end end end |
#progress_element(props) ⇒ Object
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/scarpe/components/calzini/misc.rb', line 93 def progress_element(props) HTML.render do |h| h.progress( id: html_id, style: drawable_style(props), role: "progressbar", "aria-valuenow": props["fraction"], "aria-valuemin": 0.0, "aria-valuemax": 1.0, max: 1, value: props["fraction"], ) end end |
#radians_to_degrees(radians) ⇒ Object
233 234 235 |
# File 'lib/scarpe/components/calzini.rb', line 233 def radians_to_degrees(radians) radians * (180.0 / Math::PI) end |
#radio_element(props) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/scarpe/components/calzini/misc.rb', line 68 def radio_element(props) # This is wrong - need to default to the parent slot -- maybe its linkable ID? group_name = props["group"] || "no_group" HTML.render do |h| h.input( type: :radio, id: html_id, onclick: handler_js_code("click"), name: group_name, value: props["text"], checked: props["checked"], style: drawable_style(props), ) end end |
#rect_element(props) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/scarpe/components/calzini/art_drawables.rb', line 17 def rect_element(props) dc = props["draw_context"] || {} rotate = dc["rotate"] HTML.render do |h| h.div(id: html_id, style: drawable_style(props)) do width = props["width"].to_i height = props["height"].to_i if props["curve"] width += 2 * props["curve"].to_i height += 2 * props["curve"].to_i end h.svg(width:, height:) do attrs = { x: props["left"], y: props["top"], width: props["width"], height: props["height"], style: rect_svg_style(props) } attrs[:rx] = props["curve"] if props["curve"] h.rect(**attrs, transform: "rotate(#{rotate} #{width / 2} #{height / 2})") end end end end |
#render(drawable_name, properties = shoes_styles, &block) ⇒ String
Render the Shoes drawable of type ‘drawable_name` with the given properties to HTML and return it. If the drawable type takes a block (e.g. Stack or Flow) then the block will be properly rendered.
45 46 47 |
# File 'lib/scarpe/components/calzini.rb', line 45 def render(drawable_name, properties = shoes_styles, &block) send("#{drawable_name}_element", properties, &block) end |
#rgb_to_hex(color) ⇒ Object
Convert an [r, g, b, a] array to an HTML hex color code Arrays support alpha. HTML hex does not. So premultiply.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/scarpe/components/calzini.rb', line 200 def rgb_to_hex(color) return nil if color.nil? return "#000000" if color == "" # TODO: need to figure out if it's a color name like "aquamarine" # or a hex code or an image file to use as a pattern or what. return color if color.is_a?(String) r, g, b, a = *color if r.is_a?(Float) a ||= 1.0 r_float = r * a g_float = g * a b_float = b * a else a ||= 255 a_float = (a / 255.0) r_float = (r.to_f / 255.0) * a_float g_float = (g.to_f / 255.0) * a_float b_float = (b.to_f / 255.0) * a_float end r_int = (r_float * 255.0).to_i.clamp(0, 255) g_int = (g_float * 255.0).to_i.clamp(0, 255) b_int = (b_float * 255.0).to_i.clamp(0, 255) "#%0.2X%0.2X%0.2X" % [r_int, g_int, b_int] end |
#slot_element(props, &block) ⇒ Object
4 5 6 7 8 9 10 |
# File 'lib/scarpe/components/calzini/slots.rb', line 4 def slot_element(props, &block) HTML.render do |h| h.div((props["html_attributes"] || {}).merge(id: html_id, style: slot_style(props))) do h.div(style: { height: "100%", width: "100%" }, &block) end end end |
#spacing_styles_for_attr(attr, props, styles, with_options: true) ⇒ Object
We extract the appropriate margin and padding from the margin and padding properties. If there are no margin or padding properties, we fall back to props margin or padding, if it exists.
Margin or padding (in either props or props) can be a Hash with directions as keys, or an Array of left/right/top/bottom, or a constant, which means all four are that constant. You can also specify a “margin” plus “margin-top” which is constant but margin-top is overridden, or similar.
If any margin or padding property exists in props then we don’t check props.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/scarpe/components/calzini.rb', line 155 def spacing_styles_for_attr(attr, props, styles, with_options: true) spacing_styles = {} case props[attr] when Hash props[attr].each do |dir, value| spacing_styles[:"#{attr}-#{dir}"] = dimensions_length value end when Array SPACING_DIRECTIONS.zip(props[attr]).to_h.compact.each do |dir, value| spacing_styles[:"#{attr}-#{dir}"] = dimensions_length(value) end when String, Numeric spacing_styles[attr.to_sym] = dimensions_length(props[attr]) end SPACING_DIRECTIONS.each do |dir| if props["#{attr}_#{dir}"] spacing_styles[:"#{attr}-#{dir}"] = dimensions_length props["#{attr}_#{dir}"] end end unless spacing_styles.empty? return styles.merge(spacing_styles) end # We should see if there are spacing properties in props["options"], # unless we're currently doing that. if && props["options"] spacing_styles = spacing_styles_for_attr(attr, props["options"], {}, with_options: false) styles.merge spacing_styles else # No "options" or we already checked it? Return the styles we were given. styles end end |
#stack_element(props, &block) ⇒ Object
20 21 22 23 24 25 26 |
# File 'lib/scarpe/components/calzini/slots.rb', line 20 def stack_element(props, &block) HTML.render do |h| h.div((props["html_attributes"] || {}).merge(id: html_id, style: stack_style(props))) do h.div(style: { height: "100%", width: "100%", position: "relative" }, &block) end end end |
#star_element(props, &block) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/scarpe/components/calzini/art_drawables.rb', line 48 def star_element(props, &block) dc = props["draw_context"] || {} fill = first_color_of(props["fill"], dc["fill"], "black") stroke = first_color_of(props["stroke"], dc["stroke"], "black") HTML.render do |h| h.div(id: html_id, style: star_style(props)) do h.svg(width: props["outer"], height: props["outer"], style: "fill:#{fill}") do h.polygon(points: star_points(props), style: "stroke:#{stroke};stroke-width:2") end block.call(h) if block_given? end end end |
#text_drawable_element(prop_array) ⇒ Object
The text element is used to render the equivalent of Shoes cText, which includes em, strong, span, link and so on. We use a “content” tag for it which alternates plaintext with a hash of properties.
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/scarpe/components/calzini/para.rb', line 181 def text_drawable_element(prop_array) out = String.new # Need unfrozen string # Each item should be a String or a property Hash # :items, :html_id, :tag, :props prop_array.each do |item| if item.is_a?(String) out << item.gsub("\n", "<br/>") else s, extra = text_drawable_style(item[:props]) out << HTML.render do |h| if extra.empty? h.send( item[:tag] || "span", class: "id_#{item[:html_id]}", style: s, **text_drawable_attrs(item[:props]) ) do text_drawable_element(item[:items]) end else h.span(class: "id_#{item[:html_id]}", style: extra) do h.send( item[:tag] || "span", class: "id_#{item[:html_id]}", style: s, **text_drawable_attrs(item[:props]) ) do text_drawable_element(item[:items]) end end end end end end out end |
#text_size(sz) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/scarpe/components/calzini.rb', line 84 def text_size(sz) case sz when Numeric sz when Symbol SIZES[sz] when String SIZES[sz.to_sym] || sz.to_i else raise "Unexpected text size object: #{sz.inspect}" end end |