foliokit 1.4.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.
@@ -0,0 +1,92 @@
1
+ module Foliokit
2
+ class Issue
3
+ attr_reader :dir, :pages, :slides, :title, :description, :width, :height, :package
4
+
5
+ def initialize(file, dir, from: nil, to: nil)
6
+ @dir = dir
7
+
8
+ # Convert article to folio
9
+ @package = Foliokit::Package.new(file, dir, dir)
10
+ @title = package.manifest.title
11
+ @description = package.manifest.description
12
+ @width = package.manifest.device_width
13
+ @height = package.manifest.device_height
14
+ @from = from
15
+ @to = to
16
+ @pages = []
17
+
18
+ # Add Sub-Folio Content Stacks
19
+ if @package.manifest.article
20
+ convert_article!
21
+ else
22
+ if package.subfolios(package).any?
23
+ convert_folio!
24
+ else
25
+ convert_sub_folio!
26
+ end
27
+ end
28
+ end
29
+
30
+ def store_assets(assets_dir)
31
+ assets_dir.delete
32
+ dir.dir("assets").copy(assets_dir)
33
+ end
34
+
35
+ def export
36
+ document = Nokogiri::HTML("<html><head><title>#{title}</title><body></body></html>")
37
+ body = document.at("body")
38
+ pages.each do |page|
39
+ body << page.export(document)
40
+ end
41
+ document
42
+ end
43
+
44
+ def to_html
45
+ export.to_html
46
+ end
47
+
48
+ private
49
+
50
+ def convert_article!
51
+ manifest = package.subfolios(package).first.manifest
52
+ articles = manifest.content_stacks.first.articles[from..to]
53
+ if manifest.layout == "vertical"
54
+ @slides = [Slide.new(articles)]
55
+ else
56
+ @slides = articles.map do |article|
57
+ Slide.new([article])
58
+ end
59
+ end
60
+ end
61
+
62
+ def convert_folio!
63
+ subpackages = package.subfolios(package)[from..to]
64
+ @slides = subpackages.map do |subpackage|
65
+ slide = Slide.new
66
+ subpackage.manifest.content_stacks.each do |content_stack|
67
+ slide.articles.push(*content_stack.articles)
68
+ end
69
+ slide
70
+ end
71
+ end
72
+
73
+ def convert_sub_folio!
74
+ package.root_package = package
75
+ manifest = package.manifest
76
+ articles = manifest.content_stacks.first.articles[from..to]
77
+ @slides = [Slide.new(articles)]
78
+ end
79
+
80
+ def from
81
+ @from || 0
82
+ end
83
+
84
+ def to
85
+ if package.manifest.article
86
+ @to || (package.subfolios(package).first.manifest.content_stacks.first.articles.count - 1)
87
+ else
88
+ @to || (package.subfolios(package).count - 1)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,72 @@
1
+ require 'foliokit/modules/element'
2
+
3
+ module Foliokit
4
+ class Manifest
5
+ include Modules::Element
6
+
7
+ def self.element_base
8
+ element_reader :id, type: :s
9
+ element_reader :version, type: :s
10
+ element_reader :targetViewer, type: :s
11
+ element_reader :lastUpdated, type: :s
12
+ element_reader :date, type: :s
13
+ element_reader :bindingDirection, type: :s
14
+ element_reader :orientation, type: :s
15
+ element_reader :title, type: :s, selector: "metadata > magazineTitle", text: true
16
+ element_reader :description, type: :s, selector: "metadata > description", text: true
17
+ element_reader :article, type: :bool
18
+ end
19
+
20
+ def target_dimension
21
+ element.css("targetDimensions targetDimension").first
22
+ end
23
+
24
+ def wide_dimension
25
+ target_dimension["wideDimension"].to_i
26
+ end
27
+
28
+ def narrow_dimension
29
+ target_dimension["narrowDimension"].to_i
30
+ end
31
+
32
+ def device_height
33
+ (orientation == "landscape") ? narrow_dimension : wide_dimension
34
+ end
35
+
36
+ def device_width
37
+ (orientation == "landscape") ? wide_dimension : narrow_dimension
38
+ end
39
+
40
+ def referenced_ids
41
+ @_referenced_ids ||= begin
42
+ element.css("action[@target]").map { |action| hashify(action["target"]) } | element.css("action[@state]").map { |action| hashify(action["state"]) }
43
+ end
44
+ end
45
+
46
+ def valid_overlay_id?(id)
47
+ overlay_ids.include?(id)
48
+ end
49
+
50
+ def valid_referenced_id?(id)
51
+ referenced_ids.include?(id)
52
+ end
53
+
54
+ def overlay_ids
55
+ @_overlay_ids ||= begin
56
+ element.css("overlay[@id]").map { |overlay| hashify(overlay["id"]) }
57
+ end
58
+ end
59
+
60
+ def content_stack_ids
61
+ element.css("contentStacks contentStack").map { |s| s["id"] }
62
+ end
63
+
64
+ def content_stacks
65
+ element.css("contentStacks contentStack:not(:empty)").map { |element| ContentStack.new(element, package) }
66
+ end
67
+
68
+ def subfolios
69
+ element.css("contentStacks contentStack[@subfolio]:empty")
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,55 @@
1
+ module Foliokit
2
+ class ManifestArticle < Manifest
3
+ element_base
4
+ element_reader :layout, type: :s
5
+
6
+ def initialize(file, package)
7
+ article = Nokogiri::XML(file.read).css("article").first
8
+ # article["smoothScrolling"] = true
9
+ article.css("content page rendition").each do |rendition|
10
+ rendition.name = "assetRendition"
11
+ rendition["source"] = rendition.remove_attribute("href")
12
+ end
13
+ article.css("content page").each do |page|
14
+ page.name = "asset"
15
+ end
16
+
17
+ # build manifest
18
+ root_manifest = package.root_package.manifest
19
+ xml = Nokogiri::XML::Builder.new do |b|
20
+ b.folio(id: file.dir.name, version: root_manifest.version, targetViewer: root_manifest.target_viewer, lastUpdated: root_manifest.last_updated, date: root_manifest.date, bindingDirection: root_manifest.binding_direction, orientation: root_manifest.orientation, layout: article["layout"]) do
21
+ b.metadata do
22
+ b.description root_manifest.description
23
+ b.magazineTitle root_manifest.title
24
+ b.folioNumber root_manifest.title
25
+ end
26
+ b.targetDimensions do
27
+ b.targetDimension(wideDimension: root_manifest.wide_dimension, narrowDimension: root_manifest.narrow_dimension)
28
+ end
29
+ b.contentStacks do
30
+ b.contentStack(id: file.dir.name, version: article["version"], lastUpdated: article["lastUpdated"], smoothScrolling: article["smoothScrolling"], layout: article["layout"], alwaysDisplayOverlays: article["alwaysDisplayOverlays"], orientation: article["orientation"]) do
31
+ b.content do
32
+ b.regions do
33
+ b.region do
34
+ b.bounds do
35
+ b.rectangle(x: 0, y: 0, width: article[:width], height: article[:height])
36
+ end
37
+ end
38
+ end
39
+ b.assets do |assets_node|
40
+ article.css("content asset").each do |asset|
41
+ b.parent << asset
42
+ end
43
+ end
44
+ if file.dir.file("Overlays.xml").exists?
45
+ b.parent << Nokogiri::XML(file.dir.file("Overlays.xml").read).css("overlays").first
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ super(xml.doc.root, package)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ module Foliokit
2
+ class ManifestFolio < Manifest
3
+ element_base
4
+
5
+ def initialize(file, package)
6
+ super(Nokogiri::XML(file.read).root, package)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Foliokit
2
+ module Modules
3
+ module Core
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ module Foliokit
2
+ module Modules
3
+ module Element
4
+ extend ActiveSupport::Concern
5
+
6
+ included do |cls|
7
+ class << cls
8
+ attr_accessor :element_reader_attributes
9
+ end
10
+
11
+ def self.element_reader(name, options = {})
12
+ attr_accessor name.to_s.underscore
13
+ self.element_reader_attributes ||= {}
14
+ self.element_reader_attributes[name] = options
15
+ end
16
+
17
+ def hashify(value)
18
+ hashable = "#{package.id}/#{value}"
19
+ "id_#{Digest::MD5.hexdigest(hashable)}"
20
+ end
21
+
22
+ def self.element_collection(name, selector, klass = nil)
23
+ define_method :"#{name}" do
24
+ (element > selector).map do |el|
25
+ if klass and klass.class == Module
26
+ klass.factory(el, package)
27
+ elsif klass
28
+ klass.new(el, @package)
29
+ else
30
+ el
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.element_xpath(name, selector, klass)
37
+ define_method :"#{name}" do
38
+ el = element.xpath(selector).first
39
+ if klass.class == Module
40
+ klass.factory(el, package)
41
+ else
42
+ klass.new(el, @package)
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.element_xpath_collection(name, selector, klass)
48
+ define_method :"#{name}" do
49
+ element.xpath(selector).map do |el|
50
+ if klass.class == Module
51
+ klass.factory(el, package)
52
+ else
53
+ klass.new(el, @package)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ attr_reader :package
60
+
61
+ private
62
+
63
+ attr_reader :element
64
+ end
65
+
66
+ def initialize(element, package)
67
+ @element = element
68
+ @package = package
69
+ self.class.element_reader_attributes.each do |name, options|
70
+ key = name.to_s.underscore
71
+ value = nil
72
+ target = element
73
+ if options[:selector]
74
+ if options[:selector].respond_to?(:call)
75
+ target = (target > instance_exec(options, &(options[:selector]))).first
76
+ else
77
+ target = (target > options[:selector]).first
78
+ end
79
+ end
80
+ unless target.nil?
81
+ if options[:type].class == Class
82
+ value = options[:type].convert(target, name, options)
83
+ else
84
+ attribute_name = options[:attribute] || name
85
+ value = options[:text] ? target.text.strip : target[attribute_name]
86
+ unless value.nil?
87
+ value = value.send(:"to_#{options[:type]}") unless options[:type].nil?
88
+ value.gsub!(/\s/, "_") if options[:clean]
89
+ value = hashify(value) if options[:hash]
90
+ end
91
+ end
92
+ end
93
+ value = options[:default] if value.nil? and options[:default]
94
+ instance_variable_set("@#{key}", value)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,20 @@
1
+ module Foliokit
2
+ module Overlay
3
+ extend ActiveSupport::Autoload
4
+ autoload :OverlayBase
5
+ autoload :OverlayContainer
6
+ autoload :OverlayHyperlink
7
+ autoload :OverlayImage
8
+ autoload :OverlayButton
9
+ autoload :Overlay360
10
+ autoload :OverlayImagepan
11
+ autoload :OverlayAudio
12
+ autoload :OverlayVideo
13
+ autoload :OverlayWebview
14
+
15
+ def self.factory(el, package)
16
+ klass = "Foliokit::Overlay::Overlay#{el[:type].camelcase}".constantize
17
+ klass.new(el, package)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class Overlay360 < OverlayBase
4
+ element_base
5
+ element_reader :clockwise, type: "bool", selector: "data", attribute: "clockwise"
6
+ element_reader :loopCount, type: "i", selector: "data > loopCount", text: true
7
+ element_reader :autoStart, type: "bool", selector: "data > autoStart", text: true
8
+ element_reader :autoStartDelay, type: "f", selector: "data > autoStartDelay", text: true
9
+ element_reader :tapEnabled, type: "bool", selector: "data > tapEnabled", text: true
10
+ element_reader :swipeEnabled, type: "bool", selector: "data > swipeEnabled", text: true
11
+ element_reader :circularViewEnabled, type: "bool", selector: "data > circularViewEnabled", text: true
12
+ element_reader :autoPlayStopAtLast, type: "bool", selector: "data > autoPlayStopAtLast", text: true
13
+ element_reader :framesPerSecond, type: "f", selector: "data > framesPerSecond", text: true
14
+ element_collection :images, "data > overlayAsset"
15
+
16
+ def tagname
17
+ "div"
18
+ end
19
+
20
+ def export(document, options = {})
21
+ node = super(document, options)
22
+ node["clockwise"] = clockwise
23
+ node["auto-start"] = auto_start
24
+ node["auto-start-delay"] = auto_start_delay
25
+ node["tap-enabled"] = tap_enabled
26
+ node["swipe-enabled"] = swipe_enabled
27
+ node["circular-view-enabled"] = circular_view_enabled
28
+ node["auto-play-stop-at-last"] = auto_play_stop_at_last
29
+ node["frames-per-second"] = frames_per_second
30
+
31
+ # build container
32
+ container = Nokogiri::XML::Node.new("div", document)
33
+ container["class"] = "circlr-container"
34
+ if node["swipe-enabled"] == "true"
35
+ container["class"] += " prevent-swipe-horizontal prevent-swipe-vertical"
36
+ container["touchable"] = true
37
+ end
38
+ node << container
39
+
40
+ # build images
41
+ images.each do |image|
42
+ img = Nokogiri::XML::Node.new("img", document)
43
+ img["src"] = package.store_asset(image.text)
44
+ container << img
45
+ end
46
+ node
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class OverlayAudio < OverlayBase
4
+ element_base
5
+ element_reader :audio_url, selector: "data > audioUrl", text: true
6
+ element_reader :keep_alive, type: "bool", selector: "data > continuePlayingInBackground", text: true
7
+ element_reader :autoStart, type: "bool", selector: "data > autoStart", text: true
8
+ element_reader :autoStartDelay, type: "f", selector: "data > autoStartDelay", text: true
9
+
10
+ def tagname
11
+ "audio"
12
+ end
13
+
14
+ def export(document, options = {})
15
+ node = super(document, options)
16
+ if audio_url.present?
17
+ source = Nokogiri::XML::Node.new("source", document)
18
+ package.apply_asset(source, "src", audio_url)
19
+ source["type"] = "audio/mpeg"
20
+ node << source
21
+ end
22
+ node["auto-start"] = true if auto_start
23
+ node["keep-alive"] = true if keep_alive
24
+ node["data-ignore"] = true
25
+ node
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,108 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class OverlayBase
4
+ include Modules::Element
5
+ attr_reader :state, :section_index
6
+
7
+ def self.element_base
8
+ element_reader :id, hash: true
9
+ element_reader :type
10
+ element_reader :lastUpdated, type: :datetime
11
+ element_reader :bounds, type: Types::Bounds, selector: ->(options) { "#{package.manifest.orientation}Bounds > rectangle" }
12
+ element_reader :stateTransitionDuration, type: :f, selector: "data > stateTransitionDuration", text: true, default: 0.5
13
+ element_reader :swipe_event, attribute: "type", selector: "data > eventHandling > event"
14
+ element_reader :swipe_handler, attribute: "handler", selector: "data > eventHandling > event"
15
+ element_collection :states, "data > states > state", State
16
+ element_collection :events, "data > bindings > onevent", Event
17
+ element_collection :overlays, "data > children > overlay", Overlay
18
+ end
19
+
20
+ def tagname
21
+ "div"
22
+ end
23
+
24
+ def initialize(element, package)
25
+ super(element, package)
26
+
27
+ # calculate bounds
28
+ bounds_target = element.css("#{package.manifest.orientation}Bounds > rectangle")
29
+ bounds_target = element if bounds_target.empty?
30
+ bounds_target = bounds_target.first if bounds_target.kind_of?(Nokogiri::XML::NodeSet)
31
+ @bounds = Types::Bounds.convert(bounds_target, "bounds")
32
+
33
+ # set initial state
34
+ @state = states.first
35
+ end
36
+
37
+ def stateful?
38
+ !!state
39
+ end
40
+
41
+ def export(document, parent: nil, section_index: nil)
42
+ @section_index = section_index
43
+ node = Nokogiri::XML::Node.new(tagname.to_s, document)
44
+ # node["id"] = id if package.manifest.valid_referenced_id?(id)
45
+ node["id"] = id
46
+ node["class"] = "overlay-#{type}" unless tagname.include? "overlay"
47
+ style = {
48
+ width: "#{bounds.width}px",
49
+ height: "#{bounds.height}px",
50
+ left: "#{bounds.x}px",
51
+ top: "#{bounds.y}px"
52
+ }
53
+ if parent&.stateful?
54
+ node["name"] = id
55
+ if state_transition_duration != 0
56
+ style[:transition] = "opacity #{state_transition_duration}s"
57
+ elsif parent.state_transition_duration != 0
58
+ style[:transition] = "opacity #{parent.state_transition_duration}s"
59
+ else
60
+ style[:transition] = "opacity 0s"
61
+ end
62
+ end
63
+ node["style"] = style.map { |k, v| "#{k}:#{v}" }.join(";")
64
+
65
+ # stateful behaviour
66
+ node["active-state"] = state.id if state
67
+
68
+ # event / action behaviour
69
+ bindings = {}
70
+ unless swipe_event.nil?
71
+ if swipe_event == "verticalSwipe"
72
+ node["class"] += " prevent-swipe-vertical"
73
+ elsif swipe_event == "horizontalSwipe"
74
+ node["class"] += " prevent-swipe-horizontal"
75
+ else
76
+ node["class"] += " prevent-swipe-horizontal prevent-swipe-vertical"
77
+ end
78
+ node["touchable"] = true
79
+ if swipe_handler == "changeState"
80
+ bindings[swipe_event] = { selector: swipe_handler.downcase }
81
+ elsif swipe_handler == "scroll"
82
+ node["scroll"] = swipe_event.gsub("Swipe", "")
83
+ end
84
+ end
85
+ if events.any?
86
+ events.each do |event|
87
+ actions = event.all_actions
88
+ if actions.any?
89
+ bindings[event.name] = event.all_actions.map(&:to_hash)
90
+ end
91
+ end
92
+ end
93
+
94
+ if bindings.any?
95
+ node["bindings"] = JSON.dump(bindings)
96
+ if bindings["click"] or bindings["horizontalSwipe"] or bindings["verticalSwipe"]
97
+ node["touchable"] = true
98
+ end
99
+ end
100
+ overlays.each do |overlay|
101
+ child_node = overlay.export(document, parent: self, section_index: section_index)
102
+ node << child_node unless child_node.nil?
103
+ end
104
+ node
105
+ end
106
+ end
107
+ end
108
+ end