iT邦幫忙

DAY 30
11

且戰且走HTML5系列 第 30

且戰且走HTML5(30) 利用Bootstrap逐步整合出UI

我不是做美術設計的料,但是又希望成品可以美觀一點,這時還是借助一些方法來讓介面好看一點。
自從twitter發佈了bootstrap,看起來真的不少人做過嘗試。利用他,可以做出簡潔美觀的介面,而不需要花太多的功夫,不過嘗試以後,也發現一些缺點。

首先不管三七二十一,先把視訊以及白板放上去好了。希望的layout是有一個header,上面有login的輸入框,然後下面使用tab來把各個功能區隔開來。

嘗試了一下,介面的html:

<html lang='en'>
<meta charset='utf-8'>

<link rel="stylesheet" type="text/css" href="/css/normalize.css">
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="js/colorPicker.css" type="text/css" />
<style>
video {
	border: solid 1px #6699cc;
	border-radius: 10px;
	padding: 15px 15px 15px 15px;
}
.header {
	background: #223344;
	color: #FFFFFF;
	padding: 5px 5px 5px 5px;
	text-align: right;
	height: 32px;
	vertical-align: middle;
}
#loginpanel {
	text-align: right;
	padding: 0 0 0 0;
	margin: 0 0 0 0;
}
#accountpanel {
	font-size: 16px;
}
.lineup {
	display: inline-block;
}
#canvas {
	border: solid 1px gray;
}
#preview {
	border: solid 1px gray;
	position:absolute;
	z-index:100;
	left: 479px;
	top: 120px;
}
.panel {
	margin: 5px 5px 5px 5px;
	padding: 5px 5px 5px 5px;
	border: solid 2px #336699;
	width: 778px;
}
.tools {
	margin-right: 5px;
	padding: 2px 2px 2px 2px;
	border: solid 2px #6699CC;
	width: 240px;
	height: 474px;
	float:left;
	line-height: 14px;
}
label {
	font-size: 10px;
}
span {
	font-size: 9px;
	color: red;
	font-weight: bold;
}
</style>
<script src='http://code.jquery.com/jquery-1.8.2.js'></script>
<script src='js/jquery.colorPicker.js'></script>
<script src="/js/bootstrap.min.js"></script>
<script src='amd.js'></script>
<script src='EventEmitter.js'></script>
<script src='PaintTools.js'></script>
<script src='PaintTools.tools.js'></script>
<script src='/ws.io/ws.io.js'></script>
<script src="PaintTools.wstool.js"></script>
<script>
var socket = io.connect('ws://127.0.0.1:8443');
$(document).ready(function() {
	$('#myTab a').click(function (e) {
		e.preventDefault();
		$(this).tab('show');
	});
	require(['test848a'], function(conference) {
		conference(socket);
	});
	// global variables
	var preview = $('#preview');
	var canvas = $('#canvas');
	preview.offset({left:canvas.offset().left, top:canvas.offset().top});

	var context = canvas[0].getContext('2d');
	var context1 = preview[0].getContext('2d');
	var tool = $C(context, context1);

	// drawing tool setup
	tool.on('drawType', function(v) {$('#drawtypestatus').html(v);});
	tool.lineCap = 'round';
	tool.lineJoin = 'round';
	tool.lineWidth = 1;

	// color picker setup
	$('#color1').colorPicker({
		pickerDefault: 'ffffff',
		onColorChange: function(id, val) {
			tool.fillStyle = val;
		}
	});
	$('#color2').colorPicker({
		pickerDefault: 'ffffff',
		onColorChange: function(id, val) {
			tool.strokeStyle = val;
		}
	});

	// change fillStyle and font of PaintTools will also change those of text input
	tool.on('fillStyle', function(val) {
		$('#keyin').css('color', val);
	});
	tool.on('font', function(val) {
		$('#keyin').css('font', val);
	});
	function wsCallBack(obj) {
		switch(obj.type) {
			case 'drawText':
				obj.type = 'wsDrawText';
				break;
			case 'freestyle':
				obj.type = 'wsDrawLine';
				break;
			case 'strokeRect':
				obj.type = 'wsStrokeRect';
				break;
			case 'fillRect':
				obj.type = 'wsFillRect';
				break;
			case 'strokeCircle':
				obj.type = 'wsStrokeCircle';
				break;
			case 'fillCircle':
				obj.type = 'wsFillCircle';
				break;
			case 'strokeEclipse':
				obj.type = 'wsStrokeEclipse';
				break;
			case 'fillEclipse':
				obj.type = 'wsFillEclipse';
				break;
			case 'eraser':
				obj.type = 'wsEraser';
				break;
		}
		socket.emit('wsdraw', obj);
	}

	tool.registCallback('drawText', 'keypress', wsCallBack);
	socket.on('wsdraw', function(m) {
		var _old = tool.drawType;
		tool.drawType = m.type;
		tool.handle('wsdraw', m);
		tool.drawType = _old;
	});

	//drawing dom event
	preview.bind('mousedown', function(e) {
		e.preventDefault();
		tool.handle('mousedown', e, wsCallBack);
	});
	preview.bind('mousemove', function(e) {
		e.preventDefault();
		tool.handle('mousemove', e, wsCallBack);
	});
	preview.bind('mouseup', function(e) {
		tool.handle('mouseup', e, wsCallBack);
	});
	preview.bind('mouseleave', function(e) {
		tool.handle('mouseleave', e, wsCallBack);
	});

	// tool panel event
	$('#linewidth').bind('change', function() {
		tool.lineWidth = $(this).val();
	});
	$('#freestyle').bind('click', function() {
		tool.drawType = 'freestyle';
	});
	$('#strokerect').bind('click', function() {
		tool.drawType = 'strokeRect';
	});
	$('#fillrect').bind('click', function() {
		tool.drawType = 'fillRect';
	});
	$('#strokecircle').bind('click', function() {
		tool.drawType = 'strokeCircle';
	});
	$('#fillcircle').bind('click', function() {
		tool.drawType = 'fillCircle';
	});
	$('#strokeeclipse').bind('click', function() {
		tool.drawType = 'strokeEclipse';
	});
	$('#filleclipse').bind('click', function() {
		tool.drawType = 'fillEclipse';
	});
	$('#drawtext').bind('click', function() {
		tool.drawType = 'drawText';
	});
	$('#fontface').bind('change', function() {
		tool.fontFace = $(this).val();
	});
	$('#fontsize').bind('change', function() {
		tool.fontSize = $(this).val();
	});
	$('#fontweight').bind('change', function() {
		tool.fontWeight = $(this).val();
	});
	$('#fontstyle').bind('change', function() {
		tool.fontStyle = $(this).val();
	});
	$('#eraser').bind('click', function() {
		tool.drawType = 'eraser';
	});
});
</script>


<div class='header'>
	<div id='accountpanel' style='display:none'></div>
	<div id='loginpanel'><form id='form1' class='form-inline' method='get'>
		<input type='text' id='account' placeholder='Account' class='input-small'>
		<input type='password' id='password' placeholder='Password' class='input-small'>
		<button type='submit' class='btn'>Signin</button>
	</form></div>
</div>
<br>
<div class="container" style="text-align: center">
	<ul class='nav nav-tabs' id='myTab'>
		<li class='active'><a href='#conference'>Video Conference</a></li>
		<li><a href='#whiteboard'>White Board</a></li>
	</ul>
	<div class='tab-content'>
		<div class='tab-pane active' id='conference'>
			<div class='lineup'>
				<video id="local" width="320" autoplay></video><br>
				<form class='form-inline'>
					<button class="btn-small" id="btnStart" disabled>start</button>
					<select id='users'>
					</select>
					<button class="btn-small" id="btnCall" disabled>call</button>
				</form>
			</div>
			<div id='remote'></div>
		</div>
		<div class='tab-pane' id='whiteboard'>

<div>
	<div class='tools' id='tools'>
		<div>
			<label for='drawtypestatus'>Draw Type: </label><span id='drawtypestatus'></span><br>
			<hr size='1' width='100%'>
		</div>
		<div>
			<label for='color1'>Fill: </label><div style='display:inline-block'><input type='text' id='color1' name='color1' value='#000000'></div><br>
			<label for='color2'>Stroke: </label><div style='display:inline-block'><input type='text' id='color2' name='color2' value='#000000'></div><br>
			<label for='linewidth'>Line Width: </label><select id='linewidth'>
				<option value="1">1</option>
				<option value="3">3</option>
				<option value="5">5</option>
				<option value="7">7</option>
			</select>
			<hr size='1' width='100%'>
		</div>
		<div>
			<button id="freestyle" class='btn btn-mini'>freesytle</button>
			<button id='strokerect' class='btn btn-mini'>Stroke Rectangle</button>
			<button id='fillrect' class='btn btn-mini'>Fill Rectangle</button>
			<button id='strokecircle' class='btn btn-mini'>Stroke Circle</button>
			<button id='fillcircle' class='btn btn-mini'>Fill Circle</button>
			<button id='strokeeclipse' class='btn btn-mini'>Stroke Eclipse</button>
			<button id='filleclipse' class='btn btn-mini'>Fill Eclipse</button>
			<button id='drawtext' class='btn btn-mini'>Draw Text</button>
			<button id='eraser' class='btn btn-mini'>Eraser</button>
			<hr size='1' width='100%'>
		</div>
		<div>
			<label for='fontface'>Font: </label><select id='fontface'>
				<option value='sans-serif'>sans-serif</option>
				<option value='serif'>serif</option>
				<option value='cursive'>cursive</option>
				<option value='fantasy'>fantasy</option>
				<option value='monospace'>monospace</option>
			</select>
			<label>Size: </label><select id='fontsize'>
				<option value='10px'>10px</option>
				<option value='12px'>12px</option>
				<option value='14px'>14px</option>
				<option value='16px'>16px</option>
				<option value='18px'>18px</option>
				<option value='20px'>20px</option>
				<option value='24px'>24px</option>
			</select>
			<label>Weight: </label><select id='fontweight'>
				<option value='400'>default</option>
				<option value='700'>bold</option>
			</select>
			<label>Style: </label><select id='fontstyle'>
				<option value='normal'>normal</option>
				<option value='italic'>italic</option>
			</select>
		</div>
	</div>
	<canvas id="canvas" width='640' height='480'></canvas>
	<canvas id="preview" width='640' height='480'></canvas>
</div>

		</div>
	</div>
</div>

其中也常是利用AMD的方式,逐步把操作獨立成模組。例如先把視訊需要用的的操作,放入獨立的檔案中:

define('test848a', [], function() {
	return function(socket) {
		var localStream,remoteStream;
		var connections = {};
		$('#btnStart').click(function() {
			navigator.webkitGetUserMedia({video:true,audio:true}, function(stream) {
				localStream = stream;
				$('#local').attr('src', webkitURL.createObjectURL(stream));
			}, function(info) {
				console.log('getUserMedia Error:' + info);
			});
			this.disabled = true;
			$('#btnCall').attr('disabled', false);
		});
		$('#btnCall').click(function(e) {
			e.preventDefault;
			e.stopPropagation();
			console.log('btnCall pressed');
			var to = $('#users').val();
			connections[to] = {connection:null, localStream:null, remoteStream:null};
			this.disable = true;
			connections[to].connection = new webkitPeerConnection00('STUN stun.1.google.com:19302', function(candidate, more) {
				if(candidate) {
					console.log(candidate);
					socket.emit('ice', {from:id, to:to, sdp:candidate.toSdp(), label:candidate.label});
				}
			});
			connections[to].connection.onaddstream = function(e) {
				var video = document.createElement('video');
				var contain = document.createElement('div');
				var btn = document.createElement('button');
				$(btn).html('Hang up');
				$(contain).attr('class', 'lineup')
				$(video).attr('autoplay', true);
				$(video).attr('id', to);
				$(video).attr('width', 320);
				connections[to].remoteStream = e.stream;
				$(video).attr('src', webkitURL.createObjectURL(e.stream));
				$(contain).append(video);
				$(contain).append(btn);
				$('#remote').append(contain);
				//$('#btnHangup').attr('disabled', false);
			};
			connections[to].connection.addStream(localStream);
			var offer = connections[to].connection.createOffer(null);
			connections[to].connection.setLocalDescription(connections[to].connection.SDP_OFFER, offer);
			socket.emit('offer', {to:to, from:id, sdp:offer.toSdp()});
			console.log('')
			return false;
		});
		$('#form1').bind('submit', function(e) {
			console.log('submit triggered');
			e.preventDefault();
			e.stopPropagation();
			socket.emit('login', {account:$('#account').val(),password:$('#password').val()});
			return false;
		});
		socket.on('ice', function(data) {
			console.log('ice triggered.');
			connections[data.from].connection.processIceMessage(new IceCandidate(data.label,data.sdp));
			console.log('socket on ice: ', getIceStateDesc(connections[data.from].connection.iceState));
		});
		socket.on('offer', function(data) {
			console.log('offer triggered');
			if(!connections[data.from]) {
				connections[data.from] = {connection:null,remoteStream:null};
			}
			connections[data.from].connection = new webkitPeerConnection00(null, function(candidate, more) {
				if(candidate) {
					console.log(candidate);
					socket.emit('ice', {to: data.from, from: data.to, sdp:candidate.toSdp(), label:candidate.label});
				}
			});
			connections[data.from].connection.onaddstream = function(e) {
				var video = document.createElement('video');
				var contain = document.createElement('div');
				var btn = document.createElement('button');
				$(btn).html('Hang up');
				$(contain).attr('class', 'lineup')
				$(video).attr('autoplay', true);
				$(video).attr('id', data.from);
				$(video).attr('width', 320);
				connections[to].remoteStream = e.stream;
				$(video).attr('src', webkitURL.createObjectURL(e.stream));
				$(contain).append(video);
				$(contain).append(btn);
				$('#remote').append(contain);
				//$('#btnHangup').attr('disabled', false);
			};
			connections[data.from].connection.addStream(localStream);
			connections[data.from].connection.setRemoteDescription(connections[data.from].connection.SDP_OFFER, new SessionDescription(data.sdp));
			var answer = connections[data.from].connection.createAnswer(data.sdp, {has_video:true,has_audio:true});
			connections[data.from].connection.setLocalDescription(connections[data.from].connection.SDP_ANSWER, answer);
			socket.emit('answer', {to:data.from, from:data.to, sdp:answer.toSdp()});
		});
		socket.on('answer', function(data) {
			console.log('answer triggered');
			connections[data.from]['connection'].setRemoteDescription(connections[data.from].connection.SDP_ANSWER, new SessionDescription(data.sdp));
			socket.emit('startice', {to: data.from, from: data.to});
			connections[data.from].connection.startIce();
		});
		socket.on('startice', function(data) {
			console.log('startice triggered');
			connections[data.from].connection.startIce();
		});
		/*socket.on('hangup', function() {
			$('#btnHangup').attr('disabled', true);
			$('#btnCall').attr('disabled', false);
			connections[id].connection.close();
			delete connections[id];
		});*/
		var id,account;
		socket.on('loginsuccess', function(data) {
			$('#loginpanel').css('display', 'none');
			$('#accountpanel').css('display', 'block');
			$('#accountpanel').html('Welcome '+data.account+'  ');
			id = data.id;
			account = data.account;
			$('#btnStart').attr('disabled', false);
		});
		socket.on('loginfail', function(data) {
			$('#account').val('');
			$('#password').val('');
		});
		socket.on('updatelist', function(data) {
			$('#users').html('');
			for(var i in data) {
				var opt = document.createElement('option');
				$(opt).html(data[i])
				$(opt).attr('value', i);
				$('#users').append(opt);
			}
		});
		function getIceStateDesc(state) {
			switch(state) {
				case 0x100:
					return 'ICE_GATHERING';
					break;
				case 0x200:
					return 'ICE_WAITING';
					break;
				case 0x300:
					return 'ICE_CHECKING';
					break;
				case 0x400:
					return 'ICE_CONNECTED';
					break;
				case 0x500:
					return 'ICE_COMPLETED';
					break;
				case 0x600:
					return 'ICE_FAILED';
					break;
				case 0x700:
					return 'ICE_CLOSED';
					break;
				default:
					return '';
			}
		}
	};
});

然後透過幾行程式碼載入執行:

	require(['test848a'], function(conference) {
		conference(socket);
	});

伺服器部分,也只是把需要的部份組合進去:

var fs = require('fs'),
url = require('url'),
mime = require('mime'),
index = '/test848.html',
cache = {},
app = require('http').createServer(function(req, res) {
	var resource = url.parse(req.url).pathname;
	if(typeof cache[resource] === 'undefined') {
		fs.stat(__dirname + resource, function(err, stat) {
			if(err) {
				if(typeof cache[index] === 'undefined') {
					fs.readFile(__dirname+index, function(err, data) {
						if(err) {
							console.log('cond 1');
							res.writeHead(500);
							res.end();
						} else {
							//cache[index] = data;
							res.setHeader('Content-Type', 'text/html');
							res.writeHead(200);
							res.end(data);
						}
					})
				} else {
					res.setHeader('Content-Type', 'text/html');
					res.writeHead(200);
					res.end(cache[index]);
				}
			} else {
				fs.readFile(__dirname + resource, function(err, data) {
					if(err) {
						if(typeof cache[index] === 'undefined') {
							fs.readFile(__dirname+index, function(err, data) {
								if(err) {
									res.writeHead(500);
									res.end();
								} else {
									//cache[index] = data;
									res.setHeader('Content-Type', 'text/html');
									res.writeHead(200);
									res.end(data);
								}
							});
						} else {
							res.setHeader('Content-Type', 'text/html');
							res.writeHead(200);
							res.end(cache[index]);
						}
					} else {
						//cache[resource] = data;
						var type = mime.lookup(resource);
						res.setHeader('Content-Type', type);
						res.writeHead(200);
						res.end(data);
					}
				})
			}
		})
	} else {
		var type = mime.lookup(resource);
		res.setHeader('Content-Type', type);
		res.writeHead(200);
		res.end(cache[resource]);
	}
}),
io = require('./ws.io').listen(app);

io.sockets.on('connection', function (socket) {
  
	socket.on('offer', function(data) {
		socket.to(data.to).emit('offer', {to:data.to, from:data.from, sdp: data.sdp});
	});
  
	socket.on('answer', function(data) {
		socket.to(data.to).emit('answer', {to:data.to, from:data.from, sdp: data.sdp});
	});
	
	socket.on('ice', function(data) {
		socket.to(data.to).emit('ice', {to:data.to, from:data.from, sdp:data.sdp,label:data.label});
	});
	
	socket.on('startice', function(data) {
		socket.to(data.to).emit('startice', {to:data.to, from:data.from});
	});

	socket.on('hangup', function() {
		socket.to(data.to).emit('hangup', {});
	});
	var users = {
		'fillano': 'abcd',
		'hildegard': 'efgh',
		'wolfram': 'ijkl'
	};
	socket.on('login', function(data) {
		if(!!users[data.account]) {
			if(data.password===users[data.account]) {
				socket.emit('loginsuccess', {account:data.account,id:socket.id});
				if(!socket.manager.list) {
					socket.manager.list = {};
				}
				socket.manager.list[socket.id] = data.account;
				for(var i in this.manager.namespaces[this.namespace]['all']) {
					this.manager.namespaces[this.namespace]['all'][i].emit('updatelist', socket.manager.list);
				}
			} else {
				socket.emit('loginfail', {})
			}
		} else {
			socket.emit('loginfail',{})
		}
	});
	socket.on('wsdraw', function(m) {
		console.log('user provided wsdraw handler');
		socket.broadcast.emit('wsdraw', m);
		console.log('wsdraw broadcasted');
	});

});

app.listen(8443);

(為了測試,把file cache機制關掉了)

接下來就啟動伺服器程式來看看(html是透過程式中的http傳送到瀏覽器,WebSocket與http共享一個port)...

畫面乍看之下還好:

登入看起來也還ok:

打開視訊看看:

看起來沒太大問題。但是打開白板:

Orz問題看起來還不小,這已經是調整過button大小的了,但是select元素實在大的很誇張...看起來還需要花一些時間調整介面。不過白板是介面上比較複雜的,其他部分調整應該就沒那麼複雜。

網路上是有一些select的解決方法,不過還是想辦法從CSS條比較徹底。但是...差不多要截稿了,就暫時停下腳步吧,今天是最後一天。

目前看起來一些還沒做完的事情有:

  1. 多人視訊
  2. 利用Bootstrap整合各個應用,尤其是一些CSS調整
  3. 利用AMD把程式模組化,其實大致上是依照之前獨立開發的部份,每個部分一到多個模組。另外之前開發的繪圖方法,也需要再包裝。
  4. 伺服器端反而簡單,可以利用Node.js的模組功能來做,就不需要自己大費周章了

這三十天,除了嘗試這些HTML5應用,也整理了一些東西,包含:

  1. 一個繪圖工具的簡單framework
  2. 包裝ws這個WebSocket模組:ws.io,讓他可以用與Socket.IO差不多的方式來使用
  3. 簡單的AMD支援(支援基本的define與require)
  4. Client端的EventEmitter支援(跟Node.js的自定事件機制EventEmitter類似)

這裡面最複雜的是ws.io,比預計花了多了幾天才包好。AMD其實也有點複雜,他需要用非同步的方式載入模組,由於是一層一層透過依賴關係找,這樣的遞迴需要用特別方式處理。然後處理require時,依照語法,需要把多個在define定義的函數,每個有不定數量的參數,組合起來。想的時候覺得有點難,不過寫的時候倒是幾行就結束了,因為這裡是使用同步的方式遞迴,沒有非同步遞迴那麼複雜。

就這樣啦,雖然沒有在三十天完成,不過之後還會持續調整。(不會像現在這麼趕就是了XD

(2012-11-11 21:33 補充)
ㄝ,Chrome改版到23後,PeerConnection的介面全改了,連物件的名字都改了...現在叫做RTCPeerConnection(在chrome要加上webkit前綴),所以之前寫的視訊會議不會動了。可能得花幾天測試一下新的API...XD


上一篇
且戰且走HTML5(29) 逐步完成整合應用
系列文
且戰且走HTML530
0
ted99tw
iT邦高手 1 級 ‧ 2012-11-07 23:57:43

灑花灑花灑花

恭喜費公鐵人煉成!!鐵人煉成!!

讚讚讚

0
fillano
iT邦超人 1 級 ‧ 2012-11-08 00:09:49

謝謝,勉強撐完了...

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-08 06:50:57

iT邦幫忙MVPfillano提到:
謝謝,勉強撐完了...

最後這兩天真是精彩絕倫,因為費公自己當男模粉墨登場耶!!!

簽名簽名簽名

0
老鷹(eagle)
iT邦高手 1 級 ‧ 2012-11-08 08:16:31

灑花灑花灑花
恭喜費公鐵人練成~~!駭客

0
海綿寶寶
iT邦超人 1 級 ‧ 2012-11-08 08:39:17

恭喜費大鐵人鍊成
拍手灑花灑花拍手

fillano提到:
看起來沒太大問題。

問題大了怎麼沒問題

這是在廚房還是浴室拍的呀
疑惑

fillano iT邦超人 1 級‧ 2012-11-08 09:32:44 檢舉

在廚房,這裡比較能專心...在浴室會有困難,沒桌子

0
jackyliang
iT邦新手 5 級 ‧ 2012-11-08 09:57:32

精采絕倫
感謝分享
恭喜鐵人鍊成rock

0
SunAllen
iT邦高手 1 級 ‧ 2012-11-08 22:03:57

恭喜費大鐵人鍊成灑花灑花灑花

0
jamesjan
iT邦高手 1 級 ‧ 2012-11-09 08:58:15

恭喜費大鐵人鍊成 灑花灑花讚

0
fillano
iT邦超人 1 級 ‧ 2012-11-09 10:37:15

謝謝大家謝謝謝謝謝謝

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-09 18:24:51

費公的關門鐵文一定要一再支持的~~~XD
祝各位邦友 週末愉快啦~~~開心

0
fillano
iT邦超人 1 級 ‧ 2012-11-11 21:38:42

門又開了啦...Chrome改版後,API全改了.........

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-11 21:45:56

iT邦幫忙MVPfillano提到:
“API”全改了.........

是改成BQJ嗎? 囧

0
fillano
iT邦超人 1 級 ‧ 2012-11-12 09:31:13

ㄝ,其實只有WebRTC的PeerConnection改了,不過這樣之前寫的視訊會議程式就要全翻掉。

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-12 09:38:08

iT邦幫忙MVPfillano提到:
不過這樣之前寫的視訊會議程式就要全翻掉。

不管如何,盡善盡美是一定要的啦~~灑花

費公改好後,若要大家在浴室開視訊會議再通知大家一下(廚房就不用了)...噎到

0
fillano
iT邦超人 1 級 ‧ 2012-11-13 10:22:13

門關了一半,概念驗證程式改在 且戰且走HTML5(28) 建立視訊會議

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-13 10:27:41

iT邦幫忙MVPfillano提到:
門關了一半,概念驗證程式改在 且戰且走HTML5(28) 建立視訊會議 。

哇,好厲駭喔!!喜歡喜歡

費公不能再這樣威猛下去了,不然小囉囉們可能就要為費公建生祠了...囧

0
fillano
iT邦超人 1 級 ‧ 2012-11-14 12:40:43

又發現新的問題XD,想要在ws.io加上真正的router,讓不同的websoket路徑有不同的namespace來handle,卻發現我把ws實體化與onconnection處理等都放在Namespace了,應該放在Manager才對,我不需要為不同的namespace啟動不同的server阿XD

找個時間改一改......果然處處碰壁阿

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-14 14:47:31

iT邦幫忙MVPfillano提到:
果然處處碰壁阿

這鐵文果然徹底絕對百分百符合觀眾期待......看費公處處碰壁~~~偷笑

fillano iT邦超人 1 級‧ 2012-11-14 15:06:22 檢舉

這樣我應該改姓繆....不過繆拉也不姓繆....

0
fillano
iT邦超人 1 級 ‧ 2012-11-15 08:02:57

發現ws沒有提供在runtime時的path資訊...原來他是可以一個path一個server,然後共享一個http伺服器,那部就是我之前做的調一下就可以...做完了才發現,只好再改回來...這樣又要花幾天了Orz

我該改名叫鐵壁繆拉XD

fillano iT邦超人 1 級‧ 2012-11-15 10:58:34 檢舉

「部」->「不」

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-15 08:14:34

iT邦幫忙MVPfillano提到:
我該改名叫鐵壁繆拉

其實修煉之路是暢通無壁的,只是這路有十萬八千里,法顯與玄奘都有走過....驚

蛤?這條路本身就是壁?....開心

0
fillano
iT邦超人 1 級 ‧ 2012-11-15 23:13:21

ok,趁中午休息把ws.io改好了,晚上來貼一貼。

原本的完整程式就放在:且戰且走HTML5(26) 使用ws.io完成資源共享,所以把改過的程式碼也放在那裡。有空時再把它放到github上。

0
ted99tw
iT邦高手 1 級 ‧ 2012-11-16 08:17:18

就知道發漏費公的文最爽了,因為可以跟海綿寶寶分出高下....開心

fillano 大大實在太精實了
喜酒都吃完散場了
他還在改新人戀愛史投影片
筆記

ted99tw iT邦高手 1 級‧ 2012-11-16 08:37:07 檢舉

antijava提到:
他還在改“新人戀愛”史投影片

當然要改啊:先買票,還是先上車,這粉重要....噎到

蛤?已經結婚啦....囧

我要留言

立即登入留言