A whole bunch of ViXEn stuff, part 1. Basic aggregate transactions and
undo/redo fit and finish. NOT PART OF BUILD git-svn-id: svn://10.0.0.236/trunk@81450 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
c08a906df3
commit
f44f9dd029
@ -27,26 +27,99 @@
|
||||
var vxPalette =
|
||||
{
|
||||
// we want to use controllers, but this will do to test our txns.
|
||||
nButtons: 0, // XXX hack
|
||||
mElementCount: { },
|
||||
|
||||
insertButtonElement: function (aType)
|
||||
{
|
||||
_dd("insertButtonElement");
|
||||
var nElements = this.incrementElementCount("button");
|
||||
var attributes = ["value", "id"];
|
||||
var values = ["Button " + nElements, "button_" + nElements];
|
||||
this.insertSingleElement("button", attributes, values);
|
||||
},
|
||||
|
||||
insertTextFieldElement: function (aType)
|
||||
{
|
||||
var nElements = this.incrementElementCount("textfield");
|
||||
var attributes = ["value", "id"];
|
||||
var values = ["Textfield " + nElements, "textfield_" + nElements];
|
||||
this.insertSingleElement("textfield", attributes, values);
|
||||
},
|
||||
|
||||
insertRadioGroup: function ()
|
||||
{
|
||||
var nRadios = this.incrementElementCount("radio");
|
||||
var nRadiogroups = this.incrementElementCount("radiogroup");
|
||||
|
||||
var vixenMain = vxUtils.getWindow("vixen:main");
|
||||
var focusedWindow = vixenMain.vxShell.mFocusedWindow;
|
||||
|
||||
// need to check to see if the focused window is a vfd
|
||||
|
||||
var insertionPoint = focusedWindow.vxVFD.getInsertionPoint();
|
||||
|
||||
var vfdDocument = focusedWindow.vxVFD.getContent(true).document;
|
||||
_dd(vfdDocument);
|
||||
|
||||
// create a button
|
||||
this.nButtons++;
|
||||
var buttontxn = new vxCreateElementTxn(vfdDocument, "button", insertionPoint.parent, insertionPoint.index);
|
||||
var attributes = ["value", "id"];
|
||||
var values = ["Button " + this.nButtons, "button_" + this.nButtons];
|
||||
var buttonattrtxn = new vxChangeAttributeTxn(buttontxn.mElement, attributes, values, false);
|
||||
|
||||
var radiogroupTxn = new vxCreateElementTxn(vfdDocument, "radiogroup", insertionPoint.parent, insertionPoint.index);
|
||||
radiogroupTxn.init();
|
||||
var radiogroupAttributes = ["id"];
|
||||
var radiogroupValues = ["radiogroup_" + nRadiogroups];
|
||||
var radiogroupAttrTxn = new vxChangeAttributeTxn(radiogroupTxn.mID,
|
||||
radiogroupAttributes,
|
||||
radiogroupValues, false);
|
||||
radiogroupAttrTxn.init();
|
||||
|
||||
var aggregateTxn = new vxAggregateTxn([buttontxn, buttonattrtxn]);
|
||||
focusedWindow.vxVFD.mTxMgrShell.doTransaction(aggregateTxn);
|
||||
var radioTxn = new vxCreateElementTxn(vfdDocument, "radio", radiogroupTxn.mID, insertionPoint.index);
|
||||
radioTxn.init();
|
||||
var radioAttributes = ["value", "group", "id"];
|
||||
var radioValues = ["Radio " + nRadios, "radiogroup_" + nRadiogroups, "radio_" + nRadios];
|
||||
var radioAttrTxn = new vxChangeAttributeTxn(radioTxn.mID,
|
||||
radioAttributes,
|
||||
radioValues, false);
|
||||
radioAttrTxn.init();
|
||||
|
||||
// batch the transactions
|
||||
var txns = [radiogroupTxn, radiogroupAttrTxn, radioTxn, radioAttrTxn];
|
||||
var aggregateTxn = new vxAggregateTxn(txns);
|
||||
aggregateTxn.init();
|
||||
|
||||
var txmgr = focusedWindow.vxVFD.mTxMgrShell;
|
||||
txmgr.addListener([radioAttrTxn, radioTxn, radioAttrTxn]);
|
||||
txmgr.doTransaction(aggregateTxn);
|
||||
txmgr.removeListener([radioAttrTxn, radioTxn, radioAttrTxn]);
|
||||
},
|
||||
|
||||
incrementElementCount: function (aNodeName)
|
||||
{
|
||||
// create an node count entry for the specified node name
|
||||
if (!(aNodeName in this.mElementCount))
|
||||
this.mElementCount[aNodeName] = 0;
|
||||
return ++this.mElementCount[aNodeName];
|
||||
},
|
||||
|
||||
insertSingleElement: function (aNodeName, aAttributes, aValues)
|
||||
{
|
||||
var vixenMain = vxUtils.getWindow("vixen:main");
|
||||
var focusedWindow = vixenMain.vxShell.mFocusedWindow;
|
||||
|
||||
// need to check to see if the focused window is a vfd
|
||||
|
||||
var insertionPoint = focusedWindow.vxVFD.getInsertionPoint();
|
||||
|
||||
var vfdDocument = focusedWindow.vxVFD.getContent(true).document;
|
||||
|
||||
var elementTxn = new vxCreateElementTxn(vfdDocument, aNodeName, insertionPoint.parent, insertionPoint.index);
|
||||
elementTxn.init();
|
||||
var elementAttrTxn = new vxChangeAttributeTxn(elementTxn.mID, aAttributes, aValues, false);
|
||||
elementAttrTxn.init();
|
||||
|
||||
// batch the transactions
|
||||
var aggregateTxn = new vxAggregateTxn([elementTxn, elementAttrTxn]);
|
||||
aggregateTxn.init();
|
||||
|
||||
var txmgr = focusedWindow.vxVFD.mTxMgrShell;
|
||||
txmgr.addListener(elementAttrTxn);
|
||||
txmgr.doTransaction(aggregateTxn);
|
||||
txmgr.removeListener(elementAttrTxn);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -19,7 +19,13 @@
|
||||
<script src="chrome://vixen/content/vfd/vxCreateElementTxn.js"></script>
|
||||
|
||||
|
||||
<button value="Button" oncommand="vxPalette.insertButtonElement('button');"/>
|
||||
|
||||
<button value="Button"
|
||||
oncommand="vxPalette.insertButtonElement();"/>
|
||||
<button value="TextField"
|
||||
oncommand="vxPalette.insertTextFieldElement();"/>
|
||||
<button value="RadioGroup"
|
||||
oncommand="vxPalette.insertRadioGroup();"/>
|
||||
|
||||
|
||||
</window>
|
||||
|
||||
|
||||
@ -21,14 +21,14 @@
|
||||
<script src="chrome://vixen/content/vfd/vxVFDSelectionManager.js"></script>
|
||||
|
||||
<tabcontrol class="tabbed-edit" flex="1" orient="vertical">
|
||||
<tabpanel flex="1">
|
||||
<iframe id="vfdDocument" name="vfdDocument" flex="1"/>
|
||||
<box id="vfdDOMTree" flex="1"/> <!-- from overlay -->
|
||||
</tabpanel>
|
||||
<tabbox>
|
||||
<tab value="Design"/>
|
||||
<tab value="DOM Tree"/>
|
||||
</tabbox>
|
||||
<tabpanel flex="1">
|
||||
<iframe id="vfdDocument" name="vfdDocument" flex="1"/>
|
||||
<box id="vfdDOMTree" flex="1"/> <!-- from overlay -->
|
||||
</tabpanel>
|
||||
</tabcontrol>
|
||||
|
||||
</window>
|
||||
|
||||
@ -31,28 +31,37 @@
|
||||
|
||||
function vxAggregateTxn (aTransactionList)
|
||||
{
|
||||
this.mTransactionList = aTransactionList;
|
||||
this.mTransactionList = { };
|
||||
for (var i = 0; i < aTransactionList.length; i++)
|
||||
this.mTransactionList[aTransactionList[i].mID] = aTransactionList[i];
|
||||
|
||||
this.mID = "aggregate-txn::";
|
||||
}
|
||||
|
||||
vxAggregateTxn.prototype = {
|
||||
init: function ()
|
||||
{
|
||||
this.mID += generateID();
|
||||
},
|
||||
|
||||
doTransaction: function ()
|
||||
{
|
||||
_dd("vxAggregateTxn::doTransaction");
|
||||
for (var i = 0; i < this.mTransactionList.length; i++)
|
||||
for (var i in this.mTransactionList)
|
||||
this.mTransactionList[i].doTransaction();
|
||||
},
|
||||
|
||||
undoTransaction: function ()
|
||||
{
|
||||
_dd("vxAggregateTxn::undoTransaction");
|
||||
for (var i = 0; i < this.mTransactionList.length; i++)
|
||||
for (var i in this.mTransactionList)
|
||||
this.mTransactionList[i].undoTransaction();
|
||||
},
|
||||
|
||||
redoTransaction: function ()
|
||||
{
|
||||
_dd("vxAggregateTxn::redoTransaction");
|
||||
for (var i = 0; i < this.mTransactionList.length; i++)
|
||||
for (var i in this.mTransactionList)
|
||||
this.mTransactionList[i].redoTransaction();
|
||||
},
|
||||
|
||||
|
||||
@ -39,18 +39,35 @@
|
||||
|
||||
function vxChangeAttributeTxn(aElement, aAttribute, aValue, aRemoveFlag)
|
||||
{
|
||||
if (!aElement) throw Components.results.NS_ERROR_NULL_POINTER;
|
||||
|
||||
this.mElementCreated = false;
|
||||
if (typeof(aElement) === "string" && aElement.indexOf("create-element") >= 0)
|
||||
this.mElementTxnID = aElement;
|
||||
else
|
||||
this.mElementCreated = true;
|
||||
|
||||
this.mElement = aElement;
|
||||
this.mAttributes = aAttribute.splice ? aAttribute : [aAttribute];
|
||||
this.mValues = aValue.splice ? aValue : [aValue];
|
||||
this.mRemoveFlags = aRemoveFlag.splice ? aRemoveFlag : [aRemoveFlag];
|
||||
this.mAttributes = [].concat(aAttribute);
|
||||
this.mValues = [].concat(aValue);
|
||||
this.mRemoveFlags = [].concat(aRemoveFlag);
|
||||
this.mUndoValues = [];
|
||||
|
||||
this.mID = "create-element::";
|
||||
}
|
||||
|
||||
|
||||
vxChangeAttributeTxn.prototype = {
|
||||
init: function ()
|
||||
{
|
||||
this.mID += generateID();
|
||||
},
|
||||
|
||||
doTransaction: function ()
|
||||
{
|
||||
if (!this.mElementCreated)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < this.mAttributes.length; i++) {
|
||||
_ddf("this attribute", this.mAttributes[i]);
|
||||
this.mUndoValues[i] = this.mElement.getAttribute(this.mAttributes[i]);
|
||||
if (!this.mRemoveFlags[i])
|
||||
this.mElement.setAttribute(this.mAttributes[i], this.mValues[i]);
|
||||
@ -83,11 +100,26 @@ vxChangeAttributeTxn.prototype = {
|
||||
get commandString()
|
||||
{
|
||||
var commandString = "change-attribute,";
|
||||
commandString += this.mRemoveFlag ? "remove," : "set,";
|
||||
/* commandString += this.mRemoveFlag ? "remove," : "set,";
|
||||
commandString += this.mElement.id + ",";
|
||||
commandString += this.mAttribute + ",";
|
||||
commandString += this.mValue;
|
||||
commandString += this.mValue;*/
|
||||
return commandString;
|
||||
},
|
||||
|
||||
/**
|
||||
* Implementation of nsITransactionListener
|
||||
*/
|
||||
didDo: function (aTransactionManager, aTransaction, aInterrupt)
|
||||
{
|
||||
var createElementTxn = aTransaction.mTransactionList[this.mElementTxnID];
|
||||
_ddf("the transaction ahead of us is", createElementTxn.commandString);
|
||||
_ddf("and its element is", createElementTxn.mLocalName);
|
||||
if (createElementTxn.commandString.indexOf("create-element") >= 0) {
|
||||
this.mElement = createElementTxn.mElement;
|
||||
this.mElementCreated = true;
|
||||
this.doTransaction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -31,24 +31,40 @@ function vxCreateElementTxn(aDocument, aLocalName, aParentNode, aChildOffset)
|
||||
{
|
||||
this.mDocument = aDocument;
|
||||
this.mLocalName = aLocalName;
|
||||
|
||||
this.mElementCreated = false;
|
||||
if (typeof(aParentNode) === "string" && aParentNode.indexOf("create-element") >= 0)
|
||||
this.mElementTxnID = aParentNode;
|
||||
else
|
||||
this.mElementCreated = true;
|
||||
|
||||
this.mParentNode = aParentNode;
|
||||
this.mChildOffset = aChildOffset;
|
||||
this.mElement = this.mDocument.createElement(this.mLocalName);
|
||||
|
||||
this.mID = "create-element::";
|
||||
}
|
||||
|
||||
vxCreateElementTxn.prototype = {
|
||||
init: function ()
|
||||
{
|
||||
this.mID += generateID();
|
||||
},
|
||||
|
||||
doTransaction: function ()
|
||||
{
|
||||
_ddf("creating element", this.mLocalName);
|
||||
if (!this.mElementCreated) {
|
||||
_dd("aParentNode is an id, bailing early");
|
||||
_ddf("localname is", this.mLocalName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.mElement = this.mDocument.createElement(this.mLocalName);
|
||||
this.insertNode();
|
||||
},
|
||||
|
||||
undoTransaction: function ()
|
||||
{
|
||||
_ddf("element", this.mElement.localName);
|
||||
_ddf("parent", this.mParentNode.localName);
|
||||
for (var i = 0; i < this.mParentNode.childNodes.length; i++) {
|
||||
_ddf("childnode at " + i, this.mParentNode.childNodes[i]);
|
||||
}
|
||||
this.mParentNode.removeChild(this.mElement);
|
||||
},
|
||||
|
||||
@ -72,12 +88,22 @@ vxCreateElementTxn.prototype = {
|
||||
get commandString()
|
||||
{
|
||||
var commandString = "create-element";
|
||||
commandString = this.mRemoveFlag ? "remove," : "set,";
|
||||
commandString += this.mElement.id + ",";
|
||||
commandString += this.mAttribute + ",";
|
||||
commandString += this.mValue;
|
||||
return commandString;
|
||||
},
|
||||
|
||||
/**
|
||||
* Implementation of nsITransactionListener
|
||||
*/
|
||||
didDo: function (aTransactionManager, aTransaction, aInterrupt)
|
||||
{
|
||||
var createElementTxn = aTransaction.mTransactionList[this.mElementTxnID];
|
||||
if (createElementTxn.commandString.indexOf("create-element") >= 0) {
|
||||
this.mElement = createElementTxn.mElement;
|
||||
this.mElementCreated = true;
|
||||
this.doTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -24,6 +24,10 @@
|
||||
function vxVFDTransactionManager()
|
||||
{
|
||||
this.mTxnStack = new vxVFDTransactionStack();
|
||||
|
||||
// a record of transaction execution listeners that have attached
|
||||
// themselves to us.
|
||||
this.mTransactionListeners = [];
|
||||
}
|
||||
|
||||
vxVFDTransactionManager.prototype =
|
||||
@ -35,6 +39,14 @@ vxVFDTransactionManager.prototype =
|
||||
|
||||
doTransaction: function (aTransaction)
|
||||
{
|
||||
for (var i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
var interruptRequest = {};
|
||||
if ("willDo" in this.mTransactionListeners[i]) {
|
||||
this.mTransactionListeners[i].willDo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
|
||||
// perform the transaction
|
||||
aTransaction.doTransaction();
|
||||
|
||||
@ -49,7 +61,7 @@ vxVFDTransactionManager.prototype =
|
||||
|
||||
// likewise with the RDF Seq.
|
||||
var seqCount = this.mTxnSeq.GetCount();
|
||||
for (var i = this.mTxnStack.index; i < seqCount; i++)
|
||||
for (i = this.mTxnStack.index; i < seqCount; i++)
|
||||
this.mTxnSeq.RemoveElementAt(i);
|
||||
}
|
||||
|
||||
@ -59,10 +71,26 @@ vxVFDTransactionManager.prototype =
|
||||
// add the transaction to the stack
|
||||
this.mTxnSeq.AppendElement(new RDFLiteral(this.mTxnStack.index-1));
|
||||
|
||||
for (i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
interruptRequest = {};
|
||||
if ("didDo" in this.mTransactionListeners[i]) {
|
||||
_ddf("transaction listener " + i, this.mTransactionListeners[i].commandString);
|
||||
this.mTransactionListeners[i].didDo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
undoTransaction: function ()
|
||||
{
|
||||
for (var i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
var interruptRequest = {};
|
||||
if ("willUndo" in this.mTransactionListeners[i]) {
|
||||
this.mTransactionListeners[i].willUndo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve the previous transaction
|
||||
var txn = this.mTxnStack.mStack[this.mTxnStack.index-1];
|
||||
|
||||
@ -72,18 +100,42 @@ vxVFDTransactionManager.prototype =
|
||||
txn.undoTransaction();
|
||||
this.mTxnStack.index--;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
interruptRequest = {};
|
||||
if ("didUndo" in this.mTransactionListeners[i]) {
|
||||
this.mTransactionListeners[i].didUndo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
redoTransaction: function (aTransaction)
|
||||
{
|
||||
for (var i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
var interruptRequest = {};
|
||||
if ("willRedo" in this.mTransactionListeners[i]) {
|
||||
this.mTransactionListeners[i].willRedo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve the previous transaction
|
||||
var txn = this.mTxnStack.mStack[this.mTxnStack.index+1];
|
||||
var txn = this.mTxnStack.mStack[this.mTxnStack.index];
|
||||
|
||||
// redo the transaction
|
||||
if (txn) {
|
||||
txn.redoTransaction();
|
||||
this.mTxnStack.index++;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
interruptRequest = {};
|
||||
if ("didRedo" in this.mTransactionListeners[i]) {
|
||||
this.mTransactionListeners[i].didRedo(this, aTransaction, interruptRequest);
|
||||
// do something with irq here once we figure out what it does.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
makeSeq: function (aResource)
|
||||
@ -92,8 +144,34 @@ vxVFDTransactionManager.prototype =
|
||||
const kContainerUtilsIID = "nsIRDFContainerUtils";
|
||||
var utils = nsJSComponentManager.getServiceByID(kContainerUtilsCID, kContainerUtilsIID);
|
||||
return utils.MakeSeq(vxVFDTransactionDS, aResource);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* add and remove nsITransactionListeners
|
||||
*/
|
||||
addListener: function (aTransactionListener)
|
||||
{
|
||||
if ("splice" in aTransactionListener) {
|
||||
for (var i = 0; i < aTransactionListener.length; i++)
|
||||
this.mTransactionListeners.push(aTransactionListener[i]);
|
||||
}
|
||||
else
|
||||
this.mTransactionListeners.join(aTransactionListener);
|
||||
_ddf("transaction listeners :: ", this.mTransactionListeners.length);
|
||||
},
|
||||
|
||||
removeListener: function (aTransactionListeners)
|
||||
{
|
||||
var listeners = [].join(aTransactionListeners)
|
||||
for (var i = 0; i < this.mTransactionListeners.length; i++) {
|
||||
for (var k = 0; k < listeners.length; k++) {
|
||||
if (this.mTransactionListeners[i] == listeners[k]) {
|
||||
this.mTransactionListeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -161,7 +239,7 @@ var vxVFDTransactionDS =
|
||||
mObservers: [],
|
||||
AddObserver: function (aObserver)
|
||||
{
|
||||
this.mObservers.push(aObserver);
|
||||
this.mObservers[this.mObservers.length] = aObserver;
|
||||
},
|
||||
|
||||
RemoveObserver: function (aObserver)
|
||||
|
||||
@ -137,7 +137,7 @@ var vxShell =
|
||||
this.mFocusedWindow.vxVFD.mTxMgrShell.redoTransaction();
|
||||
},
|
||||
|
||||
|
||||
|
||||
appAbout: function ()
|
||||
{
|
||||
// XXX TEMP
|
||||
@ -145,3 +145,9 @@ var vxShell =
|
||||
}
|
||||
};
|
||||
|
||||
function XXX_dumpDOM()
|
||||
{
|
||||
var focusedWindow = vxShell.mFocusedWindow;
|
||||
var insertionPoint = focusedWindow.vxVFD.getInsertionPoint();
|
||||
dumpDOM(insertionPoint.parent);
|
||||
}
|
||||
@ -71,7 +71,7 @@
|
||||
</menu>
|
||||
</menubar>
|
||||
<toolbar id="xuledit-toolbar" flex="1">
|
||||
<button class="button-toolbar" value="Button" oncommand="goDoCommand('cmd_insert_button');"/>
|
||||
<button class="button-toolbar" value="Dump DOM" oncommand="XXX_dumpDOM();"/>
|
||||
</toolbar>
|
||||
</toolbox>
|
||||
|
||||
|
||||
@ -44,3 +44,23 @@ var vxUtils = {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a unique identifier for a transaction
|
||||
*/
|
||||
function generateID()
|
||||
{
|
||||
var val = ((new Date()).getUTCMilliseconds())*Math.random()*100000;
|
||||
_ddf("value", val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* dumps the DOM tree under a given node.
|
||||
*/
|
||||
function dumpDOM(aNode)
|
||||
{
|
||||
_dd("<" + aNode.localName + " id=" + aNode.id + ">");
|
||||
for (var i = 0; i < aNode.childNodes.length; i++)
|
||||
dumpDOM(aNode.childNodes[i]);
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ vixen.jar:
|
||||
content/vixen/vfd/scratch.xul (content/vfd/scratch.xul)
|
||||
content/vixen/vfd/vfd.js (content/vfd/vfd.js)
|
||||
content/vixen/vfd/vfd.xul (content/vfd/vfd.xul)
|
||||
content/vixen/vfd/vxChangeAttributeTxn.js (content/vfd/vxChangeAttributeTxn.js)
|
||||
+ content/vixen/vfd/vxChangeAttributeTxn.js (content/vfd/vxChangeAttributeTxn.js)
|
||||
content/vixen/vfd/vxCreateElementTxn.js (content/vfd/vxCreateElementTxn.js)
|
||||
content/vixen/vfd/vxVFDSelectionManager.js (content/vfd/vxVFDSelectionManager.js)
|
||||
content/vixen/vfd/vxVFDTransactionManager.js (content/vfd/vxVFDTransactionManager.js)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user