hyper-console 0.1.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,14 @@
1
+ require "hyperloop/console/version"
2
+ require 'hyper-store'
3
+ require 'hyper-operation'
4
+ require 'hyperloop/console/hyper_console'
5
+ require 'hyperloop/console/evaluate'
6
+ Hyperloop.import 'hyper-console'
7
+
8
+ if RUBY_ENGINE == 'opal'
9
+ require 'hyperloop/console/object_space'
10
+ require 'securerandom'
11
+ else
12
+ require 'hyperloop/console/engine'
13
+ Opal.append_path File.expand_path('../', __FILE__).untaint
14
+ end
@@ -0,0 +1,244 @@
1
+ class DebugConsole < Hyperloop::Component
2
+ param :application_window_id
3
+ param :context
4
+ param :title
5
+
6
+ state history: [], scope: :shared
7
+ state console_state: :waiting_for_code, scope: :shared
8
+
9
+ class << self
10
+ attr_accessor :application_window_id
11
+ attr_accessor :context
12
+
13
+ def console_id
14
+ @console_id ||=
15
+ (
16
+ `sessionStorage.getItem('Hyperloop::Console::ConsoleId')` ||
17
+ SecureRandom.uuid.tap { |id| `sessionStorage.setItem('Hyperloop::Console::ConsoleId', #{id})` }
18
+ )
19
+ end
20
+
21
+ def ready!(code, compiled_code)
22
+ @code_to_send = code
23
+ mutate.error_message nil
24
+ evaluate compiled_code if @code_to_send =~ /.\n$/
25
+ end
26
+
27
+ def evaluate(compiled_code)
28
+ @code_to_send = @code_to_send[0..-2]
29
+ mutate.history << {eval: @code_to_send}
30
+ mutate.console_state :sending
31
+ Evaluate.run target_id: application_window_id, sender_id: console_id, context: context, string: compiled_code
32
+ end
33
+ end
34
+
35
+ after_mount do
36
+ DebugConsole.application_window_id = params.application_window_id
37
+ DebugConsole.context = params.context
38
+ mutate.history(JSON.parse(`sessionStorage.getItem('Hyperloop::Console.history')` || '[]'))
39
+ `document.title = #{params.title}`
40
+ `window.scrollTo(window.scrollX, 99999)`
41
+
42
+ Response.on_dispatch do |p|
43
+ next unless p.target_id == DebugConsole.console_id
44
+ mutate.console_state :waiting_for_code
45
+ mutate.history << {p.kind => p.message}
46
+ `sessionStorage.setItem('Hyperloop::Console.history', #{state.history.to_json})`
47
+ end
48
+
49
+ end
50
+
51
+ after_update do
52
+ `window.scrollTo(window.scrollX, 99999)`
53
+ end
54
+
55
+ render(DIV, style: { height: '100vh' }) do
56
+ # the outer DIV is needed to set fill the window so mouse clicks anywhere
57
+ # will go to the last line of the editor
58
+ DIV(class: :card) do
59
+ state.history.each do |item|
60
+ CodeHistoryItem(item_type: item.first.first, value: item.first.last || '')
61
+ end
62
+ CodeEditor(history: state.history, context: params.context) unless state.console_state == :sending
63
+ end.on(:mouse_down) do |e|
64
+ CodeEditor.set_focus
65
+ e.prevent_default
66
+ end
67
+ end
68
+ end
69
+
70
+ class CodeMirror < Hyperloop::Component
71
+
72
+ attr_reader :editor
73
+
74
+ after_mount do
75
+ @editor = `CodeMirror(#{dom_node}, {
76
+ value: #{@code.to_s},
77
+ mode: 'text/x-ruby',
78
+ matchBrackets: true,
79
+ lineNumbers: false,
80
+ indentUnit: 2,
81
+ theme: 'github',
82
+ readOnly: #{!!@read_only}
83
+ });`
84
+ end
85
+
86
+ render(DIV)
87
+
88
+ end
89
+
90
+ class CodeHistoryItem < CodeMirror
91
+
92
+ param :item_type
93
+ param :value
94
+
95
+ def format
96
+ case params.item_type
97
+ when :eval
98
+ params.value
99
+ when :exception
100
+ format_lines("!!", params.value)
101
+ when :result
102
+ format_lines(">>", params.value)
103
+ else
104
+ format_lines("#{params.item_type}:", params.value)
105
+ end
106
+ end
107
+
108
+ def format_lines(prefix, value)
109
+ fill = prefix.gsub(/./, ' ')
110
+ lines = value.split("\n")
111
+ (["#{prefix} #{lines.shift}"]+lines.collect do |line|
112
+ "#{fill} #{line}"
113
+ end).join("\n")
114
+ end
115
+
116
+ def mark_response_lines
117
+ return if params.item_type == :eval
118
+ lines = params.value.split("\n")
119
+ padding = [:exception, :result].include?(params.item_type) ? 3 : 2+params.item_type.to_s.length
120
+ last_line = lines[-1] || ''
121
+ `#{@editor}.markText({line: 0, ch: 0}, {line: #{lines.count-1}, ch: #{last_line.length+padding}}, {className: 'response-line'})`
122
+ end
123
+
124
+ before_mount do
125
+ @code = format
126
+ @read_only = true
127
+ end
128
+
129
+ after_mount do
130
+ mark_response_lines
131
+ end
132
+
133
+ render do
134
+ DIV {}.on(:mouse_down) { |e| e.stop_propagation }
135
+ end
136
+
137
+ end
138
+
139
+ class CodeEditor < CodeMirror
140
+ param :history
141
+ param :context
142
+
143
+ class << self
144
+ attr_accessor :editor
145
+
146
+ def set_focus
147
+ editor.set_focus_at_end if editor
148
+ end
149
+ end
150
+
151
+ before_mount do
152
+ @history = params.history.collect { |item| item[:eval] }.compact + ['']
153
+ @history_pos = @history.length-1
154
+ end
155
+
156
+ after_mount do
157
+ `#{@editor}.on('change', #{lambda {on_change} })`
158
+ `#{@editor}.on('keydown', #{lambda { |cm, e| on_key_down(cm, e) } })`
159
+ `#{@editor}.focus()`
160
+ self.class.editor = self
161
+ end
162
+
163
+ before_unmount do
164
+ self.class.editor = nil
165
+ end
166
+
167
+ def move_history(n)
168
+ @history[@history_pos] = `#{@editor}.getValue()`
169
+ @history_pos += n
170
+ text = @history[@history_pos]
171
+ lines = text.split("\n")
172
+ `#{@editor}.setValue(#{text})`
173
+
174
+ after(0) do
175
+ `window.scrollTo(window.scrollX, 99999)`
176
+ `#{@editor}.setCursor({line: #{lines.count}, ch: #{(lines.last || '').length}})`
177
+ end
178
+ end
179
+
180
+ def set_focus_at_end
181
+ `#{@editor}.focus()`
182
+ lines = `#{@editor}.getValue()`.split("\n")
183
+ `#{@editor}.setCursor({line: #{lines.count}, ch: #{(lines.last || '').length}})`
184
+ end
185
+
186
+
187
+ def on_key_down(cm, e)
188
+ if `e.metaKey` || `e.ctrlKey`
189
+ return unless `e.keyCode` == 13
190
+ `#{@editor}.setValue(#{@editor}.getValue()+#{"\n"})`
191
+ #on_change
192
+ elsif `e.key` == 'ArrowUp'
193
+ move_history(-1) if @history_pos > 0
194
+ elsif `e.key` == 'ArrowDown'
195
+ move_history(1) if @history_pos < @history.length-1
196
+ end
197
+ end
198
+
199
+ def mark_error(message)
200
+ from, to, title = parse_error(message)
201
+ @error = `#{@editor}.markText(#{from.to_n}, #{to.to_n}, {className: 'syntax-error', title: title})`
202
+ end
203
+
204
+ def parse_error(message)
205
+ message = message.split("\n")
206
+ position_info = message[3].split(':')
207
+ last_line_number = `#{@editor}.lineCount()`
208
+ last_line = `#{@editor}.getLine(last_line_number-1)`
209
+ [
210
+ {line: 0, ch: 0},
211
+ {line: last_line_number, ch: last_line.length},
212
+ message[2].split(':(file)')[0]
213
+ ]
214
+ end
215
+
216
+ def clear_error
217
+ `#{@error}.clear()` if @error
218
+ @error = nil
219
+ end
220
+
221
+ def on_change
222
+ clear_error
223
+ code = `#{@editor}.getValue()`
224
+ if params.context.empty?
225
+ compiled_code = Opal.compile(code, irb: true)
226
+ else
227
+ compiled_code = Opal.compile("#{params.context}.instance_eval {#{code}}")
228
+ end
229
+ DebugConsole.ready!(code, compiled_code)
230
+ rescue Exception => e
231
+ mark_error(e.message)
232
+ end
233
+
234
+ render do
235
+ DIV {}.on(:mouse_down) { |e| e.stop_propagation }
236
+ end
237
+ end
238
+
239
+ Document.ready? do
240
+ mount_point = Element["[data-react-class='React.TopLevelRailsComponent']"]
241
+ mount_point.render do
242
+ DebugConsole(JSON.parse(mount_point.attr('data-react-props'))[:render_params])
243
+ end
244
+ end
@@ -0,0 +1,11 @@
1
+ module Hyperloop
2
+ module Console
3
+ module Rails
4
+ class Engine < ::Rails::Engine
5
+ initializer "static assets" do |app|
6
+ app.middleware.use ::ActionDispatch::Static, "#{root}/public"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class Evaluate < Hyperloop::ServerOp
2
+ param acting_user: nil, nils: true
3
+ param :target_id
4
+ param :sender_id
5
+ param :context
6
+ param string: nil
7
+
8
+ dispatch_to { Hyperloop::Application }
9
+ end
10
+
11
+ class Response < Hyperloop::ServerOp
12
+ param acting_user: nil, nils: true
13
+ param :target_id
14
+ param :kind
15
+ param :message
16
+
17
+ dispatch_to { Hyperloop::Application }
18
+ end
@@ -0,0 +1,26 @@
1
+ require 'opal'
2
+ require 'browser/interval'
3
+ require 'browser/delay'
4
+ require 'opal-jquery'
5
+ #require 'reactrb/auto-import'
6
+ module Hyperloop
7
+ class Component
8
+ VERSION = "0.12.4"
9
+ end
10
+ end
11
+ require 'hyper-component'
12
+ require 'hyper-operation'
13
+ require 'hyper-store'
14
+ require 'react/top_level_render'
15
+ require 'opal/compiler'
16
+ require 'active_support'
17
+ require 'securerandom'
18
+ require 'hyperloop/console/evaluate'
19
+ require 'hyperloop/console/sources/codemirror'
20
+ require 'hyperloop/console/sources/jquery-2.1.4'
21
+ require 'hyperloop/console/sources/react'
22
+ require 'hyperloop/console/sources/react-dom'
23
+ require 'hyperloop/console/ruby'
24
+ require 'hyperloop/console/client_components'
25
+ #require 'action_cable'
26
+ require 'hyperloop/pusher'
@@ -0,0 +1,100 @@
1
+ if RUBY_ENGINE=='opal'
2
+ module Kernel
3
+ def console(title: nil, context: nil)
4
+ Hyperloop::Console.console(title: title, context: context)
5
+ end
6
+ end
7
+
8
+ module Hyperloop
9
+ module Console
10
+ def self.console_id
11
+ @console_id ||= (
12
+ `sessionStorage.getItem('Hyperloop::Console::MainWindowId')` ||
13
+ SecureRandom.uuid.tap { |id| `sessionStorage.setItem('Hyperloop::Console::MainWindowId', #{id})` }
14
+ )
15
+ end
16
+
17
+ def self.messenger(target_id, kind, m)
18
+ Response.run(target_id: target_id, kind: kind, message: m.gsub(/\n$/, ''))
19
+ end
20
+
21
+ def self.console(title: nil, context: nil)
22
+ title ||= context || 'Hyperloop Application Console'
23
+ title = `encodeURIComponent(#{title})`
24
+ context = `encodeURIComponent(#{context})`
25
+ location = "/hyperloop/hyperloop-debug-console/#{console_id}?context=#{context}&title=#{title}"
26
+ window_params = 'width=640,height=460,scrollbars=no,location=0'
27
+ `window.open(#{location}, #{"window#{title}"}, #{window_params}).focus()`
28
+ @__console_dispatcher__ ||= Evaluate.on_dispatch do |params|
29
+ if params.target_id == console_id
30
+ cwarn = `console.warn`
31
+ clog = `console.log`
32
+ cerror = `console.error`
33
+ cinfo = `console.info`
34
+ `console.warn = function(m) { #{messenger(params.sender_id, :warn, `m`)} }`
35
+ `console.log = function(m) { #{messenger(params.sender_id, :log, `m`)} }`
36
+ `console.error = function(m) { #{messenger(params.sender_id, :error, `m`)} }`
37
+ `console.info = function(m) { #{messenger(params.sender_id, :info, `m`)} }`
38
+ begin
39
+ message = `eval(#{params.string})`.inspect
40
+ Response.run(target_id: params.sender_id, kind: :result, message: message || '')
41
+ rescue Exception => e
42
+ Response.run(target_id: params.sender_id, kind: :exception, message: e.to_s)
43
+ ensure
44
+ `console.warn = cwarn`
45
+ `console.log = clog`
46
+ `console.error = cerror`
47
+ `console.info = cinfo`
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ self
53
+ end
54
+ `window.hyperconsole = function() { Opal.Opal.$console() }`
55
+ Hyperloop::Application::Boot.on_dispatch do
56
+ @console ||= console if `window.HyperloopConsoleAutoStart`
57
+ end
58
+ end
59
+ end
60
+ else
61
+ module Hyperloop
62
+ define_setting :console_auto_start, true
63
+
64
+ module Console
65
+ class Config
66
+ include React::IsomorphicHelpers
67
+ prerender_footer do |controller|
68
+ "<script type='text/javascript'>\n"\
69
+ "window.HyperloopConsoleAutoStart = #{!!Hyperloop.console_auto_start};\n"\
70
+ "</script>\n"
71
+ end
72
+ end
73
+ end
74
+
75
+ ::Hyperloop::Engine.routes.append do
76
+ Hyperloop.initialize_policies
77
+
78
+ HyperloopController.class_eval do
79
+ def opal_debug_console
80
+ console_params = { application_window_id: params[:application_window_id], context: params[:context], title: params[:title] }
81
+ render inline:
82
+ "<!DOCTYPE html>\n"\
83
+ "<html>\n"\
84
+ " <head>\n"\
85
+ " <title>Hyperloop Console Loading...</title>\n"\
86
+ " <%= csrf_meta_tags %>\n"\
87
+ " <%= stylesheet_link_tag 'hyper-console-client' %>\n"\
88
+ " <%= javascript_include_tag 'hyper-console-client.min.js' %>\n"\
89
+ " <%= javascript_include_tag 'action_cable' %>\n"\
90
+ " </head>\n"\
91
+ " <body>\n"\
92
+ " <%= react_component '::DebugConsole', #{console_params}, { prerender: false } %>\n"\
93
+ " </body>\n"\
94
+ "</html>\n"
95
+ end
96
+ end
97
+ match 'hyperloop-debug-console/:application_window_id', to: 'hyperloop#opal_debug_console', via: :get
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,53 @@
1
+ class Object
2
+ def self.instance
3
+ ObjectSpace.objects(self).tap do |arr|
4
+ arr.define_singleton_method(:console) { |*args| method_missing(:console, *args) }
5
+ klass = self
6
+ arr.define_singleton_method(:method_missing) do |method, *args, &block|
7
+ if length > 1 && self[0].respond_to?(method)
8
+ raise "Ambiguous #{klass}.instance.#{method} application.\n"\
9
+ "There are #{length} instances of #{klass}.\n"\
10
+ "You can either apply #{method} to a specific instance using #{klass}.instance[x].#{method};\n"\
11
+ "or use an enumerator method like each or collect."
12
+ elsif length == 0
13
+ raise "There are no #{klass} instances."
14
+ else
15
+ self[0].send(method, *args, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ module ObjectSpace
23
+ def self.objects(klass)
24
+ objs = []
25
+ matching_objs = []
26
+ %x{
27
+ var walk_the_object = function(js_obj) {
28
+ var keys = Object.keys(js_obj) //get all own property names of the object
29
+
30
+ keys.forEach( function ( key ) {
31
+ if ( key != '$$proto' ) {
32
+ var value = js_obj[ key ]; // get property value
33
+
34
+ // if the property value is an object...
35
+ if ( value && typeof value === 'object' ) {
36
+
37
+ // if we don't have this reference, and its an object of the class we want
38
+ if ( #{objs}.indexOf( value ) < 0 ) {
39
+ #{objs}.push( value ); // store the reference
40
+ if ( Object.keys(value).indexOf('$$id') >= 0 && value.$$class == #{klass} ) {
41
+ #{matching_objs}.push ( value )
42
+ }
43
+ walk_the_object(value) // traverse all its own properties
44
+ }
45
+ }
46
+ }
47
+ })
48
+ }
49
+ walk_the_object(window)
50
+ }
51
+ matching_objs
52
+ end
53
+ end