I'm trying to abstract some repetitive functions to update parts of the Monaco edit from various check boxs in the UI. The example below enables or disables linenumber in the editor.
Works fine when the editor and functions are in the same setup. When I abstract to a composable per below the editor is null, therefore does not work. Any thoughts or ideas?
Note: Some code is removed for brevity
MainTemplate.vue
import * as monaco from 'monaco-editor';
import useCodeEditor from '../composables/codeEditorFunctions';
import { ref, onMounted, reactive, inject, watch } from 'vue';
export default {
setup(props, { emit }) {
let meditor = null;
onMounted(() => {
checkDarkModeIsSet();
checkLineNumbersIsSet();
checkMiniMapIsSet();
const codeEditorDiv = document.getElementById('pf-c-code-editor__code-pre');
meditor = monaco.editor.create(codeEditorDiv, {
value: ['function x() {', '\tconsole.log("If you see this, something went wrong!");', '}'].join('\n'),
language: 'yaml',
lineNumbers: lineNumbers.value,
roundedSelection: false,
scrollBeyondLastLine: true,
readOnly: false,
theme: darkmode.value,
scrollBeyondLastLine: false,
automaticLayout: true,
wordWrap: 'on',
wrappingStrategy: 'advanced',
minimap: {
enabled: true
}
});
if (props.viewstate.editid === 0) {
getDefaultCodeBlock();
} else {
showCodeBlock();
}
getModel(props.viewstate.editid);
});
const { darkmode, checkDarkModeIsSet, toggleEditorDarkMode, lineNumbers, checkLineNumbersIsSet, toggleEditorLineNumbers } = useCodeEditor(monaco, meditor);
return {
darkmode,
lineNumbers,
toggleEditorDarkMode,
toggleEditorLineNumbers,
toggleEditorMinimap,
showConfigFullScreen
};
}
};
codeEditorFunctions.js
import { ref, reactive } from "vue";
export default function useCodeEditor(monaco, meditor) {
/** EDITOR DARKMODE */
const darkmode = ref('vs');
function checkDarkModeIsSet() {
if (localStorage.getItem('editordarkmode') === null) {
darkmode.value = 'vs';
localStorage.setItem('editordarkmode', darkmode.value);
} else {
darkmode.value = localStorage.getItem('editordarkmode');
}
}
function toggleEditorDarkMode(event) {
if (event.target.checked) {
darkmode.value = 'vs-dark';
localStorage.setItem('editordarkmode', darkmode.value);
} else {
darkmode.value = 'vs';
localStorage.setItem('editordarkmode', darkmode.value);
}
monaco.editor.setTheme(darkmode.value);
}
/** EDITOR LINNUMBERS */
const lineNumbers = ref('on');
function checkLineNumbersIsSet() {
if (localStorage.getItem('editorlineNumbers') === null) {
lineNumbers.value = 'on';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
} else {
lineNumbers.value = localStorage.getItem('editorlineNumbers');
}
}
function toggleEditorLineNumbers(event) {
if (event.target.checked) {
lineNumbers.value = 'on';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
} else {
lineNumbers.value = 'off';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
}
meditor.updateOptions({
lineNumbers: lineNumbers.value
});
}
return {
darkmode,
checkDarkModeIsSet,
toggleEditorDarkMode,
lineNumbers,
checkLineNumbersIsSet,
toggleEditorLineNumbers,
};
};
Basically, function toggleEditorLineNumbers(event) throws an error becuase meditor (meditor.updateOptions) is null.
ANy ideas?
This is the case for a ref, which basically is a recipe that allows to pass arbitrary value by reference. Since meditor isn't supposed to be used prior to useCodeEditor call, it makes more sense to define it inside a composable instead of providing it as an argument:
function useCodeEditor(monaco) {
const meditor = ref(null);
...
return {
meditor,
...
}
}
Related
Through this article I learned that you can access these magic variables through JS as well (e.g. in HTML <div x-ref="navbarCollapse">...</div> and in JS this.$refs.navbarCollapse).
I was wondering whether that would work as well with the Persist plugin. The goal is to store a boolean whether the user accepted the Cookie Banner.
So I tried setting the variable as if it were placed in x-data, which looks like:
export default () => ({
open: this.$persist(false),
});
but that throws:
module.esm.js:1656 Alpine Expression Error: Illegal invocation
Expression: "open"
Next I tried placing in my init() function (which is async because I use GraphQL in there)
export default () => ({
open: false,
async init() {
this.open = this.$persist(false);
...
}
});
but then nothing happens.
For more context here is my currently working example with my own functions for reading/writing to local-storage.
import client from './graphql';
import {
gql,
} from '#apollo/client/core';
import {
storageGet,
storageHas,
storageSet,
} from './local-storage';
const COOKIE_CONSENT_NAME = 'cookieConsentAccepted';
export default () => ({
open: false,
enabled: false,
async init() {
// Get all relevant information from the API
const response = await client.query(
{
query: gql`
{
globalSets(handle: "globalsCookieBanner") {
... on globalsCookieBanner_GlobalSet {
cookieBannerStatus
}
}
}
`,
});
const data = response.data.globalSets[0];
this.enabled = data.cookieBannerStatus;
// Check whether to open cookie banner
if (this.enabled && (!storageHas(COOKIE_CONSENT_NAME) || (storageHas(COOKIE_CONSENT_NAME) && storageGet(COOKIE_CONSENT_NAME === false)))) {
this.open = true;
}
},
toggle() {
this.open = !this.open;
storageSet(COOKIE_CONSENT_NAME, !this.open);
},
});
We're trying to convert a simple SFC to use the new Vue CompositionAPI. This code works flawless:
export default {
data() {
return {
miniState: true
}
},
methods: {
setMiniState(state) {
if (this.$q.screen.width > 1023) {
this.miniState = false;
} else if (state !== void 0) {
this.miniState = state === true
}
else {
this.miniState = true
}
},
},
watch: {
'$q.screen.width'() {
this.setMiniState()
}
}
};
Converting this to the new CompostionAPI looks like this:
export default defineComponent({
setup() {
const miniState = ref(true)
const setMiniState = (state) => {
if ($q.screen.width > 1023) {
miniState.value = false
} else if (state !== void 0) {
miniState.value = state === true
}
else {
miniState.value = true
}
}
watch('$q.screen.width'(),
setMiniState()
)
return {
miniState, setMiniState
}
}
})
However, every time I try to run this Vue complains that $q.screen.width is not a function. What am I doing wrong here?
You're calling $q.screen.width instead of adding it as a watch source.
Try this:
watch('$q.screen.width', (newVal, oldVal) => setMiniState())
I am new in the Aurelia community and it was given to me a task to make an entire upgrade of my current platform. (more info at the bottom).
Current problem:
Every time i redirect to the logout.js model a message is prompt
ERROR [app-router] TypeError: "this.view is null"
Questions:
How does a custom component "if-permission" can influence on non-view model?
Conlusions:
- I started to believe that any of the big files bellow are influencing the error at all! After commenting most of the code the error what still showing!
- Removed the noView() logic and added an empty logout.html! Guess what? Works like a charm! The logout will redirect to the login page.
This is my RouteConfig.js
{
route: 'logout',
viewPorts: {
main: {
moduleId: PLATFORM.moduleName('pages/logout/logout')
}
},
nav: false,
sidebar: false,
auth: false,
title: 'Logout',
name: 'logout',
}
This is my logout.js
import { noView } from 'aurelia-framework';
import authService from 'services/authService';
import uiService from 'services/uiService';
#noView()
export class LogoutPage {
activate() {
//THE ERROR PROMPTS EVEN WITH THE ABOVE LINES COMMENTED
uiService.impersonate(null, false);
authService.logout();
}
}
After searching a while i noticed that "this.view" is declared on this 2 files:
if-permission.js
import { inject, customAttribute, templateController, BoundViewFactory, ViewSlot } from 'aurelia-framework';
import userService from 'services/api/userService';
#customAttribute('if-permission')
#inject(BoundViewFactory, ViewSlot)
#templateController
export class IfPermission {
constructor(viewFactory, viewSlot) {
this.viewFactory = viewFactory;
this.viewSlot = viewSlot;
this.showing = false;
this.view = null;
this.bindingContext = null;
this.overrideContext = null;
}
/**
* Binds the if to the binding context and override context
* #param bindingContext The binding context
* #param overrideContext An override context for binding.
*/
bind(bindingContext, overrideContext) {
// Store parent bindingContext, so we can pass it down
this.bindingContext = bindingContext;
this.overrideContext = overrideContext;
this.valueChanged(this.value);
}
valueChanged(newValue) {
if (this.__queuedChanges) {
this.__queuedChanges.push(newValue);
return;
}
let maybePromise = this._runValueChanged(newValue);
if (maybePromise instanceof Promise) {
let queuedChanges = this.__queuedChanges = [];
let runQueuedChanges = () => {
if (!queuedChanges.length) {
this.__queuedChanges = undefined;
return;
}
let nextPromise = this._runValueChanged(queuedChanges.shift()) || Promise.resolve();
nextPromise.then(runQueuedChanges);
};
maybePromise.then(runQueuedChanges);
}
}
_runValueChanged(newValue) {
newValue = userService.hasPermission(newValue);
if (!newValue) {
let viewOrPromise;
if (this.view !== null && this.showing) {
viewOrPromise = this.viewSlot.remove(this.view);
if (viewOrPromise instanceof Promise) {
viewOrPromise.then(() => this.view.unbind());
} else {
this.view.unbind();
}
}
this.showing = false;
return viewOrPromise;
}
if (this.view === null) {
this.view = this.viewFactory.create();
}
if (!this.view.isBound) {
this.view.bind(this.bindingContext, this.overrideContext);
}
if (!this.showing) {
this.showing = true;
return this.viewSlot.add(this.view);
}
}
/**
* Unbinds the if
*/
unbind() {
if (this.view === null) {
return;
}
this.view.unbind();
if (!this.viewFactory.isCaching) {
return;
}
if (this.showing) {
this.showing = false;
this.viewSlot.remove(this.view, true, true);
}
this.view.returnToCache();
this.view = null;
}
}
if-user-role.js
import { inject, customAttribute, templateController, BoundViewFactory, ViewSlot } from 'aurelia-framework';
import userService from 'services/api/userService';
#customAttribute('if-user-role')
#inject(BoundViewFactory, ViewSlot)
#templateController
export class IfUserRole {
constructor(viewFactory, viewSlot) {
this.viewFactory = viewFactory;
this.viewSlot = viewSlot;
this.showing = false;
this.view = null;
this.bindingContext = null;
this.overrideContext = null;
}
/**
* Binds the if to the binding context and override context
* #param bindingContext The binding context
* #param overrideContext An override context for binding.
*/
bind(bindingContext, overrideContext) {
// Store parent bindingContext, so we can pass it down
this.bindingContext = bindingContext;
this.overrideContext = overrideContext;
this.valueChanged(this.value);
}
valueChanged(newValue) {
if (this.__queuedChanges) {
this.__queuedChanges.push(newValue);
return;
}
let maybePromise = this._runValueChanged(newValue);
if (maybePromise instanceof Promise) {
let queuedChanges = this.__queuedChanges = [];
let runQueuedChanges = () => {
if (!queuedChanges.length) {
this.__queuedChanges = undefined;
return;
}
let nextPromise = this._runValueChanged(queuedChanges.shift()) || Promise.resolve();
nextPromise.then(runQueuedChanges);
};
maybePromise.then(runQueuedChanges);
}
}
_runValueChanged(newValue) {
newValue = userService.hasRole(newValue);
if (!newValue) {
let viewOrPromise;
if (this.view !== null && this.showing) {
viewOrPromise = this.viewSlot.remove(this.view);
if (viewOrPromise instanceof Promise) {
viewOrPromise.then(() => this.view.unbind());
} else {
this.view.unbind();
}
}
this.showing = false;
return viewOrPromise;
}
if (this.view === null) {
this.view = this.viewFactory.create();
}
if (!this.view.isBound) {
this.view.bind(this.bindingContext, this.overrideContext);
}
if (!this.showing) {
this.showing = true;
return this.viewSlot.add(this.view);
}
}
/**
* Unbinds the if
*/
unbind() {
if (this.view === null) {
return;
}
this.view.unbind();
if (!this.viewFactory.isCaching) {
return;
}
if (this.showing) {
this.showing = false;
this.viewSlot.remove(this.view, true, true);
}
this.view.returnToCache();
this.view = null;
}
}
With this update i have integrated Aurelia-cli, updated aurelia-webpack and all the dependencies. Which made me switch some code like:
Add PLATFORM.moduleName() to all my platform
Add Require to all modules that were only getting components via < compose >
With Jupyter Notebooks, I could have a cell
%%javascript IPython.notebook.kernel.execute('x = 42')
Then, elsewhere in the document a Python code cell with x would show it bound to 42 as expected.
I'm trying to produce something similar with JupyterLab. I understand I'm supposed to write a plugin rather than using ad-hoc JS, and that's fine, but I'm not finding an interface to the kernel similar to the global IPython from notebooks:
import { JupyerLab, JupyterLabPlugin } from '#jupyterlab/application';
const extension: JupyterLabPlugin<void> = {
// ...
requires: [],
activate: (app: JupyterLab) => {
// can I get to Python evaluation through app?
// by adding another class to `requires` above?
}
}
export default extension;
Here's a hacky attempt that "works". Could still use advice if anyone knows where there is a public promise for the kernel being ready, how to avoid the intermediate class, or any other general improvements:
import { JupyterLab, JupyterLabPlugin } from '#jupyterlab/application';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable, DisposableDelegate } from '#phosphor/disposable';
declare global {
interface Window {
'execPython': {
'readyState': string,
'exec': (code: string) => any,
'ready': Promise<void>
} | null
}
}
class ExecWidgetExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
createNew(nb: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
if (window.execPython) {
return;
}
window.execPython = {
'readyState': 'waiting',
'exec': null,
'ready': new Promise((resolve) => {
const wait = setInterval(() => {
if (!context.session.kernel || window.execPython.readyState === 'ready') {
return;
}
clearInterval(wait);
window.execPython.readyState = 'ready';
window.execPython.exec = (code: string) =>
context.session.kernel.requestExecute({ code }, true);
resolve();
}, 50);
})
};
// Usage elsewhere: execPython.ready.then(() => execPython.exec('x = 42').done.then(console.log, console.error))
return new DisposableDelegate(() => {});
}
}
const extension: JupyterLabPlugin<void> = {
'id': 'jupyterlab_foo',
'autoStart': true,
'activate': (app: JupyterLab) => {
app.docRegistry.addWidgetExtension('Notebook', new ExecWidgetExtension())
}
};
export default extension;
I'm having trouble trying to read from apollo cache from within a react component the mutation works and writes to my server and returns the data but when passed to my update function it seems to lose the context of this when in inMemoryCache.js
"apollo-cache-inmemory": "^1.2.5"
"react-apollo": "^2.1.4"
"apollo-boost": "^0.1.7"
TypeError: Cannot read property 'read' of undefined
at ./node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.readQuery
import React, { Component } from "react";
import { graphql } from "react-apollo";
import trim from "lodash/trim";
import AuthorForm from '../components/author-form';
import ALL_AUTHORS from "../graphql/getPosts.query";
import CREATE_AUTHOR from "../graphql/createAuthor.mutation";
class CreateAuthor extends Component {
state = {
errors: false
};
onSubmit(event) {
event.preventDefault();
const form = new FormData(event.target);
const data = {
firstName: form.get("firstName"),
lastName: form.get("lastName")
};
if (!data.firstName || !data.lastName) {
return this.setState({ errors: true });
}
this.create({
firstName: trim(data.firstName),
lastName: trim(data.lastName)
});
}
async create(variables) {
const { createAuthor } = this.props;
this.setState({ errors: false });
try {
await createAuthor({
variables,
update: (cache, data) => this.updateCache(cache, data)
})
} catch (e) {
this.setState({ errors: true })
}
}
updateCache({ readQuery, writeQuery }, { data: { createAuthor }, errors }) {
if (errors) {
return;
}
const { allAuthors } = readQuery({
query: ALL_AUTHORS,
defaults: {
allAuthors: []
}
});
/*eslint-disable*/ console.log(allAuthors);
}
render() {
return (
<div>
<AuthorForm onSubmit={this.onSubmit.bind(this)}/>
<OnError/>
</div>
);
}
}
export default graphql(CREATE_AUTHOR, { name: "createAuthor" })(CreateAuthor);
is it to do with me binding this to the onSubmit button? if so what is the proper way to attach a function to a element without losing context of this within the component and still allow apollo cache to function properly.
I was losing the context of this because I was deconstructing the first argument. this is what I settled on in the end.
It throw errors when there were no allAuthors on the ROOT_QUERY object so added it to my return statement.
this doesn't feel like an ideal way of updating the cache shouldn't default param passed to readQuery prevent the error thrown.
updateCache(cache, { data: { createAuthor }, errors }) {
if (errors || !cache.data.data.ROOT_QUERY.allAuthors) {
return;
}
const query = ALL_AUTHORS;
const { allAuthors } = cache.readQuery({
query,
defaults: {
allAuthors: []
}
});
const data = {
allAuthors: allAuthors.concat([createAuthor])
};
cache.writeQuery({
query,
data
});
}