Commit 48168bcb authored by Bas Lijnse's avatar Bas Lijnse

Quick win optimization of GUI updates: simpler path encoding, disable layout...

Quick win optimization of GUI updates: simpler path encoding, disable layout during replace, move windows to toplevel

git-svn-id: https://svn.cs.ru.nl/repos/iTask-system/trunk@2367 63da3aa8-80fd-4f01-9db8-e6ea747a3da2
parent c9b31051
......@@ -40,7 +40,6 @@ Ext.define('itwc.component.choice.Tree',{
}
},
onItemExpand: function(record) {
console.log("expand");
var value = record.raw && record.raw.value;
this.viewport = this.viewport || this.up('viewport');
......@@ -48,7 +47,6 @@ Ext.define('itwc.component.choice.Tree',{
return false;
},
onItemCollapse: function(record) {
console.log("collapse");
var value = record.raw && record.raw.value;
this.viewport = this.viewport || this.up('viewport');
......@@ -56,7 +54,6 @@ Ext.define('itwc.component.choice.Tree',{
return false;
},
onItemClick: function(tree,record,item) {
console.log(record);
var value = record.raw && record.raw.value;
......
......@@ -26,20 +26,15 @@ Ext.define('itwc.component.edit.Editable',{
me.syncEditsEnabled = true;
},
findViewport: function() {
var viewport = this.viewport
searchIn = this;
var viewport = this.viewport,
win;
if(viewport) {
return viewport;
}
while(true) {
viewport = searchIn.up('viewport');
if(viewport) {
return viewport;
}else {
searchIn = (this.xtype == 'itwc_window') ? this.panelRef : this.up('itwc_window').panelRef;
}
}
} else if (viewport = this.up('viewport')) {
return this.viewport = viewport;
} else if (win = this.up('itwc_window')) {
return this.viewport = win.viewport;
}
},
getTaskId: function() {
return this.taskId;
......
......@@ -168,13 +168,12 @@ Ext.define('itwc.component.edit.GoogleMap',{
},
setValue: function(value) {
},
update: function(def) {
selfUpdate: function(def) {
var me = this;
//Update perspective
//Update markers
console.log(def);
},
onDestroy: function() {
if(this.map) {
......
......@@ -19,18 +19,6 @@ Ext.define('itwc.container.Panel',{
minHeight: 'wrap',
initComponent: function() {
//Create windows and link them back to this panel
if(this.windows && this.windows.length) {
var numWindows = this.windows.length,
i;
for(i = 0; i < numWindows; i++) {
this.windows[i] = Ext.create('itwc.container.Window',this.windows[i]);
//Create reference back to this panel
this.windows[i].panelRef = this;
}
}
//Set shrinkWrap using width & height values
this.shrinkWrap = (this.width === 'wrap' ? 1 : 0) | (this.height === 'wrap' ? 2 : 0);
......@@ -44,31 +32,7 @@ Ext.define('itwc.container.Panel',{
me.initHotkeys();
},
onDestroy: function () {
//Clean up the windows we created
if(this.windows && this.windows.length) {
var numWindows = this.windows.length,
i;
for(i = 0; i < numWindows; i++) {
this.windows[i].destroy();
}
}
this.destroyHotkeys();
this.callParent(arguments);
},
replaceWindow: function (index, def) {
this.windows = this.windows || [];
this.windows[index].destroy();
this.windows[index] = Ext.create('itwc.container.Window',def);
this.windows[index].panelRef = this;
},
insertWindow: function (index, def) {
this.windows = this.windows || [];
this.windows.splice(index,0,Ext.create('itwc.container.Window',def));
this.windows[index].panelRef = this;
},
removeWindow: function (index) {
this.windows = this.windows || [];
this.windows[index].destroy();
this.windows.splice(index,1);
}
});
......@@ -35,7 +35,7 @@ Ext.define('itwc.container.Tasklet', {
this.taskId = newTaskId;
},
update: function(newTasklet) {
selfUpdate: function(newTasklet) {
if(this.taskId != newTasklet.taskId)
this.setTaskId(newTasklet.taskId);
},
......
......@@ -8,6 +8,8 @@ Ext.define('itwc.container.Viewport',{
valign: 'middle',
halign: 'center',
windows: [],
initComponent: function() {
this.layout = {type:'itwc_box', direction: this.direction, halign: this.halign, valign: this.valign, padding: this.padding};
this.callParent(arguments);
......@@ -26,42 +28,42 @@ Ext.define('itwc.container.Viewport',{
},
getComponentByPath: function(path) {
var me = this,
steps = path.split('-'),
numSteps = steps.length,
numSteps,
cmp = me,
step, i, undef;
if(path == "" || path == "0") {
return me;
if(path.length && path[0] === "w") { //Select window if the path starts with "w"
path.shift();
cmp = me.windows[path.shift()];
}
for(i = 1; i < numSteps; i++) {
step = steps[i];
numSteps = path.length;
for(i = 0; i < numSteps; i++) {
step = path[i];
if(step === "m") {
cmp = cmp.getDockedComponent(0);
if(!cmp)
return undef;
} else if (step === "w") {
if(i < numSteps - 1) {
if(cmp.windows && cmp.windows.length) {
if((i+1) < numSteps && cmp.windows[parseInt(steps[i+1])]) {
cmp = cmp.windows[parseInt(steps[i+1])];
i++;
}
} else {
return undef;
}
}
} else {
if(cmp.items && cmp.items.get) {
cmp = cmp.items.get(parseInt(step));
if(!cmp)
return undef;
} else {
return undef;
}
}
cmp = cmp.items && cmp.items.get && cmp.items.get(step);
}
if(!cmp)
return undef;
}
return cmp;
},
setTitle: function(title) {
document.title = title; //Set the html document title
},
addWindow: function (index, def) {
var me = this;
me.windows = me.windows || [];
me.windows.splice(index,0,Ext.create('itwc.container.Window',def));
me.windows[index].viewport = me;
},
removeWindow: function (index) {
var me = this;
me.windows = me.windows || [];
me.windows[index].destroy();
me.windows.splice(index,1);
}
});
......@@ -227,39 +227,20 @@ Ext.define('itwc.controller.Controller', {
for(i = 0; i < numUpdates; i++) {
update = updates[i];
//Special case: title update of main window
if(update.method == 'setTitle' && update.path == '0') {
document.title = update.arguments[0];
continue;
}
//Special case: replace of the full viewport panel
//Change the main window title and remove it from the panel definition
if(update.method == 'replace' && update.path == '') {
document.title = update.arguments[1].title ? update.arguments[1].title : 'Untitled';
delete(update.arguments[1].title);
}
try {
cmp = me.viewport.getComponentByPath(update.path);
if(cmp) {
//If the operation targets the window set of a panel
//Use different remove/insert/replace functions
if(update.path.endsWith('w')) {
switch(update.method) {
case 'replace': update.method = 'replaceWindow'; break;
case 'insert': update.method = 'insertWindow'; break;
case 'remove': update.method = 'removeWindow'; break;
}
}
if(cmp = me.viewport.getComponentByPath(update.path)) {
//Try to call the update method
if(cmp && typeof cmp[update.method] == 'function') {
cmp[update.method].apply(cmp,update.arguments);
} else {
//If replace is not defined as function, try remove followed by add
if(update.method == 'replace' && typeof cmp['remove'] == 'function' && typeof cmp['insert'] == 'function') {
me.warn("Doing inefficient replace by using remove followed by add");
cmp.suspendLayout = true; //Don't layout in between remove and add
cmp.remove(update.arguments[0]);
cmp.insert(update.arguments[0],update.arguments[1]);
cmp.suspendLayout = false;
cmp.doLayout(); //Now do the layout
} else {
me.error("Can't apply " + update.method + " to " + cmp.getId() + " (" + cmp.getXType() + ")");
}
......@@ -275,5 +256,10 @@ Ext.define('itwc.controller.Controller', {
error: function(e) {
alert(e);
//window.location = window.location;
},
warn: function(w) {
if(console && console.log) {
console.log("Warning:",w);
}
}
});
......@@ -112,8 +112,8 @@ actionsToCloseId :: ![UIAction] -> (!Maybe String,![UIAction])
//Util
defToContainer :: UIDef -> UIControl
defToPanel :: UIDef -> UIControl
defToWindow :: UIDef -> UIControl
defToControl :: UIDef -> UIControl
defToWindow :: UIDef -> UIWindow
mergeAttributes :: UIAttributes UIAttributes -> UIAttributes
......
This diff is collapsed.
......@@ -9,14 +9,14 @@ derive JSONEncode TIMeta, TIReduct, TIResult, TaskTree, TaskListEntry, TaskListE
derive JSONDecode TIMeta, TIReduct, TIResult, TaskTree, TaskListEntry, TaskListEntryState
//IS ALSO DERIVED IN TASK STORE: SEEMS REDUNDANT
derive JSONEncode UIDef, UIAction, UIViewport, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONEncode UIDef, UIAction, UIViewport, UIWindow, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONEncode UIControlSequence, UIActionSet, UIControlGroup, UIAbstractContainer
derive JSONEncode UIProgressOpts, UISliderOpts, UIGoogleMapOpts, UIGoogleMapMarker, UIGoogleMapOptions, UICodeOpts, UIGridOpts, UIIconOpts, UILabelOpts, UITabOpts, UITaskletOpts, UITaskletPHOpts, UITreeNode
derive JSONEncode UIMenuButtonOpts, UIButtonOpts, UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts, UIViewportOpts
derive JSONEncode UISize, UIMinSize, UIDirection, UIHAlign, UIVAlign, UISideSizes, UIMenuItem
derive JSONDecode TaskRep, TaskCompositionType
derive JSONDecode UIDef, UIAction, UIViewport, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONDecode UIDef, UIAction, UIViewport, UIWindow, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONDecode UIControlSequence, UIActionSet, UIControlGroup, UIAbstractContainer
derive JSONDecode UIProgressOpts, UISliderOpts, UIGoogleMapOpts, UIGoogleMapMarker, UIGoogleMapOptions, UICodeOpts, UIGridOpts, UIIconOpts, UILabelOpts, UITabOpts, UITaskletOpts, UITaskletPHOpts, UITreeNode
derive JSONDecode UIMenuButtonOpts, UIButtonOpts, UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts, UIViewportOpts
......
......@@ -8,14 +8,14 @@ import SerializationGraphCopy //TODO: Make switchable from within iTasks module
//Derives required for storage of TUI definitions
derive JSONEncode TaskRep, TaskCompositionType
derive JSONEncode UIDef, UIAction, UIViewport, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONEncode UIDef, UIAction, UIViewport, UIWindow, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONEncode UIProgressOpts, UISliderOpts, UIGoogleMapOpts, UIGoogleMapMarker, UIGoogleMapOptions, UICodeOpts, UIGridOpts, UIIconOpts, UILabelOpts, UITabOpts, UITaskletOpts, UITaskletPHOpts, UITreeNode
derive JSONEncode UIControlSequence, UIActionSet, UIControlGroup, UIAbstractContainer
derive JSONEncode UIMenuButtonOpts, UIButtonOpts, UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts, UIViewportOpts
derive JSONEncode UISize, UIMinSize, UIDirection, UIHAlign, UIVAlign, UISideSizes, UIMenuItem
derive JSONDecode TaskRep, TaskCompositionType
derive JSONDecode UIDef, UIAction, UIViewport, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONDecode UIDef, UIAction, UIViewport, UIWindow, UIControl, UISizeOpts, UIViewOpts, UIEditOpts, UIActionOpts, UIChoiceOpts, UIItemsOpts
derive JSONDecode UIProgressOpts, UISliderOpts, UIGoogleMapOpts, UIGoogleMapMarker, UIGoogleMapOptions, UICodeOpts, UIGridOpts, UIIconOpts, UILabelOpts, UITabOpts, UITaskletOpts, UITaskletPHOpts, UITreeNode
derive JSONDecode UIControlSequence, UIActionSet, UIControlGroup, UIAbstractContainer
derive JSONDecode UIMenuButtonOpts, UIButtonOpts, UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts, UIViewportOpts
......
......@@ -48,7 +48,7 @@ from Map import :: Map(..)
, controls :: UIControls
, actions :: UIActions
, direction :: UIDirection
, windows :: [UIControl]
, windows :: [UIWindow]
, hotkeys :: [UIKeyAction]
}
......@@ -69,8 +69,22 @@ from Map import :: Map(..)
:: UIViewportOpts =
{ title :: !Maybe String
// , tbar :: !Maybe [UIControl]
, hotkeys :: !Maybe [UIKeyAction]
, windows :: ![UIWindow]
}
// Floating window
:: UIWindow = UIWindow !UISizeOpts !UIItemsOpts !UIWindowOpts
:: UIWindowOpts =
{ title :: !Maybe String
, tbar :: !Maybe [UIControl]
, focusTaskId :: !Maybe String
, closeTaskId :: !Maybe String
, hotkeys :: !Maybe [UIKeyAction]
, iconCls :: !Maybe String
, baseCls :: !Maybe String
, bodyCls :: !Maybe String
}
:: UIControl
......@@ -114,7 +128,6 @@ from Map import :: Map(..)
| UIContainer !UISizeOpts !UIItemsOpts !UIContainerOpts // - Container (lightweight wrapper to compose components)
| UIPanel !UISizeOpts !UIItemsOpts !UIPanelOpts // - Panel (container with decoration like a title header, icon and frame)
| UIFieldSet !UISizeOpts !UIItemsOpts !UIFieldSetOpts // - Fieldset (wrapper with a simple border and title)
| UIWindow !UISizeOpts !UIItemsOpts !UIWindowOpts // - Window (floating window)
:: UISizeOpts =
{ width :: !Maybe UISize
......@@ -309,7 +322,6 @@ from Map import :: Map(..)
{ title :: !Maybe String
, frame :: !Bool
, tbar :: !Maybe [UIControl]
, windows :: !Maybe [UIControl]
, hotkeys :: !Maybe [UIKeyAction]
, iconCls :: !Maybe String
, baseCls :: !Maybe String
......@@ -319,24 +331,13 @@ from Map import :: Map(..)
:: UIFieldSetOpts =
{ title :: !String
}
:: UIWindowOpts =
{ title :: !Maybe String
, tbar :: !Maybe [UIControl]
, focusTaskId :: !Maybe String
, closeTaskId :: !Maybe String
, hotkeys :: !Maybe [UIKeyAction]
, iconCls :: !Maybe String
, baseCls :: !Maybe String
, bodyCls :: !Maybe String
}
//Utility functions
defaultSizeOpts :: UISizeOpts
defaultItemsOpts :: [UIControl] -> UIItemsOpts
defaultContainer :: ![UIControl] -> UIControl
defaultPanel :: ![UIControl] -> UIControl
defaultWindow :: ![UIControl] -> UIControl
defaultWindow :: ![UIControl] -> UIWindow
stringDisplay :: !String -> UIControl
//Success guaranteed access to the possible parts of a ui definition
......@@ -345,7 +346,7 @@ uiDefControls :: UIDef -> [UIControl]
uiDefAnnotatedControls :: UIDef -> [(UIControl,UIAttributes)]
uiDefActions :: UIDef -> [UIAction]
uiDefDirection :: UIDef -> UIDirection
uiDefWindows :: UIDef -> [UIControl]
uiDefWindows :: UIDef -> [UIWindow]
uiDefSetAttribute :: String String UIDef -> UIDef
uiDefSetDirection :: UIDirection UIDef -> UIDef
......@@ -353,6 +354,7 @@ uiDefSetDirection :: UIDirection UIDef -> UIDef
//can be interpreted by the client framework
encodeUIDefinition :: !UIDef -> JSONNode
encodeUIControl :: !UIControl -> JSONNode
encodeUIWindow :: !UIWindow -> JSONNode
//Encoding of values for use in UI diffs
class encodeUIValue a :: a -> JSONNode
......
......@@ -13,9 +13,9 @@ defaultContainer :: ![UIControl] -> UIControl
defaultContainer items = UIContainer defaultSizeOpts (defaultItemsOpts items) {UIContainerOpts|baseCls=Nothing,bodyCls=Nothing}
defaultPanel :: ![UIControl] -> UIControl
defaultPanel items = UIPanel defaultSizeOpts (defaultItemsOpts items) {UIPanelOpts|title=Nothing,frame=False,tbar=Nothing,windows=Nothing,hotkeys=Nothing,iconCls=Nothing,baseCls=Nothing,bodyCls=Nothing}
defaultPanel items = UIPanel defaultSizeOpts (defaultItemsOpts items) {UIPanelOpts|title=Nothing,frame=False,tbar=Nothing,hotkeys=Nothing,iconCls=Nothing,baseCls=Nothing,bodyCls=Nothing}
defaultWindow :: ![UIControl] -> UIControl
defaultWindow :: ![UIControl] -> UIWindow
defaultWindow items = UIWindow defaultSizeOpts (defaultItemsOpts items) {UIWindowOpts|title=Nothing,tbar=Nothing,closeTaskId=Nothing,focusTaskId=Nothing,hotkeys=Nothing,iconCls=Nothing,baseCls=Nothing,bodyCls=Nothing}
stringDisplay :: !String -> UIControl
......@@ -54,7 +54,7 @@ uiDefDirection (UIControlGroup {UIControlGroup|direction}) = direction
uiDefDirection (UIAbstractContainer {UIAbstractContainer|direction}) = direction
uiDefDirection _ = Vertical
uiDefWindows :: UIDef -> [UIControl]
uiDefWindows :: UIDef -> [UIWindow]
uiDefWindows (UIAbstractContainer {UIAbstractContainer|windows}) = windows
uiDefWindows _ = []
......@@ -79,7 +79,7 @@ uiDefSetDirection direction (UIAbstractContainer cont)
uiDefSetDirection direction def = def
encodeUIDefinition :: !UIDef -> JSONNode
encodeUIDefinition (UIFinal (UIViewport iopts opts)) = enc "itwc_viewport" [toJSON iopts, toJSON opts]
encodeUIDefinition (UIFinal (UIViewport iopts opts)) = enc "itwc_viewport" [toJSON iopts, encViewportOpts opts]
encodeUIDefinition def = enc "itwc_viewport" [toJSON (defaultItemsOpts (uiDefControls def))]
encodeUIControl :: !UIControl -> JSONNode
......@@ -117,12 +117,14 @@ encodeUIControl (UITaskletPH sopts opts) = enc "itwc_tasklet_placeholder" [to
encodeUIControl (UIContainer sopts iopts opts) = enc "itwc_container" [toJSON sopts, toJSON iopts, toJSON opts]
encodeUIControl (UIPanel sopts iopts opts) = enc "itwc_panel" [toJSON sopts, toJSON iopts, toJSON opts]
encodeUIControl (UIFieldSet sopts iopts opts) = enc "itwc_fieldset" [toJSON sopts, toJSON iopts, toJSON opts]
encodeUIControl (UIWindow sopts iopts opts) = enc "itwc_window" [toJSON sopts, toJSON iopts, toJSON opts]
encodeUIWindow :: !UIWindow -> JSONNode
encodeUIWindow (UIWindow sopts iopts opts) = enc "itwc_window" [toJSON sopts, toJSON iopts, toJSON opts]
derive JSONEncode UISizeOpts, UIViewOpts, UIChoiceOpts, UIActionOpts, UIItemsOpts
derive JSONEncode UISliderOpts, UIProgressOpts, UIGoogleMapOpts, UIGoogleMapMarker, UIGoogleMapOptions, UICodeOpts, UIGridOpts, UIButtonOpts, UITreeNode, UILabelOpts
derive JSONEncode UIIconOpts, UITabOpts, UITaskletOpts, UITaskletPHOpts
derive JSONEncode UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts, UIViewportOpts
derive JSONEncode UIContainerOpts, UIPanelOpts, UIFieldSetOpts, UIWindowOpts
JSONEncode{|UISideSizes|} {top,right,bottom,left}
= [JSONString (toString top +++ " " +++ toString right +++ " " +++ toString bottom +++ " " +++ toString left)]
......@@ -165,7 +167,6 @@ where
optsfields = flatten [fields \\ JSONObject fields <- opts]
//Special cases
encViewOpts :: (UIViewOpts a) -> JSONNode | encodeUIValue a
encViewOpts {UIViewOpts|value}
= JSONObject [("value",encodeUIValue value)]
......@@ -174,6 +175,14 @@ encEditOpts :: (UIEditOpts a) -> JSONNode | encodeUIValue a
encEditOpts {UIEditOpts|taskId,editorId,value}
= JSONObject [("taskId",JSONString taskId),("editorId",JSONString editorId),("value",encodeUIValue value)]
encViewportOpts :: UIViewportOpts -> JSONNode
encViewportOpts {UIViewportOpts|title,hotkeys,windows}
= JSONObject (
[("xtype",JSONString "itwc_viewport"),("windows",JSONArray [encodeUIWindow w \\ w <- windows])] ++
maybe [] (\t -> [("title",JSONString t)]) title ++
maybe [] (\k -> [("hotkeys",toJSON k)]) hotkeys
)
class encodeUIValue a :: a -> JSONNode
instance encodeUIValue String where encodeUIValue v = JSONString v
instance encodeUIValue Int where encodeUIValue v = JSONInt v
......
......@@ -3,26 +3,33 @@ definition module UIDiff
import UIDefinition
from Task import :: Event
:: UIUpdate
:: UIUpdate = UIUpdate !UIPath !UIUpdateOperation
:: UIUpdateOperation
//Component updates
= UISetValue !UIPath !JSONNode // Set the value of a component
| UISetOptions !UIPath !JSONNode // Change the options in a choice component
| UISetTaskId !UIPath !String // Set taskId a component belongs to
| UISetEditorId !UIPath !String // Set taskId a component belongs to
| UISetName !UIPath !String // Set name of a component
| UISetEnabled !UIPath !Bool // Enable/disable form elements
| UISetActive !UIPath !Bool // Make a tab active/inactive
| UISetTitle !UIPath !(Maybe String) // Set/reset title of a container
| UISetHotkeys !UIPath ![UIKeyAction] // Set hotkeys for a container
| UIUpdate !UIPath !UIControl // Let a component update itself with a new UI definition (for custom components)
= UISetValue !JSONNode // Set the value of a component
| UISetOptions !JSONNode // Change the options in a choice component
| UISetTaskId !String // Set taskId a component belongs to
| UISetEditorId !String // Set taskId a component belongs to
| UISetName !String // Set name of a component
| UISetEnabled !Bool // Enable/disable form elements
| UISetActive !Bool // Make a tab active/inactive
| UISetTitle !(Maybe String) // Set/reset title of a container
| UISetHotkeys ![UIKeyAction] // Set hotkeys for a container
| UISelfUpdate !UIControl // Let a component update itself with a new UI definition (for custom components)
//Structure edits
| UIAdd !UIPath !Int !UIControl //Add child element at index
| UIRemove !UIPath !Int //Remove child element at index
| UIReplace !UIPath !Int !UIControl //Replace child element at index
| UIAdd !Int !UIControl //Add child element at index
| UIRemove !Int //Remove child element at index
| UIReplace !Int !UIControl //Replace child element at index
| UIAddWindow !Int !UIWindow //Add a window
| UIRemoveWindow !Int //Remove a window
//Changing size
| UIResize !UIPath !UISizeOpts
| UIResize !UISizeOpts
:: UIPath :== String
:: UIPath :== [UIStep]
:: UIStep
= ItemStep !Int //Select item i
| MenuStep //Select the menu bar
| WindowStep !Int //Select window i (only possible as first step)
diffUIDefinitions :: !UIDef !UIDef !Event -> [UIUpdate]
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment