mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
DivKitPro editor
This commit is contained in:
@@ -14968,6 +14968,9 @@
|
||||
"site/client/src/assets/closeWhite.svg":"divkit/public/site/client/src/assets/closeWhite.svg",
|
||||
"site/client/src/assets/copy.svg":"divkit/public/site/client/src/assets/copy.svg",
|
||||
"site/client/src/assets/copyOk.svg":"divkit/public/site/client/src/assets/copyOk.svg",
|
||||
"site/client/src/assets/divkitpro/components.svg":"divkit/public/site/client/src/assets/divkitpro/components.svg",
|
||||
"site/client/src/assets/divkitpro/palette.svg":"divkit/public/site/client/src/assets/divkitpro/palette.svg",
|
||||
"site/client/src/assets/divkitpro/sources.svg":"divkit/public/site/client/src/assets/divkitpro/sources.svg",
|
||||
"site/client/src/assets/dot.svg":"divkit/public/site/client/src/assets/dot.svg",
|
||||
"site/client/src/assets/dropdown.svg":"divkit/public/site/client/src/assets/dropdown.svg",
|
||||
"site/client/src/assets/errors.svg":"divkit/public/site/client/src/assets/errors.svg",
|
||||
@@ -14981,6 +14984,7 @@
|
||||
"site/client/src/components/App.svelte":"divkit/public/site/client/src/components/App.svelte",
|
||||
"site/client/src/components/Button.svelte":"divkit/public/site/client/src/components/Button.svelte",
|
||||
"site/client/src/components/CopyButton.svelte":"divkit/public/site/client/src/components/CopyButton.svelte",
|
||||
"site/client/src/components/Design.svelte":"divkit/public/site/client/src/components/Design.svelte",
|
||||
"site/client/src/components/Editor.svelte":"divkit/public/site/client/src/components/Editor.svelte",
|
||||
"site/client/src/components/ErrorPage.svelte":"divkit/public/site/client/src/components/ErrorPage.svelte",
|
||||
"site/client/src/components/ErrorView.svelte":"divkit/public/site/client/src/components/ErrorView.svelte",
|
||||
@@ -15001,6 +15005,7 @@
|
||||
"site/client/src/components/StructureBox.svelte":"divkit/public/site/client/src/components/StructureBox.svelte",
|
||||
"site/client/src/components/StructureCurrent.svelte":"divkit/public/site/client/src/components/StructureCurrent.svelte",
|
||||
"site/client/src/components/StructureTemplates.svelte":"divkit/public/site/client/src/components/StructureTemplates.svelte",
|
||||
"site/client/src/components/ToolbarItem.svelte":"divkit/public/site/client/src/components/ToolbarItem.svelte",
|
||||
"site/client/src/components/Tree.svelte":"divkit/public/site/client/src/components/Tree.svelte",
|
||||
"site/client/src/components/TreeLeaf.svelte":"divkit/public/site/client/src/components/TreeLeaf.svelte",
|
||||
"site/client/src/components/Viewer.svelte":"divkit/public/site/client/src/components/Viewer.svelte",
|
||||
@@ -15011,7 +15016,9 @@
|
||||
"site/client/src/components/WebViewerSidebar.svelte":"divkit/public/site/client/src/components/WebViewerSidebar.svelte",
|
||||
"site/client/src/components/WebViewerWrapper.svelte":"divkit/public/site/client/src/components/WebViewerWrapper.svelte",
|
||||
"site/client/src/ctx/tree.ts":"divkit/public/site/client/src/ctx/tree.ts",
|
||||
"site/client/src/data/defaultEditorValue.ts":"divkit/public/site/client/src/data/defaultEditorValue.ts",
|
||||
"site/client/src/data/editorMode.ts":"divkit/public/site/client/src/data/editorMode.ts",
|
||||
"site/client/src/data/editorModule.ts":"divkit/public/site/client/src/data/editorModule.ts",
|
||||
"site/client/src/data/externalViewers.ts":"divkit/public/site/client/src/data/externalViewers.ts",
|
||||
"site/client/src/data/initialValue.ts":"divkit/public/site/client/src/data/initialValue.ts",
|
||||
"site/client/src/data/jsonStore.ts":"divkit/public/site/client/src/data/jsonStore.ts",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
/client/artifacts
|
||||
/server/artifacts
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"><path fill="#fff" d="M4.167 4.167V7.5H7.5V4.167H4.167Zm-1.667 0c0-.92.746-1.667 1.667-1.667H7.5c.92 0 1.667.746 1.667 1.667V7.5c0 .92-.747 1.667-1.667 1.667H4.167C3.247 9.167 2.5 8.42 2.5 7.5V4.167ZM4.167 12.5v3.333H7.5V12.5H4.167Zm-1.667 0c0-.92.746-1.667 1.667-1.667H7.5c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.747 1.667-1.667 1.667H4.167c-.92 0-1.667-.746-1.667-1.667V12.5Zm13.333 3.333H12.5V12.5h3.333v3.333Zm-3.333-5c-.92 0-1.667.746-1.667 1.667v3.333c0 .92.746 1.667 1.667 1.667h3.333c.92 0 1.667-.746 1.667-1.667V12.5c0-.92-.746-1.667-1.667-1.667H12.5Zm.833-7.5a.833.833 0 0 1 1.667 0V5h1.667a.833.833 0 0 1 0 1.667H15v1.666a.833.833 0 1 1-1.667 0V6.667h-1.666a.833.833 0 1 1 0-1.667h1.666V3.333Z"/></svg>
|
||||
|
After Width: | Height: | Size: 789 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"><path fill="#fff" fill-rule="evenodd" d="M6.559 4.023C5.139 5.63 4.198 7.817 4.15 9.933c-.052 2.342.882 4.323 2.044 5.782a9.788 9.788 0 0 0 1.78 1.74c.586.435 1.059.66 1.321.726.782.196 1.495.244 2.018.035.425-.17.902-.396 1.085-1.578-.01-.396-.134-.988-.307-1.587l-.08-.274c-.212-.745-.443-1.656-.443-2.802 0-.856.456-1.428.952-1.835.24-.196.507-.37.76-.529l.225-.14c.182-.113.358-.223.542-.345.502-.335.961-.706 1.322-1.218.35-.498.637-1.17.732-2.15-.008-.86-.27-1.77-.925-2.507-.658-.74-1.788-1.392-3.69-1.582-1.707-.17-3.501.74-4.927 2.354Zm-1.236-1.09C6.961 1.077 9.236-.213 11.65.028c2.219.222 3.768 1.013 4.758 2.127.983 1.107 1.341 2.448 1.341 3.638v.037l-.003.038c-.115 1.266-.496 2.23-1.03 2.988-.527.75-1.175 1.254-1.755 1.64-.207.139-.419.27-.609.388l-.196.122a5.886 5.886 0 0 0-.591.408c-.29.237-.349.387-.349.56 0 .915.182 1.652.38 2.35l.075.256c.175.595.376 1.445.376 2.113v.058l-.008.058c-.237 1.655-1 2.49-2.114 2.936-1.022.409-2.163.251-3.03.034-.561-.14-1.236-.506-1.904-1.003a11.436 11.436 0 0 1-2.086-2.035C3.566 15.06 2.44 12.715 2.502 9.897c.057-2.55 1.176-5.102 2.821-6.965Z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="M11.568 4.146a1.648 1.648 0 1 0 0 3.297 1.648 1.648 0 0 0 0-3.297ZM8.27 5.794a3.297 3.297 0 1 1 6.594 0 3.297 3.297 0 0 1-6.594 0ZM7.341 9.844a.789.789 0 1 0 0 1.578.789.789 0 0 0 0-1.578Zm-2.367.79a2.367 2.367 0 1 1 4.734 0 2.367 2.367 0 0 1-4.734 0ZM9.341 14.727a.379.379 0 1 0 0 .757.379.379 0 0 0 0-.757Zm-1.894.379a1.894 1.894 0 1 1 3.788 0 1.894 1.894 0 0 1-3.788 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"><path fill="#fff" d="M16.286 4.857A.857.857 0 0 0 15.429 4h-2.572a.857.857 0 0 0 0 1.714h1.714v2.572c0 .227.09.445.252.606l.566.566c.24.22.454.328.642.381.201.057.566.1.566.1v.118s-.415.04-.566.09l-.022.008c-.13.043-.313.103-.553.32l-.633.633a.857.857 0 0 0-.252.606v2.572h-1.714a.857.857 0 0 0 0 1.714h2.572a.857.857 0 0 0 .857-.857v-3.074l1.463-1.463a.857.857 0 0 0 0-1.212L16.286 7.93V4.857ZM3.714 4.857c0-.473.384-.857.857-.857h2.572a.857.857 0 0 1 0 1.714H5.429v2.572c0 .227-.09.445-.251.606l-.52.52c-.261.25-.493.37-.693.427-.201.057-.566.1-.566.1v.118s.415.04.566.09l.022.008c.15.05.371.122.67.434l.52.52c.161.16.252.378.252.605v2.572h1.714a.857.857 0 0 1 0 1.714H4.57a.857.857 0 0 1-.857-.857v-3.074l-1.463-1.463a.857.857 0 0 1 0-1.212L3.714 7.93V4.857Z"/></svg>
|
||||
|
After Width: | Height: | Size: 846 B |
@@ -4,6 +4,7 @@
|
||||
"languageChooser": "Выбранный язык",
|
||||
"playground": "Веб песочница",
|
||||
"samples": "Примеры",
|
||||
"design": "Визуальный редактор",
|
||||
"share": "Поделиться",
|
||||
"components": "Компоненты: ",
|
||||
"timeToRender": "Время на отрисовку: ",
|
||||
@@ -32,13 +33,17 @@
|
||||
"webSupportWarning": "Данный пример содержит функционал, который не поддержан в реализации для Веба. Он может отличаться от нативных платформ.",
|
||||
"collapse": "Свернуть",
|
||||
"expand": "Развернуть",
|
||||
"selectComponent": "Выбрать компонент"
|
||||
"selectComponent": "Выбрать компонент",
|
||||
"designComponents": "Компоненты",
|
||||
"designPalette": "Палитра",
|
||||
"designVariables": "Переменные"
|
||||
},
|
||||
"en": {
|
||||
"name": "Eng",
|
||||
"languageChooser": "Selected language",
|
||||
"playground": "Web playground",
|
||||
"samples": "Samples",
|
||||
"design": "Visual editor",
|
||||
"share": "Share",
|
||||
"components": "Components: ",
|
||||
"timeToRender": "Time to render: ",
|
||||
@@ -67,6 +72,9 @@
|
||||
"webSupportWarning": "This sample contains functionality that is not supported in the implementation for the Web. It may differ from native platforms.",
|
||||
"collapse": "Collapse",
|
||||
"expand": "Expand",
|
||||
"selectComponent": "Select component"
|
||||
"selectComponent": "Select component",
|
||||
"designComponents": "Components",
|
||||
"designPalette": "Palette",
|
||||
"designVariables": "Variables"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
if (langVal !== 'ru' && langVal !== 'en') {
|
||||
langVal = 'en';
|
||||
}
|
||||
let lang = writable(langVal);
|
||||
let lang = writable<'ru' | 'en'>(langVal as 'ru' | 'en');
|
||||
const l10n = derived(lang, lang => {
|
||||
return (key: keyof typeof langObj['en'], overrideLang?: string) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -36,7 +36,7 @@
|
||||
getLanguage(): string {
|
||||
return get(lang);
|
||||
},
|
||||
setLanguage(name: string): void {
|
||||
setLanguage(name: 'ru' | 'en'): void {
|
||||
lang.set(name);
|
||||
},
|
||||
l10n,
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onDestroy, onMount } from 'svelte';
|
||||
import type * as monaco from 'monaco-editor';
|
||||
import type { DivProEditorInstance, EditorOptions, Layout, LayoutItem } from '@yandex-portal/divkit-editor';
|
||||
import { loadMonaco } from './Editor.svelte';
|
||||
import { valueStore } from '../data/valueStore';
|
||||
import { initPromise } from '../data/sessionController';
|
||||
import ToolbarItem from './ToolbarItem.svelte';
|
||||
import { LANGUAGE_CTX, type LanguageContext } from '../data/languageContext';
|
||||
|
||||
const {l10n, lang} = getContext<LanguageContext>(LANGUAGE_CTX);
|
||||
|
||||
let root: HTMLElement;
|
||||
let instance: DivProEditorInstance;
|
||||
let alive = false;
|
||||
let selectedPanel = 'components';
|
||||
|
||||
const TOOLBAR_ITEMS = [
|
||||
'components',
|
||||
'palette',
|
||||
'sources',
|
||||
] as const;
|
||||
$: TOOLBAR_TEXTS = {
|
||||
components: $l10n('designComponents'),
|
||||
palette: $l10n('designPalette'),
|
||||
sources: $l10n('designVariables'),
|
||||
};
|
||||
|
||||
function getEditorLayoutByPanel(panel: string): Layout {
|
||||
let leftList: LayoutItem[];
|
||||
|
||||
if (panel === 'palette') {
|
||||
leftList = ['palette'];
|
||||
} else if (panel === 'sources') {
|
||||
leftList = ['custom-variables'];
|
||||
} else if (panel === 'tanker') {
|
||||
leftList = ['tanker-overview'];
|
||||
} else {
|
||||
leftList = ['new-component', 'component-tree'];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
items: leftList,
|
||||
minWidth: 400,
|
||||
},
|
||||
{
|
||||
items: ['preview'],
|
||||
weight: 3,
|
||||
},
|
||||
{
|
||||
items: ['component-props:code'],
|
||||
minWidth: 360,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function onToolbarClick(item: string): void {
|
||||
selectedPanel = item;
|
||||
if (instance) {
|
||||
instance.setLayout(getEditorLayoutByPanel(selectedPanel));
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
alive = true;
|
||||
|
||||
Promise.all([
|
||||
initPromise,
|
||||
import('../data/editorModule'),
|
||||
loadMonaco()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
]).then(([_, {DivProEditor}, { monaco, jsonModelUri }]) => {
|
||||
if (!alive) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance = DivProEditor.init({
|
||||
renderTo: root,
|
||||
value: $valueStore,
|
||||
theme: 'light',
|
||||
locale: $lang,
|
||||
sources: [],
|
||||
layout: getEditorLayoutByPanel(selectedPanel),
|
||||
paletteEnabled: true,
|
||||
api: {
|
||||
onChange() {
|
||||
valueStore.set(instance.getValue());
|
||||
},
|
||||
editorFabric(options: EditorOptions) {
|
||||
const model = monaco.editor.getModel(jsonModelUri) || monaco.editor.createModel(options.value, 'json', jsonModelUri);
|
||||
const opts: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
automaticLayout: true,
|
||||
wordWrap: 'on',
|
||||
model,
|
||||
lineNumbers: 'off',
|
||||
bracketPairColorization: {
|
||||
enabled: true,
|
||||
independentColorPoolPerBracketType: true
|
||||
},
|
||||
smoothScrolling: true
|
||||
};
|
||||
|
||||
const editor = monaco.editor.create(options.node, opts);
|
||||
editor.setValue(options.value);
|
||||
let editorDecorations: monaco.editor.IEditorDecorationsCollection | null = null;
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
let val = editor && editor.getModel()?.getValue();
|
||||
options.onChange(val || '');
|
||||
});
|
||||
|
||||
editor.onMouseMove(function (e) {
|
||||
const pos = e.target.position;
|
||||
if (!pos || !pos.lineNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.onOver(model.getOffsetAt(pos));
|
||||
});
|
||||
|
||||
editor.onMouseLeave(() => {
|
||||
options.onOver(null);
|
||||
});
|
||||
|
||||
editor.onMouseDown((e) => {
|
||||
const pos = e.target.position;
|
||||
if (!pos || !pos.lineNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.onClick(model.getOffsetAt(pos));
|
||||
});
|
||||
|
||||
return {
|
||||
setValue(value) {
|
||||
if (!editor.hasTextFocus()) {
|
||||
editor.setValue(value);
|
||||
}
|
||||
},
|
||||
setTheme() {
|
||||
// not realized
|
||||
},
|
||||
setReadOnly(/* readOnly */) {
|
||||
// not realized
|
||||
},
|
||||
revealLoc(loc) {
|
||||
editor.revealPositionNearTop({ lineNumber: loc.line, column: loc.column });
|
||||
},
|
||||
decorateRanges(typedRanges) {
|
||||
if (editorDecorations) {
|
||||
editorDecorations.clear();
|
||||
editorDecorations = null;
|
||||
}
|
||||
if (typedRanges) {
|
||||
editorDecorations = editor.createDecorationsCollection(typedRanges.map(typedRange => {
|
||||
return {
|
||||
range: new monaco.Range(
|
||||
typedRange.range.start.line,
|
||||
typedRange.range.start.column,
|
||||
typedRange.range.end.line,
|
||||
typedRange.range.end.column
|
||||
),
|
||||
options: {
|
||||
blockClassName: typedRange.type === 'highlight' ? 'design__code-highlight' : 'design__code-selection'
|
||||
}
|
||||
};
|
||||
}));
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
editor.dispose();
|
||||
}
|
||||
}
|
||||
},
|
||||
uploadFile(file: File) {
|
||||
const name = file.name.toLowerCase();
|
||||
let type: string;
|
||||
|
||||
if (name.endsWith('.png')) {
|
||||
type = 'image/png';
|
||||
} else if (name.endsWith('.jpg') || name.endsWith('.jpeg')) {
|
||||
type = 'image/jpg';
|
||||
} else if (name.endsWith('.svg')) {
|
||||
type = 'image/svg+xml';
|
||||
} else if (name.endsWith('.gif')) {
|
||||
type = 'image/gif';
|
||||
} else if (name.endsWith('.json')) {
|
||||
type = 'application/json';
|
||||
} else {
|
||||
return Promise.reject(new Error('Unknown image'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const request = fetch('/api/uploadFile', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
imageData: (reader.result as string).split(',')[1],
|
||||
contentType: type
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error('Requst failed');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(json => {
|
||||
if (!json.ok) {
|
||||
throw new Error('Something went wrong');
|
||||
}
|
||||
return json.url;
|
||||
});
|
||||
|
||||
resolve(request);
|
||||
};
|
||||
reader.onerror = error => {
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
alive = false;
|
||||
instance?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="design">
|
||||
<div class="design__sidebar">
|
||||
{#each TOOLBAR_ITEMS as item}
|
||||
<ToolbarItem
|
||||
icon={item}
|
||||
selected={selectedPanel === item}
|
||||
text={TOOLBAR_TEXTS[item]}
|
||||
on:click={() => onToolbarClick(item)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="design__editor" bind:this={root}></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.design {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.design__sidebar {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
width: 56px;
|
||||
padding-top: 10px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.design__editor {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
:global(.design__code-highlight) {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:global(.design__code-selection) {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -1,49 +1,82 @@
|
||||
<script lang="ts" context="module">
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { urlPath } from '../utils/const';
|
||||
import type * as monaco from 'monaco-editor';
|
||||
import tsBuilderTypes from '../../artifacts/jsonbuilder.d.ts?inline';
|
||||
|
||||
const jsonModelUri = monaco.Uri.parse('a://b/divview.json');
|
||||
const tsModelUri = monaco.Uri.parse('file:///main.tsx');
|
||||
let monacoPromose: Promise<{
|
||||
monaco: typeof import('monaco-editor');
|
||||
jsonModelUri: monaco.Uri;
|
||||
tsModelUri: monaco.Uri;
|
||||
}> | undefined;
|
||||
export function loadMonaco() {
|
||||
if (monacoPromose) {
|
||||
return monacoPromose;
|
||||
}
|
||||
|
||||
const schemas = require.context('../../../../schema/', false, /\.json$/);
|
||||
let schema = schemas.keys().map((key: string) => {
|
||||
const filename = key.replace(/^\.\//, '');
|
||||
monacoPromose = import('monaco-editor').then(monaco => {
|
||||
const jsonModelUri = monaco.Uri.parse('a://b/divview.json');
|
||||
const tsModelUri = monaco.Uri.parse('file:///main.tsx');
|
||||
|
||||
return {
|
||||
uri: 'schema://div2/' + filename,
|
||||
fileMatch: [] as string [],
|
||||
schema: schemas(key)
|
||||
};
|
||||
});
|
||||
const schemas = require.context('../../../../schema/', false, /\.json$/);
|
||||
let schema = schemas.keys().map((key: string) => {
|
||||
const filename = key.replace(/^\.\//, '');
|
||||
|
||||
schema.push({
|
||||
uri: 'schema://div2/root.json',
|
||||
fileMatch: [jsonModelUri.toString()],
|
||||
schema: require('../schema/root.json')
|
||||
});
|
||||
return {
|
||||
uri: 'schema://div2/' + filename,
|
||||
fileMatch: [] as string [],
|
||||
schema: schemas(key)
|
||||
};
|
||||
});
|
||||
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
schemas: schema,
|
||||
allowComments: false
|
||||
});
|
||||
schema.push({
|
||||
uri: 'schema://div2/root.json',
|
||||
fileMatch: [jsonModelUri.toString()],
|
||||
schema: require('../schema/root.json')
|
||||
});
|
||||
|
||||
// validation settings
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false
|
||||
});
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
schemas: schema,
|
||||
allowComments: false
|
||||
});
|
||||
|
||||
// compiler options
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2015,
|
||||
allowNonTsExtensions: true
|
||||
});
|
||||
// validation settings
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
`declare module '@divkitframework/jsonbuilder' {${tsBuilderTypes}}`,
|
||||
'@types/divcard2/index.d.ts'
|
||||
);
|
||||
// compiler options
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2015,
|
||||
allowNonTsExtensions: true
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
`declare module '@divkitframework/jsonbuilder' {${tsBuilderTypes}}`,
|
||||
'@types/divcard2/index.d.ts'
|
||||
);
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl(_moduleId: string, label: string) {
|
||||
if (label === 'json') {
|
||||
return urlPath + '/json.worker.js';
|
||||
} else if (label === 'typescript') {
|
||||
return urlPath + '/typescript.worker.js';
|
||||
}
|
||||
return urlPath + '/editor.worker.js';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
monaco,
|
||||
jsonModelUri,
|
||||
tsModelUri,
|
||||
};
|
||||
});
|
||||
|
||||
return monacoPromose;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -52,7 +85,7 @@
|
||||
import { codeRunStore, valueStore } from '../data/valueStore';
|
||||
import PanelHeader from './PanelHeader.svelte';
|
||||
import Select from './Select.svelte';
|
||||
import { urlPath, serverHostPath } from '../utils/const';
|
||||
import { serverHostPath } from '../utils/const';
|
||||
import { LANGUAGE_CTX, LanguageContext } from '../data/languageContext';
|
||||
import { runCode as runCodeShortcut } from '../utils/shortcuts';
|
||||
import { jsonStore } from '../data/jsonStore';
|
||||
@@ -62,17 +95,6 @@
|
||||
import type { ShortcutList } from '../utils/useShortcuts';
|
||||
import { shortcuts } from '../utils/useShortcuts';
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl(_moduleId: string, label: string) {
|
||||
if (label === 'json') {
|
||||
return urlPath + '/json.worker.js';
|
||||
} else if (label === 'typescript') {
|
||||
return urlPath + '/typescript.worker.js';
|
||||
}
|
||||
return urlPath + '/editor.worker.js';
|
||||
}
|
||||
};
|
||||
|
||||
const {l10n} = getContext<LanguageContext>(LANGUAGE_CTX);
|
||||
|
||||
type Language = 'json' | 'ts';
|
||||
@@ -90,35 +112,6 @@
|
||||
let isRunning = false;
|
||||
let runButton: HTMLElement;
|
||||
|
||||
valueStore.subscribe(val => {
|
||||
if (editor && editor.getValue() !== val) {
|
||||
editor.setValue(val);
|
||||
}
|
||||
});
|
||||
|
||||
function getModel(type: 'ts' | 'json' | null): monaco.editor.ITextModel {
|
||||
if (type === 'json') {
|
||||
const model = monaco.editor.getModel(jsonModelUri) ||
|
||||
monaco.editor.createModel($valueStore, 'json', jsonModelUri);
|
||||
model.setValue($valueStore);
|
||||
return model;
|
||||
} else {
|
||||
const model = monaco.editor.getModel(tsModelUri) ||
|
||||
monaco.editor.createModel($valueStore, 'typescript', tsModelUri);
|
||||
model.setValue($valueStore);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
editorMode.subscribe(val => {
|
||||
if (val) {
|
||||
if (editor) {
|
||||
editor.setModel(getModel(val));
|
||||
}
|
||||
currentLanguage = val;
|
||||
}
|
||||
});
|
||||
|
||||
function onLanguageChange(): void {
|
||||
if (currentLanguage === $editorMode) {
|
||||
return;
|
||||
@@ -182,16 +175,52 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
onMount(async() => {
|
||||
const { monaco, jsonModelUri, tsModelUri } = await loadMonaco();
|
||||
|
||||
function getModel(type: 'ts' | 'json' | null): monaco.editor.ITextModel {
|
||||
if (type === 'json') {
|
||||
const model = monaco.editor.getModel(jsonModelUri) ||
|
||||
monaco.editor.createModel($valueStore, 'json', jsonModelUri);
|
||||
model.setValue($valueStore);
|
||||
return model;
|
||||
} else {
|
||||
const model = monaco.editor.getModel(tsModelUri) ||
|
||||
monaco.editor.createModel($valueStore, 'typescript', tsModelUri);
|
||||
model.setValue($valueStore);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
valueStore.subscribe(val => {
|
||||
if (editor && editor.getValue() !== val) {
|
||||
editor.setValue(val);
|
||||
}
|
||||
});
|
||||
|
||||
editorMode.subscribe(val => {
|
||||
if (val) {
|
||||
if (editor) {
|
||||
editor.setModel(getModel(val));
|
||||
}
|
||||
currentLanguage = val;
|
||||
}
|
||||
});
|
||||
|
||||
const opts: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
automaticLayout: true,
|
||||
wordWrap: 'on',
|
||||
wordWrap: 'off',
|
||||
scrollBeyondLastLine: false,
|
||||
model: getModel($editorMode)
|
||||
model: getModel($editorMode),
|
||||
bracketPairColorization: {
|
||||
enabled: true,
|
||||
independentColorPoolPerBracketType: true
|
||||
},
|
||||
smoothScrolling: true
|
||||
};
|
||||
|
||||
editor = monaco.editor.create(node, opts);
|
||||
|
||||
@@ -64,6 +64,11 @@
|
||||
{$l10n('samples')}
|
||||
</a>
|
||||
</li>
|
||||
<li class="header__subnav-item">
|
||||
<a class="header__subnav-link" href="/playground?design=1">
|
||||
{$l10n('design')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import SplitView from './SplitView.svelte';
|
||||
import Editor from './Editor.svelte';
|
||||
import Viewer from './Viewer.svelte';
|
||||
import { isSamples } from '../data/session';
|
||||
import { isDesign, isSamples } from '../data/session';
|
||||
import Samples from './Samples.svelte';
|
||||
import { Truthy } from '../utils/truthy';
|
||||
import Design from './Design.svelte';
|
||||
|
||||
$: components = [$isSamples && {
|
||||
component: Samples,
|
||||
@@ -20,7 +21,11 @@
|
||||
</script>
|
||||
|
||||
<main class="main">
|
||||
<SplitView {components} />
|
||||
{#if $isDesign}
|
||||
<Design />
|
||||
{:else}
|
||||
<SplitView {components} />
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
export let icon: string;
|
||||
export let selected: boolean;
|
||||
export let text: string;
|
||||
</script>
|
||||
|
||||
<button class="toolbar-item" class:toolbar-item_selected={selected} on:click>
|
||||
<div class="toolbar-item__border">
|
||||
<div class="toolbar-item__icon toolbar-item__icon_{icon}"></div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-item__tooltip">
|
||||
{text}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.toolbar-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 36px;
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.toolbar-item:not(.toolbar-item_selected) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar-item__border {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
transition: background-color .15s ease-in-out;
|
||||
}
|
||||
|
||||
.toolbar-item:not(.toolbar-item_selected):hover .toolbar-item__border {
|
||||
background-color: rgba(0, 0, 0, .12);
|
||||
}
|
||||
|
||||
.toolbar-item_selected .toolbar-item__border {
|
||||
background: #5959E8;
|
||||
}
|
||||
|
||||
.toolbar-item__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
filter: invert(100%);
|
||||
background: no-repeat 50% 50%;
|
||||
}
|
||||
|
||||
.toolbar-item_selected .toolbar-item__icon {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.toolbar-item__icon_components {
|
||||
background-image: url(../assets/divkitpro/components.svg);
|
||||
}
|
||||
|
||||
.toolbar-item__icon_palette {
|
||||
background-image: url(../assets/divkitpro/palette.svg);
|
||||
}
|
||||
|
||||
.toolbar-item__icon_sources {
|
||||
background-image: url(../assets/divkitpro/sources.svg);
|
||||
}
|
||||
|
||||
.toolbar-item__tooltip {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #000;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
filter: drop-shadow(0 1px 8px rgba(0, 0, 0, 0.14));
|
||||
pointer-events: none;
|
||||
transform: translateX(1rem);
|
||||
transition: .2s ease-in-out;
|
||||
transition-property: visibility, opacity, transform;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.toolbar-item:hover .toolbar-item__tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toolbar-item__tooltip::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 10px;
|
||||
left: -4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: inherit;
|
||||
transform: rotate(45deg);
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,130 @@
|
||||
export const DEFAULT_EDITOR_VALUE = `{
|
||||
"card": {
|
||||
"log_id": "div2_sample_card",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"items": [
|
||||
{
|
||||
"type": "_template_close",
|
||||
"alignment_horizontal": "right",
|
||||
"height": {
|
||||
"type": "fixed",
|
||||
"value": 28
|
||||
},
|
||||
"margins": {
|
||||
"top": 20,
|
||||
"right": 24
|
||||
},
|
||||
"width": {
|
||||
"type": "fixed",
|
||||
"value": 28
|
||||
}
|
||||
}
|
||||
],
|
||||
"background": [
|
||||
{
|
||||
"color": "#F1EBDC",
|
||||
"type": "solid"
|
||||
}
|
||||
],
|
||||
"height": {
|
||||
"type": "match_parent"
|
||||
},
|
||||
"orientation": "overlap",
|
||||
"type": "container"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"palette": {
|
||||
"light": [],
|
||||
"dark": []
|
||||
},
|
||||
"templates": {
|
||||
"_template_lottie": {
|
||||
"type": "gif",
|
||||
"scale": "fit",
|
||||
"extensions": [
|
||||
{
|
||||
"id": "lottie",
|
||||
"params": {
|
||||
"$lottie_url": "lottie_url"
|
||||
}
|
||||
}
|
||||
],
|
||||
"gif_url": "https://empty"
|
||||
},
|
||||
"_template_button": {
|
||||
"type": "text",
|
||||
"content_alignment_horizontal": "center",
|
||||
"border": {
|
||||
"$corner_radius": "corners"
|
||||
},
|
||||
"paddings": {
|
||||
"bottom": 24,
|
||||
"left": 28,
|
||||
"right": 28,
|
||||
"top": 22
|
||||
},
|
||||
"width": {
|
||||
"type": "wrap_content"
|
||||
}
|
||||
},
|
||||
"_template_close": {
|
||||
"accessibility": {
|
||||
"description": "Закрыть",
|
||||
"mode": "merge",
|
||||
"type": "button"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "close_popup",
|
||||
"url": "div-screen://close"
|
||||
}
|
||||
],
|
||||
"image_url": "https://yastatic.net/s3/home/div/div_fullscreens/cross2.3.png",
|
||||
"tint_color": "#73000000",
|
||||
"type": "image"
|
||||
},
|
||||
"_template_list_item": {
|
||||
"type": "container",
|
||||
"orientation": "horizontal",
|
||||
"items": [
|
||||
{
|
||||
"type": "image",
|
||||
"image_url": "https://yastatic.net/s3/home/div/div_fullscreens/hyphen.4.png",
|
||||
"$tint_color": "list_color",
|
||||
"width": {
|
||||
"type": "fixed",
|
||||
"value": 28,
|
||||
"unit": 28
|
||||
},
|
||||
"height": {
|
||||
"type": "fixed",
|
||||
"value": 28,
|
||||
"unit": 28
|
||||
},
|
||||
"margins": {
|
||||
"top": 2,
|
||||
"right": 12,
|
||||
"bottom": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"$text": "list_text",
|
||||
"$text_color": "list_color",
|
||||
"font_size": 24,
|
||||
"line_height": 32,
|
||||
"font_weight": "medium",
|
||||
"width": {
|
||||
"type": "wrap_content",
|
||||
"constrained": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`;
|
||||
@@ -0,0 +1,2 @@
|
||||
import '@yandex-portal/divkit-editor/dist/divkit-editor.css';
|
||||
export { DivProEditor } from '@yandex-portal/divkit-editor';
|
||||
@@ -4,7 +4,7 @@ import type lang from '../auto/lang.json';
|
||||
export const LANGUAGE_CTX = Symbol('language');
|
||||
|
||||
export interface LanguageContext {
|
||||
lang: Readable<string>;
|
||||
lang: Readable<'ru' | 'en'>;
|
||||
getLanguage(): string;
|
||||
setLanguage(name: string): void;
|
||||
languagesList(): string[];
|
||||
|
||||
@@ -10,3 +10,5 @@ export const isInitialLoading = writable(false);
|
||||
export const isLoadError = writable(false);
|
||||
|
||||
export const isSamples = writable(false);
|
||||
|
||||
export const isDesign = writable(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { initialValueStore, valueStore } from './valueStore';
|
||||
import { isInitialLoading, isLoadError, isSamples, session } from './session';
|
||||
import { isDesign, isInitialLoading, isLoadError, isSamples, session } from './session';
|
||||
import { debounce } from '../utils/debounce';
|
||||
import { clientHostPath, serverHostPath } from '../utils/const';
|
||||
import { savedStore } from './savedStore';
|
||||
@@ -10,6 +10,7 @@ import { jsonStore } from './jsonStore';
|
||||
// import { addListener, wsPromise } from './ws';
|
||||
// import { listenToDevices } from './externalViewers';
|
||||
import { getLs, setLs } from '../utils/localStorage';
|
||||
import { DEFAULT_EDITOR_VALUE } from './defaultEditorValue';
|
||||
|
||||
/* function listenJsonForPreview(uuid: string): void {
|
||||
wsPromise.then(ws => {
|
||||
@@ -35,9 +36,13 @@ async function init() {
|
||||
const uuid = params.get('uuid');
|
||||
// mode = params.get('mode') || '';
|
||||
|
||||
const design = params.get('design') === '1';
|
||||
const samples = params.get('samples') === '1';
|
||||
|
||||
if (samples) {
|
||||
if (design) {
|
||||
isDesign.set(true);
|
||||
valueStore.set(DEFAULT_EDITOR_VALUE);
|
||||
} else if (samples) {
|
||||
isSamples.set(true);
|
||||
}
|
||||
|
||||
@@ -134,8 +139,10 @@ function unsavedPrompt(event: BeforeUnloadEvent) {
|
||||
}
|
||||
|
||||
async function genLinks(uuid: string) {
|
||||
const isEditor = get(isDesign);
|
||||
|
||||
return {
|
||||
linkToEdit: `${location.protocol}//${clientHostPath}?uuid=${uuid}`,
|
||||
linkToEdit: `${location.protocol}//${clientHostPath}?uuid=${uuid}${isEditor ? '&design=1' : ''}`,
|
||||
linkToPreview: `${location.protocol}//${clientHostPath}?uuid=${uuid}&mode=preview`,
|
||||
linkToJSON: `${location.protocol}//${serverHostPath}api/json?uuid=${uuid}`
|
||||
};
|
||||
@@ -190,4 +197,4 @@ export async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
export const initPromise = init();
|
||||
|
||||
Reference in New Issue
Block a user