Skip to content

Commit 857cc77

Browse files
authored
Merge pull request #192 from JetBrains/ktl-1138-generate-link
feat; KTL-1138: add method to generate a link to the playground
2 parents f895dc6 + 6fb0016 commit 857cc77

File tree

18 files changed

+319
-154
lines changed

18 files changed

+319
-154
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "kotlin-playground",
3-
"version": "1.28.0",
3+
"version": "1.29.0-alpha.1",
44
"description": "Self-contained component to embed in websites for running Kotlin code",
55
"keywords": [
66
"kotlin",

playwright.config.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { env } from 'process';
22
import { config as dotenv } from 'dotenv';
33
import { defineConfig, devices } from '@playwright/test';
4+
import { isKeyOfObject } from './src/utils/types';
45

56
dotenv({ path: `.env.local`, override: true });
67

@@ -24,7 +25,7 @@ const isDevMode = Boolean(mode === 'DEV');
2425

2526
export default defineConfig({
2627
testDir: './tests',
27-
testMatch: /.*\.e2e\.tsx?$/,
28+
testMatch: /.*\.(e2e|test)\.tsx?$/,
2829
snapshotPathTemplate: `{testDir}/{testFileDir}/__screenshots__/${mode.toLowerCase()}/{projectName}/{testFilePath}-{arg}{ext}`,
2930

3031
timeout: 30000,
@@ -58,10 +59,3 @@ export default defineConfig({
5859
use: { ...devices[project] },
5960
})),
6061
});
61-
62-
export function isKeyOfObject<T extends object>(
63-
key: string | number | symbol,
64-
obj: T,
65-
): key is keyof T {
66-
return key in obj;
67-
}

src/config.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {getConfigFromElement, getCurrentScript} from './utils';
2-
import TargetPlatform from "./target-platform";
2+
import {TargetPlatforms} from "./utils/platforms";
33

44
const currentScript = getCurrentScript();
55

@@ -16,22 +16,22 @@ export const API_URLS = {
1616
let url;
1717

1818
switch (platform) {
19-
case TargetPlatform.JAVA:
19+
case TargetPlatforms.JAVA:
2020
url = `${this.server}/api/${version}/compiler/run`;
2121
break;
22-
case TargetPlatform.CANVAS:
22+
case TargetPlatforms.CANVAS:
2323
url = `${this.server}/api/${version}/compiler/translate`;
2424
break;
25-
case TargetPlatform.JS:
25+
case TargetPlatforms.JS:
2626
url = `${this.server}/api/${version}/compiler/translate`;
2727
break;
28-
case TargetPlatform.JS_IR:
28+
case TargetPlatforms.JS_IR:
2929
url = `${this.server}/api/${version}/compiler/translate?ir=true`;
3030
break;
31-
case TargetPlatform.WASM:
31+
case TargetPlatforms.WASM:
3232
url = `${this.server}/api/${version}/compiler/translate?ir=true&compiler=wasm`;
3333
break;
34-
case TargetPlatform.JUNIT:
34+
case TargetPlatforms.JUNIT:
3535
url = `${this.server}/api/${version}/compiler/test`;
3636
break;
3737
default:

src/executable-code/executable-fragment.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ import directives from 'monkberry-directives';
66
import 'monkberry-events';
77
import ExecutableCodeTemplate from './executable-fragment.monk';
88
import WebDemoApi from '../webdemo-api';
9-
import TargetPlatform from "../target-platform";
9+
import {TargetPlatforms, isJsRelated, isJavaRelated} from "../utils/platforms";
1010
import JsExecutor from "../js-executor"
11+
1112
import {
12-
countLines,
13-
escapeRegExp, MARK_PLACEHOLDER_CLOSE,
13+
escapeRegExp,
14+
MARK_PLACEHOLDER_CLOSE,
1415
MARK_PLACEHOLDER_OPEN,
1516
SAMPLE_END,
1617
SAMPLE_START,
17-
THEMES,
1818
unEscapeString
19-
} from "../utils";
19+
} from "../utils/escape";
20+
21+
import { countLines, THEMES } from "../utils";
2022
import debounce from 'debounce';
2123
import CompletionView from "../view/completion-view";
2224
import {processErrors} from "../view/output-view";
@@ -94,7 +96,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
9496
let sample;
9597
let hasMarkers = false;
9698
let platform = state.targetPlatform;
97-
if (state.compilerVersion && TargetPlatform.isJsRelated(platform)) {
99+
if (state.compilerVersion && isJsRelated(platform)) {
98100
this.jsExecutor = new JsExecutor(state.compilerVersion);
99101
}
100102

@@ -257,7 +259,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
257259
onConsoleCloseButtonEnter() {
258260
const {jsLibs, onCloseConsole, targetPlatform } = this.state;
259261
// creates a new iframe and removes the old one, thereby stops execution of any running script
260-
if (TargetPlatform.isJsRelated(targetPlatform))
262+
if (isJsRelated(targetPlatform))
261263
this.jsExecutor.reloadIframeScripts(jsLibs, this.getNodeForMountIframe(), targetPlatform);
262264
this.update({output: "", openConsole: false, exception: null});
263265
if (onCloseConsole) onCloseConsole();
@@ -291,7 +293,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
291293
});
292294
if (onOpenConsole) onOpenConsole(); //open when waitingForOutput=true
293295
if (onRun) onRun();
294-
if (TargetPlatform.isJavaRelated(targetPlatform)) {
296+
if (isJavaRelated(targetPlatform)) {
295297
WebDemoApi.executeKotlinCode(
296298
this.getCode(),
297299
compilerVersion,
@@ -349,7 +351,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
349351
state.output = "";
350352
if (onCloseConsole) onCloseConsole();
351353
}
352-
if (targetPlatform === TargetPlatform.CANVAS) {
354+
if (targetPlatform === TargetPlatforms.CANVAS) {
353355
if (onOpenConsole) onOpenConsole();
354356
state.openConsole = true;
355357
}

src/executable-code/index.js

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,12 @@ import 'codemirror/mode/swift/swift';
1818
import merge from 'deepmerge';
1919
import Set from 'es6-set/polyfill';
2020
import defaultConfig, {API_URLS} from '../config';
21-
import {
22-
arrayFrom,
23-
escapeRegExp,
24-
getConfigFromElement,
25-
insertAfter, MARK_PLACEHOLDER_CLOSE, MARK_PLACEHOLDER_OPEN,
26-
READ_ONLY_TAG,
27-
replaceWhiteSpaces, SAMPLE_END, SAMPLE_START,
28-
THEMES
29-
} from '../utils';
21+
import {arrayFrom, getConfigFromElement, insertAfter, READ_ONLY_TAG, replaceWhiteSpaces, THEMES} from '../utils';
3022
import WebDemoApi from "../webdemo-api";
31-
import TargetPlatform from '../target-platform'
3223
import ExecutableFragment from './executable-fragment';
3324
import { generateCrosslink } from '../lib/crosslink';
3425
import '../styles.scss';
26+
import {getTargetById, isJsRelated, TargetPlatforms} from "../utils/platforms";
3527

3628
const INITED_ATTRIBUTE_NAME = 'data-kotlin-playground-initialized';
3729
const DEFAULT_INDENT = 4;
@@ -94,7 +86,7 @@ export default class ExecutableCode {
9486
const args = targetNode.hasAttribute(ATTRIBUTES.ARGUMENTS) ? targetNode.getAttribute(ATTRIBUTES.ARGUMENTS) : "";
9587
const hiddenDependencies = this.getHiddenDependencies(targetNode);
9688
const outputHeight = targetNode.getAttribute(ATTRIBUTES.OUTPUT_HEIGHT) || null;
97-
const targetPlatform = TargetPlatform.getById(targetNode.getAttribute(ATTRIBUTES.PLATFORM));
89+
const targetPlatform = getTargetById(targetNode.getAttribute(ATTRIBUTES.PLATFORM));
9890
const targetNodeStyle = targetNode.getAttribute(ATTRIBUTES.STYLE);
9991
const jsLibs = this.getJsLibraries(targetNode, targetPlatform);
10092
const isFoldedButton = targetNode.getAttribute(ATTRIBUTES.FOLDED_BUTTON) !== "false";
@@ -130,13 +122,8 @@ export default class ExecutableCode {
130122
)
131123
);
132124

133-
if (!isCrosslinkDisabled) crosslink = generateCrosslink({
134-
code: code
135-
.replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_OPEN), 'g'), "")
136-
.replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_CLOSE), 'g'), "")
137-
.replace(new RegExp(escapeRegExp(SAMPLE_START), 'g'), "")
138-
.replace(new RegExp(escapeRegExp(SAMPLE_END), 'g'), ""),
139-
125+
if (!isCrosslinkDisabled) crosslink = generateCrosslink(code, {
126+
code: code,
140127
targetPlatform: targetPlatform.id,
141128
// hiddenDependencies, // multi-file support needs
142129
compilerVersion: cfg.compilerVersion,
@@ -209,8 +196,8 @@ export default class ExecutableCode {
209196
* @returns {Set} - set of additional libraries
210197
*/
211198
getJsLibraries(targetNode, platform) {
212-
if (TargetPlatform.isJsRelated(platform)) {
213-
if (platform === TargetPlatform.WASM) {
199+
if (isJsRelated(platform)) {
200+
if (platform === TargetPlatforms.WASM) {
214201
return new Set()
215202
}
216203
const jsLibs = targetNode.getAttribute(ATTRIBUTES.JS_LIBS);

src/js-executor/index.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import './index.scss'
22
import {API_URLS} from "../config";
3-
import TargetPlatform from "../target-platform";
43
import {showJsException} from "../view/output-view";
54
import {processingHtmlBrackets} from "../utils";
5+
import { TargetPlatforms } from "../utils/platforms";
66

77
const INIT_SCRIPT = "if(kotlin.BufferedOutput!==undefined){kotlin.out = new kotlin.BufferedOutput()}" +
88
"else{kotlin.kotlin.io.output = new kotlin.kotlin.io.BufferedOutput()}";
@@ -26,11 +26,11 @@ export default class JsExecutor {
2626
}
2727

2828
async executeJsCode(jsCode, wasm, jsLibs, platform, outputHeight, theme, onError) {
29-
if (platform === TargetPlatform.CANVAS) {
29+
if (platform === TargetPlatforms.CANVAS) {
3030
this.iframe.style.display = "block";
3131
if (outputHeight) this.iframe.style.height = `${outputHeight}px`;
3232
}
33-
if (platform === TargetPlatform.WASM) {
33+
if (platform === TargetPlatforms.WASM) {
3434
return await this.executeWasm(jsCode, wasm, theme, onError)
3535
}
3636
return await this.execute(jsCode, jsLibs, theme, onError, platform);
@@ -39,7 +39,7 @@ export default class JsExecutor {
3939
async execute(jsCode, jsLibs, theme, onError, platform) {
4040
const loadedScripts = (this.iframe.contentDocument || this.iframe.document).getElementsByTagName('script').length;
4141
let offset;
42-
if (platform === TargetPlatform.JS_IR) {
42+
if (platform === TargetPlatforms.JS_IR) {
4343
// 1 scripts by default: INIT_SCRIPT_IR
4444
offset = 1;
4545
} else {
@@ -106,15 +106,15 @@ export default class JsExecutor {
106106
node.appendChild(this.iframe);
107107
let iframeDoc = this.iframe.contentDocument || this.iframe.document;
108108
iframeDoc.open();
109-
if (targetPlatform === TargetPlatform.JS || targetPlatform === TargetPlatform.CANVAS) {
109+
if (targetPlatform === TargetPlatforms.JS || targetPlatform === TargetPlatforms.CANVAS) {
110110
const kotlinScript = API_URLS.KOTLIN_JS + `${normalizeJsVersion(this.kotlinVersion)}/kotlin.js`;
111111
iframeDoc.write("<script src='" + kotlinScript + "'></script>");
112112
}
113-
if (targetPlatform !== TargetPlatform.WASM) {
113+
if (targetPlatform !== TargetPlatforms.WASM) {
114114
for (let lib of jsLibs) {
115115
iframeDoc.write("<script src='" + lib + "'></script>");
116116
}
117-
if (targetPlatform === TargetPlatform.JS_IR) {
117+
if (targetPlatform === TargetPlatforms.JS_IR) {
118118
iframeDoc.write(`<script>${INIT_SCRIPT_IR}</script>`);
119119
} else {
120120
iframeDoc.write(`<script>${INIT_SCRIPT}</script>`);

src/lib/crosslink.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/lib/crosslink.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { compressToBase64 } from 'lz-string';
2+
3+
import { isKeyOfObject } from '../utils/types';
4+
import { TargetPlatforms, TargetPlatformsKeys } from '../utils/platforms';
5+
6+
import {
7+
escapeRegExp,
8+
MARK_PLACEHOLDER_CLOSE,
9+
MARK_PLACEHOLDER_OPEN,
10+
SAMPLE_END,
11+
SAMPLE_START,
12+
} from '../utils/escape';
13+
14+
type LinkOptions = {
15+
targetPlatform?: TargetPlatformsKeys | Lowercase<TargetPlatformsKeys>;
16+
compilerVersion?: string;
17+
};
18+
19+
/**
20+
* Assign the project to an employee.
21+
* @param {Object} code - The employee who is responsible for the project.
22+
* @param {Object} options - The employee who is responsible for the project.
23+
* @param {string} options.targetPlatform - The name of the employee.
24+
* @param {string} options.compilerVersion - The employee's department.
25+
*/
26+
export function generateCrosslink(code: string, options?: LinkOptions) {
27+
const opts: { code: string } & LinkOptions = {
28+
code: code
29+
.replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_OPEN), 'g'), '')
30+
.replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_CLOSE), 'g'), '')
31+
.replace(new RegExp(escapeRegExp(SAMPLE_START), 'g'), '')
32+
.replace(new RegExp(escapeRegExp(SAMPLE_END), 'g'), ''),
33+
};
34+
35+
if (options && options.targetPlatform) {
36+
const target =
37+
options.targetPlatform && options.targetPlatform.toUpperCase();
38+
39+
if (!isKeyOfObject(target, TargetPlatforms))
40+
throw new Error('Invalid target platform');
41+
42+
opts.targetPlatform = options.targetPlatform;
43+
}
44+
45+
if (options && options.compilerVersion)
46+
opts.compilerVersion = options.compilerVersion;
47+
48+
return `https://play.kotlinlang.org/editor/v1/${encodeURIComponent(
49+
compressToBase64(JSON.stringify(opts)),
50+
)}`;
51+
}

src/target-platform.js

Lines changed: 0 additions & 41 deletions
This file was deleted.

src/utils/escape.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const SAMPLE_START = '//sampleStart';
2+
export const SAMPLE_END = '//sampleEnd';
3+
4+
export const MARK_PLACEHOLDER_OPEN = "[mark]";
5+
export const MARK_PLACEHOLDER_CLOSE = "[/mark]";
6+
7+
8+
/**
9+
* Use instead of @escape-string-regexp
10+
*/
11+
12+
export /*#__PURE__*/ function escapeRegExp(str) {
13+
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
14+
}
15+
16+
/**
17+
* Unescape special characters from string
18+
* @param string
19+
* @returns {string}
20+
*/
21+
export /*#__PURE__*/ function unEscapeString(string) {
22+
const tagsToReplace = {
23+
"<": "&amp;lt;",
24+
">": "&amp;gt;",
25+
"&": "&amp;",
26+
" ": "%20"
27+
};
28+
let unEscapedString = string;
29+
Object.keys(tagsToReplace).forEach(function (key) {
30+
unEscapedString = unEscapedString.replace(new RegExp(tagsToReplace[key], 'g'), key)
31+
});
32+
return unEscapedString
33+
}

0 commit comments

Comments
 (0)