UX: Show loading spinner while loading dependencies for ace-editor (#26099)
Why this change? On a slow network, using the `AceEditor` component will result in a blob of text being shown first before being swapped out with the `ace.js` editor after it has completed loading. There is also a problem when setting the theme for the editor which would result in a "flash" as reported in https://github.com/ajaxorg/ace/issues/3286. To avoid this, we need to load the theme js file before displaying the editor. What does this change do? 1. Adds a loading spinner and set the `div.ace` with a `.hidden` class. 2. Once all the relevant scripts and initialization is done, we will then remove the loading spinner and remove `div.ace`.
This commit is contained in:
parent
f8964f8f8f
commit
7d8dd0d8e3
|
@ -1 +1,5 @@
|
||||||
|
{{#if this.isLoading}}
|
||||||
|
{{loading-spinner size="small"}}
|
||||||
|
{{else}}
|
||||||
<div class="ace">{{this.content}}</div>
|
<div class="ace">{{this.content}}</div>
|
||||||
|
{{/if}}
|
|
@ -1,5 +1,6 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import { next } from "@ember/runloop";
|
||||||
import { classNames } from "@ember-decorators/component";
|
import { classNames } from "@ember-decorators/component";
|
||||||
import { observes, on } from "@ember-decorators/object";
|
import { observes, on } from "@ember-decorators/object";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
@ -13,6 +14,7 @@ const COLOR_VARS_REGEX =
|
||||||
|
|
||||||
@classNames("ace-wrapper")
|
@classNames("ace-wrapper")
|
||||||
export default class AceEditor extends Component {
|
export default class AceEditor extends Component {
|
||||||
|
isLoading = true;
|
||||||
mode = "css";
|
mode = "css";
|
||||||
disabled = false;
|
disabled = false;
|
||||||
htmlPlaceholder = false;
|
htmlPlaceholder = false;
|
||||||
|
@ -95,7 +97,12 @@ export default class AceEditor extends Component {
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
super.didInsertElement(...arguments);
|
super.didInsertElement(...arguments);
|
||||||
|
|
||||||
loadScript("/javascripts/ace/ace.js").then(() => {
|
loadScript("/javascripts/ace/ace.js").then(() => {
|
||||||
|
loadScript(`/javascripts/ace/theme-${this.aceTheme}.js`).then(() => {
|
||||||
|
this.set("isLoading", false);
|
||||||
|
|
||||||
|
next(() => {
|
||||||
window.ace.require(["ace/ace"], (loadedAce) => {
|
window.ace.require(["ace/ace"], (loadedAce) => {
|
||||||
loadedAce.config.set("loadWorkerFromBlob", false);
|
loadedAce.config.set("loadWorkerFromBlob", false);
|
||||||
loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers
|
loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers
|
||||||
|
@ -107,10 +114,13 @@ export default class AceEditor extends Component {
|
||||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const editor = loadedAce.edit(this.element.querySelector(".ace"));
|
const aceElement = this.element.querySelector(".ace");
|
||||||
|
const editor = loadedAce.edit(aceElement);
|
||||||
editor.setShowPrintMargin(false);
|
editor.setShowPrintMargin(false);
|
||||||
editor.setOptions({ fontSize: "14px", placeholder: this.placeholder });
|
editor.setOptions({
|
||||||
|
fontSize: "14px",
|
||||||
|
placeholder: this.placeholder,
|
||||||
|
});
|
||||||
editor.getSession().setMode("ace/mode/" + this.mode);
|
editor.getSession().setMode("ace/mode/" + this.mode);
|
||||||
editor.on("change", () => {
|
editor.on("change", () => {
|
||||||
this._skipContentChangeEvent = true;
|
this._skipContentChangeEvent = true;
|
||||||
|
@ -152,12 +162,15 @@ export default class AceEditor extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAceTheme();
|
this.setAceTheme();
|
||||||
|
|
||||||
this._darkModeListener = window.matchMedia(
|
this._darkModeListener = window.matchMedia(
|
||||||
"(prefers-color-scheme: dark)"
|
"(prefers-color-scheme: dark)"
|
||||||
);
|
);
|
||||||
this._darkModeListener.addListener(this.setAceTheme);
|
this._darkModeListener.addListener(this.setAceTheme);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroyElement() {
|
willDestroyElement() {
|
||||||
|
@ -165,15 +178,17 @@ export default class AceEditor extends Component {
|
||||||
this._darkModeListener?.removeListener(this.setAceTheme);
|
this._darkModeListener?.removeListener(this.setAceTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
get aceTheme() {
|
||||||
setAceTheme() {
|
|
||||||
const schemeType = getComputedStyle(document.body)
|
const schemeType = getComputedStyle(document.body)
|
||||||
.getPropertyValue("--scheme-type")
|
.getPropertyValue("--scheme-type")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
this._editor.setTheme(
|
return schemeType === "dark" ? "chaos" : "chrome";
|
||||||
`ace/theme/${schemeType === "dark" ? "chaos" : "chrome"}`
|
}
|
||||||
);
|
|
||||||
|
@bind
|
||||||
|
setAceTheme() {
|
||||||
|
this._editor.setTheme(`ace/theme/${this.aceTheme}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
warnSCSSDeprecations() {
|
warnSCSSDeprecations() {
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
export const PUBLIC_JS_VERSIONS = {
|
export const PUBLIC_JS_VERSIONS = {
|
||||||
"ace/ace.js": "ace.js/1.4.13/ace.js",
|
"ace/ace.js": "ace.js/1.4.13/ace.js",
|
||||||
|
"ace/theme-chrome.js": "ace.js/1.4.13/theme-chrome.js",
|
||||||
|
"ace/theme-chaos.js": "ace.js/1.4.13/theme-chaos.js",
|
||||||
"jsoneditor.js": "@json-editor/json-editor/2.10.0/jsoneditor.js",
|
"jsoneditor.js": "@json-editor/json-editor/2.10.0/jsoneditor.js",
|
||||||
"chart.min.js": "chart.js/3.5.1/chart.min.js",
|
"chart.min.js": "chart.js/3.5.1/chart.min.js",
|
||||||
"chartjs-plugin-datalabels.min.js":
|
"chartjs-plugin-datalabels.min.js":
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { render } from "@ember/test-helpers";
|
import { render, waitUntil } from "@ember/test-helpers";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
@ -56,9 +56,10 @@ module(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}} />`);
|
}} />`);
|
||||||
const lines = document.querySelectorAll(".ace_line");
|
|
||||||
const indexOf = lines[0].innerHTML.indexOf("[");
|
await waitUntil(() => document.querySelectorAll(".ace_line")[0]);
|
||||||
assert.ok(indexOf >= 0);
|
|
||||||
|
assert.dom(".ace_line").hasText("[");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("input is valid json", async function (assert) {
|
test("input is valid json", async function (assert) {
|
||||||
|
|
|
@ -232,27 +232,29 @@ task "javascript:update" => "clean_up" do
|
||||||
dest = "#{path}/#{filename}"
|
dest = "#{path}/#{filename}"
|
||||||
|
|
||||||
FileUtils.mkdir_p(path) unless File.exist?(path)
|
FileUtils.mkdir_p(path) unless File.exist?(path)
|
||||||
end
|
|
||||||
else
|
|
||||||
dest = "#{vendor_js}/#{filename}"
|
|
||||||
end
|
|
||||||
|
|
||||||
if src.include? "ace.js"
|
if src.include? "ace.js"
|
||||||
versions["ace/ace.js"] = versions.delete("ace.js")
|
versions["ace/ace.js"] = versions.delete("ace.js")
|
||||||
|
|
||||||
|
themes = %w[theme-chrome theme-chaos]
|
||||||
|
|
||||||
|
themes.each do |file|
|
||||||
|
versions["ace/#{file}.js"] = "#{package_dir_name}/#{package_version}/#{file}.js"
|
||||||
|
end
|
||||||
|
|
||||||
ace_root = "#{library_src}/ace-builds/src-min-noconflict/"
|
ace_root = "#{library_src}/ace-builds/src-min-noconflict/"
|
||||||
addtl_files = %w[
|
|
||||||
ext-searchbox
|
addtl_files = %w[ext-searchbox mode-html mode-scss mode-sql mode-yaml worker-html].concat(
|
||||||
mode-html
|
themes,
|
||||||
mode-scss
|
)
|
||||||
mode-sql
|
|
||||||
mode-yaml
|
|
||||||
theme-chrome
|
|
||||||
theme-chaos
|
|
||||||
worker-html
|
|
||||||
]
|
|
||||||
dest_path = dest.split("/")[0..-2].join("/")
|
dest_path = dest.split("/")[0..-2].join("/")
|
||||||
addtl_files.each { |file| FileUtils.cp_r("#{ace_root}#{file}.js", dest_path) }
|
addtl_files.each { |file| FileUtils.cp_r("#{ace_root}#{file}.js", dest_path) }
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
dest = "#{vendor_js}/#{filename}"
|
||||||
|
end
|
||||||
|
|
||||||
STDERR.puts "New dependency added: #{dest}" unless File.exist?(dest)
|
STDERR.puts "New dependency added: #{dest}" unless File.exist?(dest)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue