webmachine 0.4.2 → 1.0.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.
- data/README.md +70 -7
- data/Rakefile +19 -0
- data/examples/debugger.rb +32 -0
- data/examples/webrick.rb +6 -2
- data/lib/webmachine.rb +2 -0
- data/lib/webmachine/adapters/rack.rb +16 -3
- data/lib/webmachine/adapters/webrick.rb +7 -1
- data/lib/webmachine/application.rb +10 -10
- data/lib/webmachine/cookie.rb +168 -0
- data/lib/webmachine/decision/conneg.rb +1 -1
- data/lib/webmachine/decision/flow.rb +1 -1
- data/lib/webmachine/decision/fsm.rb +19 -12
- data/lib/webmachine/decision/helpers.rb +25 -1
- data/lib/webmachine/dispatcher.rb +34 -5
- data/lib/webmachine/dispatcher/route.rb +2 -0
- data/lib/webmachine/media_type.rb +3 -3
- data/lib/webmachine/request.rb +11 -0
- data/lib/webmachine/resource.rb +3 -1
- data/lib/webmachine/resource/authentication.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +16 -0
- data/lib/webmachine/resource/tracing.rb +20 -0
- data/lib/webmachine/response.rb +38 -8
- data/lib/webmachine/trace.rb +74 -0
- data/lib/webmachine/trace/fsm.rb +60 -0
- data/lib/webmachine/trace/pstore_trace_store.rb +39 -0
- data/lib/webmachine/trace/resource_proxy.rb +107 -0
- data/lib/webmachine/trace/static/http-headers-status-v3.png +0 -0
- data/lib/webmachine/trace/static/trace.erb +54 -0
- data/lib/webmachine/trace/static/tracelist.erb +14 -0
- data/lib/webmachine/trace/static/wmtrace.css +123 -0
- data/lib/webmachine/trace/static/wmtrace.js +725 -0
- data/lib/webmachine/trace/trace_resource.rb +129 -0
- data/lib/webmachine/version.rb +1 -1
- data/spec/spec_helper.rb +19 -0
- data/spec/webmachine/adapters/rack_spec.rb +77 -41
- data/spec/webmachine/configuration_spec.rb +1 -1
- data/spec/webmachine/cookie_spec.rb +99 -0
- data/spec/webmachine/decision/conneg_spec.rb +9 -8
- data/spec/webmachine/decision/flow_spec.rb +52 -4
- data/spec/webmachine/decision/helpers_spec.rb +36 -6
- data/spec/webmachine/dispatcher_spec.rb +1 -1
- data/spec/webmachine/headers_spec.rb +1 -1
- data/spec/webmachine/media_type_spec.rb +1 -1
- data/spec/webmachine/request_spec.rb +10 -0
- data/spec/webmachine/resource/authentication_spec.rb +3 -3
- data/spec/webmachine/response_spec.rb +45 -0
- data/spec/webmachine/trace/fsm_spec.rb +32 -0
- data/spec/webmachine/trace/resource_proxy_spec.rb +36 -0
- data/spec/webmachine/trace/trace_store_spec.rb +29 -0
- data/spec/webmachine/trace_spec.rb +17 -0
- data/webmachine.gemspec +2 -0
- metadata +130 -15
@@ -0,0 +1,60 @@
|
|
1
|
+
module Webmachine
|
2
|
+
module Trace
|
3
|
+
# This module is injected into {Webmachine::Decision::FSM} when
|
4
|
+
# tracing is enabled for a resource, enabling the capturing of
|
5
|
+
# traces.
|
6
|
+
module FSM
|
7
|
+
# Adds the request to the trace.
|
8
|
+
# @param [Webmachine::Request] request the request to be traced
|
9
|
+
def trace_request(request)
|
10
|
+
response.trace << {
|
11
|
+
:type => :request,
|
12
|
+
:method => request.method,
|
13
|
+
:path => request.uri.request_uri.to_s,
|
14
|
+
:headers => request.headers,
|
15
|
+
:body => request.body.to_s
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds the response to the trace.
|
20
|
+
# @param [Webmachine::Response] response the response to be traced
|
21
|
+
def trace_response(response)
|
22
|
+
response.trace << {
|
23
|
+
:type => :response,
|
24
|
+
:code => response.code.to_s,
|
25
|
+
:headers => response.headers,
|
26
|
+
:body => trace_response_body(response.body)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a decision to the trace.
|
31
|
+
# @param [Symbol] decision the decision being processed
|
32
|
+
def trace_decision(decision)
|
33
|
+
response.trace << {:type => :decision, :decision => decision}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Overrides the default resource accessor so that incoming
|
37
|
+
# callbacks are traced.
|
38
|
+
def resource
|
39
|
+
@resource_proxy ||= ResourceProxy.new(@resource)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
# Works around streaming encoders where possible
|
44
|
+
def trace_response_body(body)
|
45
|
+
case body
|
46
|
+
when FiberEncoder
|
47
|
+
# TODO: figure out how to properly rewind or replay the
|
48
|
+
# fiber
|
49
|
+
body.inspect
|
50
|
+
when EnumerableEncoder
|
51
|
+
body.body.join
|
52
|
+
when CallableEncoder
|
53
|
+
body.body.call.to_s
|
54
|
+
else
|
55
|
+
body.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
|
3
|
+
module Webmachine
|
4
|
+
module Trace
|
5
|
+
# Implements a trace storage using PStore from Ruby's standard
|
6
|
+
# library. To use this trace store, specify the :pstore engine
|
7
|
+
# and a path where it can store traces:
|
8
|
+
# @example
|
9
|
+
# Webmachine::Trace.trace_store = :pstore, "/tmp/webmachine.trace"
|
10
|
+
class PStoreTraceStore
|
11
|
+
# @api private
|
12
|
+
# @param [String] path where to store traces in a PStore
|
13
|
+
def initialize(path)
|
14
|
+
@pstore = PStore.new(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lists the recorded traces
|
18
|
+
# @api private
|
19
|
+
# @return [Array] a list of recorded traces
|
20
|
+
def keys
|
21
|
+
@pstore.transaction(true) { @pstore.roots }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fetches a trace from the store
|
25
|
+
# @api private
|
26
|
+
# @param [String] key the trace to fetch
|
27
|
+
# @return [Array] a raw trace
|
28
|
+
def fetch(key)
|
29
|
+
@pstore.transaction(true) { @pstore[key] }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Records a trace in the store
|
33
|
+
# @api private
|
34
|
+
def []=(key, trace)
|
35
|
+
@pstore.transaction { @pstore[key] = trace }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Webmachine
|
2
|
+
module Trace
|
3
|
+
# This class is injected into the decision FSM as a stand-in for
|
4
|
+
# the resource when tracing is enabled. It proxies all callbacks
|
5
|
+
# to the resource so that they get logged in the trace.
|
6
|
+
class ResourceProxy
|
7
|
+
# @return [Webmachine::Resource] the wrapped resource
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
# Callback methods that can return data that refers to
|
11
|
+
# user-defined callbacks that are not in the canonical set,
|
12
|
+
# including body-producing or accepting methods, encoders and
|
13
|
+
# charsetters.
|
14
|
+
CALLBACK_REFERRERS = [:content_types_accepted, :content_types_provided,
|
15
|
+
:encodings_provided, :charsets_provided]
|
16
|
+
|
17
|
+
# Creates a {ResourceProxy} that decorates the passed
|
18
|
+
# {Webmachine::Resource} such that callbacks invoked by the
|
19
|
+
# {Webmachine::Decision::FSM} will be logged in the response's
|
20
|
+
# trace.
|
21
|
+
def initialize(resource)
|
22
|
+
@resource = resource
|
23
|
+
@dynamic_callbacks = Module.new
|
24
|
+
extend @dynamic_callbacks
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create wrapper methods for every exposed callback
|
28
|
+
Webmachine::Resource::Callbacks.instance_methods(false).each do |c|
|
29
|
+
define_method c do |*args|
|
30
|
+
proxy_callback c, *args
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def charset_nop(*args)
|
35
|
+
proxy_callback :charset_nop, *args
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calls the resource's finish_request method and then commits
|
39
|
+
# the trace to separate storage which can be discovered by the
|
40
|
+
# debugger.
|
41
|
+
def finish_request(*args)
|
42
|
+
proxy_callback :finish_request, *args
|
43
|
+
ensure
|
44
|
+
resource.response.headers['X-Webmachine-Trace-Id'] = object_id.to_s
|
45
|
+
Trace.record(object_id.to_s, resource.response.trace)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Proxy a given callback to the inner resource, decorating with traces
|
50
|
+
def proxy_callback(callback, *args)
|
51
|
+
# Log inputs and attempt
|
52
|
+
resource.response.trace << attempt(callback, args)
|
53
|
+
# Do the call
|
54
|
+
_result = resource.send(callback, *args)
|
55
|
+
add_dynamic_callback_proxies(_result) if CALLBACK_REFERRERS.include?(callback.to_sym)
|
56
|
+
resource.response.trace << result(_result)
|
57
|
+
_result
|
58
|
+
rescue => exc
|
59
|
+
exc.backtrace.reject! {|s| s.include?(__FILE__) }
|
60
|
+
resource.response.trace << exception(exc)
|
61
|
+
raise
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a log entry for the entry to a resource callback.
|
65
|
+
def attempt(callback, args)
|
66
|
+
log = {:type => :attempt}
|
67
|
+
method = resource.method(callback)
|
68
|
+
if method.owner == ::Webmachine::Resource::Callbacks
|
69
|
+
log[:name] = "(default)##{method.name}"
|
70
|
+
else
|
71
|
+
log[:name] = "#{method.owner.name}##{method.name}"
|
72
|
+
log[:source] = method.source_location.join(":") if method.respond_to?(:source_location)
|
73
|
+
end
|
74
|
+
unless args.empty?
|
75
|
+
log[:args] = args
|
76
|
+
end
|
77
|
+
log
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates a log entry for the result of a resource callback
|
81
|
+
def result(result)
|
82
|
+
{:type => :result, :value => result}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Creates a log entry for an exception that was raised from a callback
|
86
|
+
def exception(e)
|
87
|
+
{:type => :exception,
|
88
|
+
:class => e.class.name,
|
89
|
+
:backtrace => e.backtrace,
|
90
|
+
:message => e.message }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Adds proxy methods for callbacks that are dynamically referred to.
|
94
|
+
def add_dynamic_callback_proxies(pairs)
|
95
|
+
pairs.to_a.each do |(_, m)|
|
96
|
+
unless respond_to?(m)
|
97
|
+
@dynamic_callbacks.module_eval do
|
98
|
+
define_method m do |*args|
|
99
|
+
proxy_callback m, *args
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
Binary file
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Webmachine Trace <%= name %></title>
|
5
|
+
<link rel="stylesheet" type="text/css" href="static/wmtrace.css" />
|
6
|
+
<script type="text/javascript" src="static/wmtrace.js"></script>
|
7
|
+
<script type="text/javascript">
|
8
|
+
<!--
|
9
|
+
var request = <%= treq %>;
|
10
|
+
var response = <%= tres %>;
|
11
|
+
var trace = <%= trace %>
|
12
|
+
-->
|
13
|
+
</script>
|
14
|
+
</head>
|
15
|
+
<body>
|
16
|
+
<div id="zoompanel">
|
17
|
+
<button id="zoomout">zoom out</button>
|
18
|
+
<button id="zoomin">zoom in</button>
|
19
|
+
</div>
|
20
|
+
<canvas id="v3map" width="3138" height="2184"></canvas>
|
21
|
+
<div id="sizetest"></div>
|
22
|
+
<div id="preview">
|
23
|
+
<div id="previewid"></div>
|
24
|
+
<ul id="previewcalls"></ul>
|
25
|
+
</div>
|
26
|
+
<div id="infopanel">
|
27
|
+
<div id="infocontrols">
|
28
|
+
<div id="requesttab" class="selectedtab">Q</div>
|
29
|
+
<div id="responsetab">R</div>
|
30
|
+
<div id="decisiontab">D</div>
|
31
|
+
</div>
|
32
|
+
<div id="requestdetail">
|
33
|
+
<div>
|
34
|
+
<span id="requestmethod"></span> <span id="requestpath"></span>
|
35
|
+
</div>
|
36
|
+
<table id="requestheaders" class="headers"></table>
|
37
|
+
<pre id="requestbody"></pre>
|
38
|
+
</div>
|
39
|
+
<div id="responsedetail">
|
40
|
+
<div id="responsecode"></div>
|
41
|
+
<table id="responseheaders" class="headers"></table>
|
42
|
+
<pre id="responsebody"></pre>
|
43
|
+
</div>
|
44
|
+
<div id="decisiondetail">
|
45
|
+
<div>Decision: <select id="decisionid"></select></div>
|
46
|
+
<div>Calls: <select id="decisioncalls"></select></div>
|
47
|
+
<div>Input:</div>
|
48
|
+
<pre id="callinput"></pre>
|
49
|
+
<div>Output:</div>
|
50
|
+
<pre id="calloutput"></pre>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
</body>
|
54
|
+
</html>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Webmachine Trace List</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>Traces</h1>
|
8
|
+
<ul>
|
9
|
+
<% traces.each do |trace| %>
|
10
|
+
<li><a href="<%= trace %>"><%= trace %></a></li>
|
11
|
+
<% end %>
|
12
|
+
</ul>
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,123 @@
|
|
1
|
+
body {
|
2
|
+
margin:0px;
|
3
|
+
padding:0px;
|
4
|
+
}
|
5
|
+
|
6
|
+
canvas#v3map {
|
7
|
+
margin-top:2em;
|
8
|
+
z-index: 1;
|
9
|
+
}
|
10
|
+
|
11
|
+
div#sizetest {
|
12
|
+
width:100%;
|
13
|
+
}
|
14
|
+
|
15
|
+
div#zoompanel {
|
16
|
+
height:2em;
|
17
|
+
position:fixed;
|
18
|
+
z-index:10;
|
19
|
+
}
|
20
|
+
|
21
|
+
div#preview {
|
22
|
+
position:absolute;
|
23
|
+
display:none;
|
24
|
+
background:#dddddd;
|
25
|
+
border:1px solid #999999;
|
26
|
+
}
|
27
|
+
|
28
|
+
div#preview ul {
|
29
|
+
padding: 0px 0px 0px 0.5em;
|
30
|
+
margin: 0px;
|
31
|
+
list-style: none;
|
32
|
+
}
|
33
|
+
|
34
|
+
div#infopanel {
|
35
|
+
z-index:20;
|
36
|
+
background:#dddddd;
|
37
|
+
position:fixed;
|
38
|
+
top:0px;
|
39
|
+
right:0px;
|
40
|
+
bottom:0px;
|
41
|
+
left:75%;
|
42
|
+
min-width:30em;
|
43
|
+
padding:5px;
|
44
|
+
}
|
45
|
+
|
46
|
+
div#infocontrols {
|
47
|
+
position:absolute;
|
48
|
+
top:0px;
|
49
|
+
bottom:0px;
|
50
|
+
left:-5px;
|
51
|
+
width:5px;
|
52
|
+
background:#999999;
|
53
|
+
cursor:ew-resize;
|
54
|
+
}
|
55
|
+
|
56
|
+
div#infocontrols div {
|
57
|
+
position:absolute;
|
58
|
+
left:-15px;
|
59
|
+
width:20px;
|
60
|
+
height:49px;
|
61
|
+
background:#999999;
|
62
|
+
cursor:pointer;
|
63
|
+
}
|
64
|
+
|
65
|
+
div#infocontrols div.selectedtab {
|
66
|
+
background:#dddddd;
|
67
|
+
border-top: 1px solid #999999;
|
68
|
+
border-left: 1px solid #999999;
|
69
|
+
border-bottom: 1px solid #999999;
|
70
|
+
}
|
71
|
+
|
72
|
+
div#requesttab {
|
73
|
+
top:2px;
|
74
|
+
}
|
75
|
+
|
76
|
+
div#responsetab {
|
77
|
+
top:54px;
|
78
|
+
}
|
79
|
+
|
80
|
+
div#decisiontab {
|
81
|
+
top:106px;
|
82
|
+
}
|
83
|
+
|
84
|
+
div#requestdetail, div#responsedetail, div#decisiondetail {
|
85
|
+
height:100%;
|
86
|
+
}
|
87
|
+
|
88
|
+
div#responsedetail, div#decisiondetail {
|
89
|
+
display:none;
|
90
|
+
}
|
91
|
+
|
92
|
+
div#infopanel ul {
|
93
|
+
list-style:none;
|
94
|
+
padding-left:0px;
|
95
|
+
height:5em;
|
96
|
+
overflow-y:scroll;
|
97
|
+
}
|
98
|
+
|
99
|
+
pre {
|
100
|
+
height:40%;
|
101
|
+
overflow: auto;
|
102
|
+
}
|
103
|
+
|
104
|
+
table.headers {
|
105
|
+
border-collapse: collapse;
|
106
|
+
margin: 10px 20px 10px 0;
|
107
|
+
}
|
108
|
+
|
109
|
+
table.headers th {
|
110
|
+
text-align: right ! important;
|
111
|
+
text-transform: capitalize;
|
112
|
+
white-space: nowrap;
|
113
|
+
vertical-align: top;
|
114
|
+
}
|
115
|
+
|
116
|
+
table.headers td, table.headers th {
|
117
|
+
padding: 5px 3px;
|
118
|
+
}
|
119
|
+
|
120
|
+
div#responsebody, div#requestbody {
|
121
|
+
height:70%;
|
122
|
+
overflow-y:scroll;
|
123
|
+
}
|
@@ -0,0 +1,725 @@
|
|
1
|
+
var HIGHLIGHT = '#cc00cc';
|
2
|
+
var REGULAR = '#666666';
|
3
|
+
|
4
|
+
var cols = {
|
5
|
+
'a':173,
|
6
|
+
'b':325,
|
7
|
+
'c':589,
|
8
|
+
'd':797,
|
9
|
+
'e':1005,
|
10
|
+
'f':1195,
|
11
|
+
'g':1402,
|
12
|
+
'gg':1515,
|
13
|
+
'h':1572,
|
14
|
+
'i':1799,
|
15
|
+
'j':1893,
|
16
|
+
'k':1988,
|
17
|
+
'l':2157,
|
18
|
+
'll':2346,
|
19
|
+
'm':2403,
|
20
|
+
'mm':2535,
|
21
|
+
'n':2554,
|
22
|
+
'o':2649,
|
23
|
+
'oo':2781,
|
24
|
+
'ooo':2801,
|
25
|
+
'p':2894,
|
26
|
+
'q':3007
|
27
|
+
};
|
28
|
+
|
29
|
+
var rows = {
|
30
|
+
'1':221,
|
31
|
+
'2':298,
|
32
|
+
'3':373,
|
33
|
+
'4':448,
|
34
|
+
'5':524,
|
35
|
+
'6':599,
|
36
|
+
'7':675,
|
37
|
+
'8':751,
|
38
|
+
'9':826,
|
39
|
+
'10':902,
|
40
|
+
'11':977,
|
41
|
+
'12':1053,
|
42
|
+
'13':1129,
|
43
|
+
'14':1204,
|
44
|
+
'15':1280,
|
45
|
+
'16':1355,
|
46
|
+
'17':1431,
|
47
|
+
'18':1506,
|
48
|
+
'19':1583,
|
49
|
+
'20':1658,
|
50
|
+
'21':1734,
|
51
|
+
'22':1809,
|
52
|
+
'23':1885,
|
53
|
+
'24':1961,
|
54
|
+
'25':2036,
|
55
|
+
'26':2112
|
56
|
+
};
|
57
|
+
|
58
|
+
var edges = {
|
59
|
+
'b14b13':['b14','b13'],
|
60
|
+
|
61
|
+
'b13b12':['b13','b12'],
|
62
|
+
'b13503':['b13','503'],
|
63
|
+
|
64
|
+
'b12b11':['b12','b11'],
|
65
|
+
'b12501':['b12','501'],
|
66
|
+
|
67
|
+
'b11b10':['b11','b10'],
|
68
|
+
'b11414':['b11','414'],
|
69
|
+
|
70
|
+
'b10b9':['b10','b9'],
|
71
|
+
'b10405':['b10','405'],
|
72
|
+
|
73
|
+
'b9b8':['b9','b8'],
|
74
|
+
'b9400':['b9','400'],
|
75
|
+
|
76
|
+
'b8b7':['b8','b7'],
|
77
|
+
'b8401':['b8','401'],
|
78
|
+
|
79
|
+
'b7b6':['b7','b6'],
|
80
|
+
'b7403':['b7','403'],
|
81
|
+
|
82
|
+
'b6b5':['b6','b5'],
|
83
|
+
'b6501':['b6','501a'],
|
84
|
+
|
85
|
+
'b5b4':['b5','b4'],
|
86
|
+
'b5415':['b5','415'],
|
87
|
+
|
88
|
+
'b4b3':['b4','b3'],
|
89
|
+
'b4413':['b4','b4'],
|
90
|
+
|
91
|
+
'b3c3':['b3','c3'],
|
92
|
+
'b3200':['b3','200'],
|
93
|
+
|
94
|
+
'c3c4':['c3','c4'],
|
95
|
+
'c3d4':['c3','d3','d4'],
|
96
|
+
|
97
|
+
'c4d4':['c4','d4'],
|
98
|
+
'c4406':['c4','406'],
|
99
|
+
|
100
|
+
'd4d5':['d4','d5'],
|
101
|
+
'd4e5':['d4','e4','e5'],
|
102
|
+
|
103
|
+
'd5e5':['d5','e5'],
|
104
|
+
'd5406':['d5','d7','406'],
|
105
|
+
|
106
|
+
'e5e6':['e5','e6'],
|
107
|
+
'e5f6':['e5','f5','f6'],
|
108
|
+
|
109
|
+
'e6f6':['e6','f6'],
|
110
|
+
'e6406':['e6','e7','406'],
|
111
|
+
|
112
|
+
'f6f7':['f6','f7'],
|
113
|
+
'f6g7':['f6','g6','g7'],
|
114
|
+
|
115
|
+
'f7g7':['f7','g7'],
|
116
|
+
'f7406':['f7','406'],
|
117
|
+
|
118
|
+
'g7g8':['g7','g8'],
|
119
|
+
'g7h7':['g7','h7'],
|
120
|
+
|
121
|
+
'g8g9':['g8','g9'],
|
122
|
+
'g8h10':['g8','h8','h10'],
|
123
|
+
|
124
|
+
'g9g11':['g9','g11'],
|
125
|
+
'g9h10':['g9','gg9','gg10','h10'],
|
126
|
+
|
127
|
+
'g11h10':['g11','gg11','gg10','h10'],
|
128
|
+
'g11412':['g11','g18','412a'],
|
129
|
+
|
130
|
+
'h7i7':['h7','i7'],
|
131
|
+
'h7412':['h7','412'],
|
132
|
+
|
133
|
+
'h10h11':['h10','h11'],
|
134
|
+
'h10i12':['h10','i10','i12'],
|
135
|
+
|
136
|
+
'h11h12':['h11','h12'],
|
137
|
+
'h11i12':['h11','i11','i12'],
|
138
|
+
|
139
|
+
'h12i12':['h12','i12'],
|
140
|
+
'h12412':['h12','412a'],
|
141
|
+
|
142
|
+
'i4p3':['i4','i3','p3'],
|
143
|
+
'i4301':['i4','301'],
|
144
|
+
|
145
|
+
'i7i4':['i7','i4'],
|
146
|
+
'i7k7':['i7','k7'],
|
147
|
+
|
148
|
+
'i12l13':['i12','l12','l13'],
|
149
|
+
'i12i13':['i12','i13'],
|
150
|
+
|
151
|
+
'i13k13':['i13','k13'],
|
152
|
+
'i13j18':['i13','i17','j17','j18'],
|
153
|
+
|
154
|
+
'j18412':['j18','412a'],
|
155
|
+
'j18304':['j18','304'],
|
156
|
+
|
157
|
+
'k5l5':['k5','l5'],
|
158
|
+
'k5301':['k5','301'],
|
159
|
+
|
160
|
+
'k7k5':['k7','k5'],
|
161
|
+
'k7l7':['k7','l7'],
|
162
|
+
|
163
|
+
'k13j18':['k13','k17','j17','j18'],
|
164
|
+
'k13l13':['k13','l13'],
|
165
|
+
|
166
|
+
'l5m5':['l5','m5'],
|
167
|
+
'l5307':['l5','307'],
|
168
|
+
|
169
|
+
'l7m7':['l7','m7'],
|
170
|
+
'l7404':['l7','l8','404'],
|
171
|
+
|
172
|
+
'l13l14':['l13','l14'],
|
173
|
+
'l13m16':['l13','m13','m16'],
|
174
|
+
|
175
|
+
'l14l15':['l14','l15'],
|
176
|
+
'l14m16':['l14','m14','m16'],
|
177
|
+
|
178
|
+
'l15l17':['l15','l17'],
|
179
|
+
'l15m16':['l15','ll15','ll16','m16'],
|
180
|
+
|
181
|
+
'l17m16':['l17','ll17','ll16','m16'],
|
182
|
+
'l17304':['l17','304'],
|
183
|
+
|
184
|
+
'm5n5':['m5','n5'],
|
185
|
+
'm5410':['m5','m4','410'],
|
186
|
+
|
187
|
+
'm7n11':['m7','n7','n11'],
|
188
|
+
'm7404':['m7','404'],
|
189
|
+
|
190
|
+
'm16m20':['m16','m20'],
|
191
|
+
'm16n16':['m16','n16'],
|
192
|
+
|
193
|
+
'm20o20':['m20','o20'],
|
194
|
+
'm20202':['m20','202'],
|
195
|
+
|
196
|
+
'n5n11':['n5','n11'],
|
197
|
+
'n5410':['n5','410'],
|
198
|
+
|
199
|
+
'n11p11':['n11','p11'],
|
200
|
+
'n11303':['n11','303'],
|
201
|
+
|
202
|
+
'n16n11':['n16','n11'],
|
203
|
+
'n16o16':['n16','o16'],
|
204
|
+
|
205
|
+
'o14p11':['o14','o11','p11'],
|
206
|
+
'o14409':['o14','409a'],
|
207
|
+
|
208
|
+
'o16o14':['o16','o14'],
|
209
|
+
'o16o18':['o16','o18'],
|
210
|
+
|
211
|
+
'o18200':['o18','200a'],
|
212
|
+
'o18300':['o18','oo18','300'],
|
213
|
+
|
214
|
+
'o20o18':['o20','o18'],
|
215
|
+
'o20204':['o20','204'],
|
216
|
+
|
217
|
+
'p3p11':['p3','p11'],
|
218
|
+
'p3409':['p3','409'],
|
219
|
+
|
220
|
+
'p11o20':['p11','p20','o20'],
|
221
|
+
'p11201':['p11','q11','201']
|
222
|
+
};
|
223
|
+
|
224
|
+
var ends = {
|
225
|
+
'200': {col:'a', row:'3', width:190},
|
226
|
+
'200a': {col:'mm', row:'18', width:116},
|
227
|
+
'201': {col:'q', row:'12', width:154},
|
228
|
+
'202': {col:'m', row:'21', width:116},
|
229
|
+
'204': {col:'o', row:'21', width:152},
|
230
|
+
|
231
|
+
'300': {col:'oo', row:'19', width:152},
|
232
|
+
'301': {col:'k', row:'4', width:154},
|
233
|
+
'303': {col:'m', row:'11', width:116},
|
234
|
+
'304': {col:'l', row:'18', width:116},
|
235
|
+
'307': {col:'l', row:'4', width:154},
|
236
|
+
|
237
|
+
'400': {col:'a', row:'9', width:190},
|
238
|
+
'401': {col:'a', row:'8', width:190},
|
239
|
+
'403': {col:'a', row:'7', width:190},
|
240
|
+
'404': {col:'m', row:'8', width:116},
|
241
|
+
'405': {col:'a', row:'10', width:190},
|
242
|
+
'406': {col:'c', row:'7', width:152},
|
243
|
+
'409': {col:'p', row:'2', width:116},
|
244
|
+
'409a': {col:'oo', row:'14', width:116},
|
245
|
+
'410': {col:'n', row:'4', width:116},
|
246
|
+
'412': {col:'h', row:'6', width:152},
|
247
|
+
'412a': {col:'h', row:'18', width:152},
|
248
|
+
'413': {col:'a', row:'4', width:190},
|
249
|
+
'414': {col:'a', row:'11', width:190},
|
250
|
+
'415': {col:'a', row:'5', width:190},
|
251
|
+
|
252
|
+
'501a': {col:'a', row:'6', width:190},
|
253
|
+
'501': {col:'a', row:'12', width:190},
|
254
|
+
'503': {col:'a', row:'13', width:190}
|
255
|
+
};
|
256
|
+
|
257
|
+
var canvas;
|
258
|
+
|
259
|
+
function decorateTrace() {
|
260
|
+
trace[0].x = cols[trace[0].d[0]];
|
261
|
+
trace[0].y = rows[trace[0].d.slice(1)];
|
262
|
+
trace[0].previewCalls = previewCalls(trace[0]);
|
263
|
+
|
264
|
+
for (var i = 1; i < trace.length; i++) {
|
265
|
+
trace[i].x = cols[trace[i].d[0]];
|
266
|
+
trace[i].y = rows[trace[i].d.slice(1)];
|
267
|
+
trace[i].previewCalls = previewCalls(trace[i]);
|
268
|
+
|
269
|
+
var path = edges[trace[i-1].d+trace[i].d];
|
270
|
+
if (path) {
|
271
|
+
trace[i].path = [path.length-1];
|
272
|
+
for (var p = 1; p < path.length; p++) {
|
273
|
+
trace[i].path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
|
274
|
+
}
|
275
|
+
} else {
|
276
|
+
trace[i].path = [];
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
var path = edges[trace[i-1].d+response.code];
|
281
|
+
if (path) {
|
282
|
+
var end = ends[path[path.length-1]];
|
283
|
+
response.x = cols[end.col];
|
284
|
+
response.y = rows[end.row];
|
285
|
+
response.width = end.width;
|
286
|
+
response.type = 'normal';
|
287
|
+
|
288
|
+
response.path = [path.length-1];
|
289
|
+
for (var p = 1; p < path.length; p++) {
|
290
|
+
response.path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
|
291
|
+
}
|
292
|
+
} else {
|
293
|
+
var ld = trace[trace.length-1];
|
294
|
+
response.x = ld.x+50;
|
295
|
+
response.y = ld.y-50;
|
296
|
+
response.width = 38;
|
297
|
+
response.type = 'other';
|
298
|
+
|
299
|
+
response.path = [
|
300
|
+
{x1: ld.x+10, y1: ld.y-10,
|
301
|
+
x2: ld.x+36, y2: ld.y-36}
|
302
|
+
];
|
303
|
+
}
|
304
|
+
};
|
305
|
+
|
306
|
+
function previewCalls(dec) {
|
307
|
+
var prev = '';
|
308
|
+
for (var i = 0; i < dec.calls.length; i++) {
|
309
|
+
if(dec.calls[i].call.indexOf("(default)") !== 0) {
|
310
|
+
prev += '<li>'+dec.calls[i].call;
|
311
|
+
if(dec.calls[i].source !== null)
|
312
|
+
prev += " (" + dec.calls[i].source + ")";
|
313
|
+
prev+='</li>';
|
314
|
+
}
|
315
|
+
}
|
316
|
+
return prev;
|
317
|
+
};
|
318
|
+
|
319
|
+
function drawTrace() {
|
320
|
+
drawDecision(trace[0]);
|
321
|
+
for (var i = 1; i < trace.length; i++) {
|
322
|
+
drawPath(trace[i].path);
|
323
|
+
drawDecision(trace[i]);
|
324
|
+
}
|
325
|
+
|
326
|
+
drawPath(response.path);
|
327
|
+
drawResponse();
|
328
|
+
};
|
329
|
+
|
330
|
+
function drawResponse() {
|
331
|
+
if (response.type == 'normal') {
|
332
|
+
var context = canvas.getContext('2d');
|
333
|
+
context.strokeStyle=HIGHLIGHT;
|
334
|
+
context.lineWidth=4;
|
335
|
+
|
336
|
+
context.beginPath();
|
337
|
+
context.rect(response.x-(response.width/2),
|
338
|
+
response.y-19,
|
339
|
+
response.width,
|
340
|
+
38);
|
341
|
+
context.stroke();
|
342
|
+
} else {
|
343
|
+
var context = canvas.getContext('2d');
|
344
|
+
context.strokeStyle='#ff0000';
|
345
|
+
context.lineWidth=4;
|
346
|
+
|
347
|
+
context.beginPath();
|
348
|
+
context.arc(response.x, response.y, 19,
|
349
|
+
0, 2*3.14159, false);
|
350
|
+
context.stroke();
|
351
|
+
|
352
|
+
}
|
353
|
+
};
|
354
|
+
|
355
|
+
function drawDecision(dec) {
|
356
|
+
var context = canvas.getContext('2d');
|
357
|
+
|
358
|
+
if (dec.previewCalls == '')
|
359
|
+
context.strokeStyle=REGULAR;
|
360
|
+
else
|
361
|
+
context.strokeStyle=HIGHLIGHT;
|
362
|
+
context.lineWidth=4;
|
363
|
+
|
364
|
+
context.beginPath();
|
365
|
+
context.moveTo(dec.x, dec.y-19);
|
366
|
+
context.lineTo(dec.x+19, dec.y);
|
367
|
+
context.lineTo(dec.x, dec.y+19);
|
368
|
+
context.lineTo(dec.x-19, dec.y);
|
369
|
+
context.closePath();
|
370
|
+
context.stroke();
|
371
|
+
};
|
372
|
+
|
373
|
+
function drawPath(path) {
|
374
|
+
var context = canvas.getContext('2d');
|
375
|
+
context.strokeStyle=REGULAR;
|
376
|
+
context.lineWidth=4;
|
377
|
+
|
378
|
+
context.beginPath();
|
379
|
+
context.moveTo(path[0].x1, path[0].y1);
|
380
|
+
for (var p = 0; p < path.length; p++) {
|
381
|
+
context.lineTo(path[p].x2, path[p].y2);
|
382
|
+
}
|
383
|
+
context.stroke();
|
384
|
+
};
|
385
|
+
|
386
|
+
function getSeg(p1, p2, last) {
|
387
|
+
var seg = {
|
388
|
+
x1:cols[p1[0]],
|
389
|
+
y1:rows[p1.slice(1)]
|
390
|
+
};
|
391
|
+
if (ends[p2]) {
|
392
|
+
seg.x2 = cols[ends[p2].col];
|
393
|
+
seg.y2 = rows[ends[p2].row];
|
394
|
+
} else {
|
395
|
+
seg.x2 = cols[p2[0]];
|
396
|
+
seg.y2 = rows[p2.slice(1)];
|
397
|
+
}
|
398
|
+
|
399
|
+
if (seg.x1 == seg.x2) {
|
400
|
+
if (seg.y1 < seg.y2) {
|
401
|
+
seg.y1 = seg.y1+19;
|
402
|
+
if (last) seg.y2 = seg.y2-19;
|
403
|
+
} else {
|
404
|
+
seg.y1 = seg.y1-19;
|
405
|
+
if (last) seg.y2 = seg.y2+19;
|
406
|
+
}
|
407
|
+
} else {
|
408
|
+
//assume seg.y1 == seg.y2
|
409
|
+
if (seg.x1 < seg.x2) {
|
410
|
+
seg.x1 = seg.x1+19;
|
411
|
+
if (last) seg.x2 = seg.x2-(ends[p2] ? (ends[p2].width/2) : 19);
|
412
|
+
} else {
|
413
|
+
seg.x1 = seg.x1-19;
|
414
|
+
if (last) seg.x2 = seg.x2+(ends[p2] ? (ends[p2].width/2) : 19);
|
415
|
+
}
|
416
|
+
}
|
417
|
+
return seg;
|
418
|
+
};
|
419
|
+
|
420
|
+
function traceDecision(name) {
|
421
|
+
for (var i = trace.length-1; i >= 0; i--)
|
422
|
+
if (trace[i].d == name) return trace[i];
|
423
|
+
};
|
424
|
+
|
425
|
+
var detailPanels = {};
|
426
|
+
function initDetailPanels() {
|
427
|
+
var windowWidth = document.getElementById('sizetest').clientWidth;
|
428
|
+
var infoPanel = document.getElementById('infopanel');
|
429
|
+
var panelWidth = windowWidth-infoPanel.offsetLeft;
|
430
|
+
|
431
|
+
var panels = {
|
432
|
+
'request': document.getElementById('requestdetail'),
|
433
|
+
'response': document.getElementById('responsedetail'),
|
434
|
+
'decision': document.getElementById('decisiondetail')
|
435
|
+
};
|
436
|
+
|
437
|
+
var tabs = {
|
438
|
+
'request': document.getElementById('requesttab'),
|
439
|
+
'response': document.getElementById('responsetab'),
|
440
|
+
'decision': document.getElementById('decisiontab')
|
441
|
+
};
|
442
|
+
|
443
|
+
var decisionId = document.getElementById('decisionid');
|
444
|
+
var decisionCalls = document.getElementById('decisioncalls');
|
445
|
+
var callInput = document.getElementById('callinput');
|
446
|
+
var callOutput = document.getElementById('calloutput');
|
447
|
+
|
448
|
+
var lastUsedPanelWidth = windowWidth-infoPanel.offsetLeft;
|
449
|
+
|
450
|
+
var setPanelWidth = function(width) {
|
451
|
+
infoPanel.style.left = (windowWidth-width)+'px';
|
452
|
+
canvas.style.marginRight = (width+20)+'px';
|
453
|
+
panelWidth = width;
|
454
|
+
};
|
455
|
+
setPanelWidth(panelWidth);
|
456
|
+
|
457
|
+
var ensureVisible = function() {
|
458
|
+
if (windowWidth-infoPanel.offsetLeft < 10)
|
459
|
+
setPanelWidth(lastUsedPanelWidth);
|
460
|
+
};
|
461
|
+
|
462
|
+
var decChoices = '';
|
463
|
+
for (var i = 0; i < trace.length; i++) {
|
464
|
+
decChoices += '<option value="'+trace[i].d+'">'+trace[i].d+'</option>';
|
465
|
+
}
|
466
|
+
decisionId.innerHTML = decChoices;
|
467
|
+
decisionId.selectedIndex = -1;
|
468
|
+
|
469
|
+
decisionId.onchange = function() {
|
470
|
+
detailPanels.setDecision(traceDecision(decisionId.value));
|
471
|
+
}
|
472
|
+
|
473
|
+
detailPanels.setDecision = function(dec) {
|
474
|
+
decisionId.value = dec.d;
|
475
|
+
|
476
|
+
var calls = [];
|
477
|
+
for (var i = 0; i < dec.calls.length; i++) {
|
478
|
+
calls.push('<option value="'+dec.d+'-'+i+'">');
|
479
|
+
calls.push(dec.calls[i].call);
|
480
|
+
if(dec.calls[i].source !== null)
|
481
|
+
calls.push(' (' + dec.calls[i].source + ')');
|
482
|
+
calls.push('</option>');
|
483
|
+
}
|
484
|
+
decisionCalls.innerHTML = calls.join('');
|
485
|
+
decisionCalls.selectedIndex = 0;
|
486
|
+
|
487
|
+
decisionCalls.onchange();
|
488
|
+
};
|
489
|
+
|
490
|
+
detailPanels.show = function(name) {
|
491
|
+
for (p in panels) {
|
492
|
+
if (p == name) {
|
493
|
+
panels[p].style.display = 'block';
|
494
|
+
tabs[p].className = 'selectedtab';
|
495
|
+
}
|
496
|
+
else {
|
497
|
+
panels[p].style.display = 'none';
|
498
|
+
tabs[p].className = '';
|
499
|
+
}
|
500
|
+
}
|
501
|
+
ensureVisible();
|
502
|
+
};
|
503
|
+
|
504
|
+
detailPanels.hide = function() {
|
505
|
+
setPanelWidth(0);
|
506
|
+
}
|
507
|
+
|
508
|
+
decisionCalls.onchange = function() {
|
509
|
+
var val = decisionCalls.value;
|
510
|
+
if (val) {
|
511
|
+
var dec = traceDecision(val.substring(0, val.indexOf('-')));
|
512
|
+
var call = dec.calls[parseInt(val.substring(val.indexOf('-')+1, val.length))];
|
513
|
+
|
514
|
+
if (call.call.indexOf("(default)") !== 0) {
|
515
|
+
callInput.style.color='#000000';
|
516
|
+
callInput.textContent = call.input;
|
517
|
+
if (call.output != null) {
|
518
|
+
callOutput.style.color = '#000000';
|
519
|
+
callOutput.textContent = call.output;
|
520
|
+
} else {
|
521
|
+
if(call.exception !== null){
|
522
|
+
callOutput.style.color = '#ff0000';
|
523
|
+
callOutput.textContent = 'Exception raised!\n\n' + call.exception['class'] + ': ' +
|
524
|
+
call.exception.message + '\n ' + call.exception.backtrace.split('\n').join('\n ');
|
525
|
+
}
|
526
|
+
}
|
527
|
+
} else {
|
528
|
+
callInput.style.color='#999999';
|
529
|
+
callInput.textContent = call.call.replace('(default)', '') + " was not overridden by the resource";
|
530
|
+
callOutput.textContent = '';
|
531
|
+
}
|
532
|
+
} else {
|
533
|
+
callInput.textContent = '';
|
534
|
+
callOutput.textContent = '';
|
535
|
+
}
|
536
|
+
};
|
537
|
+
|
538
|
+
var headersList = function(headers) {
|
539
|
+
var h = '';
|
540
|
+
for (n in headers) h += '<tr><th><code>'+n+':</code></th><td><code>'+headers[n] + '</code></td></tr>';
|
541
|
+
return h;
|
542
|
+
};
|
543
|
+
|
544
|
+
document.getElementById('requestmethod').innerHTML = request.method;
|
545
|
+
document.getElementById('requestpath').innerHTML = request.path;
|
546
|
+
document.getElementById('requestheaders').innerHTML = headersList(request.headers);
|
547
|
+
document.getElementById('requestbody').textContent = request.body;
|
548
|
+
|
549
|
+
document.getElementById('responsecode').innerHTML = response.code;
|
550
|
+
document.getElementById('responseheaders').innerHTML = headersList(response.headers);
|
551
|
+
document.getElementById('responsebody').textContent = response.body;
|
552
|
+
|
553
|
+
|
554
|
+
var infoControls = document.getElementById('infocontrols');
|
555
|
+
var md = false;
|
556
|
+
var dragged = false;
|
557
|
+
var msoff = 0;
|
558
|
+
infoControls.onmousedown = function(ev) {
|
559
|
+
md = true;
|
560
|
+
dragged = false;
|
561
|
+
msoff = ev.clientX-infoPanel.offsetLeft;
|
562
|
+
};
|
563
|
+
|
564
|
+
infoControls.onclick = function(ev) {
|
565
|
+
if (dragged) {
|
566
|
+
lastUsedPanelWidth = panelWidth;
|
567
|
+
}
|
568
|
+
else if (panelWidth < 10) {
|
569
|
+
switch(ev.target.id) {
|
570
|
+
case 'requesttab': detailPanels.show('request'); break;
|
571
|
+
case 'responsetab': detailPanels.show('response'); break;
|
572
|
+
case 'decisiontab': detailPanels.show('decision'); break;
|
573
|
+
default: ensureVisible();
|
574
|
+
}
|
575
|
+
} else {
|
576
|
+
var name = 'none';
|
577
|
+
switch(ev.target.id) {
|
578
|
+
case 'requesttab': name = 'request'; break;
|
579
|
+
case 'responsetab': name = 'response'; break;
|
580
|
+
case 'decisiontab': name = 'decision'; break;
|
581
|
+
}
|
582
|
+
|
583
|
+
if (panels[name] && panels[name].style.display != 'block')
|
584
|
+
detailPanels.show(name);
|
585
|
+
else
|
586
|
+
detailPanels.hide();
|
587
|
+
}
|
588
|
+
|
589
|
+
return false;
|
590
|
+
};
|
591
|
+
|
592
|
+
document.onmousemove = function(ev) {
|
593
|
+
if (md) {
|
594
|
+
dragged = true;
|
595
|
+
panelWidth = windowWidth-(ev.clientX-msoff);
|
596
|
+
if (panelWidth < 0) {
|
597
|
+
panelWidth = 0;
|
598
|
+
infoPanel.style.left = windowWidth+"px";
|
599
|
+
}
|
600
|
+
else if (panelWidth > windowWidth-21) {
|
601
|
+
panelWidth = windowWidth-21;
|
602
|
+
infoPanel.style.left = '21px';
|
603
|
+
}
|
604
|
+
else
|
605
|
+
infoPanel.style.left = (ev.clientX-msoff)+"px";
|
606
|
+
|
607
|
+
canvas.style.marginRight = panelWidth+20+"px";
|
608
|
+
return false;
|
609
|
+
}
|
610
|
+
};
|
611
|
+
|
612
|
+
document.onmouseup = function() { md = false; };
|
613
|
+
|
614
|
+
window.onresize = function() {
|
615
|
+
windowWidth = document.getElementById('sizetest').clientWidth;
|
616
|
+
infoPanel.style.left = windowWidth-panelWidth+'px';
|
617
|
+
};
|
618
|
+
|
619
|
+
detailPanels.setDecision(trace[0]);
|
620
|
+
};
|
621
|
+
|
622
|
+
window.onload = function() {
|
623
|
+
canvas = document.getElementById('v3map');
|
624
|
+
|
625
|
+
initDetailPanels();
|
626
|
+
|
627
|
+
var scale = 0.25;
|
628
|
+
var coy = canvas.offsetTop;
|
629
|
+
function findDecision(ev) {
|
630
|
+
var x = (ev.clientX+window.pageXOffset)/scale;
|
631
|
+
var y = (ev.clientY+window.pageYOffset-coy)/scale;
|
632
|
+
|
633
|
+
for (var i = trace.length-1; i >= 0; i--) {
|
634
|
+
if (x >= trace[i].x-19 && x <= trace[i].x+19 &&
|
635
|
+
y >= trace[i].y-19 && y <= trace[i].y+19)
|
636
|
+
return trace[i];
|
637
|
+
}
|
638
|
+
};
|
639
|
+
|
640
|
+
var preview = document.getElementById('preview');
|
641
|
+
var previewId = document.getElementById('previewid');
|
642
|
+
var previewCalls = document.getElementById('previewcalls');
|
643
|
+
function previewDecision(dec) {
|
644
|
+
preview.style.left = (dec.x*scale)+'px';
|
645
|
+
preview.style.top = (dec.y*scale+coy+15)+'px';
|
646
|
+
preview.style.display = 'block';
|
647
|
+
previewId.textContent = dec.d;
|
648
|
+
|
649
|
+
previewCalls.innerHTML = dec.previewCalls;
|
650
|
+
};
|
651
|
+
|
652
|
+
function overResponse(ev) {
|
653
|
+
var x = (ev.clientX+window.pageXOffset)/scale;
|
654
|
+
var y = (ev.clientY+window.pageYOffset-coy)/scale;
|
655
|
+
|
656
|
+
return (x >= response.x-(response.width/2)
|
657
|
+
&& x <= response.x+(response.width/2)
|
658
|
+
&& y >= response.y-19 && y <= response.y+19);
|
659
|
+
};
|
660
|
+
|
661
|
+
decorateTrace();
|
662
|
+
|
663
|
+
var bg = new Image(3138, 2184);
|
664
|
+
|
665
|
+
function drawMap() {
|
666
|
+
var ctx = canvas.getContext("2d");
|
667
|
+
|
668
|
+
ctx.save();
|
669
|
+
ctx.scale(1/scale, 1/scale);
|
670
|
+
ctx.fillStyle = '#ffffff';
|
671
|
+
ctx.fillRect(0, 0, 3138, 2184);
|
672
|
+
ctx.restore();
|
673
|
+
|
674
|
+
ctx.drawImage(bg, 0, 0);
|
675
|
+
drawTrace();
|
676
|
+
};
|
677
|
+
|
678
|
+
bg.onload = function() {
|
679
|
+
canvas.getContext("2d").scale(scale, scale);
|
680
|
+
|
681
|
+
document.getElementById('zoomin').onclick = function() {
|
682
|
+
scale = scale*2;
|
683
|
+
canvas.getContext("2d").scale(2, 2);
|
684
|
+
drawMap();
|
685
|
+
};
|
686
|
+
|
687
|
+
document.getElementById('zoomout').onclick = function() {
|
688
|
+
scale = scale/2;
|
689
|
+
canvas.getContext("2d").scale(0.5, 0.5);
|
690
|
+
drawMap();
|
691
|
+
};
|
692
|
+
|
693
|
+
drawMap(scale);
|
694
|
+
|
695
|
+
canvas.onmousemove = function(ev) {
|
696
|
+
if (findDecision(ev)) {
|
697
|
+
canvas.style.cursor = 'pointer';
|
698
|
+
previewDecision(findDecision(ev));
|
699
|
+
}
|
700
|
+
else {
|
701
|
+
preview.style.display = 'none';
|
702
|
+
if (overResponse(ev))
|
703
|
+
canvas.style.cursor = 'pointer';
|
704
|
+
else
|
705
|
+
canvas.style.cursor = 'default';
|
706
|
+
}
|
707
|
+
};
|
708
|
+
|
709
|
+
canvas.onclick = function(ev) {
|
710
|
+
var dec = findDecision(ev);
|
711
|
+
if (dec) {
|
712
|
+
detailPanels.setDecision(dec);
|
713
|
+
detailPanels.show('decision');
|
714
|
+
} else if (overResponse(ev)) {
|
715
|
+
detailPanels.show('response');
|
716
|
+
}
|
717
|
+
};
|
718
|
+
};
|
719
|
+
|
720
|
+
bg.onerror = function() {
|
721
|
+
alert('Failed to load background image.');
|
722
|
+
};
|
723
|
+
|
724
|
+
bg.src = 'static/map.png';
|
725
|
+
};
|