Skip to content

Add offline mode #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ playground('.selector', options)

```

**Options description:**

- `server` — string, server where the code will be sent to be compiled and analyzed
- `offline` — boolean, if true don't send any requests to server

**Events description:**

- `onChange(code)` — Fires every time the content of the editor is changed. Debounce time: 0.5s.
Expand Down
34 changes: 33 additions & 1 deletion examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,39 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
```
Add additional hidden files:

## Offline mode

You can opt out of any server communication (code completion, execution, etc) by passing an `offline` flag:

```html
<script>
document.addEventListener('DOMContentLoaded', function() {
KotlinPlayground('.kotlin-playground', {offline: true});
});
</script>
```

<script>
document.addEventListener('DOMContentLoaded', function() {
KotlinPlayground('.kotlin-code-offline', {offline: true});
});
</script>
<div class="kotlin-code-offline">

```kotlin
class Contact(val id: Int, var email: String)

fun main(args: Array<String>) {
val contact = Contact(1, "mary@gmail.com")
println(contact.id)
}
```

</div>


## Add additional hidden files:
Put your files between `<textarea>` tag with class `hidden-dependency`.

Look at example:
Expand Down
132 changes: 70 additions & 62 deletions src/executable-code/executable-fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
'directives': directives
});

instance.offline = options.offline;
instance.arrayClasses = [];
instance.initialized = false;
instance.state = {
Expand All @@ -60,20 +61,21 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
instance.update({folded: !instance.state.folded});
});

instance.on('keyup', (event) => {
if (window.navigator.appVersion.indexOf("Mac") !== -1) {
if (event.keyCode === KEY_CODES.R && event.ctrlKey) {
instance.execute();
}
} else {
if (event.keyCode === KEY_CODES.F9 && event.ctrlKey) {
instance.execute();
if (!options.offline) {
instance.on('keyup', (event) => {
if (window.navigator.appVersion.indexOf("Mac") !== -1) {
if (event.keyCode === KEY_CODES.R && event.ctrlKey) {
instance.execute();
}
} else {
if (event.keyCode === KEY_CODES.F9 && event.ctrlKey) {
instance.execute();
}
}
}
});
});
}

const events = options.eventFunctions;
if (events && events.getInstance) events.getInstance(instance);
if (options.getInstance) options.getInstance(instance);

return instance;
}
Expand Down Expand Up @@ -241,6 +243,10 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
}

execute() {
if (this.offline) {
throw new Error('Can\'t execude code in offline mode');
}

const {
onOpenConsole, targetPlatform, waitingForOutput, compilerVersion, onRun, onError,
args, theme, hiddenDependencies, onTestPassed, onTestFailed, onCloseConsole, jsLibs, outputHeight, getJsCode
Expand Down Expand Up @@ -418,58 +424,60 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
codemirrorOptions.cursorBlinkRate = -1;
}

/**
* Register own helper for autocomplete.
* Getting completions from try.kotlinlang.org.
* CodeMirror.hint.default => getting list from codemirror kotlin keywords.
*
* {@see WebDemoApi} - getting data from WebDemo
* {@see CompletionView} - implementation completion view
*/
CodeMirror.registerHelper('hint', 'kotlin', (mirror, callback) => {
let cur = mirror.getCursor();
let token = mirror.getTokenAt(cur);
let code = this.state.folded
? this.prefix + mirror.getValue() + this.suffix
: mirror.getValue();
let currentCursor = this.state.folded
? {line: cur.line + this.prefix.split('\n').length - 1, ch: cur.ch}
: cur;
WebDemoApi.getAutoCompletion(
code,
currentCursor,
this.state.compilerVersion,
this.state.targetPlatform,
this.state.hiddenDependencies,
processingCompletionsList
);

function processingCompletionsList(results) {
const anchorCharPosition = mirror.findWordAt({line: cur.line, ch: cur.ch}).anchor.ch;
const headCharPosition = mirror.findWordAt({line: cur.line, ch: cur.ch}).head.ch;
const currentSymbol = mirror.getRange({line: cur.line, ch: anchorCharPosition}, {
line: cur.line,
ch: headCharPosition
});
if (results.length === 0 && /^[a-zA-Z]+$/.test(currentSymbol)) {
CodeMirror.showHint(mirror, CodeMirror.hint.default, {completeSingle: false});
} else {
callback({
list: results.map(result => {
return new CompletionView(result)
}),
from: {line: cur.line, ch: token.start},
to: {line: cur.line, ch: token.end}
})
if (!this.offline) {
/**
* Register own helper for autocomplete.
* Getting completions from try.kotlinlang.org.
* CodeMirror.hint.default => getting list from codemirror kotlin keywords.
*
* {@see WebDemoApi} - getting data from WebDemo
* {@see CompletionView} - implementation completion view
*/
CodeMirror.registerHelper('hint', 'kotlin', (mirror, callback) => {
let cur = mirror.getCursor();
let token = mirror.getTokenAt(cur);
let code = this.state.folded
? this.prefix + mirror.getValue() + this.suffix
: mirror.getValue();
let currentCursor = this.state.folded
? {line: cur.line + this.prefix.split('\n').length - 1, ch: cur.ch}
: cur;
WebDemoApi.getAutoCompletion(
code,
currentCursor,
this.state.compilerVersion,
this.state.targetPlatform,
this.state.hiddenDependencies,
processingCompletionsList
);

function processingCompletionsList(results) {
const anchorCharPosition = mirror.findWordAt({line: cur.line, ch: cur.ch}).anchor.ch;
const headCharPosition = mirror.findWordAt({line: cur.line, ch: cur.ch}).head.ch;
const currentSymbol = mirror.getRange({line: cur.line, ch: anchorCharPosition}, {
line: cur.line,
ch: headCharPosition
});
if (results.length === 0 && /^[a-zA-Z]+$/.test(currentSymbol)) {
CodeMirror.showHint(mirror, CodeMirror.hint.default, {completeSingle: false});
} else {
callback({
list: results.map(result => {
return new CompletionView(result)
}),
from: {line: cur.line, ch: token.start},
to: {line: cur.line, ch: token.end}
})
}
}
}
});
});

CodeMirror.hint.kotlin.async = true;
CodeMirror.hint.kotlin.async = true;

CodeMirror.commands.autocomplete = (cm) => {
CodeMirror.showHint(cm, CodeMirror.hint.kotlin);
};
CodeMirror.commands.autocomplete = (cm) => {
CodeMirror.showHint(cm, CodeMirror.hint.kotlin);
};
}

this.codemirror = CodeMirror.fromTextArea(textarea, codemirrorOptions);

Expand Down Expand Up @@ -502,7 +510,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
const {onChange, onFlyHighLight, compilerVersion, targetPlatform, hiddenDependencies} = this.state;
if (onChange) onChange(cm.getValue());
this.removeStyles();
if (onFlyHighLight) {
if (onFlyHighLight && !this.offline) {
WebDemoApi.getHighlight(
this.getCode(),
compilerVersion,
Expand Down
4 changes: 2 additions & 2 deletions src/executable-code/executable-fragment.monk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<div class="executable-fragment-wrapper">
<div class="executable-fragment {{ theme }}">
{% if (!highlightOnly) %}
{% if (!highlightOnly && !this.offline) %}
<div class="run-button {{ waitingForOutput ? '_disabled' : ''}}" :onclick={{ this.execute.bind(this) }}></div>
{% endif %}
<div class="code-area {{ folded ? '_folded' : '_unfolded' }}">
Expand Down Expand Up @@ -42,7 +42,7 @@
</div>
</div>

{% if (!highlightOnly) %}
{% if (!highlightOnly && !this.offline) %}
<div class="compiler-info">
<span>Target platform: {{ targetPlatform.printableName }}</span>
<span>Running on kotlin v. {{ compilerVersion }}</span>
Expand Down
95 changes: 51 additions & 44 deletions src/executable-code/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ export default class ExecutableCode {
/**
* @param {string|HTMLElement} target
* @param {{compilerVersion: *}} [config]
* @param {Object} eventFunctions
* @param {Object} options
*/
constructor(target, config = {}, eventFunctions) {
constructor(target, config = {}, options) {
const targetNode = typeof target === 'string' ? document.querySelector(target) : target;
let highlightOnly = config.highlightOnly ? true
: targetNode.getAttribute(ATTRIBUTES.HIGHLIGHT_ONLY) === READ_ONLY_TAG
Expand Down Expand Up @@ -104,7 +104,7 @@ export default class ExecutableCode {
const mountNode = document.createElement('div');
insertAfter(mountNode, targetNode);

const view = ExecutableFragment.render(mountNode, {eventFunctions});
const view = ExecutableFragment.render(mountNode, options);
view.update(Object.assign({
code: code,
lines: lines,
Expand All @@ -126,16 +126,17 @@ export default class ExecutableCode {
jsLibs: jsLibs,
isFoldedButton: isFoldedButton,
outputHeight
}, eventFunctions));
}, options));

this.offline = options && options.offline;
this.config = cfg;
this.node = mountNode;
this.targetNode = targetNode;
this.targetNodeStyle = targetNodeStyle;
this.view = view;

targetNode.KotlinPlayground = this;
if (eventFunctions && eventFunctions.callback) eventFunctions.callback(targetNode, mountNode);
if (options && options.callback) options.callback(targetNode, mountNode);
}

/**
Expand Down Expand Up @@ -235,10 +236,10 @@ export default class ExecutableCode {

/**
* @param {string|Node|NodeList} target
* @param {Function} eventFunctions
* @param {Function} options
* @return {Promise<Array<ExecutableCode>>}
*/
static create(target, eventFunctions) {
static async create(target, options) {
let targetNodes;

if (typeof target === 'string') {
Expand All @@ -251,53 +252,59 @@ export default class ExecutableCode {

// Return empty array if there is no nodes attach to
if (targetNodes.length === 0) {
return Promise.resolve([]);
return [];
}

return WebDemoApi.getCompilerVersions().then((versions) => {
const instances = [];
let versions = null;
if (!options.offline) {
versions = await WebDemoApi.getCompilerVersions();
if (versions == null) {
console.error('Can\'t get kotlin version from server');
options.offline = true
}
}

targetNodes.forEach((node) => {
const config = getConfigFromElement(node, true);
const minCompilerVersion = config.minCompilerVersion;
let latestStableVersion = null;
let compilerVersion = null;
const instances = [];

// Skip empty and already initialized nodes
if (
node.textContent.trim() === '' ||
node.getAttribute(INITED_ATTRIBUTE_NAME) === 'true'
) {
return;
}
targetNodes.forEach((node) => {
const config = getConfigFromElement(node, true);
const minCompilerVersion = config.minCompilerVersion;
let latestStableVersion = null;
let compilerVersion = null;

if (versions) {
let listOfVersions = versions.map(version => version.version);
// Skip empty and already initialized nodes
if (
node.textContent.trim() === '' ||
node.getAttribute(INITED_ATTRIBUTE_NAME) === 'true'
) {
return;
}

if (listOfVersions.includes(config.version)) {
compilerVersion = config.version;
} else {
versions.forEach((compilerConfig) => {
if (compilerConfig.latestStable) {
latestStableVersion = compilerConfig.version;
}
});
compilerVersion = latestStableVersion;
}
if (!options.offline && versions) {
let listOfVersions = versions.map(version => version.version);

if (minCompilerVersion) {
compilerVersion = minCompilerVersion > latestStableVersion
? versions[versions.length - 1].version
: latestStableVersion;
}
instances.push(new ExecutableCode(node, {compilerVersion}, eventFunctions));
if (listOfVersions.includes(config.version)) {
compilerVersion = config.version;
} else {
console.error('Cann\'t get kotlin version from server');
instances.push(new ExecutableCode(node, {highlightOnly: true}));
versions.forEach((compilerConfig) => {
if (compilerConfig.latestStable) {
latestStableVersion = compilerConfig.version;
}
});
compilerVersion = latestStableVersion;
}
});

return instances;
if (minCompilerVersion) {
compilerVersion = minCompilerVersion > latestStableVersion
? versions[versions.length - 1].version
: latestStableVersion;
}
instances.push(new ExecutableCode(node, {compilerVersion}, options));
} else {
instances.push(new ExecutableCode(node, {}, options));
}
});

return instances;
}
}
Loading