scheduled_resource 0.0.1

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 318a50d7695cf7ce846a78ccb22a41ef8aed0451
4
+ data.tar.gz: 4efe3c0200deefd46219b4fe1096478922493fbb
5
+ SHA512:
6
+ metadata.gz: 723748c0c600788fc3283f335a208db385a4343659d2d3a1e04a7fcc8bddf268b53c9ef5df30f19dc02bfb39161831a28c8feaa6bffb4f8ed2f2844833ae0c34
7
+ data.tar.gz: c08da4242b8d12d4d0e56f9fb2c7c7c82e021e381b73b9c6298c9d93d3660d2da8792b3239d257d0b7e46268b08ed8555f78b80db5b9854532a201df24886be0
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scheduled_resource.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 emeyekayee (Mike Cannon)
2
+
3
+ The MIT License (MIT)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,136 @@
1
+ #ScheduledResource
2
+
3
+ This gem is for displaying how things are used
4
+ over time -- a schedule for a set of resources. It
5
+ provides a way to describe what is being displayed,
6
+ along with utilities and protocols to connect them:
7
+
8
+ - Configuration (specification and management),
9
+ - Query interfaces (a REST-like API and internal protocols to query the models), and
10
+ - A basic Rails controller implementation.
11
+
12
+ So we have a way to configure the schedule, internal
13
+ methods to generate the data, and a way to retrieve
14
+ data from the client. But the gem is largely
15
+ view-framework agnostic. We could use a variety of
16
+ client-side packages or even more traditional Rails
17
+ view templates to generate HTML.
18
+
19
+ In any case, to get a the most from a display like this
20
+ we need some client-side code. The gem includes
21
+ client-side modules to:
22
+
23
+ - Manage <b>time and display geometries</b> with "infinite" scroll along the time axis.
24
+ - <b>Format display cells</b> in ways specific to the resource models.
25
+ - <b>Update text justification</b> as the display is scrolled horizontally.
26
+
27
+
28
+ ## Configuration Management
29
+
30
+ A **scheduled resource** is something that can be
31
+ used for one thing at a time. Say "Rocky & Bullwinkle"
32
+ is on channel 3 from 10am to 11am on Saturday. Then
33
+ 'channel 3' is the <u>resource</u> and that showing of
34
+ the is a <u>resource-use block</u>. Resources and
35
+ use-blocks are typically Rails models. Each resource
36
+ and its use-blocks get one row in the display. That
37
+ row has a label to the left with some timespan visible
38
+ on the rest of the row.
39
+
40
+ The <b>ScheduledResource</b> class manages resource and
41
+ use-block class names, id's and labels for a schedule.
42
+ A ScheduledResource instance ties together:
43
+
44
+ 1. A resource class (eg TvStation),
45
+ 2. An id (a channel number in this example), and
46
+ 3. Strings and other assets that will go into the DOM.
47
+
48
+ The id is used to
49
+ - select a resource <em>instance</em> and
50
+ - select instances of the <em>resource use block</em> class (eg Program instances).
51
+
52
+ The id <em>could</em> be a database id but more
53
+ often is something a little more suited to human use
54
+ in the configuration. In any case it is used by model
55
+ class method
56
+ <tt>(resource_use_block_class).get_all_blocks()</tt>
57
+ to select the right use-blocks for the resource.
58
+ A resource class name and id are are joined with
59
+ a '_' to form a tag that also serves as an id for the DOM.
60
+
61
+ Something else you would expect see in a schedule
62
+ would be headers and labels -- perhaps one row with
63
+ the date and another row with the hour. Headers and
64
+ labels also fit the model of resources and use_blocks.
65
+ Basic timezone-aware classes (ZTime*) for those are
66
+ included in this gem.
67
+
68
+
69
+ ### Configuration File
70
+
71
+ The schedule configuration comes from
72
+ <tt>config/resource_schedule.yml</tt> which has
73
+ three top-level sections:
74
+
75
+ - ResourceKinds: A hash where the key is a Resource and the value is a UseBlock. (Both are class names),
76
+ - Resources: A list where each item is a Resource Class followed by one or more resource ids, and
77
+ - visibleTime: The visible timespan of the schedule in seconds.
78
+
79
+ The example file <tt>config/resource_schedule.yml</tt>
80
+ (installed when you run <tt>schedulize</tt>) should be
81
+ enough to display a two-row schedule with just the date
82
+ above and the hour below. Of course you can monkey-patch
83
+ or subclass these classes for your own needs.
84
+
85
+
86
+ ### The schedule API
87
+
88
+ The endpoint uses parameters <tt>t1</tt> and
89
+ <tt>t2</tt> to specify a time interval for the request.
90
+ A third parameter <tt>inc</tt> allows an initial time
91
+ window to be expanded without repeating blocks that
92
+ span those boundaries. The time parameters _plus the
93
+ configured resources_ define the data to be returned.
94
+
95
+
96
+ ### More About Configuration Management
97
+
98
+ Once the configuration yaml is loaded that data is
99
+ maintained in the session structure. Of course having
100
+ a single configuration file limits the application's
101
+ usefulness. A more general approach would be to
102
+ have a user model with login and configuration would
103
+ be associated with the user.
104
+
105
+
106
+
107
+
108
+
109
+
110
+ ## Installation
111
+
112
+ Add this line to your application's Gemfile:
113
+
114
+ ```ruby
115
+ gem 'scheduled_resource'
116
+ ```
117
+
118
+ And then execute:
119
+
120
+ $ bundle
121
+
122
+ Or install it yourself as:
123
+
124
+ $ gem install scheduled_resource
125
+
126
+ ## Usage
127
+
128
+ To Do: Write usage instructions here
129
+
130
+ ## Contributing
131
+
132
+ 1. Fork it ( https://github.com/emeyekayee/scheduled_resource/fork )
133
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
134
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
135
+ 4. Push to the branch (`git push origin my-new-feature`)
136
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file is based heavily on Capistrano's `capify` command
4
+
5
+ require 'optparse'
6
+ require 'fileutils'
7
+
8
+ def place_file( base, file, content )
9
+ file = File.join(base, file)
10
+ if File.exists?(file)
11
+ warn "[skip] `#{file}' already exists"
12
+ elsif File.exists?(file.downcase)
13
+ warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
14
+ elsif !File.exists?(File.dirname(file))
15
+ warn "[skip] directory `#{File.dirname(file)}' does not exist"
16
+ else
17
+ puts "[add] writing `#{file}'"
18
+ File.open(file, "w") { |f| f.write(content) }
19
+ end
20
+ end
21
+
22
+ def js_files_to_copy
23
+ dir = File.expand_path('../../lib/assets/javascripts' , __FILE__)
24
+ Dir[ dir + "/*" ]
25
+ end
26
+
27
+ def css_files_to_copy
28
+ dir = File.expand_path('../../lib/assets/stylesheets' , __FILE__)
29
+ Dir[ dir + "/*" ]
30
+ end
31
+
32
+ def to_js_dir(base) File.join base, 'vendor/assets/javascripts' end
33
+ def to_css_dir(base) File.join base, 'vendor/assets/stylesheets' end
34
+ def js_manifest(base) File.join base, 'app/assets/javascripts/application.js' end
35
+ def css_manifest(base) File.join base, 'app/assets/stylesheets/application.css' end
36
+
37
+ def js_require_content
38
+ <<EOF
39
+ //= require use_block
40
+ //= require time_pix
41
+ //= require justify_tweaks
42
+ EOF
43
+ end
44
+
45
+ def insert_js_requires( file )
46
+ manifest_lines = IO.readlines( file )
47
+ ix = manifest_lines.rindex{|l| l =~ %r[^//= require.+(jquery|angular)] }
48
+
49
+ manifest_lines[ix] << js_require_content
50
+ puts "[modify] writing `#{file}'"
51
+ File.open(file, "w") { |f| f.write( manifest_lines.join ) }
52
+ rescue Exception => e
53
+ puts "Failed to edit file #{file};\n#{e}; continuing..."
54
+ puts "Be sure these js dependencies are handled:\n#{js_require_content}\n\n"
55
+ end
56
+
57
+ def css_require_content
58
+ <<EOF
59
+ *= require scheduled_resource
60
+ EOF
61
+ end
62
+
63
+ def insert_css_requires( file )
64
+ manifest_lines = IO.readlines( file )
65
+ ix = manifest_lines.index{|l| l =~ %r[^ *\*= require] }
66
+
67
+ manifest_lines[ix] = css_require_content + manifest_lines[ix]
68
+ puts "[modify] writing `#{file}'"
69
+ File.open(file, "w") { |f| f.write( manifest_lines.join ) }
70
+ rescue Exception => e
71
+ puts "Failed to edit file #{file};\n#{e}; continuing..."
72
+ puts "Be sure these css dependencies are handled:\n#{css_require_content}\n\n"
73
+ end
74
+
75
+
76
+ PROGRAM = File.basename($0)
77
+
78
+ OptionParser.new do |opts|
79
+ opts.banner = "Usage: #{PROGRAM} [path]"
80
+
81
+ begin
82
+ opts.parse!(ARGV)
83
+ rescue OptionParser::ParseError => e
84
+ warn e.message
85
+ puts opts
86
+ exit 1
87
+ end
88
+ end
89
+
90
+ unless ARGV.empty?
91
+ if !File.exists?(ARGV.first)
92
+ abort "`#{ARGV.first}' does not exist."
93
+ elsif !File.directory?(ARGV.first)
94
+ abort "`#{ARGV.first}' is not a directory."
95
+ elsif ARGV.length > 1
96
+ abort "Too many arguments; please specify only the directory to wheneverize."
97
+ end
98
+ end
99
+
100
+ base = ARGV.empty? ? '.' : ARGV.shift
101
+
102
+ FileUtils.cp js_files_to_copy, to_js_dir(base)
103
+ FileUtils.cd( to_js_dir(base) ) do
104
+ FileUtils.cp "blank.jpg", "Day.jpg"
105
+ FileUtils.cp "blank.jpg", "Hour.jpg"
106
+ end
107
+
108
+ FileUtils.cp css_files_to_copy, to_css_dir(base)
109
+
110
+ insert_js_requires js_manifest(base)
111
+ insert_css_requires css_manifest(base)
112
+
113
+
114
+ config_content = %q{
115
+ # Configuration file for ScheduledResource schedule widget.
116
+ #
117
+
118
+ # Classes that represent a resource and resource-use pair.
119
+ ResourceKinds:
120
+ # Resource: Resource Use
121
+ ZTimeHeaderDay: ZTimeLabelDay
122
+ ZTimeHeaderHour: ZTimeLabelHour
123
+
124
+ # Other examples:
125
+ # ConferenceRoom: Meeting
126
+ # TvStation: ProgramEvent
127
+
128
+ # The schedule display sizes itself to the page width. What span
129
+ # of time should this be in seconds? (Value string is eval'd)
130
+ visibleTime: 3.hours
131
+
132
+ # A row in the display is specified by a pair: (Resource_class, Resource_id)
133
+ # The Resource_id is given as a string to be interpreted by the specific
134
+ # classes. For the included ZTime* classes the Resource_id (rid) indicates
135
+ # the timezone, [-8 .. -5] represent Pacific .. Eastern US timezones.
136
+ Resources:
137
+ - ZTimeHeaderDay -8
138
+ - ZTimeHeaderHour -8
139
+ - ZTimeHeaderDay -5
140
+ - ZTimeHeaderHour -5
141
+ }
142
+
143
+ place_file( base, 'config/resource_schedule.yml', config_content)
144
+
145
+
146
+
147
+ controller_content = <<-CONTROLLER
148
+ # Needed this here to get the ZTime* classes in time for session.
149
+ require 'scheduled_resource'
150
+
151
+ class ScheduleController < ApplicationController
152
+ include ScheduledResource::Helper
153
+
154
+ before_action :ensure_schedule_config
155
+
156
+
157
+ # Returns angularjs page (template) which in turn fetches data.
158
+ def index
159
+ end
160
+
161
+
162
+ # Json data.
163
+ def schedule
164
+ get_data_for_time_span
165
+
166
+ render json: @blockss
167
+ end
168
+
169
+ end
170
+ CONTROLLER
171
+
172
+ place_file( base, 'app/controllers/schedule_controller.rb', controller_content)
173
+
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+
3
+ // ============================================================================
4
+ // Hi-lock: ((" ?[T]o ?Do:.*" (0 'accent10 t)))
5
+ // Hi-lock: (("\\(^\\|\\W\\)\\*\\(\\w.*\\w\\)\\*\\(\\W\\|$\\)" (2 'accent3 t)))
6
+ // Hi-lock: end
7
+ // ============================================================================
8
+
9
+ // The idea here is to have *smooth* horizontal scrolling for a mobile device,
10
+ // track-pad or touch-sensitive mouse like Apple's. It makes navigating a
11
+ // time-window feel much more natural.
12
+ //
13
+ // If you have text within the scrolled content you may want to re-justify that
14
+ // text for readability as it scrolls left or right.
15
+ //
16
+ // This is one approach that avoids the jerkiness associated with a normal
17
+ // scroll event-listener. The idea is just to wait for the scroll position to
18
+ // stabilize, recalculate the visible parts of each container, then animate
19
+ // changes to the '.text_locator' with a little randomness thrown in.
20
+
21
+ // ToDo: Convert this to an object w/ methods, for better encapsulation.
22
+
23
+ // Sole exported function:
24
+ function filter_justify_tweaks(sc) {
25
+ var scrollLeft = sc.scrollLeft()
26
+
27
+ // Don't repeat the calculation unnecessarily....
28
+ if ( filter_justify_tweaks.old_srcoll == scrollLeft ) return
29
+ filter_justify_tweaks.old_srcoll = scrollLeft
30
+
31
+ do_justify_tweaks( sc, scrollLeft )
32
+ }
33
+
34
+
35
+ function do_justify_tweaks (sc, scrollLeft) {
36
+ $('.ZTimeHeaderDayrow .timespan').each( function() { // Centered
37
+ justify( scrollLeft, $(this).children() )
38
+ });
39
+
40
+ $('.Stationrow .timespan').each( function() { // Left-aligned
41
+ justify_left( scrollLeft, $(this).children() )
42
+ });
43
+
44
+ }
45
+
46
+
47
+ function may_straddle (scrollLeft, scrollRight, blockdivs) {
48
+ var divs = [], bdiv, cd;
49
+ var len = blockdivs.length;
50
+
51
+ for (var i = 0;
52
+ (i<len) && (cd = common_data(blockdivs[i])) && cd.bdiv_right < scrollLeft;
53
+ i++) {}
54
+
55
+ for (;
56
+ (i<len) && (bdiv= blockdivs[i]) && parseInt(bdiv.style.left) < scrollRight;
57
+ i++) {
58
+ divs.push( bdiv )
59
+ }
60
+ return divs
61
+ }
62
+
63
+ // function sort_em (divs) {
64
+ // divs.sort( function(a, b) {
65
+ // return parseInt(a.style.left) - parseInt(b.style.left)
66
+ // })
67
+ // }
68
+
69
+
70
+ //////////////////////////////////////////////////////////////////////////////
71
+ // Re-align left-aligned block content (typ. Channelrows)
72
+
73
+ function justify_left (scrollLeft, blockdivs) {
74
+ // sort_em( blockdivs )
75
+
76
+ var scrollRight = scrollLeft + TimePix.pixWindow,
77
+ bdivs = may_straddle (scrollLeft, scrollRight, blockdivs);
78
+ if (! bdivs.length) {return}
79
+
80
+ var cd = common_data( bdivs[0] )
81
+ if ( cd.bdiv_left < scrollLeft ) {
82
+ bdivs.shift()
83
+ justify_left_1 ( scrollLeft, cd );
84
+ }
85
+ undo_any_justify_left( bdivs );
86
+ }
87
+
88
+ function justify_left_1 ( scrollLeft, cd ) {
89
+ if ( cd.bdiv_left + cd.bdiv_width > scrollLeft ) {
90
+ var jleft = scrollLeft - cd.bdiv_left
91
+
92
+ // cd.tl.css('left', jleft + 'px')
93
+ cd.tl.animate({left: jleft}, rand_speed())
94
+ }
95
+ }
96
+
97
+ function undo_any_justify_left (bdivs) {
98
+ bdivs.forEach( function( bdiv ) {
99
+ // $('.text_locator', bdiv).css( 'left', '')
100
+ var tl = $('.text_locator', bdiv)
101
+ if ( parseInt(tl.css('left')) == 2 ) {return}
102
+ tl.animate({ left: "2" }, rand_speed())
103
+ })
104
+ }
105
+
106
+ function rand_speed() { return { duration: 200 + Math.random() * 800 } }
107
+
108
+ //////////////////////////////////////////////////////////////////////////////
109
+ // Re-align center-aligned block content.
110
+
111
+ function justify (scrollLeft, blockdivs) {
112
+ // sort_em(blockdivs)
113
+ var scrollRight = scrollLeft + TimePix.pixWindow,
114
+ bdivs = may_straddle (scrollLeft, scrollRight, blockdivs);
115
+ if (! bdivs.length) {return}
116
+
117
+
118
+ if ( straddles(scrollLeft, bdivs[0]) && straddles(scrollRight, bdivs[0]) ) {
119
+ straddles_both( scrollLeft, scrollRight, common_data(bdivs[0]) )
120
+ } else {
121
+
122
+ while ( bdivs.length > 0 && straddles( scrollLeft, bdivs[0] ) ) {
123
+ straddles_left (scrollLeft, common_data(bdivs.shift()));
124
+ }
125
+
126
+ while ( bdivs.length > 0 && straddles( scrollRight, bdivs.slice(-1)[0] ) ) {
127
+ straddles_right( scrollRight, common_data(bdivs.pop()) );
128
+ }
129
+
130
+ bdivs.forEach( function(bdiv) {
131
+ straddles_none( common_data(bdiv));
132
+ })
133
+ }
134
+ }
135
+
136
+ function straddles(edge, bdiv) {
137
+ var cd = common_data( bdiv )
138
+ return ( cd.bdiv_left < edge && edge < cd.bdiv_right )
139
+ }
140
+
141
+ function common_data(bdiv) {
142
+ var left = parseInt(bdiv.style.left)
143
+ var width = parseInt(bdiv.style.width)
144
+ return { tl: $('.text_locator', bdiv),
145
+ bdiv_left: left,
146
+ bdiv_width: width,
147
+ bdiv_right: left + width }
148
+ }
149
+
150
+ function straddles_both (scrollLeft, scrollRight, cd) {
151
+ var nleft = scrollLeft - cd.bdiv_left
152
+ var nwidth = scrollRight - scrollLeft
153
+ relocate (cd.tl, nleft, nwidth)
154
+ }
155
+
156
+ function straddles_right (scrollRight, cd) {
157
+ if ( cd.bdiv_left + cd.bdiv_width > scrollRight ) {
158
+ var room = scrollRight - parseInt( cd.tl.parent().css('left') )
159
+ var jwidth = Math.max( room, 190 ) // 15 in vp
160
+ relocate (cd.tl, 0, jwidth)
161
+ }
162
+ }
163
+
164
+ function straddles_left (scrollLeft, cd) {
165
+ if ( cd.bdiv_left + cd.bdiv_width > scrollLeft ) {
166
+ var room = parseInt( cd.tl.parent().css('width') )
167
+ var jleft = Math.min (scrollLeft - cd.bdiv_left, room - 190 ) // 15 in vp
168
+ var jwidth = room - jleft // Should calculate ^^^ this Fix Me
169
+ relocate (cd.tl, jleft, jwidth)
170
+ }
171
+ }
172
+
173
+ function straddles_none(cd) {
174
+ var room = parseInt( cd.tl.parent().css('width') )
175
+ if ( parseInt(cd.tl.css('left')) != 0 ||
176
+ parseInt(cd.tl.css('width')) != room ) {
177
+ relocate (cd.tl, 0, room)
178
+ }
179
+ }
180
+
181
+
182
+ function relocate (tl, nleft, nwidth) {
183
+ tl.animate({opacity: 0}, {queue: true, duration: 200})
184
+ tl.animate({ left: nleft, width: nwidth}, {queue: true, duration: 0})
185
+ tl.animate({opacity: 1}, {queue: true, duration: 800})
186
+ }