mirror of
https://github.com/atom/atom.git
synced 2026-02-18 18:34:21 -05:00
Merge branch 'collaboration-presence' into shared-buffers
Conflicts: src/app/edit-session.coffee src/app/project.coffee src/app/text-buffer.coffee src/app/window.coffee src/packages/collaboration/lib/bootstrap.coffee src/packages/collaboration/lib/session-utils.coffee vendor/telepath
This commit is contained in:
@@ -49,7 +49,9 @@ class Directory
|
||||
# pathToCheck - the {String} path to check.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
contains: (pathToCheck='') ->
|
||||
contains: (pathToCheck) ->
|
||||
return false unless pathToCheck
|
||||
|
||||
if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
true
|
||||
else if pathToCheck.indexOf(path.join(@getRealPath(), path.sep)) is 0
|
||||
@@ -62,7 +64,9 @@ class Directory
|
||||
# fullPath - The {String} path to convert.
|
||||
#
|
||||
# Returns a {String}.
|
||||
relativize: (fullPath='') ->
|
||||
relativize: (fullPath) ->
|
||||
return fullPath unless fullPath
|
||||
|
||||
if fullPath is @getPath()
|
||||
''
|
||||
else if fullPath.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
|
||||
@@ -319,7 +319,7 @@ class EditSession
|
||||
# Retrieves the current buffer's URI.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getUri: -> @getPath()
|
||||
getUri: -> @buffer.getUri()
|
||||
|
||||
# {Delegates to: Buffer.isRowBlank}
|
||||
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
|
||||
|
||||
@@ -10,6 +10,28 @@ GitUtils = require 'git-utils'
|
||||
# Ultimately, this is an overlay to the native [git-utils](https://github.com/atom/node-git) module.
|
||||
module.exports =
|
||||
class Git
|
||||
### Public ###
|
||||
# Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# refreshOnWindowFocus: A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
path: null
|
||||
statuses: null
|
||||
upstream: null
|
||||
@@ -57,20 +79,6 @@ class Git
|
||||
|
||||
### Public ###
|
||||
|
||||
# Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# refreshOnWindowFocus: A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
# Retrieves the git repository.
|
||||
#
|
||||
# Returns a new `Repository`.
|
||||
@@ -213,6 +221,14 @@ class Git
|
||||
# Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero.
|
||||
getLineDiffs: (path, text) -> @getRepo().getLineDiffs(@relativize(path), text)
|
||||
|
||||
getConfigValue: (key) -> @getRepo().getConfigValue(key)
|
||||
|
||||
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
|
||||
|
||||
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
|
||||
|
||||
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
|
||||
|
||||
### Internal ###
|
||||
|
||||
refreshStatus: ->
|
||||
|
||||
@@ -193,6 +193,7 @@ class Project
|
||||
#
|
||||
# Returns either an {EditSession} (for text) or {ImageEditSession} (for images).
|
||||
open: (filePath, options={}) ->
|
||||
filePath = @resolve(filePath) if filePath?
|
||||
for opener in @constructor.openers
|
||||
return resource if resource = opener(filePath, options)
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class RootView extends View
|
||||
# Returns the `EditSession` for the file URI.
|
||||
open: (path, options = {}) ->
|
||||
changeFocus = options.changeFocus ? true
|
||||
path = project.resolve(path) if path?
|
||||
path = project.relativize(path)
|
||||
if activePane = @getActivePane()
|
||||
editSession = activePane.itemForUri(path) ? project.open(path)
|
||||
activePane.showItem(editSession)
|
||||
|
||||
@@ -147,6 +147,9 @@ class TextBuffer
|
||||
getPath: ->
|
||||
@file?.getPath()
|
||||
|
||||
getUri: ->
|
||||
project?.relativize(@getPath()) ? @getPath()
|
||||
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
|
||||
@@ -74,11 +74,10 @@ class TextMateGrammar
|
||||
getScore: (filePath, contents) ->
|
||||
contents = fsUtils.read(filePath) if not contents? and fsUtils.isFileSync(filePath)
|
||||
|
||||
|
||||
if syntax.grammarOverrideForPath(filePath) is @scopeName
|
||||
2 + filePath.length
|
||||
2 + (filePath?.length ? 0)
|
||||
else if @matchesContents(contents)
|
||||
1 + filePath.length
|
||||
1 + (filePath?.length ? 0)
|
||||
else
|
||||
@getPathScore(filePath)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ dialog = require 'dialog'
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
net = require 'net'
|
||||
url = require 'url'
|
||||
|
||||
socketPath = '/tmp/atom.sock'
|
||||
|
||||
@@ -38,7 +39,7 @@ class AtomApplication
|
||||
installUpdate: null
|
||||
version: null
|
||||
|
||||
constructor: ({@resourcePath, pathsToOpen, @version, test, pidToKillWhenClosed, @dev, newWindow}) ->
|
||||
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, @dev, newWindow}) ->
|
||||
global.atomApplication = this
|
||||
|
||||
@pidsToOpenWindows = {}
|
||||
@@ -56,6 +57,8 @@ class AtomApplication
|
||||
@runSpecs({exitWhenDone: true, @resourcePath})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl(urlToOpen) for urlToOpen in urlsToOpen
|
||||
else
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow})
|
||||
@@ -124,6 +127,7 @@ class AtomApplication
|
||||
menus.push
|
||||
label: 'File'
|
||||
submenu: [
|
||||
{ label: 'New Window', accelerator: 'Command+N', click: => @openPath() }
|
||||
{ label: 'Open...', accelerator: 'Command+O', click: => @promptForPath() }
|
||||
{ label: 'Open In Dev Mode...', accelerator: 'Command+Shift+O', click: => @promptForPath(devMode: true) }
|
||||
]
|
||||
@@ -170,6 +174,10 @@ class AtomApplication
|
||||
event.preventDefault()
|
||||
@openPath({pathToOpen})
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) =>
|
||||
event.preventDefault()
|
||||
@openUrl(urlToOpen)
|
||||
|
||||
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdate) =>
|
||||
event.preventDefault()
|
||||
@installUpdate = quitAndUpdate
|
||||
@@ -239,6 +247,14 @@ class AtomApplication
|
||||
console.log("Killing process #{pid} failed: #{error.code}")
|
||||
delete @pidsToOpenWindows[pid]
|
||||
|
||||
openUrl: (urlToOpen) ->
|
||||
parsedUrl = url.parse(urlToOpen)
|
||||
if parsedUrl.host is 'session'
|
||||
sessionId = parsedUrl.path.split('/')[1]
|
||||
if sessionId
|
||||
bootstrapScript = 'collaboration/lib/bootstrap'
|
||||
new AtomWindow({bootstrapScript, @resourcePath, sessionId})
|
||||
|
||||
openConfig: ->
|
||||
if @configWindow
|
||||
@configWindow.focus()
|
||||
|
||||
@@ -7,6 +7,7 @@ fs = require 'fs'
|
||||
path = require 'path'
|
||||
optimist = require 'optimist'
|
||||
nslog = require 'nslog'
|
||||
dialog = require 'dialog'
|
||||
_ = require 'underscore'
|
||||
|
||||
console.log = (args...) ->
|
||||
@@ -17,11 +18,21 @@ require 'coffee-script'
|
||||
delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
args = parseCommandLine()
|
||||
|
||||
addPathToOpen = (event, filePath) ->
|
||||
addPathToOpen = (event, pathToOpen) ->
|
||||
event.preventDefault()
|
||||
args.pathsToOpen.push(filePath)
|
||||
args.pathsToOpen.push(pathToOpen)
|
||||
|
||||
args.urlsToOpen = []
|
||||
addUrlToOpen = (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-file', addPathToOpen
|
||||
app.on 'open-url', addUrlToOpen
|
||||
|
||||
app.on 'will-finish-launching', ->
|
||||
setupCrashReporter()
|
||||
@@ -29,6 +40,7 @@ delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
|
||||
app.on 'finish-launching', ->
|
||||
app.removeListener 'open-file', addPathToOpen
|
||||
app.removeListener 'open-url', addUrlToOpen
|
||||
|
||||
args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
|
||||
path.resolve(args.executedFrom ? process.cwd(), pathToOpen)
|
||||
|
||||
3
src/packages/archive-view/keymaps/archive-view.cson
Normal file
3
src/packages/archive-view/keymaps/archive-view.cson
Normal file
@@ -0,0 +1,3 @@
|
||||
'.archive-view':
|
||||
'k': 'core:move-up'
|
||||
'j': 'core:move-down'
|
||||
@@ -7,6 +7,7 @@ File = require 'file'
|
||||
module.exports=
|
||||
class ArchiveEditSession
|
||||
registerDeserializer(this)
|
||||
@version: 1
|
||||
|
||||
@activate: ->
|
||||
Project = require 'project'
|
||||
@@ -14,10 +15,11 @@ class ArchiveEditSession
|
||||
new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)
|
||||
|
||||
@deserialize: ({path}={}) ->
|
||||
path = project.resolve(path)
|
||||
if fsUtils.isFileSync(path)
|
||||
new ArchiveEditSession(path)
|
||||
else
|
||||
console.warn "Could not build edit session for path '#{path}' because that file no longer exists"
|
||||
console.warn "Could not build archive edit session for path '#{path}' because that file no longer exists"
|
||||
|
||||
constructor: (@path) ->
|
||||
@file = new File(@path)
|
||||
@@ -27,7 +29,7 @@ class ArchiveEditSession
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'ArchiveEditSession'
|
||||
path: @path
|
||||
path: @getUri()
|
||||
|
||||
getViewClass: ->
|
||||
require './archive-view'
|
||||
@@ -38,7 +40,7 @@ class ArchiveEditSession
|
||||
else
|
||||
'untitled'
|
||||
|
||||
getUri: -> @path
|
||||
getUri: -> project?.relativize(@getPath()) ? @getPath()
|
||||
|
||||
getPath: -> @path
|
||||
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
require 'atom'
|
||||
require 'window'
|
||||
|
||||
$ = require 'jquery'
|
||||
{$$} = require 'space-pen'
|
||||
{createPeer, connectDocument} = require './session-utils'
|
||||
{createSite, Document} = require 'telepath'
|
||||
GuestSession = require './guest-session'
|
||||
|
||||
window.setDimensions(width: 350, height: 100)
|
||||
window.setDimensions(width: 350, height: 125)
|
||||
window.setUpEnvironment('editor')
|
||||
{sessionId} = atom.getLoadSettings()
|
||||
|
||||
loadingView = $$ ->
|
||||
@div style: 'margin: 10px; text-align: center', =>
|
||||
@div "Joining session #{sessionId}"
|
||||
@div style: 'margin: 10px', =>
|
||||
@h4 style: 'text-align: center', 'Joining Session'
|
||||
@div class: 'progress progress-striped active', style: 'margin-bottom: 10px', =>
|
||||
@div class: 'progress-bar', style: 'width: 0%'
|
||||
@div class: 'progress-bar-message', 'Establishing connection\u2026'
|
||||
$(window.rootViewParentSelector).append(loadingView)
|
||||
atom.show()
|
||||
|
||||
peer = createPeer()
|
||||
connection = peer.connect(sessionId, reliable: true)
|
||||
connection.on 'open', ->
|
||||
console.log 'connection opened'
|
||||
connection.once 'data', (data) ->
|
||||
loadingView.remove()
|
||||
console.log 'received document'
|
||||
atom.windowState = Document.deserialize(data, site: createSite(peer.id))
|
||||
connectDocument(atom.windowState, connection)
|
||||
window.startEditorWindow()
|
||||
updateProgressBar = (message, percentDone) ->
|
||||
loadingView.find('.progress-bar-message').text("#{message}\u2026")
|
||||
loadingView.find('.progress-bar').css('width', "#{percentDone}%")
|
||||
|
||||
guestSession = new GuestSession(sessionId)
|
||||
guestSession.on 'started', -> loadingView.remove()
|
||||
guestSession.on 'connection-opened', -> updateProgressBar('Downloading session data', 25)
|
||||
guestSession.on 'connection-document-received', -> updateProgressBar('Synchronizing repository', 50)
|
||||
operationsDone = -1
|
||||
guestSession.on 'mirror-progress', (message, command, operationCount) ->
|
||||
operationsDone++
|
||||
percentDone = Math.round((operationsDone / operationCount) * 50) + 50
|
||||
updateProgressBar(message, percentDone)
|
||||
|
||||
atom.guestSession = guestSession
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
GuestView = require './guest-view'
|
||||
HostView = require './host-view'
|
||||
HostSession = require './host-session'
|
||||
JoinPromptView = require './join-prompt-view'
|
||||
{createSite, Document} = require 'telepath'
|
||||
{createPeer, connectDocument} = require './session-utils'
|
||||
|
||||
startSession = ->
|
||||
peer = createPeer()
|
||||
peer.on 'connection', (connection) ->
|
||||
connection.on 'open', ->
|
||||
console.log 'sending document'
|
||||
windowState = atom.getWindowState()
|
||||
connection.send(windowState.serialize())
|
||||
connectDocument(windowState, connection)
|
||||
peer.id
|
||||
{getSessionUrl} = require './session-utils'
|
||||
|
||||
module.exports =
|
||||
activate: ->
|
||||
sessionId = null
|
||||
hostView = null
|
||||
|
||||
rootView.command 'collaboration:copy-session-id', ->
|
||||
pasteboard.write(sessionId) if sessionId
|
||||
if atom.getLoadSettings().sessionId
|
||||
new GuestView(atom.guestSession)
|
||||
else
|
||||
hostSession = new HostSession()
|
||||
|
||||
rootView.command 'collaboration:start-session', ->
|
||||
if sessionId = startSession()
|
||||
pasteboard.write(sessionId)
|
||||
copySession = ->
|
||||
sessionId = hostSession.getId()
|
||||
pasteboard.write(getSessionUrl(sessionId)) if sessionId
|
||||
|
||||
rootView.command 'collaboration:join-session', ->
|
||||
new JoinPromptView (id) ->
|
||||
windowSettings =
|
||||
bootstrapScript: require.resolve('collaboration/lib/bootstrap')
|
||||
resourcePath: window.resourcePath
|
||||
sessionId: id
|
||||
atom.openWindow(windowSettings)
|
||||
rootView.command 'collaboration:copy-session-id', copySession
|
||||
|
||||
rootView.command 'collaboration:start-session', ->
|
||||
hostView ?= new HostView(hostSession)
|
||||
copySession() if hostSession.start()
|
||||
|
||||
rootView.command 'collaboration:join-session', ->
|
||||
new JoinPromptView (id) ->
|
||||
return unless id
|
||||
windowSettings =
|
||||
bootstrapScript: require.resolve('collaboration/lib/bootstrap')
|
||||
resourcePath: window.resourcePath
|
||||
sessionId: id
|
||||
atom.openWindow(windowSettings)
|
||||
|
||||
57
src/packages/collaboration/lib/guest-session.coffee
Normal file
57
src/packages/collaboration/lib/guest-session.coffee
Normal file
@@ -0,0 +1,57 @@
|
||||
path = require 'path'
|
||||
remote = require 'remote'
|
||||
url = require 'url'
|
||||
|
||||
_ = require 'underscore'
|
||||
patrick = require 'patrick'
|
||||
telepath = require 'telepath'
|
||||
|
||||
{connectDocument, createPeer} = require './session-utils'
|
||||
|
||||
module.exports =
|
||||
class GuestSession
|
||||
_.extend @prototype, require('event-emitter')
|
||||
|
||||
participants: null
|
||||
repository: null
|
||||
peer: null
|
||||
|
||||
constructor: (sessionId) ->
|
||||
@peer = createPeer()
|
||||
connection = @peer.connect(sessionId, {reliable: true, connectionId: @getId()})
|
||||
connection.on 'open', =>
|
||||
console.log 'connection opened'
|
||||
@trigger 'connection-opened'
|
||||
connection.once 'data', (data) =>
|
||||
@trigger 'connection-document-received'
|
||||
console.log 'received document', data
|
||||
doc = telepath.Document.deserialize(data.doc, site: telepath.createSite(@getId()))
|
||||
atom.windowState = doc.get('windowState')
|
||||
@repository = doc.get('collaborationState.repositoryState')
|
||||
@participants = doc.get('collaborationState.participants')
|
||||
@participants.on 'changed', =>
|
||||
@trigger 'participants-changed', @participants.toObject()
|
||||
connectDocument(doc, connection)
|
||||
@mirrorRepository(data.repoSnapshot)
|
||||
|
||||
mirrorRepository: (repoSnapshot)->
|
||||
repoUrl = @repository.get('url')
|
||||
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
|
||||
repoName = repoName.replace(/\.git$/, '')
|
||||
repoPath = path.join(remote.require('app').getHomeDir(), 'github', repoName)
|
||||
|
||||
progressCallback = (args...) => @trigger 'mirror-progress', args...
|
||||
|
||||
patrick.mirror repoPath, repoSnapshot, {progressCallback}, (error) =>
|
||||
if error?
|
||||
console.error(error)
|
||||
else
|
||||
@trigger 'started'
|
||||
|
||||
atom.getLoadSettings().initialPath = repoPath
|
||||
window.startEditorWindow()
|
||||
@participants.push
|
||||
id: @getId()
|
||||
email: git.getConfigValue('user.email')
|
||||
|
||||
getId: -> @peer.id
|
||||
35
src/packages/collaboration/lib/guest-view.coffee
Normal file
35
src/packages/collaboration/lib/guest-view.coffee
Normal file
@@ -0,0 +1,35 @@
|
||||
{$$, View} = require 'space-pen'
|
||||
ParticipantView = require './participant-view'
|
||||
|
||||
module.exports =
|
||||
class GuestView extends View
|
||||
@content: ->
|
||||
@div class: 'collaboration', tabindex: -1, =>
|
||||
@div class: 'guest'
|
||||
@div outlet: 'participants'
|
||||
|
||||
guestSession: null
|
||||
|
||||
initialize: (@guestSession) ->
|
||||
@guestSession.on 'participants-changed', (participants) =>
|
||||
@updateParticipants(participants)
|
||||
|
||||
@updateParticipants(@guestSession.participants.toObject())
|
||||
|
||||
@attach()
|
||||
|
||||
updateParticipants: (participants) ->
|
||||
@participants.empty()
|
||||
guestId = @guestSession.getId()
|
||||
for participant in participants when participant.id isnt guestId
|
||||
@participants.append(new ParticipantView(participant))
|
||||
|
||||
toggle: ->
|
||||
if @hasParent()
|
||||
@detach()
|
||||
else
|
||||
@attach()
|
||||
|
||||
attach: ->
|
||||
rootView.horizontal.append(this)
|
||||
@focus()
|
||||
76
src/packages/collaboration/lib/host-session.coffee
Normal file
76
src/packages/collaboration/lib/host-session.coffee
Normal file
@@ -0,0 +1,76 @@
|
||||
fs = require 'fs'
|
||||
|
||||
_ = require 'underscore'
|
||||
patrick = require 'patrick'
|
||||
telepath = require 'telepath'
|
||||
|
||||
{createPeer, connectDocument} = require './session-utils'
|
||||
|
||||
module.exports =
|
||||
class HostSession
|
||||
_.extend @prototype, require('event-emitter')
|
||||
|
||||
doc: null
|
||||
participants: null
|
||||
peer: null
|
||||
sharing: false
|
||||
|
||||
start: ->
|
||||
return if @peer?
|
||||
|
||||
@peer = createPeer()
|
||||
@doc = telepath.Document.create({}, site: telepath.createSite(@getId()))
|
||||
@doc.set('windowState', atom.windowState)
|
||||
patrick.snapshot project.getPath(), (error, repoSnapshot) =>
|
||||
if error?
|
||||
console.error(error)
|
||||
return
|
||||
|
||||
@doc.set 'collaborationState',
|
||||
participants: []
|
||||
repositoryState:
|
||||
url: git.getConfigValue('remote.origin.url')
|
||||
branch: git.getShortHead()
|
||||
|
||||
@participants = @doc.get('collaborationState.participants')
|
||||
@participants.push
|
||||
id: @getId()
|
||||
email: git.getConfigValue('user.email')
|
||||
@participants.on 'changed', =>
|
||||
@trigger 'participants-changed', @participants.toObject()
|
||||
|
||||
@peer.on 'connection', (connection) =>
|
||||
connection.on 'open', =>
|
||||
console.log 'sending document'
|
||||
connection.send({repoSnapshot, doc: @doc.serialize()})
|
||||
connectDocument(@doc, connection)
|
||||
|
||||
connection.on 'close', =>
|
||||
console.log 'conection closed'
|
||||
@participants.each (participant, index) =>
|
||||
if connection.peer is participant.get('id')
|
||||
@participants.remove(index)
|
||||
|
||||
@peer.on 'open', =>
|
||||
console.log 'sharing session started'
|
||||
@sharing = true
|
||||
@trigger 'started'
|
||||
|
||||
@peer.on 'close', =>
|
||||
console.log 'sharing session stopped'
|
||||
@sharing = false
|
||||
@trigger 'stopped'
|
||||
|
||||
@getId()
|
||||
|
||||
stop: ->
|
||||
return unless @peer?
|
||||
|
||||
@peer.destroy()
|
||||
@peer = null
|
||||
|
||||
getId: ->
|
||||
@peer.id
|
||||
|
||||
isSharing: ->
|
||||
@sharing
|
||||
47
src/packages/collaboration/lib/host-view.coffee
Normal file
47
src/packages/collaboration/lib/host-view.coffee
Normal file
@@ -0,0 +1,47 @@
|
||||
{$$, View} = require 'space-pen'
|
||||
ParticipantView = require './participant-view'
|
||||
|
||||
module.exports =
|
||||
class HostView extends View
|
||||
@content: ->
|
||||
@div class: 'collaboration', tabindex: -1, =>
|
||||
@div outlet: 'share', type: 'button', class: 'share'
|
||||
@div outlet: 'participants'
|
||||
|
||||
hostSession: null
|
||||
|
||||
initialize: (@hostSession) ->
|
||||
if @hostSession.isSharing()
|
||||
@share.addClass('running')
|
||||
|
||||
@share.on 'click', =>
|
||||
@share.disable()
|
||||
|
||||
if @hostSession.isSharing()
|
||||
@hostSession.stop()
|
||||
else
|
||||
@hostSession.start()
|
||||
|
||||
@hostSession.on 'started stopped', =>
|
||||
@share.toggleClass('running').enable()
|
||||
|
||||
@hostSession.on 'participants-changed', (participants) =>
|
||||
@updateParticipants(participants)
|
||||
|
||||
@attach()
|
||||
|
||||
updateParticipants: (participants) ->
|
||||
@participants.empty()
|
||||
hostId = @hostSession.getId()
|
||||
for participant in participants when participant.id isnt hostId
|
||||
@participants.append(new ParticipantView(participant))
|
||||
|
||||
toggle: ->
|
||||
if @hasParent()
|
||||
@detach()
|
||||
else
|
||||
@attach()
|
||||
|
||||
attach: ->
|
||||
rootView.horizontal.append(this)
|
||||
@focus()
|
||||
@@ -2,7 +2,8 @@
|
||||
Editor = require 'editor'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
Guid = require 'guid'
|
||||
|
||||
{getSessionId} = require './session-utils'
|
||||
|
||||
module.exports =
|
||||
class JoinPromptView extends View
|
||||
@@ -19,8 +20,8 @@ class JoinPromptView extends View
|
||||
@on 'core:cancel', => @remove()
|
||||
|
||||
clipboard = pasteboard.read()[0]
|
||||
if Guid.isGuid(clipboard)
|
||||
@miniEditor.setText(clipboard)
|
||||
if sessionId = getSessionId(clipboard)
|
||||
@miniEditor.setText(sessionId)
|
||||
|
||||
@attach()
|
||||
|
||||
@@ -29,7 +30,7 @@ class JoinPromptView extends View
|
||||
@miniEditor.setText('')
|
||||
|
||||
confirm: ->
|
||||
@confirmed(@miniEditor.getText())
|
||||
@confirmed(@miniEditor.getText().trim())
|
||||
@remove()
|
||||
|
||||
attach: ->
|
||||
|
||||
12
src/packages/collaboration/lib/participant-view.coffee
Normal file
12
src/packages/collaboration/lib/participant-view.coffee
Normal file
@@ -0,0 +1,12 @@
|
||||
crypto = require 'crypto'
|
||||
{View} = require 'space-pen'
|
||||
|
||||
module.exports =
|
||||
class ParticipantView extends View
|
||||
@content: ->
|
||||
@div class: 'participant', =>
|
||||
@img class: 'avatar', outlet: 'avatar'
|
||||
|
||||
initialize: ({id, email}) ->
|
||||
emailMd5 = crypto.createHash('md5').update(email).digest('hex')
|
||||
@avatar.attr('src', "http://www.gravatar.com/avatar/#{emailMd5}?s=32")
|
||||
@@ -1,7 +1,26 @@
|
||||
Peer = require './peer'
|
||||
Peer = require '../vendor/peer.js'
|
||||
Guid = require 'guid'
|
||||
|
||||
url = require 'url'
|
||||
|
||||
module.exports =
|
||||
getSessionId: (text) ->
|
||||
return null unless text
|
||||
|
||||
text = text.trim()
|
||||
sessionUrl = url.parse(text)
|
||||
if sessionUrl.host is 'session'
|
||||
sessionId = sessionUrl.path.split('/')[1]
|
||||
else
|
||||
sessionId = text
|
||||
|
||||
if Guid.isGuid(sessionId)
|
||||
sessionId
|
||||
else
|
||||
null
|
||||
|
||||
getSessionUrl: (sessionId) -> "atom://session/#{sessionId}"
|
||||
|
||||
createPeer: ->
|
||||
id = Guid.create().toString()
|
||||
new Peer(id, key: '0njqmaln320dlsor')
|
||||
@@ -9,6 +28,7 @@ module.exports =
|
||||
connectDocument: (doc, connection) ->
|
||||
nextOutputEventId = 1
|
||||
outputListener = (event) ->
|
||||
return unless connection.open
|
||||
event.id = nextOutputEventId++
|
||||
console.log 'sending event', event.id, event
|
||||
connection.send(event)
|
||||
@@ -39,7 +59,7 @@ module.exports =
|
||||
queuedEvents.push(event)
|
||||
|
||||
connection.on 'close', ->
|
||||
doc.off('output', outputListener)
|
||||
doc.off('replicate-change', outputListener)
|
||||
|
||||
connection.on 'error', (error) ->
|
||||
console.error 'connection error', error.stack ? error
|
||||
|
||||
30
src/packages/collaboration/stylesheets/collaboration.less
Normal file
30
src/packages/collaboration/stylesheets/collaboration.less
Normal file
@@ -0,0 +1,30 @@
|
||||
@import "bootstrap/less/variables.less";
|
||||
@import "octicon-mixins.less";
|
||||
|
||||
.collaboration {
|
||||
@item-line-height: @line-height-base * 1.25;
|
||||
padding: 10px;
|
||||
-webkit-user-select: none;
|
||||
|
||||
.share {
|
||||
cursor: pointer;
|
||||
.mini-icon(notifications);
|
||||
position: relative;
|
||||
right: -1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.guest {
|
||||
.mini-icon(watchers);
|
||||
position: relative;
|
||||
right: -2px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 3px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
@@ -782,7 +782,7 @@ var util = {
|
||||
global.attachEvent('onmessage', handleMessage);
|
||||
}
|
||||
return setZeroTimeoutPostMessage;
|
||||
}(this)),
|
||||
}(window)),
|
||||
|
||||
blobToArrayBuffer: function(blob, cb){
|
||||
var fr = new FileReader();
|
||||
@@ -8,6 +8,7 @@ _ = require 'underscore'
|
||||
module.exports=
|
||||
class ImageEditSession
|
||||
registerDeserializer(this)
|
||||
@version: 1
|
||||
|
||||
@activate: ->
|
||||
# Files with these extensions will be opened as images
|
||||
@@ -18,7 +19,8 @@ class ImageEditSession
|
||||
new ImageEditSession(filePath)
|
||||
|
||||
@deserialize: ({path}={}) ->
|
||||
if fsUtils.exists(path)
|
||||
path = project.resolve(path)
|
||||
if fsUtils.isFileSync(path)
|
||||
new ImageEditSession(path)
|
||||
else
|
||||
console.warn "Could not build image edit session for path '#{path}' because that file no longer exists"
|
||||
@@ -27,7 +29,7 @@ class ImageEditSession
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'ImageEditSession'
|
||||
path: @path
|
||||
path: @getUri()
|
||||
|
||||
getViewClass: ->
|
||||
require './image-view'
|
||||
@@ -48,7 +50,7 @@ class ImageEditSession
|
||||
# Retrieves the URI of the current image.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getUri: -> @path
|
||||
getUri: -> project?.relativize(@getPath()) ? @getPath()
|
||||
|
||||
# Retrieves the path of the current image.
|
||||
#
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
'a': 'tree-view:add'
|
||||
'delete': 'tree-view:remove'
|
||||
'backspace': 'tree-view:remove'
|
||||
'k': 'core:move-up'
|
||||
'j': 'core:move-down'
|
||||
|
||||
'.tree-view-dialog .mini.editor':
|
||||
'enter': 'core:confirm'
|
||||
|
||||
@@ -43,7 +43,7 @@ class BufferedProcess
|
||||
addNodeDirectoryToPath: (options) ->
|
||||
options.env ?= process.env
|
||||
pathSegments = []
|
||||
nodeDirectoryPath = path.resolve(process.execPath, '..', '..', '..', '..', 'Resources')
|
||||
nodeDirectoryPath = path.resolve(process.execPath, '..', '..', '..', '..', '..', 'Resources')
|
||||
pathSegments.push(nodeDirectoryPath)
|
||||
pathSegments.push(options.env.PATH) if options.env.PATH
|
||||
options.env = _.extend({}, options.env, PATH: pathSegments.join(path.delimiter))
|
||||
|
||||
@@ -180,52 +180,4 @@ _.mixin
|
||||
newObject[key] = value if value?
|
||||
newObject
|
||||
|
||||
originalIsEqual = _.isEqual
|
||||
extendedIsEqual = (a, b, aStack=[], bStack=[]) ->
|
||||
return originalIsEqual(a, b) if a is b
|
||||
return originalIsEqual(a, b) if _.isFunction(a) or _.isFunction(b)
|
||||
return a.isEqual(b) if _.isFunction(a?.isEqual)
|
||||
return b.isEqual(a) if _.isFunction(b?.isEqual)
|
||||
|
||||
stackIndex = aStack.length
|
||||
while stackIndex--
|
||||
return bStack[stackIndex] is b if aStack[stackIndex] is a
|
||||
aStack.push(a)
|
||||
bStack.push(b)
|
||||
|
||||
equal = false
|
||||
if _.isArray(a) and _.isArray(b) and a.length is b.length
|
||||
equal = true
|
||||
for aElement, i in a
|
||||
unless extendedIsEqual(aElement, b[i], aStack, bStack)
|
||||
equal = false
|
||||
break
|
||||
else if _.isObject(a) and _.isObject(b)
|
||||
aCtor = a.constructor
|
||||
bCtor = b.constructor
|
||||
aCtorValid = _.isFunction(aCtor) and aCtor instanceof aCtor
|
||||
bCtorValid = _.isFunction(bCtor) and bCtor instanceof bCtor
|
||||
if aCtor isnt bCtor and not (aCtorValid and bCtorValid)
|
||||
equal = false
|
||||
else
|
||||
aKeyCount = 0
|
||||
equal = true
|
||||
for key, aValue of a
|
||||
continue unless _.has(a, key)
|
||||
aKeyCount++
|
||||
unless _.has(b, key) and extendedIsEqual(aValue, b[key], aStack, bStack)
|
||||
equal = false
|
||||
break
|
||||
if equal
|
||||
bKeyCount = 0
|
||||
for key, bValue of b
|
||||
bKeyCount++ if _.has(b, key)
|
||||
equal = aKeyCount is bKeyCount
|
||||
else
|
||||
equal = originalIsEqual(a, b)
|
||||
|
||||
aStack.pop()
|
||||
bStack.pop()
|
||||
equal
|
||||
|
||||
_.isEqual = (a, b) -> extendedIsEqual(a, b)
|
||||
_.isEqual = require 'tantamount'
|
||||
|
||||
Reference in New Issue
Block a user