DivKitPro editor

This commit is contained in:
4eb0da
2023-12-01 12:35:12 +03:00
parent dd2b3de964
commit 5bf72f61f8
17 changed files with 686 additions and 91 deletions
+7
View File
@@ -14968,6 +14968,9 @@
"site/client/src/assets/closeWhite.svg":"divkit/public/site/client/src/assets/closeWhite.svg", "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/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/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/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/dropdown.svg":"divkit/public/site/client/src/assets/dropdown.svg",
"site/client/src/assets/errors.svg":"divkit/public/site/client/src/assets/errors.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/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/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/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/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/ErrorPage.svelte":"divkit/public/site/client/src/components/ErrorPage.svelte",
"site/client/src/components/ErrorView.svelte":"divkit/public/site/client/src/components/ErrorView.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/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/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/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/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/TreeLeaf.svelte":"divkit/public/site/client/src/components/TreeLeaf.svelte",
"site/client/src/components/Viewer.svelte":"divkit/public/site/client/src/components/Viewer.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/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/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/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/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/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/initialValue.ts":"divkit/public/site/client/src/data/initialValue.ts",
"site/client/src/data/jsonStore.ts":"divkit/public/site/client/src/data/jsonStore.ts", "site/client/src/data/jsonStore.ts":"divkit/public/site/client/src/data/jsonStore.ts",
+1
View File
@@ -1 +1,2 @@
/client/artifacts
/server/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

+10 -2
View File
@@ -4,6 +4,7 @@
"languageChooser": "Выбранный язык", "languageChooser": "Выбранный язык",
"playground": "Веб песочница", "playground": "Веб песочница",
"samples": "Примеры", "samples": "Примеры",
"design": "Визуальный редактор",
"share": "Поделиться", "share": "Поделиться",
"components": "Компоненты: ", "components": "Компоненты: ",
"timeToRender": "Время на отрисовку: ", "timeToRender": "Время на отрисовку: ",
@@ -32,13 +33,17 @@
"webSupportWarning": "Данный пример содержит функционал, который не поддержан в реализации для Веба. Он может отличаться от нативных платформ.", "webSupportWarning": "Данный пример содержит функционал, который не поддержан в реализации для Веба. Он может отличаться от нативных платформ.",
"collapse": "Свернуть", "collapse": "Свернуть",
"expand": "Развернуть", "expand": "Развернуть",
"selectComponent": "Выбрать компонент" "selectComponent": "Выбрать компонент",
"designComponents": "Компоненты",
"designPalette": "Палитра",
"designVariables": "Переменные"
}, },
"en": { "en": {
"name": "Eng", "name": "Eng",
"languageChooser": "Selected language", "languageChooser": "Selected language",
"playground": "Web playground", "playground": "Web playground",
"samples": "Samples", "samples": "Samples",
"design": "Visual editor",
"share": "Share", "share": "Share",
"components": "Components: ", "components": "Components: ",
"timeToRender": "Time to render: ", "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.", "webSupportWarning": "This sample contains functionality that is not supported in the implementation for the Web. It may differ from native platforms.",
"collapse": "Collapse", "collapse": "Collapse",
"expand": "Expand", "expand": "Expand",
"selectComponent": "Select component" "selectComponent": "Select component",
"designComponents": "Components",
"designPalette": "Palette",
"designVariables": "Variables"
} }
} }
+2 -2
View File
@@ -17,7 +17,7 @@
if (langVal !== 'ru' && langVal !== 'en') { if (langVal !== 'ru' && langVal !== 'en') {
langVal = 'en'; langVal = 'en';
} }
let lang = writable(langVal); let lang = writable<'ru' | 'en'>(langVal as 'ru' | 'en');
const l10n = derived(lang, lang => { const l10n = derived(lang, lang => {
return (key: keyof typeof langObj['en'], overrideLang?: string) => return (key: keyof typeof langObj['en'], overrideLang?: string) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -36,7 +36,7 @@
getLanguage(): string { getLanguage(): string {
return get(lang); return get(lang);
}, },
setLanguage(name: string): void { setLanguage(name: 'ru' | 'en'): void {
lang.set(name); lang.set(name);
}, },
l10n, l10n,
+284
View File
@@ -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>
+109 -80
View File
@@ -1,49 +1,82 @@
<script lang="ts" context="module"> <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'; import tsBuilderTypes from '../../artifacts/jsonbuilder.d.ts?inline';
const jsonModelUri = monaco.Uri.parse('a://b/divview.json'); let monacoPromose: Promise<{
const tsModelUri = monaco.Uri.parse('file:///main.tsx'); 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$/); monacoPromose = import('monaco-editor').then(monaco => {
let schema = schemas.keys().map((key: string) => { const jsonModelUri = monaco.Uri.parse('a://b/divview.json');
const filename = key.replace(/^\.\//, ''); const tsModelUri = monaco.Uri.parse('file:///main.tsx');
return { const schemas = require.context('../../../../schema/', false, /\.json$/);
uri: 'schema://div2/' + filename, let schema = schemas.keys().map((key: string) => {
fileMatch: [] as string [], const filename = key.replace(/^\.\//, '');
schema: schemas(key)
};
});
schema.push({ return {
uri: 'schema://div2/root.json', uri: 'schema://div2/' + filename,
fileMatch: [jsonModelUri.toString()], fileMatch: [] as string [],
schema: require('../schema/root.json') schema: schemas(key)
}); };
});
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ schema.push({
validate: true, uri: 'schema://div2/root.json',
schemas: schema, fileMatch: [jsonModelUri.toString()],
allowComments: false schema: require('../schema/root.json')
}); });
// validation settings monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ validate: true,
noSemanticValidation: true, schemas: schema,
noSyntaxValidation: false allowComments: false
}); });
// compiler options // validation settings
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
target: monaco.languages.typescript.ScriptTarget.ES2015, noSemanticValidation: true,
allowNonTsExtensions: true noSyntaxValidation: false
}); });
monaco.languages.typescript.typescriptDefaults.addExtraLib( // compiler options
`declare module '@divkitframework/jsonbuilder' {${tsBuilderTypes}}`, monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
'@types/divcard2/index.d.ts' 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>
<script lang="ts"> <script lang="ts">
@@ -52,7 +85,7 @@
import { codeRunStore, valueStore } from '../data/valueStore'; import { codeRunStore, valueStore } from '../data/valueStore';
import PanelHeader from './PanelHeader.svelte'; import PanelHeader from './PanelHeader.svelte';
import Select from './Select.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 { LANGUAGE_CTX, LanguageContext } from '../data/languageContext';
import { runCode as runCodeShortcut } from '../utils/shortcuts'; import { runCode as runCodeShortcut } from '../utils/shortcuts';
import { jsonStore } from '../data/jsonStore'; import { jsonStore } from '../data/jsonStore';
@@ -62,17 +95,6 @@
import type { ShortcutList } from '../utils/useShortcuts'; import type { ShortcutList } from '../utils/useShortcuts';
import { shortcuts } 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); const {l10n} = getContext<LanguageContext>(LANGUAGE_CTX);
type Language = 'json' | 'ts'; type Language = 'json' | 'ts';
@@ -90,35 +112,6 @@
let isRunning = false; let isRunning = false;
let runButton: HTMLElement; 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 { function onLanguageChange(): void {
if (currentLanguage === $editorMode) { if (currentLanguage === $editorMode) {
return; 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 = { const opts: monaco.editor.IStandaloneEditorConstructionOptions = {
theme: 'vs', theme: 'vs',
minimap: { minimap: {
enabled: false enabled: false
}, },
automaticLayout: true, automaticLayout: true,
wordWrap: 'on', wordWrap: 'off',
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
model: getModel($editorMode) model: getModel($editorMode),
bracketPairColorization: {
enabled: true,
independentColorPoolPerBracketType: true
},
smoothScrolling: true
}; };
editor = monaco.editor.create(node, opts); editor = monaco.editor.create(node, opts);
+5
View File
@@ -64,6 +64,11 @@
{$l10n('samples')} {$l10n('samples')}
</a> </a>
</li> </li>
<li class="header__subnav-item">
<a class="header__subnav-link" href="/playground?design=1">
{$l10n('design')}
</a>
</li>
</ul> </ul>
</div> </div>
+7 -2
View File
@@ -2,9 +2,10 @@
import SplitView from './SplitView.svelte'; import SplitView from './SplitView.svelte';
import Editor from './Editor.svelte'; import Editor from './Editor.svelte';
import Viewer from './Viewer.svelte'; import Viewer from './Viewer.svelte';
import { isSamples } from '../data/session'; import { isDesign, isSamples } from '../data/session';
import Samples from './Samples.svelte'; import Samples from './Samples.svelte';
import { Truthy } from '../utils/truthy'; import { Truthy } from '../utils/truthy';
import Design from './Design.svelte';
$: components = [$isSamples && { $: components = [$isSamples && {
component: Samples, component: Samples,
@@ -20,7 +21,11 @@
</script> </script>
<main class="main"> <main class="main">
<SplitView {components} /> {#if $isDesign}
<Design />
{:else}
<SplitView {components} />
{/if}
</main> </main>
<style> <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>
+130
View File
@@ -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
}
}
]
}
}
}`;
+2
View File
@@ -0,0 +1,2 @@
import '@yandex-portal/divkit-editor/dist/divkit-editor.css';
export { DivProEditor } from '@yandex-portal/divkit-editor';
+1 -1
View File
@@ -4,7 +4,7 @@ import type lang from '../auto/lang.json';
export const LANGUAGE_CTX = Symbol('language'); export const LANGUAGE_CTX = Symbol('language');
export interface LanguageContext { export interface LanguageContext {
lang: Readable<string>; lang: Readable<'ru' | 'en'>;
getLanguage(): string; getLanguage(): string;
setLanguage(name: string): void; setLanguage(name: string): void;
languagesList(): string[]; languagesList(): string[];
+2
View File
@@ -10,3 +10,5 @@ export const isInitialLoading = writable(false);
export const isLoadError = writable(false); export const isLoadError = writable(false);
export const isSamples = writable(false); export const isSamples = writable(false);
export const isDesign = writable(false);
+11 -4
View File
@@ -1,6 +1,6 @@
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { initialValueStore, valueStore } from './valueStore'; 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 { debounce } from '../utils/debounce';
import { clientHostPath, serverHostPath } from '../utils/const'; import { clientHostPath, serverHostPath } from '../utils/const';
import { savedStore } from './savedStore'; import { savedStore } from './savedStore';
@@ -10,6 +10,7 @@ import { jsonStore } from './jsonStore';
// import { addListener, wsPromise } from './ws'; // import { addListener, wsPromise } from './ws';
// import { listenToDevices } from './externalViewers'; // import { listenToDevices } from './externalViewers';
import { getLs, setLs } from '../utils/localStorage'; import { getLs, setLs } from '../utils/localStorage';
import { DEFAULT_EDITOR_VALUE } from './defaultEditorValue';
/* function listenJsonForPreview(uuid: string): void { /* function listenJsonForPreview(uuid: string): void {
wsPromise.then(ws => { wsPromise.then(ws => {
@@ -35,9 +36,13 @@ async function init() {
const uuid = params.get('uuid'); const uuid = params.get('uuid');
// mode = params.get('mode') || ''; // mode = params.get('mode') || '';
const design = params.get('design') === '1';
const samples = params.get('samples') === '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); isSamples.set(true);
} }
@@ -134,8 +139,10 @@ function unsavedPrompt(event: BeforeUnloadEvent) {
} }
async function genLinks(uuid: string) { async function genLinks(uuid: string) {
const isEditor = get(isDesign);
return { return {
linkToEdit: `${location.protocol}//${clientHostPath}?uuid=${uuid}`, linkToEdit: `${location.protocol}//${clientHostPath}?uuid=${uuid}${isEditor ? '&design=1' : ''}`,
linkToPreview: `${location.protocol}//${clientHostPath}?uuid=${uuid}&mode=preview`, linkToPreview: `${location.protocol}//${clientHostPath}?uuid=${uuid}&mode=preview`,
linkToJSON: `${location.protocol}//${serverHostPath}api/json?uuid=${uuid}` linkToJSON: `${location.protocol}//${serverHostPath}api/json?uuid=${uuid}`
}; };
@@ -190,4 +197,4 @@ export async function save() {
} }
} }
init(); export const initPromise = init();