Unable to edit smartview

Expected behavior

I need to edit my smartview.

Actual behavior

When I type text, the editor doesn’t show me anything, or sometimes it shows my code with line breaks. I also get some errors in my browser console. Do you have a solution ? Thanks !

Context

“meta”: {
“database_type”: “postgres”,
“liana”: “forest-express-sequelize”,
“liana_version”: “7.9.1”,
“engine”: “nodejs”,
“engine_version”: “14.15.1”,
“orm_version”: “5.21.3”
}

Hello @HaFLiNGeR, welcome in the community :partying_face:

It’s definitively a bug on our side, I’ll take a look to reproduce your issue and identify the origin of this problem.

I will create a bug ticket and keep you posted about this error.

Hello agin @HaFLiNGeR,

I tried to reproduce your issue but I could not. Can you please share the content of your component.js and template.hbs? The error might be related to these contents.

Thanks

Hi,

Thanks in advance !

Here is the configuration of my smartview :

component.js :

import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action, computed } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import {
  triggerSmartAction,
  deleteRecords,
  getCollectionId,
  loadExternalStyle,
  loadExternalJavascript,
} from "client/utils/smart-view-utils";

export default class MyComponent extends Component {
  @tracked _token = null;
  @tracked _userId = null;
  @tracked _apiUrl = null;

  get token() {
    if (!this._token) {
      const cookies = document.cookie.split(";").reduce((acc, v) => {
        const [key, value] = v.split("=");
        return { ...acc, [key]: value };
      }, {});

      const jwt = cookies.forest_session_data;

      this._token = jwt;
    }

    return this._token;
  }

  getApiUrl() {
    const [env = null] = location.href.match(/DEV-[A-Za-z]*|UAT|Production/gi);
    switch (env) {
      case "UAT":
        return "https://uat.foo.bar";
      case "Production":
        return "https://prod.foo.bar";
      default:
        return "http://localhost:3000";
    }
  }

  get apiUrl() {
    if (!this._apiUrl) {
      this._apiUrl = this.getApiUrl();
    }
    return this._apiUrl;
  }

  get userId() {
    if (!this.currentRecord && !this._userId) {
      this._userId = this.args.records?.recordData.id;
    }
    return this._userId;
  }

  @action
  toggleFullScreen() {
    const iframeContainer = document.getElementById("notes-iframe-container");
    const wrapper = document.getElementById("notes-wrapper");
    if (iframeContainer?.classList?.contains("fullscreen")) {
      const fragment = document.createDocumentFragment();
      fragment.appendChild(iframeContainer);
      wrapper.append(fragment);
    } else {
      const fragment = document.createDocumentFragment();
      fragment.appendChild(iframeContainer);
      document.body.prepend(fragment);
    }
    iframeContainer?.classList?.toggle("fullscreen");
  }
}

template.hbs

<div id="notes-wrapper">
  {{#if this.userId }}
  <iframe
    src="{{ this.apiUrl }}/notes?userId={{ this.userId }}&auth={{ this.token }}"
    id="notes-iframe"
  />
  {{/if}}
</div>

When I try to execute your code in development, I have some errors that are directly related to your code. Maybe in production they’re obfuscated:

The code repeats the same pattern, that leads to errors:

  • A private @tracked property
  • A getter that will first test the value of this tracked property, and then initialize it if uninitialized.

This leads to this error:

index.js:172 Uncaught (in promise) Error: Assertion Failed: You attempted to update _apiUrl on MyComponent, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.

In your code, I replaced the access to the user ID to make it work:

import { alias } from '@ember/object/computed';

export default class MyComponent extends Component {
  @service currentUser;
  // This uses internal code from Forest Admin, so it's subject to change
  // but it is quite stable so it won't change often
  @alias('currentUser.id') userId;

The API URL can be retrieved as well directly from lianaSession, or computed with your method:

  // I keep this as a reference as it can be used in other contexts
  @computed()
  get apiUrl() {
    const [env = null] = location.href.match(/DEV-[A-Za-z]*|UAT|Production/gi);
    switch (env) {
      case "UAT":
        return "https://uat.foo.bar";
      case "Production":
        return "https://prod.foo.bar";
      default:
        return "http://localhost:3000";
    }
  }
  // A simpler version that uses Forest Admin's internal code
  // so it is subject to change, but quite stable
  @alias('lianaSession.currentEnvironment.apiEndpoint') apiUrl;

The usage of @computed ensures that the value is only computed once.

And the token can be retrieved using lianaSession.authToken as explained in another topic.

To wrap up, this code should work:

import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action, computed } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { alias } from '@ember/object/computed';
import {
  triggerSmartAction,
  deleteRecords,
  getCollectionId,
  loadExternalStyle,
  loadExternalJavascript,
} from "client/utils/smart-view-utils";

export default class MyComponent extends Component {
  @tracked _token = null;
  @tracked _apiUrl = null;
  @service currentUser;
  @service lianaSession;
  @alias('currentUser.id') userId;
  @alias('lianaSession.authToken') token;
  @alias('lianaSession.currentEnvironment.apiEndpoint') apiUrl;

  @action
  toggleFullScreen() {
    const iframeContainer = document.getElementById("notes-iframe-container");
    const wrapper = document.getElementById("notes-wrapper");
    if (iframeContainer?.classList?.contains("fullscreen")) {
      const fragment = document.createDocumentFragment();
      fragment.appendChild(iframeContainer);
      wrapper.append(fragment);
    } else {
      const fragment = document.createDocumentFragment();
      fragment.appendChild(iframeContainer);
      document.body.prepend(fragment);
    }
    iframeContainer?.classList?.toggle("fullscreen");
  }
}