forked from replit-archive/repl.it
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dom.coffee
377 lines (338 loc) · 13.4 KB
/
dom.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# Core module.
# Responsible for DOM initializations, and most interactions.
DEFAULT_CONTENT_PADDING = 200
FOOTER_HEIGHT = 30
HEADER_HEIGHT = 61
RESIZER_WIDTH = 8
DEFAULT_SPLIT = 0.5
CONSOLE_HIDDEN = 1
EDITOR_HIDDEN = 0
SNAP_THRESHOLD = 0.05
ANIMATION_DURATION = 700
MIN_PROGRESS_DURATION = 1
MAX_PROGRESS_DURATION = 1500
PROGRESS_ANIMATION_DURATION = 2000
SOCIAL_BUTTONS_DELAY = 20000
$ = jQuery
# jQuery plugin to disable text selection (x-browser).
# Used for dragging the resizer.
$.fn.disableSelection = ->
@each ->
$this = $(this)
$this.attr 'unselectable', 'on'
$this.css
'-moz-user-select':'none'
'-webkit-user-select':'none'
'user-select':'none'
$this.each -> this.onselectstart = -> return false
# jQuery plugin to enable text selection (x-browser).
$.fn.enableSelection = ->
@each ->
$this = $(this)
$this.attr 'unselectable', ''
$this.css
'-moz-user-select': ''
'-webkit-user-select': ''
'user-select': ''
$this.each -> this.onselectstart = null
$.extend REPLIT,
RESIZER_WIDTH: RESIZER_WIDTH
CONSOLE_HIDDEN: CONSOLE_HIDDEN
EDITOR_HIDDEN: EDITOR_HIDDEN
DEFAULT_CONTENT_PADDING: DEFAULT_CONTENT_PADDING
split_ratio: if REPLIT.ISMOBILE then EDITOR_HIDDEN else DEFAULT_SPLIT
# NOTE: These should be synced with PAGES.workspace.width in pager.coffee.
min_content_width: 500
max_content_width: 3000
content_padding: DEFAULT_CONTENT_PADDING
last_progress_ratio: 0
# Initialize the DOM (Runs before JSRPEL's load)
InitDOM: ->
@$doc_elem = $ 'html'
# The main container holding the pages.
@$container = $ '#main'
# The container holding the editor widget and related elements.
@$editorContainer = $ '#editor'
# The container holding the console widget and related elements.
@$consoleContainer = $ '#console'
# An object holding all the resizer elements.
@$resizer =
l: $ '#resize-left'
c: $ '#resize-center'
r: $ '#resize-right'
# The loading progress bar.
@$progress = $ '#progress'
@$progressFill = $ '#progress-fill'
# An object holding unhider elements.
@$unhider =
editor: $ '#unhide-right'
console: $ '#unhide-left'
# Show the run button on hover.
@$run = $ '#editor-run'
@$editorContainer.mouseleave =>
@$run.fadeIn 'fast'
@$editorContainer.mousemove =>
if @$run.is ':hidden' then @$run.fadeIn 'fast'
@$editorContainer.keydown =>
@$run.fadeOut 'fast'
# Initialaize the column resizers.
@InitSideResizers()
@InitCenterResizer()
# Attatch unhiders functionality.
@InitUnhider()
# Fire the onresize method to do initial resizing
@OnResize()
# When the window change size, call the container's resizer.
mobile_timer = null
$(window).bind 'resize', =>
if @ISMOBILE
mobile_timer = clearTimeout mobile_timer
cb = =>
width = document.documentElement.clientWidth
REPLIT.min_content_width = width - 2 * RESIZER_WIDTH
@OnResize()
mobile_timer = setTimeout (=> @OnResize()), 300
else
@OnResize()
# Attatches the resizers behaviors.
InitSideResizers: ->
$body = $ 'body'
# For all resizers discard right clicks,
# disable text selection on drag start.
for _, $elem of @$resizer
$elem.mousedown (e) ->
if e.button != 0
e.stopImmediatePropagation()
else
$body.disableSelection()
# On start drag bind the mousemove functionality for right/left resizers.
@$resizer.l.mousedown (e) =>
$body.bind 'mousemove.side_resizer', (e) =>
# The horizontal mouse position is simply half of the content_padding.
# Subtract half of the resizer_width for better precision.
@content_padding = ((e.pageX - (RESIZER_WIDTH / 2)) * 2)
if @content_padding / $body.width() < SNAP_THRESHOLD
@content_padding = 0
@OnResize()
@$resizer.r.mousedown (e) =>
$body.bind 'mousemove.side_resizer', (e) =>
# The mouse is on the right of the container, subtracting the horizontal
# position from the page width to get the right number.
@content_padding = ($body.width() - e.pageX - (RESIZER_WIDTH / 2)) * 2
if @content_padding / $body.width() < SNAP_THRESHOLD
@content_padding = 0
@OnResize()
# When stopping the drag unbind the mousemove handlers and enable selection.
resizer_lr_release = ->
$body.enableSelection()
$body.unbind 'mousemove.side_resizer'
@$resizer.l.mouseup resizer_lr_release
@$resizer.r.mouseup resizer_lr_release
$body.mouseup resizer_lr_release
InitCenterResizer: ->
# When stopping the drag or when the editor/console snaps into hiding,
# unbind the mousemove event for the container.
resizer_c_release = =>
@$container.enableSelection()
@$container.unbind 'mousemove.center_resizer'
# When start drag for the center resizer bind the resize logic.
@$resizer.c.mousedown (e) =>
@$container.bind 'mousemove.center_resizer', (e) =>
# Get the mouse position relative to the container.
left = e.pageX - (@content_padding / 2) + (RESIZER_WIDTH / 2)
# The ratio of the editor-to-console is the relative mouse position
# divided by the width of the container.
@split_ratio = left / @$container.width()
# If the smaller split ratio as small as 0.5% then we must hide the element.
if @split_ratio > CONSOLE_HIDDEN - SNAP_THRESHOLD
@split_ratio = CONSOLE_HIDDEN
# Stop the resize drag.
resizer_c_release()
else if @split_ratio < EDITOR_HIDDEN + SNAP_THRESHOLD
@split_ratio = EDITOR_HIDDEN
# Stop the resize drag.
resizer_c_release()
# Run the window resize handler to recalculate everything.
@OnResize()
# Release when:
@$resizer.c.mouseup resizer_c_release
@$container.mouseup resizer_c_release
@$container.mouseleave resizer_c_release
InitUnhider: ->
# Show unhider on mouse movement and hide on keyboard interactions.
getUnhider = =>
if @split_ratio not in [CONSOLE_HIDDEN, EDITOR_HIDDEN] then return $ []
side = if @split_ratio == CONSOLE_HIDDEN then 'console' else 'editor'
return @$unhider[side]
$('body').mousemove =>
unhider = getUnhider()
if unhider.is ':hidden' then unhider.fadeIn 'fast'
@$container.keydown =>
unhider = getUnhider()
if unhider.is ':visible' then unhider.fadeOut 'fast'
bindUnhiderClick = ($elem, $elemtoShow) =>
$elem.click (e) =>
# Hide the unhider.
$elem.hide()
# Set the split ratio to the default split.
@split_ratio = DEFAULT_SPLIT
# Show the hidden element.
$elemtoShow.show()
# Show the center resizer.
@$resizer.c.show()
# Recalculate all sizes.
@OnResize()
bindUnhiderClick @$unhider.editor, @$editorContainer
bindUnhiderClick @$unhider.console, @$consoleContainer
# Updates the progress bar's width and color.
OnProgress: (percentage) ->
ratio = percentage / 100.0
# TODO: Find out why this happens.
if ratio < @last_progress_ratio then return
duration = (ratio - @last_progress_ratio) * PROGRESS_ANIMATION_DURATION
@last_progress_ratio = ratio
duration = Math.max(duration, MIN_PROGRESS_DURATION)
duration = Math.min(duration, MAX_PROGRESS_DURATION)
fill = @$progressFill
fill.animate width: percentage + '%',
duration: Math.abs(duration),
easing: 'linear',
step: (now, fx) ->
ratio = now / 100.0
# A hardcoded interpolation equation between:
# red orange yellow green
# top: #fa6e43 -> #fab543 -> #fad643 -> #88f20d
# bottom: #f2220c -> #f26c0c -> #f2a40c -> #c7fa44
red_top = Math.round(if ratio < 0.75
250
else
250 + (199 - 250) * ((ratio - 0.75) / 0.25))
red_bottom = Math.round(if ratio < 0.75
242
else
250 + (136 - 250) * ((ratio - 0.75) / 0.25))
green_top = Math.round(if ratio < 0.25
110 + (181 - 110) * (ratio / 0.25)
else
181 + (250 - 181) * ((ratio - 0.25) / 0.75))
green_bottom = Math.round(34 + (242 - 34) * ratio)
blue_top = 67
blue_bottom = 12
top = "rgb(#{red_top}, #{green_top}, #{blue_top})"
bottom = "rgb(#{red_bottom}, #{green_bottom}, #{blue_bottom})"
if $.browser.webkit
fill.css 'background-image': "url('/images/progress.png'), -webkit-gradient(linear, left top, left bottom, from(#{top}), to(#{bottom}))"
else if $.browser.mozilla
fill.css 'background-image': "url('/images/progress.png'), -moz-linear-gradient(top, #{top}, #{bottom})"
else if $.browser.opera
fill.css 'background-image': "url('/images/progress.png'), -o-linear-gradient(top, #{top}, #{bottom})"
fill.css 'background-image': "url('/images/progress.png'), linear-gradient(top, #{top}, #{bottom})"
# Resize containers on each window resize, split ratio change or
# content padding change.
OnResize: ->
# Calculate container height and width.
documentWidth = document.documentElement.clientWidth
documentHeight = document.documentElement.clientHeight
height = documentHeight - HEADER_HEIGHT - FOOTER_HEIGHT
width = documentWidth - @content_padding
innerWidth = width - 2 * RESIZER_WIDTH
# Clamp width.
if innerWidth < @min_content_width
innerWidth = @min_content_width
else if innerWidth > @max_content_width
innerWidth = @max_content_width
width = innerWidth + 2 * RESIZER_WIDTH
# Resize container and current page.
@$container.css
width: width
if @ISMOBILE and not $('.page:visible').is '#content-workspace'
@$container.css 'height', 'auto'
else
@$container.css 'height', height
$('.page:visible').css
width: innerWidth
if $('.page:visible').is '#content-workspace'
@ResizeWorkspace innerWidth, height
ResizeWorkspace: (innerWidth, height) ->
# Calculate editor and console sizes.
editor_width = Math.floor @split_ratio * innerWidth
console_width = innerWidth - editor_width
if @split_ratio not in [CONSOLE_HIDDEN, EDITOR_HIDDEN]
editor_width -= RESIZER_WIDTH / 2
console_width -= RESIZER_WIDTH / 2
# Apply the new sizes.
@$resizer.c.css
left: editor_width
@$editorContainer.css
width: editor_width
height: height
@$consoleContainer.css
width: console_width
height: height
# Check if console/editor was meant to be hidden.
if @split_ratio == CONSOLE_HIDDEN
@$consoleContainer.hide()
@$resizer.c.hide()
@$unhider.console.show()
else if @split_ratio == EDITOR_HIDDEN
@$editorContainer.hide()
@$resizer.c.hide()
@$unhider.editor.show()
# Calculate paddings if any.
console_hpadding = @$console.innerWidth() - @$console.width()
console_vpadding = @$console.innerHeight() - @$console.height()
editor_hpadding = @$editor.innerWidth() - @$editor.width()
editor_vpadding = @$editor.innerHeight() - @$editor.height()
# Resize the console/editor widgets.
@$console.css 'width', @$consoleContainer.width() - console_hpadding
@$console.css 'height', @$consoleContainer.height() - console_vpadding
@$editor.css 'width', @$editorContainer.innerWidth() - editor_hpadding
@$editor.css 'height', @$editorContainer.innerHeight() - editor_vpadding
# Call to Ace editor resize.
@editor.resize() if not @ISMOBILE
InjectSocial: ->
$rootDOM = $('#social-buttons-container')
$.getScript 'http://connect.facebook.net/en_US/all.js#appId=111098168994577&xfbml=1', ->
FB.init
appId: '111098168994577'
xfbxml: true
status: true
cookie: true
FB.XFBML.parse $rootDOM.get(0)
$.getScript 'http://platform.twitter.com/widgets.js', ->
$rootDOM.find('.twitter-share-button').show()
$.getScript 'https://apis.google.com/js/plusone.js'
$ ->
if REPLIT.ISIOS then $('html, body').css 'overflow', 'hidden'
REPLIT.$this.bind 'language_loading', (_, system_name) ->
REPLIT.$progress.animate opacity: 1, 'fast'
REPLIT.$progressFill.css width: 0
REPLIT.last_progress_ratio = 0
# Update footer links.
lang = REPLIT.Languages[system_name]
$about = $ '#language-about-link'
$engine = $ '#language-engine-link'
$links = $ '#language-engine-link, #language-about-link'
$links.animate opacity: 0, 'fast', ->
$about.text 'about ' + system_name.toLowerCase()
$about.attr href: lang.about_link
$engine.text system_name.toLowerCase() + ' engine'
$engine.attr href: lang.engine_link
$links.animate opacity: 1, 'fast'
REPLIT.$this.bind 'language_loaded', ->
REPLIT.OnProgress 100
REPLIT.$progress.animate opacity: 0, 'fast'
# When the device orientation change adapt the workspace to the new width.
check_orientation = ->
cb = ->
width = document.documentElement.clientWidth
REPLIT.min_content_width = width - 2 * RESIZER_WIDTH
REPLIT.OnResize()
# iPhone scrolls to the left when changing orientation to portrait.
$(window).scrollLeft 0
# Android takes time to know its own width!
setTimeout cb, 300
$(window).bind 'orientationchange', check_orientation
if REPLIT.ISMOBILE then check_orientation()
REPLIT.InitDOM()
$(window).bind 'load', -> setTimeout REPLIT.InjectSocial, SOCIAL_BUTTONS_DELAY