Get/Restore Monaco editor Undo&Redo stack - javascript

I want to create a system to store the Undo&Redo stack of the Monaco editor.
Why?: I have a Monaco instance where I do several changes. Then I have to dispose that instance and open a new one. Here, I want to restore the same stack state that I had in the previous instance.
Question: How can I get and restore the Undo&Redo stack?
UPDATE:
When I dispose the Monaco editor instance, the JavaScript environment can be completely destroyed. It is integrated in a C# environment that is capable to communicate with JS. My goal is to store the Monaco Editor model in the C# or serialize it.

It all has to do with the Model.
If you restore the same model you will have the Undo&Redo stacks
See Example
var model = editorInstance.getModel();
var viewState = editorInstance.saveViewState();
//Destroy your instance for whatever reason
editorInstance.dispose();
//When you create the new instance load the model that you saved
var newInstance = monaco.editor.create(elem, options);
newInstance.setModel(model);
newInstance.restoreViewState(viewState);
Something that might help would be to tie into the monaco event hook
monaco.editor.onWillDisposeModel(saveModel)
The viewState can be used to resume the cursor position of the editor.

Here's the unofficial way:
const {past, future} = editor.getModel()._commandManager;
In your case you can then run JSON.stringify on past and future. Then when you recreate the editor you just do
const cm = editor.getModel()._commandManager;
cm.past = JSON.parse(past);
cm.future = JSON.parse(future);

Related

Using methods for the menu item callbacks in Google Apps Script

So, I am doing a small project in Google Apps Script, to make adding/exporting leads from it...less painful.
How do I plan to do this?
I plan on doing this via adding some Actions menu, for the importing and exporting of leads. The imported sheet will, for now, be assumed to be of the same columns as the Google Sheet this script is bound to. (We can support some sheet column conversion features later, but it's probably a YAGNI for my use case.) The exported sheet will be converted from the columns of this sheet, to some simplified, ready-to-send-to-the-mailers columns.
How do I plan to code this (or how am I coding this)?
I am using MVVM design pattern, and have spent last night plugging away at writing MVVM wrappers for everything that I need to (keeping KISS in mind).
The MenuItemViewModels have some name,functionName that the Google Apps Script seem to be looking for. I note that there is some major pain-in-the-ass limitation, though: Google Apps Script wants function NAME and it cannot be method!
OK, show me some code or gtfo
I have some SpreadsheetPageViewModel that look like this:
class SpreadsheetPageViewModel extends BaseViewModel {
init() {
this.exportVM = new ExportSpreadsheetEditViewModel();
this.importVM = new ImportSpreadsheetEditViewModel();
this.menuVM = new MenuViewModel(new MenuModel(),
[
'exportLeads', // this is utility function. I want/need to use openExport method
'importLeads', // this is utility function. I want/need to use openImport method
]);
this.childEditVM = null;
}
openExport() {
this.childEditVM = this.exportVM;
this.childEditVM.view.doShow();
}
openImport() {
this.childEditVM = this.importVM;
this.childEditVM.view.doShow();
}
}
The business logic for the modals that spawn on menu item click, will live in the child view models to this: the ExportSpreadsheetEditViewModel and ImportSpreadsheetEditViewModel.
I was trying to get around the limitation via this hack:
changing
function onOpen(event) {
// show the menu here....
new SpreadsheetPageView().doShow();
}
to something like:
var mainView;
function onOpen(event) {
mainView = new SpreadsheetPageView();
// show the menu here....
mainView.doShow();
}
and then, in the MenuActionUtils.gs, crawling down that mainView like:
function exportLeads() {
mainView.viewModel.showExport();
}
function importLeads() {
mainView.viewModel.showImport();
}
What was the result of that hack?
It didn't work. Why? Because when Google Apps Script fired that exportLeads (or importLeads), mainView was no longer defined!!
Does this mean I have to give up my approach?
How can I use the main view/view model in the onClick of the menu items?
Failing all that, is there a way to create our menu, using this MVVM design pattern (and some HTML/React/....), and inject it in?
By using Google Apps Script it's not possible to modify the look and feel of a Google Workspace editor (Docs, Forms, Sheets, Slides) custom menu, in other words, it's not possible to use HTML/React for this but you might use them in dialogs/sidebars.
Regarding using a design pattern, you might use any design pattern that you want but you should have in mind that every time that a Google Apps Script is triggered by an event the whole project is loaded, so if you need that some objects persist between events then you should find a place to save those objects.
To store an object you might use the Google Apps Script Properties Service and/or the Cache Service, just bear in mind that you should convert it to JSON before saving it. Also you might use a Google spreadsheet but this has several limitations or you might use an external service, i.e. nosql database, by using Google Apps Script URL Fetch service.
Related
using and modifying global variables within handler functions
Styling a custom spreadsheet menu item using Google Apps Script
How to define global variable in Google Apps Script
Google Apps Script (V8); why can't I use an object instance inside of onOpen?
With #Ruben's help, I was able to get this working!
What I did
I didn't give up on the MVVM/OOP design.
Instead, I created singleton static method on the drive class, like so:
static GetMainInstance() {
if (!this._mainInstance) {
this._mainInstance = new this();
}
return this._mainInstance;
}
and use it instead of directly creating the new drive object.
Also, it is view's responsibility to spawn stuff, so I did some refactoring:
In PageView.gs I added the following methods:
showExport() {
this.viewModel.showExport((childVM) => {
this.editView.viewModel = childVM;
this.editView.doShow();
})
}
showImport() {
this.viewModel.showImport((childVM) => {
this.editView.viewModel = childVM;
this.editView.doShow();
})
}
in the PageViewModel.gs, I changed the methods to accept onDone callback:
showExport(onDone) {
this.childEditVM = this.exportVM;
onDone(this.childEditVM);
}
showImport(onDone) {
this.childEditVM = this.importVM;
onDone(this.childEditVM);
}
Simple fix, it works, while staying consistent with the principles!

Toggle between multiple THREE.EffectComposer scenes using the same renderer in three.js

I am in the process of creating complex scenes with Composer in three.js.
I am wanting to know if it is possible to switch between two scenes that have different composer effects attributed to them. To gain some sort of perspective I have created an example which allows you to toggle between two normally rendered scenes.
Two scene example
From my understanding of how composer works you create an instance of it and then apply a render pass like so:
this.composer = new THREE.EffectComposer(this.renderer.default.init);
this.renderPass = new THREE.RenderPass(this.stage, this.camera);
this.renderPass.renderToScreen = true;
this.composer.addPass(this.renderPass);
and then apply a composer render like so:
this.composer.render();
So my question is if I have a second scene which a composer instance how can I then:
Use the same renderer (if possible)
Toggle between scene 1 and scene 2 like in a similar fashion to my example.
You can just switch from one effectComposer to another one the same way as you switch from one scene to the other. So that would be something like this:
const scenes = [
new THREE.Scene(),
new THREE.Scene()
];
const composers = scenes.map(function(scene) {
const composer = new THREE.EffectComposer(renderer);
// configure render-passes
const renderpass = new THREE.RenderPass(scene, camera);
renderpass.renderToScreen = true;
composer.addPass(renderpass);
scene.composer = composer;
return composer;
});
// then use the composer for the scene
let activeScene = scenes[0];
activeScene.composer.render();
You should even be able to reuse certain render-passes if you want to.
So before I begin I just want to give a shout out to Martin Schuhfuß who provided the insight to this solution.
Disclaimer
I am not an expert or professional Programmer in javascript and I by no means suggest that this is the only way of doing this, my presentation should be taken in an abstract fashion. My thoughts only explain the theoretical premises for which the solution is based on and implementation of this will depend upon your own architecture.
Architecture
I am using an OOP method in this example, you should be able to manipulate the solution to whatever method you are using.
Method
So based on Martin’s current example you can see that we able to add the composer property/object to our scene object this means that scenes could inherit the same composer effects which is brilliant, however In my case I have many scenes with different composer effects so I needed to rethink the problem.
1. Create a Composer object.
So I created an object to put composer objects in and keep to the nice ‘composer’ name convention when reference my effects.
This.composer = {};
2. Create a function to initialise RenderPasses with the concerning scenes.
So it is important to remember that we need to define and initailse the RenderPasses first before will call our composer. RenderPasses allows us to attribute the effects (know has shaders) that we want. In my example I have two scenes therefore I needed to create:
Two RenderPasses.
One RenderPass copied the scene.
The other RenderPass applied a sepia effect to its scene.
Example of Code:
this.init = function() {
stackoverflow.webgl.pass.base = new THREE.RenderPass(stackoverflow.webgl.scene.default,
stackoverflow.webgl.camera);
stackoverflow.webgl.pass.base2 = new THREE.RenderPass(stackoverflow.webgl.scene.test,
stackoverflow.webgl.camera);
stackoverflow.webgl.pass.sepia = new
THREE.ShaderPass(THREE.SepiaShader);
stackoverflow.webgl.pass.sepia.renderToScreen = true;
stackoverflow.webgl.pass.copy = new THREE.ShaderPass(THREE.CopyShader);
stackoverflow.webgl.pass.copy.renderToScreen = true;
}
Note: some file names had to be renamed due to legal reason, but the point being is that this function is called into a larger object.
3. Create a start function and lets assign composers to scenes
The purpose of this function is just to demonstrate how we combine it all together. So the code that we have is something like this.
// so we create an object to use for reference for EffectComposer objects
this.composer = {};
// this property is used to set the scene that we want
// this.scene.default = new THREE.Scene();
this.activeScene = this.scene.default;
// this function is just a wrapper for our code
this.start = function () {
// so call our initialise our RenderPasses
stackoverflow.webgl.pass.init();
// my own method of seting up the WebGLRenderer scene (You do it your Way!)
stackoverflow.webgl.renderer.default.setup;
// Create a new composer for scene 1
this.composer.ui = new THREE.EffectComposer(stackoverflow.webgl.renderer.default.init);
// Create a new composer for scene 1
this.composer.ui2 = new THREE.EffectComposer(stackoverflow.webgl.renderer.default.init);
// Now here is the cool stuff you can assign a composer to each scene
this.scene.default.composer = stackoverflow.webgl.composer.ui;
this.scene.test.composer = stackoverflow.webgl.composer.ui2;
// and i always like to check that things happen and they do ;)
console.log(this.scene.default);
console.log(this.scene.test);
console.log(this.composer);
// so you will need to add the passes some place, I created a function call render, (you do according to your code structure)
stackoverflow.webgl.render();
}
4. Define where you add the passes in your architecture
So now we will need to add the pass declarations (addpass functions from composer) for our effects to take place.
this.render = function () {
stackoverflow.webgl.composer.ui.addPass(stackoverflow.webgl.pass.base);
stackoverflow.webgl.composer.ui.addPass(stackoverflow.webgl.pass.copy);
stackoverflow.webgl.composer.ui2.addPass(stackoverflow.webgl.pass.base2);
stackoverflow.webgl.composer.ui2.addPass(stackoverflow.webgl.pass.sepia);
};
5. Add the EffectComposer render method
So this line of code will need to be placed wherever you do the composer processing (that depends on your setup).
stackoverflow.webgl.activeScene.composer.render();
Please take note that the ‘activecScene’ part makes reference to the actual scene used at the time. It is this that allows us to change the scene.
6. Create a button and toggle between the scenes
So I created two buttons in a dat.gui instance that allows me to toggle between the two scenes.
Again you can create a button, function whatever you like.
// again just change the vale of active scene to the scene you wish to change to
// button 1 value will be something like this:
stackoverflow.webgl.activeScene = stackoverflow.webgl.scene.test;
// button 2 value will takes us back to scene 1
stackoverflow.webgl.activeScene = stackoverflow.webgl.scene.default;
Conclusion
This is my method of achieving this effect, but if there are better or alternative ways then please add to the discussion and share.

Add properties to context in cordova plugin for Application Insights

I use cordova plugin with Application Insight named cordova-plugin-ms-appinsights (https://github.com/MSOpenTech/cordova-plugin-ms-appinsights/blob/master/README.md) and I tried to add my properties to context, that through each request application will send additional info, for example code name of my application.
I tried as below:
appInsights.context.application.codename = "code name app";
appInsights.trackEvent("my event");
and this did not work.
Can I add additional info to context?
There are 2 issues:
1) Cordova plugin seems to use an old version of the JS SDK. You can try to update it manually after it pulls down the old one (take the most recent one from https://github.com/Microsoft/ApplicationInsights-JS/blob/master/dist/ai.0.js)
2) The feature that adds data to ALL telemetry items is not released yet. It was implemented recently - see JS SDK commit on github. You can either wait a bit until it's released or get the latest from master and compile it yourself (and take the result from /JavaScript/min/ai.min.js)
A hacky alternative may be to create a wrapper on top of SDK methods like trackEvent() which adds the data you need (I'm sorry for giving you the JS SDK code equivalent as I haven't used cordova plugin myself):
// this is you implementation of custom data to be attached
// to all telemetry items.
// It needs to return JSON - as it's expected format for custom properties.
// In this specific case you'll create a custom property 'hey' with value 'yo'
var getMyDataJson = function() {
return { hey: "yo"};
}
// this is your wrapper, you'll call it instead of appInsights.trackEvent()
var myTrackEvent = function(data) {
var toBeAttachedToAllItems = getMyDataJson();
appInsights.trackEvent(data, toBeAttachedToAllItems);
}
<...>
// somewhere later you track your telemetry
// this will call your getMyDataJson() function which'll attach
// custom data to your event.
myTrackEvent("tracked!")

Deleting a variable in Backbone

Iam developing a cross platform application and iam using backbone framework.
i have many views in them and i render the views as follows
For eg:
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
}
Then one of my friend happen to see my code and he said, every variable created inside a function should be deleted,
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
delete sampleObject;
}
I searched the whole web and couldn't find anything relevant to his theory. In web it says a variable is never deleted.
so i showed him a sample like below,
<script>
function onBodyLoad() {
var test = "i'am alive";
delete test;
alert(test);
}
</script>
<body onLoad="onBodyLoad()">
So my question is what delete operator really do? just used for deleting an object property?
What happens to the object and variables that brought up to the DOM when using an MV framework like backbone?
Does the object and variables will be cleared when a new view is loaded?
Do we need to unbind or destory view each time just before loading another view?
Regarding the delete operator, it will delete the property from the object. It wont do anything related to Freeing memory.
In backbone, whenever model is attached with some views it'll be in DOM. view.remove(), will remove the view and the attached model from DOM.
Creating a new view wont delete existing views. You can set a listener to remove the existing views when you create new views.
See this question: Destroy or remove a view in Backbone.js

How to create a post-rename or post-delete event hook for CKFinder?

I need to edit content and inform the user about various things if a file is deleted or renamed inside CKFinder. I thought I could create a JavaScript solution and then offload the logic into the backend with some simple AJAX, with something like this:
CKFinder.on('successfulFileRename', function(event, oldpath, newpath) {
// Contact backend, see where file was used, inform user etc etc
});
But alas, I could not find any event system. How would I implement this functionality for CKFinder? Events I need are File/Folder - Rename/Delete/Move. It doesn't have to be a frontend solution, but I would prefer it for simplicity. My backend is ASP.net MVC3.
(Alternatives for CKF are welcome as comments, but they need about the same functionality as it has.)
Going through the documentation I was also not able to find any event like extension points.
However looking through some of the source I've found the sendCommandPost method on the CKFinder.dataTypes.Connector which gets invoked every time when somethings needs to be send to the server. So on every important event like File/Folder - Rename/Delete/Move.
So you can easily create a custom plugin where you can access the CKFinderAPI instance and from there you can override the sendCommandPost and add you custom logic
CKFinder.addPlugin( 'myplugin', function( api ) {
var orginalsendCommandPost = api.connector.sendCommandPost;
api.connector.sendCommandPost = function() {
// call the original function
var result = orginalsendCommandPost.apply(this, arguments);
// commandname as string: 'CreateFolder', 'MoveFiles', 'RenameFile', etc
console.log(arguments[0]);
// arguments as object
console.log(JSON.stringify(arguments[1]));
return result;
}
} );
And registering the plugin:
config.extraPlugins = "myplugin";
var ckfinder = new CKFinder( config );

Categories

Resources