Quest script assembly can now be viewed.

This commit is contained in:
Daan Vanden Bosch 2019-07-21 20:44:34 +02:00
parent 99d401e785
commit 73e199d724
21 changed files with 1208 additions and 798 deletions

View File

@ -32,6 +32,7 @@
"no-console": "warn",
"no-constant-condition": ["warn", { "checkLoops": false }],
"no-empty": "warn",
"no-useless-escape": "warn",
"prettier/prettier": "warn",
"react/no-unescaped-entities": "off",
"@typescript-eslint/no-non-null-assertion": "off"

View File

@ -1,8 +1,16 @@
const CracoAntDesignPlugin = require("craco-antd");
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
plugins: [{ plugin: CracoAntDesignPlugin }],
plugins: [
{ plugin: CracoAntDesignPlugin },
{
plugin: new MonacoWebpackPlugin({
languages: []
})
}
],
eslint: {
mode: "file"
}
};
};

View File

@ -19,6 +19,8 @@
"mobx": "^5.11.0",
"mobx-react": "^6.1.1",
"moment": "^2.24.0",
"monaco-editor": "^0.17.1",
"monaco-editor-webpack-plugin": "^1.7.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "^3.0.1",

View File

@ -309,7 +309,7 @@ export class ResizableBufferCursor implements Cursor {
const max_pos = Math.min(this.position + max_byte_length, this.size);
for (let i = this.position; i < max_pos; ++i) {
if (this.dv.getUint8(i) === value) {
if (this.dv.getUint8(this.offset + i) === value) {
return i;
}
}
@ -321,7 +321,7 @@ export class ResizableBufferCursor implements Cursor {
const max_pos = Math.min(this.position + max_byte_length, this.size);
for (let i = this.position; i < max_pos; i += 2) {
if (this.dv.getUint16(i, this.little_endian) === value) {
if (this.dv.getUint16(this.offset + i, this.little_endian) === value) {
return i;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import { Cursor } from "../../cursor/Cursor";
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
import { Vec3 } from "../../vector";
import { BB_MAP_DESIGNATE, Instruction, OP_RET, parse_bin, SET_EPISODE, write_bin } from "./bin";
import { BB_MAP_DESIGNATE, Instruction, parse_bin, SET_EPISODE, write_bin, BinFile } from "./bin";
import { DatFile, DatNpc, DatObject, parse_dat, write_dat } from "./dat";
import { parse_qst, QstContainedFile, write_qst } from "./qst";
@ -58,17 +58,21 @@ export function parse_quest(cursor: Cursor, lenient: boolean = false): Quest | u
let episode = 1;
let area_variants: AreaVariant[] = [];
if (bin.function_offsets.length) {
const func_0_ops = get_func_instructions(bin.instructions, bin.function_offsets[0]);
if (bin.labels.size) {
if (bin.labels.has(0)) {
const label_0_instructions = bin.get_label_instructions(0);
if (func_0_ops) {
episode = get_episode(func_0_ops);
area_variants = get_area_variants(dat, episode, func_0_ops, lenient);
if (label_0_instructions) {
episode = get_episode(label_0_instructions);
area_variants = get_area_variants(dat, episode, label_0_instructions, lenient);
} else {
logger.warn(`Index ${bin.labels.get(0)} for label 0 is invalid.`);
}
} else {
logger.warn(`Offset ${bin.function_offsets[0]} for function 0 is invalid.`);
logger.warn(`Label 0 not found.`);
}
} else {
logger.warn("File contains no functions.");
logger.warn("File contains no labels.");
}
return new Quest(
@ -82,7 +86,8 @@ export function parse_quest(cursor: Cursor, lenient: boolean = false): Quest | u
parse_obj_data(dat.objs),
parse_npc_data(episode, dat.npcs),
dat.unknowns,
bin.function_offsets,
bin.labels,
bin.instructions,
bin.object_code,
bin.unknown
);
@ -94,17 +99,19 @@ export function write_quest_qst(quest: Quest, file_name: string): ArrayBuffer {
npcs: npcs_to_dat_data(quest.npcs),
unknowns: quest.dat_unknowns,
});
const bin = write_bin({
quest_id: quest.id,
language: quest.language,
quest_name: quest.name,
short_description: quest.short_description,
long_description: quest.long_description,
function_offsets: quest.function_offsets,
instructions: [],
object_code: quest.object_code,
unknown: quest.bin_unknown,
});
const bin = write_bin(
new BinFile(
quest.id,
quest.language,
quest.name,
quest.short_description,
quest.long_description,
quest.labels,
[],
quest.object_code,
quest.bin_unknown
)
);
const ext_start = file_name.lastIndexOf(".");
const base_file_name =
ext_start === -1 ? file_name.slice(0, 12) : file_name.slice(0, Math.min(12, ext_start));
@ -194,34 +201,6 @@ function get_area_variants(
return area_variants_array.sort((a, b) => a.area.order - b.area.order || a.id - b.id);
}
function get_func_instructions(
instructions: Instruction[],
func_offset: number
): Instruction[] | undefined {
let position = 0;
let func_found = false;
const func_ops: Instruction[] = [];
for (const instruction of instructions) {
if (position === func_offset) {
func_found = true;
}
if (func_found) {
func_ops.push(instruction);
// Break when ret is encountered.
if (instruction.opcode === OP_RET) {
break;
}
}
position += instruction.size;
}
return func_found ? func_ops : undefined;
}
function parse_obj_data(objs: DatObject[]): QuestObject[] {
return objs.map(obj_data => {
return new QuestObject(

View File

@ -5,6 +5,7 @@ import { enum_values } from "../enums";
import { ItemType } from "./items";
import { NpcType } from "./NpcType";
import { ObjectType } from "./ObjectType";
import { Instruction } from "../data_formats/parsing/quest/bin";
export * from "./items";
export * from "./NpcType";
@ -92,7 +93,8 @@ export class Quest {
* (Partial) raw DAT data that can't be parsed yet by Phantasmal.
*/
dat_unknowns: DatUnknown[];
function_offsets: number[];
labels: Map<number, number>;
instructions: Instruction[];
object_code: ArrayBuffer;
bin_unknown: ArrayBuffer;
@ -107,7 +109,8 @@ export class Quest {
objects: QuestObject[],
npcs: QuestNpc[],
dat_unknowns: DatUnknown[],
function_offsets: number[],
labels: Map<number, number>,
instructions: Instruction[],
object_code: ArrayBuffer,
bin_unknown: ArrayBuffer
) {
@ -119,7 +122,8 @@ export class Quest {
if (!objects || !(objects instanceof Array)) throw new Error("objs is required.");
if (!npcs || !(npcs instanceof Array)) throw new Error("npcs is required.");
if (!dat_unknowns) throw new Error("dat_unknowns is required.");
if (!function_offsets) throw new Error("function_offsets is required.");
if (!labels) throw new Error("labels is required.");
if (!instructions) throw new Error("instructions is required.");
if (!object_code) throw new Error("object_code is required.");
if (!bin_unknown) throw new Error("bin_unknown is required.");
@ -133,7 +137,8 @@ export class Quest {
this.objects = objects;
this.npcs = npcs;
this.dat_unknowns = dat_unknowns;
this.function_offsets = function_offsets;
this.labels = labels;
this.instructions = instructions;
this.object_code = object_code;
this.bin_unknown = bin_unknown;
}

View File

@ -18,6 +18,7 @@ class QuestEditorStore {
@observable current_quest_filename?: string;
@observable current_quest?: Quest;
@observable current_area?: Area;
@observable selected_entity?: QuestEntity;
@observable save_dialog_filename?: string;

View File

@ -1,6 +1,6 @@
import { Quest, ObjectType, QuestObject, Episode, QuestNpc, NpcType } from "../domain";
import { area_store } from "./AreaStore";
import { Vec3 } from "../data_formats/vector";
import { Episode, NpcType, ObjectType, Quest, QuestNpc, QuestObject } from "../domain";
import { area_store } from "./AreaStore";
export function create_new_quest(episode: Episode): Quest {
if (episode === Episode.II) throw new Error("Episode II not yet supported.");
@ -16,6 +16,7 @@ export function create_new_quest(episode: Episode): Quest {
create_default_objects(),
create_default_npcs(),
[],
new Map(),
[],
new ArrayBuffer(0),
new ArrayBuffer(0)

View File

@ -1,3 +0,0 @@
.RendererComponent {
overflow: hidden;
}

View File

@ -1,33 +1,31 @@
import React, { Component, ReactNode } from "react";
import { Renderer } from "../rendering/Renderer";
import "./RendererComponent.less";
import { Camera } from "three";
import { Renderer } from "../rendering/Renderer";
type Props = {
renderer: Renderer<Camera>;
width: number;
height: number;
debug?: boolean;
className?: string;
on_will_unmount?: () => void;
};
export class RendererComponent extends Component<Props> {
render(): ReactNode {
let className = "RendererComponent";
if (this.props.className) className += " " + this.props.className;
return <div className={className} ref={this.modifyDom} />;
return <div className="RendererComponent" ref={this.modify_dom} />;
}
componentWillReceiveProps(props: Props): void {
this.props.renderer.debug = !!props.debug;
}
UNSAFE_componentWillReceiveProps(props: Props): void {
if (this.props.debug !== props.debug) {
this.props.renderer.debug = !!props.debug;
}
componentDidMount(): void {
window.addEventListener("resize", this.onResize);
if (this.props.width !== props.width || this.props.height !== props.height) {
this.props.renderer.set_size(props.width, props.height);
}
}
componentWillUnmount(): void {
window.removeEventListener("resize", this.onResize);
this.props.on_will_unmount && this.props.on_will_unmount();
}
@ -35,15 +33,10 @@ export class RendererComponent extends Component<Props> {
return false;
}
private modifyDom = (div: HTMLDivElement | null) => {
private modify_dom = (div: HTMLDivElement | null) => {
if (div) {
this.props.renderer.set_size(div.clientWidth, div.clientHeight);
this.props.renderer.set_size(this.props.width, this.props.height);
div.appendChild(this.props.renderer.dom_element);
}
};
private onResize = () => {
const wrapper_div = this.props.renderer.dom_element.parentNode as HTMLDivElement;
this.props.renderer.set_size(wrapper_div.clientWidth, wrapper_div.clientHeight);
};
}

View File

@ -1,14 +0,0 @@
.qe-QuestEditorComponent {
display: flex;
flex-direction: column;
}
.qe-QuestEditorComponent-main {
flex: 1;
display: flex;
overflow: hidden;
}
.qe-QuestEditorComponent-main > div:nth-child(2) {
flex: 1;
}

View File

@ -0,0 +1,31 @@
.qe-QuestEditorComponent {
display: flex;
flex-direction: column;
}
.qe-QuestEditorComponent-main {
flex: 1;
display: flex;
overflow: hidden;
}
.qe-QuestEditorComponent-tabcontainer {
flex: 1;
display: flex;
flex-direction: column;
& > .ant-tabs-content {
flex: 1;
display: flex;
flex-direction: column;
}
}
.qe-QuestEditorComponent-tab.ant-tabs-tabpane-active {
flex: 1;
display: flex;
}
.qe-QuestEditorComponent-tab-main {
flex: 1;
}

View File

@ -5,9 +5,12 @@ import { application_store } from "../../stores/ApplicationStore";
import { quest_editor_store } from "../../stores/QuestEditorStore";
import { RendererComponent } from "../RendererComponent";
import { EntityInfoComponent } from "./EntityInfoComponent";
import "./QuestEditorComponent.css";
import "./QuestEditorComponent.less";
import { QuestInfoComponent } from "./QuestInfoComponent";
import { Toolbar } from "./Toolbar";
import { Tabs } from "antd";
import { ScriptEditorComponent } from "./ScriptEditorComponent";
import { AutoSizer } from "react-virtualized";
@observer
export class QuestEditorComponent extends Component<{}, { debug: boolean }> {
@ -25,8 +28,34 @@ export class QuestEditorComponent extends Component<{}, { debug: boolean }> {
<Toolbar />
<div className="qe-QuestEditorComponent-main">
<QuestInfoComponent quest={quest} />
<RendererComponent renderer={get_quest_renderer()} debug={this.state.debug} />
<EntityInfoComponent entity={quest_editor_store.selected_entity} />
<Tabs type="card" className="qe-QuestEditorComponent-tabcontainer">
<Tabs.TabPane
tab="Entities"
key="entities"
className="qe-QuestEditorComponent-tab"
>
<div className="qe-QuestEditorComponent-tab-main">
<AutoSizer>
{({ width, height }) => (
<RendererComponent
renderer={get_quest_renderer()}
width={width}
height={height}
debug={this.state.debug}
/>
)}
</AutoSizer>
</div>
<EntityInfoComponent entity={quest_editor_store.selected_entity} />
</Tabs.TabPane>
<Tabs.TabPane
tab="Script"
key="script"
className="qe-QuestEditorComponent-tab"
>
<ScriptEditorComponent className="qe-QuestEditorComponent-tab-main" />
</Tabs.TabPane>
</Tabs>
</div>
</div>
);

View File

@ -0,0 +1,178 @@
import { editor, languages } from "monaco-editor";
import React, { Component, createRef, ReactNode } from "react";
import { AutoSizer } from "react-virtualized";
import { OPCODES } from "../../data_formats/parsing/quest/bin";
import { quest_editor_store } from "../../stores/QuestEditorStore";
import "./ScriptEditorComponent.less";
import { disassemble } from "../scripting/disassembly";
import { IReactionDisposer, autorun } from "mobx";
const ASM_SYNTAX: languages.IMonarchLanguage = {
defaultToken: "invalid",
tokenizer: {
root: [
// Identifiers.
[/[a-z][\w=<>!]*/, "identifier"],
// Labels.
[/^\d+:/, "tag"],
// Registers.
[/r\d+/, "predefined"],
// Whitespace.
[/[ \t\r\n]+/, "white"],
[/\/\*/, "comment", "@comment"],
[/\/\/.*$/, "comment"],
// Numbers.
[/-?\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
[/-?0[xX][0-9a-fA-F]+/, "number.hex"],
[/-?\d+/, "number"],
// Delimiters.
[/,/, "delimiter"],
// Strings.
[/"([^"\\]|\\.)*$/, "string.invalid"], // Unterminated string.
[/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
],
comment: [
[/[^/*]+/, "comment"],
[/\/\*/, "comment", "@push"], // Nested comment.
[/\*\//, "comment", "@pop"],
[/[/*]/, "comment"],
],
string: [
[/[^\\"]+/, "string"],
[/\\(?:[n\\"])/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
},
};
const INSTRUCTION_SUGGESTIONS = OPCODES.map(opcode => {
return ({
label: opcode.mnemonic,
kind: languages.CompletionItemKind.Function,
insertText: opcode.mnemonic,
} as any) as languages.CompletionItem;
});
languages.register({ id: "psoasm" });
languages.setMonarchTokensProvider("psoasm", ASM_SYNTAX);
languages.registerCompletionItemProvider("psoasm", {
provideCompletionItems: (model, position) => {
const value = model.getValueInRange({
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: 1,
endColumn: position.column + 1,
});
const suggest = /^\s*([a-z][\w=<>!]*)?$/.test(value);
return {
suggestions: suggest ? INSTRUCTION_SUGGESTIONS : [],
incomplete: false,
};
},
});
languages.setLanguageConfiguration("psoasm", {
indentationRules: {
increaseIndentPattern: /\d+:/,
decreaseIndentPattern: /\d+/,
},
autoClosingPairs: [{ open: '"', close: '"' }],
surroundingPairs: [{ open: '"', close: '"' }],
});
editor.defineTheme("phantasmal-world", {
base: "vs-dark",
inherit: true,
rules: [{ token: "", background: "151c21" }],
colors: {
"editor.background": "#151c21",
"editor.lineHighlightBackground": "#1a2228",
},
});
export class ScriptEditorComponent extends Component<{ className?: string }> {
render(): ReactNode {
let className = "qe-ScriptEditorComponent";
if (this.props.className) className += " " + this.props.className;
return (
<section className={className}>
<AutoSizer>
{({ width, height }) => <MonacoComponent width={width} height={height} />}
</AutoSizer>
</section>
);
}
}
type MonacoProps = {
width: number;
height: number;
};
class MonacoComponent extends Component<MonacoProps> {
private div_ref = createRef<HTMLDivElement>();
private editor?: editor.IStandaloneCodeEditor;
private disposer?: IReactionDisposer;
render(): ReactNode {
return <div ref={this.div_ref} />;
}
componentDidMount(): void {
if (this.div_ref.current) {
// model.onDidChangeContent(e => {
// e.changes[0].range
// })
this.editor = editor.create(this.div_ref.current, {
theme: "phantasmal-world",
scrollBeyondLastLine: false,
autoIndent: true,
});
this.disposer = autorun(() => {
const quest = quest_editor_store.current_quest;
const model = quest && editor.createModel(disassemble(quest), "psoasm");
if (model && this.editor) {
this.editor.setModel(model);
}
});
}
}
componentWillUnmount(): void {
if (this.editor) {
const model = this.editor.getModel();
if (model) model.dispose();
this.editor.dispose();
}
if (this.disposer) this.disposer();
}
shouldComponentUpdate(): boolean {
return false;
}
UNSAFE_componentWillReceiveProps(props: MonacoProps): void {
if (
(this.props.width !== props.width || this.props.height !== props.height) &&
this.editor
) {
this.editor.layout(props);
}
}
}

View File

@ -0,0 +1,68 @@
import { Arg, Type } from "../../data_formats/parsing/quest/bin";
import { Quest } from "../../domain";
export function disassemble(quest: Quest, manual_stack: boolean = false): string {
const lines: string[] = [];
const index_to_label = [...quest.labels.entries()].reduce(
(map, [l, i]) => map.set(i, l),
new Map<number, number>()
);
const stack: Arg[] = [];
for (let i = 0; i < quest.instructions.length; ++i) {
const ins = quest.instructions[i];
const label = index_to_label.get(i);
if (!manual_stack && ins.opcode.push_stack) {
stack.push(...ins.args);
} else {
let args: string[] = [];
for (let j = 0; j < ins.opcode.params.length; j++) {
const param_type = ins.opcode.params[j];
const arg = ins.args[j];
args.push(...arg_to_strings(param_type, arg));
}
if (!manual_stack) {
for (let j = ins.opcode.stack_params.length - 1; j >= 0; j--) {
const param_type = ins.opcode.stack_params[j];
const arg = stack.pop();
if (!arg) {
break;
}
args.push(...arg_to_strings(param_type, arg));
}
}
if (label != null) {
lines.push(`${label}:`);
}
lines.push(" " + ins.opcode.mnemonic + (args.length ? " " + args.join(", ") : ""));
}
}
return lines.join("\n");
}
function arg_to_strings(param_type: Type, arg: Arg): string[] {
switch (param_type) {
case Type.U8:
case Type.U16:
case Type.U32:
case Type.I32:
case Type.F32:
return [arg.value.toString()];
case Type.Register:
return ["r" + arg.value];
case Type.SwitchData:
case Type.JumpData:
return arg.value.map(String);
case Type.String:
return [JSON.stringify(arg.value)];
}
}

View File

@ -28,8 +28,8 @@
flex: 1;
display: flex;
overflow: hidden;
& > div:nth-child(3) {
flex: 1;
}
}
.v-m-ModelViewerComponent-renderer {
flex: 1;
}

View File

@ -3,12 +3,13 @@ import { UploadChangeParam } from "antd/lib/upload";
import { UploadFile } from "antd/lib/upload/interface";
import { observer } from "mobx-react";
import React, { Component, ReactNode } from "react";
import { AutoSizer } from "react-virtualized";
import { get_model_renderer } from "../../../rendering/ModelRenderer";
import { model_viewer_store } from "../../../stores/ModelViewerStore";
import { RendererComponent } from "../../RendererComponent";
import { AnimationSelectionComponent } from "./AnimationSelectionComponent";
import { ModelSelectionComponent } from "./ModelSelectionComponent";
import "./ModelViewerComponent.less";
import { get_model_renderer } from "../../../rendering/ModelRenderer";
import { RendererComponent } from "../../RendererComponent";
@observer
export class ModelViewerComponent extends Component {
@ -25,10 +26,18 @@ export class ModelViewerComponent extends Component {
<div className="v-m-ModelViewerComponent-main">
<ModelSelectionComponent />
<AnimationSelectionComponent />
<RendererComponent
renderer={get_model_renderer()}
on_will_unmount={model_viewer_store.pause_animation}
/>
<div className="v-m-ModelViewerComponent-renderer">
<AutoSizer>
{({ width, height }) => (
<RendererComponent
renderer={get_model_renderer()}
width={width}
height={height}
on_will_unmount={model_viewer_store.pause_animation}
/>
)}
</AutoSizer>
</div>
</div>
</div>
);

View File

@ -6,16 +6,24 @@ import { get_texture_renderer } from "../../../rendering/TextureRenderer";
import { texture_viewer_store } from "../../../stores/TextureViewerStore";
import { RendererComponent } from "../../RendererComponent";
import "./TextureViewerComponent.less";
import { AutoSizer } from "react-virtualized";
export class TextureViewerComponent extends Component {
render(): ReactNode {
return (
<section className="v-t-TextureViewerComponent">
<Toolbar />
<RendererComponent
renderer={get_texture_renderer()}
className={"v-t-TextureViewerComponent-renderer"}
/>
<div className="v-t-TextureViewerComponent-renderer">
<AutoSizer>
{({ width, height }) => (
<RendererComponent
renderer={get_texture_renderer()}
width={width}
height={height}
/>
)}
</AutoSizer>
</div>
</section>
);
}

View File

@ -1270,6 +1270,11 @@
"@svgr/plugin-svgo" "^4.0.3"
loader-utils "^1.1.0"
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
"@types/babel__core@^7.1.0":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
@ -1426,11 +1431,23 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/tapable@*":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
"@types/text-encoding@^0.0.35":
version "0.0.35"
resolved "https://registry.yarnpkg.com/@types/text-encoding/-/text-encoding-0.0.35.tgz#6f14474e0b232bc70c59677aadc65dcc5a99c3a9"
integrity sha512-jfo/A88XIiAweUa8np+1mPbm3h2w0s425YrI8t3wk5QxhH6UI7w517MboNVnGDeMSuoFwA8Rwmklno+FicvV4g==
"@types/uglify-js@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==
dependencies:
source-map "^0.6.1"
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
@ -1453,6 +1470,17 @@
"@types/unist" "*"
"@types/vfile-message" "*"
"@types/webpack@^4.4.19":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.32.0.tgz#bd4a149964cd471538f2781f2be5f9815175463b"
integrity sha512-kpz5wHDyG/WEpzX9gcwFp/w0oSsq0n/rmFdJelk/QBMHmNIOZdiTDInV0Lj8itGKBahQrBgJGJRss/6UHgLuKg==
dependencies:
"@types/anymatch" "*"
"@types/node" "*"
"@types/tapable" "*"
"@types/uglify-js" "*"
source-map "^0.6.0"
"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
version "12.0.12"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
@ -6851,6 +6879,18 @@ moment@2.x, moment@^2.24.0:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
monaco-editor-webpack-plugin@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz#920cbeecca25f15d70d568a7e11b0ba4daf1ae83"
integrity sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==
dependencies:
"@types/webpack" "^4.4.19"
monaco-editor@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.17.1.tgz#8fbe96ca54bfa75262706e044f8f780e904aa45c"
integrity sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"