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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +49 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +235 -0
- data/LICENSE.txt +21 -0
- data/README.md +135 -0
- data/Rakefile +27 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hyper-console.gemspec +34 -0
- data/lib/hyper-console-client.js +90197 -0
- data/lib/hyper-console-client.min.js +1914 -0
- data/lib/hyper-console.rb +14 -0
- data/lib/hyperloop/console/client_components.rb +244 -0
- data/lib/hyperloop/console/engine.rb +11 -0
- data/lib/hyperloop/console/evaluate.rb +18 -0
- data/lib/hyperloop/console/hyper-console-client-manifest.rb +26 -0
- data/lib/hyperloop/console/hyper_console.rb +100 -0
- data/lib/hyperloop/console/object_space.rb +53 -0
- data/lib/hyperloop/console/ruby.js +285 -0
- data/lib/hyperloop/console/sources/codemirror.js +9231 -0
- data/lib/hyperloop/console/sources/jquery-2.1.4.js +9210 -0
- data/lib/hyperloop/console/sources/react-dom.js +18939 -0
- data/lib/hyperloop/console/sources/react.js +3987 -0
- data/lib/hyperloop/console/version.rb +5 -0
- data/lib/react/config/client.rb +21 -0
- data/screen_shot.png +0 -0
- data/vendor/assets/stylesheets/hyper-console-client.css +559 -0
- metadata +255 -0
@@ -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,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
|