Page MenuHomeCode

No OneTemporary

diff --git a/content/scenes/B00001001.tpl b/content/scenes/B00001001.tpl
index 239dcc7..e2f8a1c 100644
--- a/content/scenes/B00001001.tpl
+++ b/content/scenes/B00001001.tpl
@@ -1,169 +1,173 @@
<!-- Tower navigation and passage CSS -->
<style>
body {
overflow: hidden;
}
/* Tower map */
#tower {
background-image: url({$SCENE_URL}/{$CurrentPerso->location_global}/all.png);
background-position: left;
background-repeat: no-repeat;
height: 442px;
}
#tower_hl {
position: relative;
}
/* Passage */
#passage {
width: 960px;
height: 401px;
background-image: url({$SCENE_URL}/{$CurrentPerso->location_global}/couloir/bay/void.png);
background-position: top left;
background-repeat: no-repeat;
}
#passage_left {
position: relative;
background-image: url({$SCENE_URL}/{$CurrentPerso->location_global}/couloir/GoLeft.png);
width: 38px;
height: 38px;
}
#passage_right {
position: relative;
background-image: url({$SCENE_URL}/{$CurrentPerso->location_global}/couloir/GoRight.png);
width: 38px;
height: 38px;
}
#passage_gallery ul {
position: relative;
top: 145px;
left: 120px;
margin-top: inherit;
margin-bottom: inherit;
}
#passage_gallery li {
display: block;
float: left;
margin-right: 60px;
width: 170px;
height: 170px;
padding: 4px 4px;
background-image: url({$SCENE_URL}/{$CurrentPerso->location_global}/couloir/frame.png);
background-repeat: no-repeat;
background-position: top left;
}
#screen {
position: absolute;
left: 10%;
top: 10%;
width: 80%;
height: 80%;
}
#screen img {
position: absolute;
cursor: pointer;
visibility: hidden;
width: 0px;
height: 0px;
}
#screen .tvover {
border: solid #343434;
opacity: 1;
filter: alpha(opacity=100);
}
#screen .tvout {
border: solid #fff;
opacity: 0.7;
}
#bankImages {
display: none;
}
+
+ #uploadDialog label {
+ color: black;
+ }
</style>
<!-- Javascript bits: dojo -->
<script>
dojo.require("dojo.parser");
dojo.require("dijit.Dialog");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.TextBox");
</script>
<!-- Tower -->
<!--<div id="tower"></div>-->
<!-- Navigation -->
{$loc = explode('C', $CurrentPerso->location_local)}{$loc[0] = substr($loc[0], 1)}
<div class="grid_7 alpha">
- {#Tower#} » {#Floor#} <span id="TowerFloor">{$loc[0]}</span> » {#Couloir#} <span id="TowerCouloir">{$loc[1]}</span>
+ {#Sector#} T<span id="TowerFloor">{$loc[0]}</span>
</div>
<div class="grid_2" style="text-align: center">
- [<span id="TowerLocation">{$CurrentPerso->location_local}</span>]
+ {#Couloir#} <span id="TowerCouloir">{$loc[1]}</span>
</div>
<div class="grid_7 omega" style="text-align: right">
<a href="{if $loc[0] == 1}#{else}/do.php/set_local_location/T{$loc[0] - 1}C{$loc[1]}?redirectTo={get_url()}{/if}" onClick="passage.goUp(); return false;">{#GoUp#}</a> | <a href="/do.php/set_local_location/T{$loc[0] + 1}C{$loc[1]}?redirectTo={get_url()}" onClick="passage.goDown(); return false;">{#GoDown#}</a>
</div>
<!-- Passage -->
<div class="grid_16 alpha omega">
<div id="passage">
<div id="passage_gallery"></div>
<!-- Navigation -->
</div>
</div>
<!-- Upload dialog -->
<div dojoType="dijit.Dialog" id="uploadDialog" style="display: none;" title="{#UploadNewArtwork#}">
<p>{#UploadNewArtworkDescription#}</p>
<form method="post" id="test" action="{get_xhr_hashed_url('upload_content', {$CurrentPerso->location_global})}?redirectTo={get_url()}" enctype="multipart/form-data">
<input type="hidden" id="location_local" name="location_local" value='{$CurrentPerso->location_local}' />
<input type="hidden" id="i" name="location_k" value="-1">
<div class="row">
<label for="artwork" class="firstLabel">{#UploadNewArtworkPicture#} (max. {ini_get('upload_max_filesize')})</label>
<input type="file" name="artwork" id="artwork" class="long" />
</div>
<div class="row">
<label for="title" class="firstLabel">{#UploadNewArtworkTitle#}</label>
<input dojoType="dijit.form.TextBox" name="title" id="title" type="text" class="long" />
</div>
<div class="row center">
<button dojoType="dijit.form.Button" iconClass="dijitEditorIcon dijitEditorIconSave" type="submit" value="Save" />OK</button>
</div>
</form>
</div>
<!-- Javascript bits: script.aculo.us -->
<script src="{#StaticContentURL#}/js/prototype.js"></script>
<script src="{#StaticContentURL#}/js/effects.js"></script>
<!-- Javascript bits: tower and couloir/passage/corridor/gallery/whatNameYouGiveToIt -->
<script src="{$SCENE_URL}/{$CurrentPerso->location_global}/tower.js"></script>
<script>
//Initializes tower map
//tower.hl = '{$SCENE_URL}/{$CurrentPerso->location_global}/hl.png';
//tower.highlight(1);
//Initializes passage view
passage.bayPath = '{$SCENE_URL}/{$CurrentPerso->location_global}/couloir/bay/';
passage.initialize('{$location->global}', '{$CurrentPerso->location_local}');
//Initializes gallery
gallery.initialize('{#StaticContentURL#}', '{get_xhr_hashed_url('get_content', {$CurrentPerso->location_global})}');
</script>
\ No newline at end of file
diff --git a/content/scenes/B00001001/tower.js b/content/scenes/B00001001/tower.js
index 569218f..39c6849 100644
--- a/content/scenes/B00001001/tower.js
+++ b/content/scenes/B00001001/tower.js
@@ -1,605 +1,608 @@
/* -------------------------------------------------------------
Zed hypership tower javascript code
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Authors: Dereckson (tower, passage and gallery classes)
G. Fernandez (tv, Library classes)
Tags: animation
Filename: login.js
Version: 1.0
Created: 2010-01-31
Updated: 2010-02-23
Licences: Dereckson code is dual licensed:
BSD and Creative Commons BY 3.0.
G. Fernandez code is licensed under CC-BY-NC 2.0.
Dependencies: dojo (xhr calls, dialog ui)
------------------------------------------------------------- */
/* -------------------------------------------------------------
Tower class
Prints a tower map, with hl on current corridor
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var tower = {
//The source to highlight picture
hl: 'hl.png',
//The highlighted corridor (1-6). 0 = no hl
i: 0,
//The corridor 4 (top) hl starts at:
hlStartPosition: [163, 93],
//Gets CurrentPerso html code
getHighlightCode: function () {
return '<img id="tower_hl" src="' + this.hl + '" alt="Corridor ' + this.i + '" />';
},
highlight: function (i) {
//If already there, nothing to do
if (this.i == i) return;
//Puts hl
this.i = i;
var tower = document.getElementById("tower");
if (tower != null) {
tower.innerHTML = this.getHighlightCode();
var towerHl = document.getElementById("tower_hl");
if (towerHl != null) {
towerHl.style.left = this.hlStartPosition[0] + "px";
towerHl.style.top = this.hlStartPosition[1] + "px";
//The 4 is okay
if (i == 4) return;
//Gets rotation angle
if (i > 4) {
angle = 60 * (i - 4);
} else {
angle = 180 + (i -1) * 60;
}
}
}
}
}
/* -------------------------------------------------------------
Passage class
Prints relevant bay
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//Corridors are like art galleries
var passage = {
//Parameters
shipGlobalLocation: null,
persoLocalLocation: null,
bayPath: '',
id: 'passage',
//gets hyperspace bay
//randomly selected from 23 choices
getHyperspaceBay: function () {
var n = Math.floor(Math.random() * 23 + 1);
return 'hyper/' + ((n.toString().length == 1) ? "0" + n : n) + '.png';
},
getBay: function () {
//The bay depends of ship location
if (this.shipGlobalLocation[0] == 'B') {
//TODO: handle orientation of the ship and objects
//{$bay = substr($location->global, 0, 6)}
return this.shipGlobalLocation.substring(0, 6) + '.png';
} else {
//TODO: check if we're really in hyperspace.
//If not, we should fallback on default.png
return this.getHyperspaceBay();
}
return 'default.png'; //will be reachable, cf. upper
},
updateBay: function () {
var bgImage = 'url("' + this.bayPath + this.getBay() + '")';
document.getElementById(this.id).style.backgroundImage = bgImage;
},
getLocalLocation: function () {
//Splits TtCc expression at C
// '0' => "T2"
// '1' => "1"
var location = this.persoLocalLocation.split('C');
if (location.length == 2 && location[0][0] == 'T') {
//Current t, c coordinates
var t = location[0].substring(1);
var c = location[1];
}
return [t, c];
},
goLeft: function () {
//TxC1 -> TxC6 -> TxC5 -> TxC4 -> TxC3 -> TxC2 -> TxC1
tc = this.getLocalLocation();
t = tc[0];
c = tc[1];
//New c coordinates
var nc = (c == 1) ? 6 : c - 1;
this.moveTo(t, nc);
},
goUp: function () {
//TxCy -> T[x+1]Cy
tc = this.getLocalLocation();
t = tc[0];
c = tc[1];
if (t > 1) {
this.moveTo(--t, c);
}
},
goDown: function () {
//TxCy -> T[x+1]Cy
tc = this.getLocalLocation();
t = tc[0];
c = tc[1];
this.moveTo(++t, c);
},
goRight: function () {
//TxC1 -> TxC2 -> TxC3 -> TxC4 -> TxC5 -> TxC6 -> TxC1
tc = this.getLocalLocation();
t = tc[0];
c = tc[1];
//New c coordinates
var nc = (c == 6) ? 1 : eval(c) + 1;
this.moveTo(t, nc);
},
moveTo: function (t, c) {
//New local location
local_location = 'T' + t + 'C' + c;
passage.persoLocalLocation = local_location;
//Updates coordinates
dojo.byId('TowerCouloir').innerHTML = c;
dojo.byId('TowerFloor').innerHTML = t;
- dojo.byId('TowerLocation').innerHTML = local_location;
dojo.byId('location_local').value = local_location;
//Updates bays
//passage.updateBay();
//TODO: make url parameter compatible to any URL scheme
//TODO: check in dojo doc if the local_location have to be encoded
dojo.xhrGet({
handleAs: "json",
url: "/do.php/set_local_location/" + local_location,
preventCache: true,
handle: function (response, ioArgs) {
//Loads new gallery content
if (gallery.initialized) {
gallery.loadPics();
}
}
});
},
onGalleryInitialized: function () {
//Adds left and right arrows
this.addLeftArrow();
this.addRightArrow();
},
addLeftArrow: function () {
//Adds left arrow
var element = document.getElementById(this.id);
var left = element.offsetLeft + 28;
var top = element.offsetTop + 173;
element.innerHTML += '<div id="passage_left" onClick="passage.goLeft()" style="display: none; position: absolute; top: ' + top + 'px; left: ' + left + 'px"></div>';
},
addRightArrow: function () {
//Adds right arrow
var element = document.getElementById(this.id);
var left = element.offsetLeft + 898;
var top = element.offsetTop + 173;
element.innerHTML += '<div id="passage_right" onClick="passage.goRight()" style="display: none; position: absolute; top: ' + top + 'px; left: ' + left + 'px"></div>';
},
displayArrows: function (state) {
//Displays left and right arrows?
var display = [false, false];
switch (state) {
case "left":
display[0] = true;
break;
case "right":
display[1] = true;
break;
}
document.getElementById("passage_left").style.display = display[0] ? 'block' : 'none';
document.getElementById("passage_right").style.display = display[1] ? 'block' : 'none';
},
onmousemove: function () {
var element = document.getElementById(this.id);
var left = element.offsetLeft;
var top = element.offsetTop;
//alert(element.offsetWidth);
var inPassage = (
mouse.x >= left && mouse.y >= top &&
mouse.x <= left + element.offsetWidth &&
mouse.y <= top + element.offsetHeight
)
if (inPassage) {
var x = mouse.x - left;
var y = mouse.y - top;
if (x < 130) {
this.displayArrows('left');
} else if (x > 830) {
this.displayArrows('right');
} else {
this.displayArrows('none');
}
}
},
+ isValidLocalLocation: function (localLocation) {
+ return /^T[1-9][0-9]*C[1-6]$/.test(localLocation);
+ },
+
initialize: function (shipGlobalLocation, persoLocalLocation) {
this.shipGlobalLocation = shipGlobalLocation;
this.persoLocalLocation = persoLocalLocation;
- if (persoLocalLocation == "") {
+ if (!this.isValidLocalLocation(persoLocalLocation)) {
this.moveTo(2, 1);
//Notify (this code requires prototype.js (or jquery, but in this case, simplify it with a insertAfter call))
//TODO: ensure this code have dojo as only dependency
$("header").replace('<div id="header">' + $("header").innerHTML + '</div><div class="container_16"><div class="grid_16 alpha omega"><div class="notify">As you were losing yourself in the hypership tower, your paths guide you to the second floor.</div></div></div>');
}
this.updateBay();
}
}
/* -------------------------------------------------------------
Gallery class
Prints the gallery and calls artwork script
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var gallery = {
pics: [],
initialized: false,
artworkDisplayMode: false,
currentPic: -1,
path: '',
loadXhrUrl: null,
getSquare: function (url) {
//Gets extension position (last . position)
var pos = url.lastIndexOf('.');
return url.substring(0, pos) + 'Square' + url.substring(pos);
},
showGallery: function () {
var html = '<ul>';
for (var i = 0 ; i < this.pics.length ; i++) {
if (this.pics[i] != null) {
//Shows artwork
html += '\n\t<li><img src="' + this.getSquare(this.pics[i]) + '" onClick="gallery.show(' + i + ')" /></li>';
} else {
//Shows placeholder
html += '\n\t<li onClick="gallery.uploadDialog(' + i + ');"></li>';
}
}
html += '\n</ul>';
document.getElementById('passage_gallery').innerHTML = html;
passage.onGalleryInitialized();
},
hideGallery: function () {
var elem = document.getElementById('passage_gallery');
Effect.Puff(elem);
},
loadPics: function () {
dojo.xhrGet({
handleAs: 'json',
url: this.loadXhrUrl + '?location_local=' + passage.persoLocalLocation,
preventCache: true,
handle: function (response, ioArgs) {
//TODO: in the future, we should print some metadata,
// and the objects will be useful.
// Meanwhile, we transform it to an array line.
var pics = [null, null, null];
//Builds pics array
for (var i = 0 ; i < response.length ; i++) {
if (response[i]['location_k'] > -1 && response[i]['location_k'] < 3) {
pics[response[i]['location_k']] = gallery.path + '/' + response[i]['path'];
}
}
//alert(dump(response));
gallery.loadPicsCallback(pics);
}
});
},
loadPicsCallback: function (pics) {
//Sets pics array
this.pics = pics;
//Show gallery
this.showGallery();
//Init done
if (!this.initialized) {
this.initialized = true;
}
},
show: function (i) {
//Hides gallery
this.hideGallery();
//Shows image
setTimeout('gallery.showImage(' + i + ')', 800);
},
showImage: function (i) {
//Sets image information
this.currentPic = i; //currently not used
this.artworkDisplayMode = true; //to handle properly ESC key down.
//New HTML passage code
document.getElementById('passage').innerHTML = '<div id="screen"></div><div id="bankImages"><img src="' + this.pics[i] + '" /></div>';
//Calls Photo3D script
onresize = tv.resize;
tv.init();
},
backToGallery: function () {
//Fades current picture
Effect.Fade(document.getElementById('screen'));
//Rebuilds gallery (in 800 ms, to let time to fading effect)
setTimeout("document.getElementById('passage').innerHTML = '<div id=\"passage_gallery\"></div>'; gallery.showGallery()", 800);
//Sets image information
this.currentPic = -1; //currently not used
this.artworkDisplayMode = false; //to handle properly ESC key down.
},
uploadDialog: function (i) {
document.getElementById('i').value = i;
dijit.byId('uploadDialog').show();
},
initialize: function (path, loadXhrUrl) {
//Sets load xhr url
this.path = path;
this.loadXhrUrl = loadXhrUrl;
//Loads pics
this.loadPics();
//Listens ESC key
document.onkeydown = function (e) {
if (e.keyCode == 27) {
if (gallery.artworkDisplayMode) {
gallery.backToGallery();
} else {
gallery.hideGallery();
}
}
}
}
}
/* -------------------------------------------------------------
Photo3D G. Fernandez script
http://www.dhteumeuleu.com/runscript.php?scr=photo3D.html
TODO: simplify code to handle one picture, and not an array
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var gridsize = 1;
var Library = {};
Library.ease = function() {
this.target = 0;
this.position = 0;
this.move = function(_1, _2) {
this.position += (_1 - this.position) * _2;
};
};
var tv = {
O: [],
screen: {},
grid: {
size: gridsize,
borderSize: 6,
zoomed: false
},
angle: {
x: new Library.ease(),
y: new Library.ease()
},
camera: {
x: new Library.ease(),
y: new Library.ease(),
zoom: new Library.ease(),
focalLength: 750
},
init: function() {
this.screen.obj = document.getElementById("screen");
var images = document.getElementById("bankImages").getElementsByTagName("img");
this.screen.obj.onselectstart = function () {
return false;
};
this.screen.obj.ondrag = function () {
return false;
};
var ni = 0;
var n = (tv.grid.size / 2) - 0.5;
for (var y = - n ; y <= n ; y++) {
for (var x = - n ; x <= n; x++) {
var o = document.createElement("img");
var i = images[(ni++) % images.length];
o.className = "tvout";
o.src = i.src;
tv.screen.obj.appendChild(o);
o.point3D = {
x: x,
y: y,
z: new Library.ease()
};
o.point2D = {};
o.ratioImage = 1;
tv.O.push(o);
o.onmouseover = function () {
if (!tv.grid.zoomed) {
if (tv.o) {
tv.o.point3D.z.target = 0;
tv.o.className = "tvout";
}
this.className = "tvover";
this.point3D.z.target = - 0.5;
tv.o = this;
}
};
o.onclick = function () {
if (!tv.grid.zoomed) {
tv.camera.x.target = this.point3D.x;
tv.camera.y.target = this.point3D.y;
tv.camera.zoom.target = tv.screen.w * 1.25;
tv.grid.zoomed = this;
} else {
if (this == tv.grid.zoomed) {
tv.camera.x.target = 0;
tv.camera.y.target = 0;
tv.camera.zoom.target = tv.screen.w / (tv.grid.size + 0.1);
tv.grid.zoomed = false;
}
}
};
o.calc = function () {
this.point3D.z.move(this.point3D.z.target, 0.5);
var x = (this.point3D.x - tv.camera.x.position) * tv.camera.zoom.position;
var y = (this.point3D.y - tv.camera.y.position) * tv.camera.zoom.position;
var z = this.point3D.z.position * tv.camera.zoom.position;
var xy = tv.angle.cx * y - tv.angle.sx * z;
var xz = tv.angle.sx * y+tv.angle.cx * z;
var yz = tv.angle.cy * xz - tv.angle.sy * x;
var yx = tv.angle.sy * xz+tv.angle.cy * x;
this.point2D.scale = tv.camera.focalLength / (tv.camera.focalLength+yz);
this.point2D.x = yx * this.point2D.scale;
this.point2D.y = xy * this.point2D.scale;
this.point2D.w = Math.round(Math.max(0, this.point2D.scale * tv.camera.zoom.position * 0.8));
if (this.ratioImage > 1) {
this.point2D.h = Math.round(this.point2D.w / this.ratioImage);
} else {
this.point2D.h = this.point2D.w;this.point2D.w = Math.round(this.point2D.h * this.ratioImage);
}
};
o.draw = function () {
if (this.complete) {
if (!this.loaded) {
if (!this.img) {
this.img = new Image();
this.img.src = this.src;
}
if (this.img.complete) {
this.style.visibility = "visible";
this.ratioImage = this.img.width / this.img.height;
this.loaded = true;this.img = false;
}
}
this.style.left = Math.round(this.point2D.x * this.point2D.scale + tv.screen.w - this.point2D.w * 0.5) + "px";
this.style.top = Math.round(this.point2D.y * this.point2D.scale + tv.screen.h - this.point2D.h * 0.5) + "px";
this.style.width = this.point2D.w + "px";
this.style.height = this.point2D.h + "px";
this.style.borderWidth = Math.round(Math.max(this.point2D.w, this.point2D.h) * tv.grid.borderSize * 0.01) + "px";
this.style.zIndex = Math.floor(this.point2D.scale * 100);
}
};
}
}
tv.resize();
mouse.y = tv.screen.y+tv.screen.h;
mouse.x = tv.screen.x+tv.screen.w;
tv.run();
},
resize: function() {
var o = tv.screen.obj;
tv.screen.w = o.offsetWidth / 2;
tv.screen.h = o.offsetHeight / 2;
tv.camera.zoom.target = tv.screen.w / (tv.grid.size + 0.1);
for (tv.screen.x = 0, tv.screen.y = 0 ; o != null ; o = o.offsetParent) {
tv.screen.x += o.offsetLeft;
tv.screen.y = o.offsetTop;
}
},
run: function () {
tv.angle.x.move(-(mouse.y - tv.screen.h - tv.screen.y) * 0.0025, 0.1);
tv.angle.y.move((mouse.x - tv.screen.w - tv.screen.x) * 0.0025, 0.1);
tv.camera.x.move(tv.camera.x.target, tv.grid.zoomed ? 0.25: 0.025);
tv.camera.y.move(tv.camera.y.target, tv.grid.zoomed ? 0.25: 0.025);
tv.camera.zoom.move(tv.camera.zoom.target, 0.05);
tv.angle.cx = Math.cos(tv.angle.x.position);
tv.angle.sx = Math.sin(tv.angle.x.position);
tv.angle.cy = Math.cos(tv.angle.y.position);
tv.angle.sy = Math.sin(tv.angle.y.position);
for (var i = 0, o ; o = tv.O[i] ; i++) {
o.calc();
o.draw();
}
setTimeout(tv.run, 32);
}
};
var mouse = {x: 0, y: 0};
document.onmousemove = function (e) {
if (window.event) {
e = window.event;
}
mouse.x = e.clientX;
mouse.y = e.clientY;
passage.onmousemove();
return false;
};
\ No newline at end of file
diff --git a/lang/en/scenes.conf b/lang/en/scenes.conf
index 5d32790..0221a92 100644
--- a/lang/en/scenes.conf
+++ b/lang/en/scenes.conf
@@ -1,20 +1,21 @@
#Zed language config file - scenes
#Language: English
#Code: en
#Author: Dereckson
###
### Hypership tower
###
[B00001001]
Tower = Tour
Floor = Floor
+Sector = Sector
Couloir = Couloir
GoUp = Climb up
GoDown = Climb down
UploadNewArtwork = "Upload new artwork"
UploadNewArtworkDescription = "This frame is free. You can display here your artwork or any other artwork you like."
UploadNewArtworkPicture = "Picture"
UploadNewArtworkTitle = "Title"
\ No newline at end of file
diff --git a/lang/fr/scenes.conf b/lang/fr/scenes.conf
index 9d4ed5d..03ecbbf 100644
--- a/lang/fr/scenes.conf
+++ b/lang/fr/scenes.conf
@@ -1,20 +1,21 @@
#Zed language config file - scenes
#Language: French
#Code: fr
#Author: Dereckson
###
### Hypership tower
###
[B00001001]
Tower = Tour
Floor = Étage
+Sector = Secteur
Couloir = Couloir
GoUp = Monter
GoDown = Descendre
UploadNewArtwork = "Téléverser une œuvre"
UploadNewArtworkDescription = "Ce cadre est libre. Vous pouvez exposer ici l'un de vos travaux ou tout autre que vous appréciez."
UploadNewArtworkPicture = "Œuvre"
UploadNewArtworkTitle = "Titre"
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 10, 16:18 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
20868
Default Alt Text
(29 KB)

Event Timeline