Initial commit of Islams Shared Notes module. Module is off by default
This commit is contained in:
parent
941882409e
commit
fc7296d88c
113
bigbluebutton-client/src/SharedNotesModule.mxml
Normal file
113
bigbluebutton-client/src/SharedNotesModule.mxml
Normal file
@ -0,0 +1,113 @@
|
||||
<!--
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
-->
|
||||
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
|
||||
layout="absolute"
|
||||
implements="org.bigbluebutton.common.IBigBlueButtonModule"
|
||||
creationComplete="onCreationComplete()" >
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import flexlib.mdi.containers.MDIWindow;
|
||||
|
||||
import org.bigbluebutton.common.IBbbModuleWindow;
|
||||
import org.bigbluebutton.common.LogUtil;
|
||||
import org.bigbluebutton.main.events.CloseWindowEvent;
|
||||
import org.bigbluebutton.main.events.OpenWindowEvent;
|
||||
import org.bigbluebutton.main.events.ToolbarButtonEvent;
|
||||
import org.bigbluebutton.modules.sharednotes.SharedNotesWindow;
|
||||
import org.bigbluebutton.modules.sharednotes.ToolbarButton;
|
||||
import org.bigbluebutton.modules.sharednotes.infrastructure.Client;
|
||||
import org.bigbluebutton.modules.sharednotes.infrastructure.HTTPServerConnection;
|
||||
|
||||
private var _moduleId:String = "NotesModule";
|
||||
private var _moduleName:String = "Notes Module";
|
||||
private var _attributes:Object;
|
||||
|
||||
private var globalDispatcher:Dispatcher = new Dispatcher();
|
||||
|
||||
private function onCreationComplete():void {
|
||||
LogUtil.debug("NotesModule Initialized");
|
||||
globalDispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
public function get moduleId():String {
|
||||
return _moduleId;
|
||||
}
|
||||
|
||||
public function get moduleName():String {
|
||||
return _moduleName;
|
||||
}
|
||||
|
||||
public function get uri():String {
|
||||
if (_attributes.mode == "PLAYBACK") {
|
||||
return _attributes.uri + "/" + _attributes.playbackRoom;
|
||||
}
|
||||
|
||||
return _attributes.uri + "/" + _attributes.room;
|
||||
}
|
||||
|
||||
public function get username():String {
|
||||
return _attributes.username;
|
||||
}
|
||||
|
||||
public function get connection():NetConnection {
|
||||
return _attributes.connection;
|
||||
}
|
||||
|
||||
public function get mode():String {
|
||||
if (_attributes.mode == null) {
|
||||
_attributes.mode = "LIVE"
|
||||
LogUtil.debug('Setting NotesModule mode: ' + _attributes.mode);
|
||||
}
|
||||
LogUtil.debug('NotesModule mode: ' + _attributes.mode);
|
||||
return _attributes.mode;
|
||||
}
|
||||
|
||||
public function get userid():Number {
|
||||
return _attributes.userid as Number;
|
||||
}
|
||||
|
||||
public function get role():String {
|
||||
return _attributes.userrole as String;
|
||||
}
|
||||
|
||||
public function start(attributes:Object):void {
|
||||
LogUtil.debug("notes attr: " + attributes.username);
|
||||
_attributes = attributes;
|
||||
SharedNotesWindow.document = _attributes.room;
|
||||
// The following line has been removed, as the uri should be in
|
||||
// the URI property in the config.xml file of the client
|
||||
// HTTPServerConnection.syncURL = (_attributes.uri as String).replace("RTMP", "http") + "/notes/notes.jsp";
|
||||
addToolbarButton();
|
||||
}
|
||||
|
||||
public function addToolbarButton():void{
|
||||
var button:ToolbarButton = new ToolbarButton();
|
||||
var event:ToolbarButtonEvent = new ToolbarButtonEvent(ToolbarButtonEvent.ADD);
|
||||
event.button = button;
|
||||
globalDispatcher.dispatchEvent(event);
|
||||
}
|
||||
|
||||
public function stop():void {}
|
||||
]]>
|
||||
</mx:Script>
|
||||
</mx:Module>
|
@ -0,0 +1,220 @@
|
||||
<!--
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
-->
|
||||
<containers:MDIWindow xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:containers="flexlib.mdi.containers.*"
|
||||
layout="absolute" width="508" height="494" implements="org.bigbluebutton.common.IBbbModuleWindow"
|
||||
title="Notes" creationComplete="init()" xmlns:components="org.bigbluebutton.modules.sharednotes.components.*">
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import flash.utils.getTimer;
|
||||
|
||||
import flexlib.mdi.events.MDIWindowEvent;
|
||||
|
||||
import mx.events.ResizeEvent;
|
||||
import mx.events.SliderEvent;
|
||||
|
||||
import org.bigbluebutton.common.IBbbModuleWindow;
|
||||
import org.bigbluebutton.main.views.MainCanvas;
|
||||
import org.bigbluebutton.modules.sharednotes.infrastructure.Client;
|
||||
import org.bigbluebutton.modules.sharednotes.infrastructure.ServerConnection;
|
||||
|
||||
private var _xPosition:int, _yPosition:int;
|
||||
private var _defaultWidth:int = 400;
|
||||
private var _defaultHeight:int = 300;
|
||||
private var client:org.bigbluebutton.modules.sharednotes.infrastructure.Client;
|
||||
|
||||
private static var _room:String;
|
||||
|
||||
public static const SHARED_NOTES_CLOSED:String = "SHARED_NOTES_CLOSED";
|
||||
|
||||
private var resizeTimer:Timer = new Timer(500);
|
||||
|
||||
public function init():void {
|
||||
textArea.addEventListener(Event.CHANGE, onTextChange);
|
||||
addEventListener(ServerConnection.SYNCED_EVENT, onSynced);
|
||||
addEventListener(ServerConnection.SYNCING_EVENT, onSyncing);
|
||||
addEventListener(MDIWindowEvent.RESIZE, onResize);
|
||||
addEventListener(MDIWindowEvent.MAXIMIZE, function(e:Event):void { resizeTimer.start(); });
|
||||
addEventListener(MDIWindowEvent.RESTORE, function(e:Event):void { resizeTimer.start(); });
|
||||
addEventListener(MDIWindowEvent.CLOSE, function(e:Event):void { if (client) client.shutdown(); });
|
||||
resizeTimer.addEventListener(TimerEvent.TIMER, onResizeTimer);
|
||||
|
||||
client = new Client(textArea, this);
|
||||
}
|
||||
|
||||
private function onResizeTimer(e:Event):void {
|
||||
onResize();
|
||||
resizeTimer.stop();
|
||||
}
|
||||
|
||||
private function addBlur():void {
|
||||
var bf:BlurFilter = new BlurFilter(0,0,0);
|
||||
|
||||
var myFilters:Array = new Array();
|
||||
|
||||
myFilters.push(bf);
|
||||
|
||||
textArea.filters = myFilters;
|
||||
}
|
||||
|
||||
private function onResize(e:Event = null):void {
|
||||
const RIGHT_PADDING:int = textArea.x * 2, TOP_PADDING:int = 35, BOTTOM_PADDING:int = 60;
|
||||
|
||||
textArea.width = canvas.width - RIGHT_PADDING;
|
||||
textArea.height = canvas.height - BOTTOM_PADDING;
|
||||
txtPlayback.width = canvas.width - RIGHT_PADDING;
|
||||
txtPlayback.height = canvas.height - BOTTOM_PADDING - 20;
|
||||
btnPlayback.y = canvas.height - TOP_PADDING;
|
||||
btnBackPlayback.y = canvas.height - TOP_PADDING;
|
||||
icoSynced.y = canvas.height - TOP_PADDING;
|
||||
icoSyncing.y = canvas.height - TOP_PADDING;
|
||||
icoSynced.x = canvas.width - RIGHT_PADDING - 10;
|
||||
icoSyncing.x = canvas.width - RIGHT_PADDING - 10;
|
||||
playbackSlider.width = canvas.width - playbackSlider.x - RIGHT_PADDING;
|
||||
playbackSlider.y = canvas.height - BOTTOM_PADDING - 10;
|
||||
lblPlaybackVersion.y = playbackSlider.y + 3;
|
||||
}
|
||||
|
||||
private function onSynced(e:Event):void {
|
||||
if (client.version > 0 && !btnBackPlayback.visible) { btnPlayback.visible = true; }
|
||||
icoSynced.visible = true;
|
||||
icoSyncing.visible = false;
|
||||
lblConnecting.visible = false;
|
||||
}
|
||||
|
||||
private function onSyncing(e:Event):void {
|
||||
icoSyncing.visible = true;
|
||||
icoSynced.visible = false;
|
||||
}
|
||||
|
||||
private function onTextChange(e:Event):void {
|
||||
onSyncing(e);
|
||||
}
|
||||
|
||||
protected function btnPlay_clickHandler(event:MouseEvent):void
|
||||
{
|
||||
if (client.isTypingTestRunning())
|
||||
{
|
||||
client.stopTyping();
|
||||
btnPlay.label = "Start";
|
||||
}
|
||||
else
|
||||
{
|
||||
client.startTyping();
|
||||
btnPlay.label = "Stop";
|
||||
}
|
||||
}
|
||||
|
||||
private var previousSliderValue:int = 0;
|
||||
protected function playbackSlider_changeHandler(event:SliderEvent):void
|
||||
{
|
||||
txtPlayback.text = client.getSnapshotAtVersion(previousSliderValue, event.value, txtPlayback.text);
|
||||
lblPlaybackVersion.text = "Version " + event.value + ":";
|
||||
}
|
||||
|
||||
protected function btnPlayback_clickHandler(event:MouseEvent):void
|
||||
{
|
||||
playbackSlider.maximum = client.version;
|
||||
playbackSlider.value = 0;
|
||||
textArea.visible = false;
|
||||
btnBackPlayback.visible = true;
|
||||
btnPlayback.visible = false;
|
||||
previousSliderValue = 0;
|
||||
txtPlayback.text = client.getSnapshotAtVersion(0, 0, "");
|
||||
}
|
||||
|
||||
protected function btnBackPlayback_clickHandler(event:MouseEvent):void
|
||||
{
|
||||
textArea.visible = true;
|
||||
btnBackPlayback.visible = false;
|
||||
btnPlayback.visible = true;
|
||||
}
|
||||
|
||||
public function get xPosition():int {
|
||||
return _xPosition;
|
||||
}
|
||||
|
||||
public function get yPosition():int {
|
||||
return _yPosition;
|
||||
}
|
||||
|
||||
public function set xPosition(x:int):void {
|
||||
_xPosition = x;
|
||||
}
|
||||
|
||||
public function set yPosition(y:int):void {
|
||||
_yPosition = y;
|
||||
}
|
||||
|
||||
public function get defaultWidth():int{
|
||||
return _defaultWidth;
|
||||
}
|
||||
|
||||
public function get defaultHeight():int{
|
||||
return _defaultHeight;
|
||||
}
|
||||
|
||||
public function set defaultHeight(height:int):void{
|
||||
this._defaultHeight = height;
|
||||
}
|
||||
|
||||
public function set defaultWidth(width:int):void{
|
||||
this._defaultWidth = width;
|
||||
}
|
||||
|
||||
public function resetWidthAndHeight():void{
|
||||
this.width = _defaultWidth;
|
||||
this.height = _defaultHeight;
|
||||
}
|
||||
|
||||
public function getPrefferedPosition():String{
|
||||
return MainCanvas.MIDDLE;
|
||||
}
|
||||
|
||||
public static function set document(document:String):void {
|
||||
Client.documentName = document;
|
||||
}
|
||||
|
||||
override public function close(event:MouseEvent=null):void {
|
||||
new Dispatcher().dispatchEvent(new Event(SHARED_NOTES_CLOSED, true));
|
||||
super.close(event);
|
||||
}
|
||||
]]>
|
||||
</mx:Script>
|
||||
<mx:Fade id="dissolveOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
|
||||
<mx:Fade id="dissolveIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>
|
||||
|
||||
<mx:Canvas id="canvas" x="0" y="0" width="100%" height="100%">
|
||||
|
||||
<mx:Button x="10" y="425" label="Playback" id="btnPlayback" click="btnPlayback_clickHandler(event)" icon="@Embed(source='images/play-icon.png')" visible="false"/>
|
||||
<mx:HSlider x="79" y="395" width="413" id="playbackSlider" liveDragging="true" snapInterval="1" change="playbackSlider_changeHandler(event)" maximum="0"/>
|
||||
<mx:TextArea x="10" y="10" width="482" height="387" id="txtPlayback"/>
|
||||
<mx:Image x="476" y="431" source="@Embed(source='images/tick.png')" id="icoSynced" visible="false" toolTip="Document up to date."/>
|
||||
<mx:Image x="476" y="431" source="@Embed(source='images/action_refresh.gif')" id="icoSyncing" visible="false" toolTip="Updating document..."/>
|
||||
<mx:Button x="10" y="425" icon="@Embed(source='images/arrow_left.png')" id="btnBackPlayback" click="btnBackPlayback_clickHandler(event)" visible="false"/>
|
||||
<mx:Label id="lblPlaybackVersion" x="10" y="399" text="Version 0:"/>
|
||||
<components:PatchableTextArea x="10" y="10" id="textArea" hideEffect="{dissolveOut}" showEffect="{dissolveIn}" creationComplete="addBlur()" width="482" height="407"/>
|
||||
<mx:Button x="114" y="425" label="Start" id="btnPlay" click="btnPlay_clickHandler(event)" visible="false"/>
|
||||
<mx:Label x="211.5" y="186" text="Connecting..." id="lblConnecting"/>
|
||||
|
||||
</mx:Canvas>
|
||||
</containers:MDIWindow>
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
BigBlueButton - http://www.bigbluebutton.org
|
||||
|
||||
Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
|
||||
|
||||
BigBlueButton is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
Foundation; either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
$Id: $
|
||||
-->
|
||||
|
||||
<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" icon="{notesIcon}"
|
||||
click="openPublishWindow()" creationComplete="init()" toolTip="{ResourceUtil.getInstance().getString('bbb.toolbar.video.toolTip')}" xmlns:mate="http://mate.asfusion.com/">
|
||||
|
||||
<mate:Listener type="{SharedNotesWindow.SHARED_NOTES_CLOSED}" method="closeEventHandler" />
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import org.bigbluebutton.common.IBbbModuleWindow;
|
||||
import org.bigbluebutton.common.Images;
|
||||
import org.bigbluebutton.main.events.OpenWindowEvent;
|
||||
import org.bigbluebutton.util.i18n.ResourceUtil;
|
||||
|
||||
[Embed(source="images/note_edit.png")]
|
||||
public var notes:Class;
|
||||
|
||||
|
||||
[Bindable] public var notesIcon:Class = notes;
|
||||
|
||||
private var dispatcher:Dispatcher;
|
||||
|
||||
private function init():void{
|
||||
dispatcher = new Dispatcher();
|
||||
this.addEventListener(SharedNotesWindow.SHARED_NOTES_CLOSED, closeEventHandler);
|
||||
}
|
||||
|
||||
private function openPublishWindow():void{
|
||||
var window:IBbbModuleWindow = new SharedNotesWindow();
|
||||
|
||||
var event:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT);
|
||||
event.window = window;
|
||||
dispatcher.dispatchEvent(event);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public function show():void{
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
private function closeEventHandler(e:Event):void {
|
||||
show();
|
||||
}
|
||||
]]>
|
||||
</mx:Script>
|
||||
</mx:Button>
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.components
|
||||
{
|
||||
import mx.controls.TextArea;
|
||||
|
||||
import org.bigbluebutton.modules.sharednotes.util.DiffPatch;
|
||||
|
||||
public class PatchableTextArea extends TextArea
|
||||
{
|
||||
private var _tackOnText : String = "";
|
||||
private var _tackOnTextChanged : Boolean = false;
|
||||
|
||||
private var _patch : String = "";
|
||||
private var _patchChanged : Boolean = false;
|
||||
|
||||
public function set tackOnText(value:String):void
|
||||
{
|
||||
_tackOnText = value;
|
||||
_tackOnTextChanged = true;
|
||||
invalidateProperties();
|
||||
}
|
||||
|
||||
public function get tackOnText():String
|
||||
{
|
||||
return _tackOnText;
|
||||
}
|
||||
|
||||
public function set patch(value:String):void
|
||||
{
|
||||
_patch = value;
|
||||
_patchChanged = true;
|
||||
invalidateProperties();
|
||||
}
|
||||
|
||||
public function get patch():String
|
||||
{
|
||||
return _patch;
|
||||
}
|
||||
|
||||
override protected function commitProperties():void
|
||||
{
|
||||
super.commitProperties();
|
||||
|
||||
if (_patchChanged) {
|
||||
patchClientText();
|
||||
patch = "";
|
||||
_patchChanged = false;
|
||||
}
|
||||
|
||||
if(_tackOnTextChanged) {
|
||||
this.textField.text += tackOnText;
|
||||
tackOnText = "";
|
||||
_tackOnTextChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function get textFieldText():String {
|
||||
return this.textField.text;
|
||||
}
|
||||
|
||||
private function patchClientText():void {
|
||||
var results:Array = DiffPatch.patchClientText(patch, textField.text, selectionBeginIndex, selectionEndIndex);
|
||||
|
||||
textField.text = results[1];
|
||||
|
||||
var cursorSelection:Array = results[0];
|
||||
textField.setSelection(cursorSelection[0], cursorSelection[1]);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 248 B |
Binary file not shown.
After Width: | Height: | Size: 345 B |
Binary file not shown.
After Width: | Height: | Size: 731 B |
Binary file not shown.
After Width: | Height: | Size: 860 B |
Binary file not shown.
After Width: | Height: | Size: 537 B |
@ -0,0 +1,207 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
import com.adobe.crypto.SHA1;
|
||||
import com.adobe.serialization.json.JSON;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.IEventDispatcher;
|
||||
import flash.events.TimerEvent;
|
||||
import flash.utils.Timer;
|
||||
|
||||
import org.bigbluebutton.modules.sharednotes.components.PatchableTextArea;
|
||||
import org.bigbluebutton.modules.sharednotes.util.DiffPatch;
|
||||
|
||||
|
||||
public class Client
|
||||
{
|
||||
private var _id:int;
|
||||
|
||||
private var textArea:PatchableTextArea; // the text component to display the document
|
||||
private var documentShadow:String = ""; // the shadow of the current document
|
||||
private var initialDocument:String; // for storing the initial document
|
||||
|
||||
private var logPrefix:String; // used for logging
|
||||
|
||||
private var patchHistory:Array = new Array(); // a history of the patches
|
||||
|
||||
private var server:ServerConnection;
|
||||
|
||||
private var testCharacterTimer:Timer;
|
||||
private var documentCheckTimer:Timer; // timer to check for changes in the document
|
||||
private var timeoutTimer:Timer = new Timer(5000); // setting the timeout for server requests to 5 seconds
|
||||
|
||||
public static var documentName:String = "";
|
||||
|
||||
|
||||
public function Client(textComponent:PatchableTextArea, dispatcher:IEventDispatcher) {
|
||||
textArea = textComponent;
|
||||
server = new HTTPServerConnection(this, dispatcher);
|
||||
}
|
||||
|
||||
public function initClient(id:int, serverConnection:ServerConnection, document:String = ""):void {
|
||||
_id = id;
|
||||
documentShadow = new String(document);
|
||||
textArea.text = new String(document);
|
||||
initialDocument = new String(document);
|
||||
server = serverConnection;
|
||||
|
||||
logPrefix = "[Client " + id + "] ";
|
||||
initDocumentCheckTimer();
|
||||
|
||||
timeoutTimer.addEventListener(TimerEvent.TIMER, function(e:Event):void {
|
||||
timeoutTimer.stop();
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
// used for testing
|
||||
testCharacterTimer = new Timer(10);
|
||||
testCharacterTimer.addEventListener(TimerEvent.TIMER, playSentence);
|
||||
}
|
||||
|
||||
private function initDocumentCheckTimer():void {
|
||||
documentCheckTimer = new Timer(500);
|
||||
documentCheckTimer.addEventListener(TimerEvent.TIMER, documentCheckEventHandler);
|
||||
documentCheckTimer.start();
|
||||
}
|
||||
|
||||
private function documentCheckEventHandler(e:TimerEvent):void {
|
||||
if (!server.pendingResponse) {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function sendMessage():void {
|
||||
var messageToSend:Message = new Message(id, documentName, ServerConnection.connectionType);
|
||||
|
||||
if (documentShadow != textArea.textFieldText) {
|
||||
trace("****** SENDING MESSAGE *******");
|
||||
|
||||
textArea.editable = false;
|
||||
var clientText:String = new String(textArea.textFieldText); // a snapshot of the client text
|
||||
|
||||
messageToSend.patchData = DiffPatch.diff(documentShadow, clientText);
|
||||
|
||||
patchHistory.push(messageToSend.patchData);
|
||||
|
||||
documentShadow = clientText;
|
||||
|
||||
messageToSend.checksum = SHA1.hash(documentShadow);
|
||||
|
||||
textArea.editable = true;
|
||||
|
||||
trace(logPrefix + "sending " + messageToSend);
|
||||
}
|
||||
|
||||
server.send("m, " + JSON.encode(messageToSend));
|
||||
|
||||
timeoutTimer.start();
|
||||
}
|
||||
|
||||
public function receiveMessage(serverMessage:Message): void {
|
||||
timeoutTimer.stop(); // we received a response - cancel the time out
|
||||
|
||||
trace(logPrefix + "received message.\nMessage: " + serverMessage);
|
||||
|
||||
if (serverMessage.patchData != "") {
|
||||
var result:String = DiffPatch.patch(serverMessage.patchData, documentShadow);
|
||||
|
||||
if (SHA1.hash(result) == serverMessage.checksum) {
|
||||
documentShadow = result;
|
||||
textArea.patch = serverMessage.patchData;
|
||||
patchHistory.push(serverMessage.patchData);
|
||||
}
|
||||
else {
|
||||
throw new Error("Checksum mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
server.pendingResponse = false;
|
||||
}
|
||||
|
||||
public function getSnapshotAtVersion(initialVersion:int, finalVersion:int, documentSnapshot:String = ""):String {
|
||||
if (initialVersion == 0) documentSnapshot = initialDocument;
|
||||
|
||||
if (initialVersion < finalVersion) {
|
||||
for (var i:int = initialVersion; i < finalVersion; i++) {
|
||||
documentSnapshot = DiffPatch.patch(patchHistory[i], documentSnapshot);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = finalVersion; i < initialVersion;i++) {
|
||||
documentSnapshot = DiffPatch.unpatch(patchHistory[i], documentSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
return documentSnapshot;
|
||||
}
|
||||
|
||||
public function startTyping():void {
|
||||
testCharacterTimer.start();
|
||||
}
|
||||
|
||||
public function stopTyping():void {
|
||||
testCharacterTimer.stop();
|
||||
}
|
||||
|
||||
private var testSentence:String = "The quick brown fox jumps over the lazy dog.";
|
||||
|
||||
private var testCounter:int = 0;
|
||||
|
||||
private function playSentence(event:TimerEvent):void {
|
||||
if (textArea.editable) {
|
||||
if (testCounter == testSentence.length) {
|
||||
testCounter = 0;
|
||||
textArea.tackOnText += "\n";
|
||||
}
|
||||
else {
|
||||
textArea.tackOnText += testSentence.charAt(testCounter++);
|
||||
}
|
||||
textArea.editable = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function isTypingTestRunning():Boolean {
|
||||
return testCharacterTimer.running;
|
||||
}
|
||||
|
||||
private var pendingServerMessage:Message;
|
||||
private function startReceive(e:TimerEvent):void {
|
||||
receiveMessage(pendingServerMessage);
|
||||
}
|
||||
|
||||
private function timeoutHandler(event:TimerEvent):void {
|
||||
sendMessage();
|
||||
}
|
||||
|
||||
public function get version():int { return patchHistory.length; }
|
||||
|
||||
public function get id():int { return _id; }
|
||||
|
||||
public function shutdown():void {
|
||||
server.shutdown();
|
||||
if (testCharacterTimer) testCharacterTimer.stop();
|
||||
if (documentCheckTimer) documentCheckTimer.stop();
|
||||
if (timeoutTimer) timeoutTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
import flash.events.Event;
|
||||
import flash.events.IEventDispatcher;
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.net.URLRequestMethod;
|
||||
import flash.net.URLVariables;
|
||||
|
||||
public class HTTPServerConnection extends ServerConnection
|
||||
{
|
||||
public static var syncURL:String = "";
|
||||
private var loader:URLLoader = new URLLoader(); // used for connecting to the server
|
||||
|
||||
public function HTTPServerConnection(client:Client, dispatcher:IEventDispatcher)
|
||||
{
|
||||
super(client, dispatcher);
|
||||
ServerConnection.connectionType = "http";
|
||||
loader.addEventListener(Event.COMPLETE, completeHandler);
|
||||
sendConnectRequest();
|
||||
}
|
||||
|
||||
private function completeHandler(event:Event):void {
|
||||
var loader:URLLoader = URLLoader(event.target);
|
||||
loader.close();
|
||||
|
||||
receive(loader.data);
|
||||
}
|
||||
|
||||
public override function send(message:String):void {
|
||||
var params : URLVariables = new URLVariables();
|
||||
params.message = message;
|
||||
var request:URLRequest = new URLRequest(syncURL);
|
||||
request.data = params;
|
||||
request.method = URLRequestMethod.POST;
|
||||
try {
|
||||
loader.load(request);
|
||||
pendingResponse = true;
|
||||
} catch (error:Error) {
|
||||
trace("Unable to load requested document.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
public var senderId:int;
|
||||
public var patchData:String;
|
||||
public var checksum:String;
|
||||
public var documentName:String;
|
||||
public var conType:String;
|
||||
|
||||
public function Message(senderId:int, documentName:String, conType:String, patchData:String = "" , checksum:String = ""){
|
||||
this.senderId = senderId;
|
||||
this.documentName = documentName;
|
||||
this.patchData = patchData;
|
||||
this.checksum = checksum;
|
||||
this.conType = conType;
|
||||
}
|
||||
|
||||
public static function deserialize(o:Object):Message {
|
||||
var patchData:String = "", checksum:String = "";
|
||||
|
||||
if (o.checksum) checksum = o.checksum;
|
||||
if (o.patchData) patchData = o.patchData;
|
||||
|
||||
return new Message(o.senderId, o.documentName, o.conType, patchData, checksum);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return "Message: " + ", senderId " + senderId + ", patchData: " + patchData + ", checksum " + checksum
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
public class Patch
|
||||
{
|
||||
public var version:int, data:String;
|
||||
|
||||
public function Patch(version:int, data:String)
|
||||
{
|
||||
this.version = version;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
import com.adobe.serialization.json.JSON;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.IEventDispatcher;
|
||||
import flash.events.TimerEvent;
|
||||
import flash.utils.Timer;
|
||||
|
||||
public class ServerConnection
|
||||
{
|
||||
public static const SYNCING_EVENT : String = "SN_SYNCING_EVENT";
|
||||
public static const SYNCED_EVENT : String = "SN_SYNCED_EVENT";
|
||||
|
||||
private var client:Client;
|
||||
protected var dispatcher:IEventDispatcher;
|
||||
|
||||
private var _pendingResponse:Boolean = false; // flag indicating if a response has been received from the server
|
||||
private var connectionTimeout:Timer = new Timer(5000); //TODO: change the timeout
|
||||
|
||||
public static var connectionType:String = ""; // determines the type of server connection (whether socket or http)
|
||||
|
||||
public function ServerConnection(client:Client, dispatcher:IEventDispatcher) {
|
||||
this.client = client;
|
||||
this.dispatcher = dispatcher;
|
||||
connectionTimeout.addEventListener(TimerEvent.TIMER, function(e:Event):void {
|
||||
connectionTimeout.stop();
|
||||
sendConnectRequest();
|
||||
});
|
||||
}
|
||||
|
||||
protected function sendConnectRequest():void {
|
||||
var request:Object = new Object();
|
||||
request.documentName = Client.documentName;
|
||||
request.connectionType = ServerConnection.connectionType;
|
||||
send("c, " + JSON.encode(request));
|
||||
connectionTimeout.start();
|
||||
}
|
||||
|
||||
public function send(message:String):void { } // to be overridden
|
||||
|
||||
protected function receive(data:String):void {
|
||||
if (data.indexOf("c,") == 0) {
|
||||
trace("Received connection data: " + data);
|
||||
var clientData:Object = JSON.decode(data.substring(2));
|
||||
client.initClient(clientData.id, this, clientData.initialDocument);
|
||||
connectionTimeout.stop();
|
||||
pendingResponse = false;
|
||||
}
|
||||
else if (data.indexOf("m,") == 0) {
|
||||
var message:Message = Message.deserialize(JSON.decode(data.substring(2)));
|
||||
client.receiveMessage(message);
|
||||
}
|
||||
else {
|
||||
trace("unrecognized data: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
public function get pendingResponse():Boolean {
|
||||
return _pendingResponse;
|
||||
}
|
||||
|
||||
public function set pendingResponse(value : Boolean):void {
|
||||
_pendingResponse = value;
|
||||
|
||||
if (_pendingResponse) {
|
||||
dispatcher.dispatchEvent(new Event(SYNCING_EVENT));
|
||||
} else {
|
||||
dispatcher.dispatchEvent(new Event(SYNCED_EVENT));
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown():void {
|
||||
connectionTimeout.stop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
This file is part of BBB-Notes.
|
||||
|
||||
Copyright (c) Islam El-Ashi. All rights reserved.
|
||||
|
||||
BBB-Notes is free software: you can redistribute it and/or modify
|
||||
it under the terms of the Lesser GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
BBB-Notes is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
Lesser GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the Lesser GNU General Public License
|
||||
along with BBB-Notes. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Islam El-Ashi <ielashi@gmail.com>, <http://www.ielashi.com>
|
||||
*/
|
||||
package org.bigbluebutton.modules.sharednotes.infrastructure
|
||||
{
|
||||
import flash.events.DataEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.IEventDispatcher;
|
||||
import flash.events.TimerEvent;
|
||||
import flash.net.XMLSocket;
|
||||
import flash.utils.Timer;
|
||||
|
||||
public class XMLServerConnection extends ServerConnection
|
||||
{
|
||||
public static var serverURL:String = "192.168.0.238";
|
||||
public static const XML_CONNECTION_FAILED:String = "XML_CONNECTION_FAILED";
|
||||
private static const MAXIMUM_CONNECTION_ATTEMPTS:int = 5;
|
||||
|
||||
private var _socket: XMLSocket;
|
||||
private var timeoutTimer: Timer = new Timer(5000);
|
||||
private var connectionAttempts:int = MAXIMUM_CONNECTION_ATTEMPTS; // attempt to connect five times before dispatching a failure
|
||||
|
||||
private function get socket():XMLSocket {
|
||||
return _socket;
|
||||
}
|
||||
|
||||
public function XMLServerConnection(client:Client, dispatcher:IEventDispatcher)
|
||||
{
|
||||
super(client, dispatcher);
|
||||
ServerConnection.connectionType = "xmlsocket";
|
||||
|
||||
timeoutTimer.addEventListener(TimerEvent.TIMER, function(e:Event):void { timeoutTimer.stop(); connect(); });
|
||||
connect();
|
||||
}
|
||||
|
||||
public function connect():void {
|
||||
_socket = new XMLSocket();
|
||||
_socket.addEventListener(Event.CONNECT, function(e:Event):void {
|
||||
connectionAttempts = MAXIMUM_CONNECTION_ATTEMPTS;
|
||||
|
||||
sendConnectRequest();
|
||||
});
|
||||
_socket.addEventListener(DataEvent.DATA, function(e:DataEvent):void { receive(e.data); });
|
||||
socket.connect(serverURL, 8095);
|
||||
}
|
||||
|
||||
public override function send(message:String):void {
|
||||
try {
|
||||
pendingResponse = true;
|
||||
socket.send(message);
|
||||
} catch(e:Error) {
|
||||
//socket.close();
|
||||
if (connectionAttempts > 0) {
|
||||
trace(e.message);
|
||||
connectionAttempts--;
|
||||
timeoutTimer.start();
|
||||
} else {
|
||||
dispatcher.dispatchEvent(new Event(XML_CONNECTION_FAILED));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.bigbluebutton.modules.sharednotes.util
|
||||
{
|
||||
import flash.external.ExternalInterface;
|
||||
|
||||
public class DiffPatch
|
||||
{
|
||||
public static function diff(originalText:String, modifiedText:String):String {
|
||||
var result:String = ExternalInterface.call("diff", escape(originalText), escape(modifiedText));
|
||||
|
||||
if (result == null) throw new Error("Javascript files not found.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function patch(diff:String, text:String):String {
|
||||
var result:String = ExternalInterface.call("patch", diff, escape(text));
|
||||
|
||||
if (result == null) throw new Error("Javascript files not found.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function unpatch(diff:String, text:String):String {
|
||||
var result:String = unescape(ExternalInterface.call("unpatch", diff, escape(text)));
|
||||
|
||||
if (result == null) throw new Error("Javascript files not found.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function patchClientText(patches:String, clientText:String, selectionStart:int, selectionEnd:int):Array {
|
||||
var result:Array = ExternalInterface.call("patchClientText", patches, escape(clientText), selectionStart, selectionEnd);
|
||||
|
||||
if (result == null) throw new Error("Javascript files not found.");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user