scheduled_resource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }