Skip to content

Commit

Permalink
#231 added frontend for displaying inline-images
Browse files Browse the repository at this point in the history
  • Loading branch information
bugy committed Sep 2, 2019
1 parent f0f5fd7 commit 80a8fd5
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 24 deletions.
25 changes: 13 additions & 12 deletions src/features/file_download_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,16 @@ def prepare_images(output):
images = script_handler._prepare_downloadable_files(
image_paths,
script_handler.config,
output)
output,
should_exist=False)

for key, value in images.items():
script_handler._add_inline_image(key, value)

self.output_stream.subscribe(InlineImageListener())

def _prepare_downloadable_files(self, output_files, config, script_output, *, should_exist=True):
correct_files = []
found_files = {}

for output_file in output_files:
files = find_matching_files(output_file, script_output)
Expand All @@ -185,32 +186,32 @@ def _prepare_downloadable_files(self, output_files, config, script_output, *, sh
LOGGER.warning('file ' + file + ' (full path = ' + file_path + ') not found')
elif os.path.isdir(file_path):
LOGGER.warning('file ' + file + ' is a directory. Not allowed')
elif file_path not in correct_files:
correct_files.append(file_path)
elif file_path not in found_files:
found_files[file] = file_path
elif should_exist:
LOGGER.warning("Couldn't find file for " + output_file)

if not correct_files:
if not found_files:
return {}

result = {}
for file in correct_files:
if file in self.prepared_files:
result[file] = self.prepared_files[file]
for original_file_path, normalized_path in found_files.items():
if original_file_path in self.prepared_files:
result[original_file_path] = self.prepared_files[original_file_path]
continue

preferred_download_file = os.path.join(self.download_folder, os.path.basename(file))
preferred_download_file = os.path.join(self.download_folder, os.path.basename(normalized_path))

try:
download_file = create_unique_filename(preferred_download_file)
except file_utils.FileExistsException:
LOGGER.exception('Cannot get unique name')
continue

copyfile(file, download_file)
copyfile(normalized_path, download_file)

result[file] = download_file
self.prepared_files[file] = download_file
result[original_file_path] = download_file
self.prepared_files[original_file_path] = download_file

return result

Expand Down
15 changes: 14 additions & 1 deletion src/tests/file_download_feature_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,19 @@ def test_single_dynamic_image(self):

self.assert_images(full_path)

def test_single_dynamic_image_when_unnormalized(self):
test_utils.create_file('sub/test.png')
config = create_config_model('my_script', output_files=[inline_image('#([\.\w]+/)+\w+.png#')])

execution_id = self.start_execution(config)

unnormalized_path = os.path.join(test_utils.temp_folder, '.', 'sub', '..', 'sub', 'test.png')
self.write_output(execution_id, '_ ' + unnormalized_path + ' _\n')
self.wait_output_chunks(execution_id, chunks_count=1)

image_keys = [img[0] for img in self.images]
self.assertEqual([unnormalized_path], image_keys)

def test_mixed_images_when_multiple_output(self):
path1 = test_utils.create_file('test123.png')
path2 = test_utils.create_file('images/test.png')
Expand Down Expand Up @@ -551,7 +564,7 @@ def start_execution(self, config):

def assert_images(self, *paths):
normalized_paths = [file_utils.normalize_path(p) for p in paths]
actual_paths = [image[0] for image in self.images]
actual_paths = [file_utils.normalize_path(image[0]) for image in self.images]

self.assertCountEqual(normalized_paths, actual_paths)

Expand Down
20 changes: 15 additions & 5 deletions src/web/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ def open(self, execution_id):
output_stream = execution_service.get_raw_output_stream(execution_id, user_id)
pipe_output_to_http(output_stream, self.safe_write)

downloads_folder = self.application.downloads_folder
file_download_feature = self.application.file_download_feature
web_socket = self

Expand All @@ -321,10 +320,7 @@ def finished():

for file in downloadable_files:
filename = os.path.basename(file)
relative_path = file_utils.relative_path(file, downloads_folder)

url_path = relative_path.replace(os.path.sep, '/')
url_path = 'result_files/' + url_path
url_path = web_socket.prepare_download_url(file)

web_socket.safe_write(wrap_to_server_event(
'file',
Expand All @@ -340,6 +336,8 @@ def finished():
output_stream.wait_close(timeout=5)
web_socket.ioloop.add_callback(web_socket.close, code=1000)

file_download_feature.subscribe_on_inline_images(execution_id, self.send_inline_image)

execution_service.add_finish_listener(finished, execution_id)

def on_message(self, text):
Expand All @@ -353,6 +351,18 @@ def safe_write(self, message):
if self.ws_connection is not None:
self.ioloop.add_callback(self.write_message, message)

def send_inline_image(self, original_path, download_path):
self.safe_write(wrap_to_server_event(
'inline-image',
{'output_path': original_path, 'download_url': self.prepare_download_url(download_path)}))

def prepare_download_url(self, file):
downloads_folder = self.application.downloads_folder
relative_path = file_utils.relative_path(file, downloads_folder)
url_path = relative_path.replace(os.path.sep, '/')
url_path = 'result_files/' + url_path
return url_path


@tornado.web.stream_request_body
class ScriptExecute(BaseRequestHandler):
Expand Down
12 changes: 12 additions & 0 deletions web-src/js/components/log_panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@
this.terminalModel.write(text);
this.revalidateScroll();
},
removeInlineImage: function (output_path) {
this.terminalModel.removeInlineImage(output_path);
},
setInlineImage: function (output_path, download_url) {
this.terminalModel.setInlineImage(output_path, download_url);
}
},
Expand Down Expand Up @@ -162,4 +170,8 @@
-moz-box-shadow: 0 -7px 8px -4px #888888 inset;
}
.log-panel >>> .log-content img {
max-width: 100%
}
</style>
31 changes: 31 additions & 0 deletions web-src/js/components/terminal/terminal_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class TerminalModel {

clear() {
this.lines = [];
this.inlineImages = {};
this.lineStyles = new Map();

this.currentLine = 0;
Expand Down Expand Up @@ -358,6 +359,36 @@ export class TerminalModel {
}
}
}

removeInlineImage(output_path) {
if (this.inlineImages.hasOwnProperty(output_path)) {
delete this.inlineImages[output_path];

this._notify_inline_image_change(output_path);
}
}

setInlineImage(output_path, download_url) {
this.inlineImages[output_path] = download_url;

this._notify_inline_image_change(output_path)
}

_notify_inline_image_change(output_path) {
const changedLines = [];
for (let i = 0; i < this.lines.length; i++) {
const line = this.lines[i];
if (line.includes(output_path)) {
changedLines.push(i);
}
}

if (changedLines.length > 0) {
for (const listener of this.listeners) {
listener.linesChanges(changedLines);
}
}
}
}

export class Style {
Expand Down
26 changes: 22 additions & 4 deletions web-src/js/components/terminal/terminal_view.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {destroyChildren, isNull} from '../../common';
import {destroyChildren, forEachKeyValue, isNull} from '../../common';

const lineElementTemplate = document.createElement('div');

Expand Down Expand Up @@ -150,10 +150,17 @@ export class Terminal {
}

const lineText = modelLines[lineIndex];
const styleRanges = terminalModel.getStyle(lineIndex);
const imageUrl = this.findImage(lineText);
if (isNull(imageUrl)) {
const styleRanges = terminalModel.getStyle(lineIndex);

const children = Terminal.createLogLineChildren(lineText, styleRanges);
children.forEach(child => lineElement.appendChild(child));
const children = Terminal.createLogLineChildren(lineText, styleRanges);
children.forEach(child => lineElement.appendChild(child));
} else {
const img = document.createElement('img');
img.src = imageUrl;
lineElement.appendChild(img);
}
}

for (const child of newLinesFragment.children) {
Expand Down Expand Up @@ -183,4 +190,15 @@ export class Terminal {
this.element.removeChild(childNode);
}
}

findImage(lineText) {
let result = null;
forEachKeyValue(this.terminalModel.inlineImages, (path, url) => {
if (lineText.includes(path)) {
result = url;
}
});

return result;
}
}
35 changes: 33 additions & 2 deletions web-src/js/main-app/script-view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import marked from 'marked';
import {mapActions, mapState} from 'vuex'
import {forEachKeyValue, isEmptyObject, isEmptyString, isNull} from '../common';
import {deepCloneObject, forEachKeyValue, isEmptyObject, isEmptyString, isNull} from '../common';
import LogPanel from '../components/log_panel'
import {
STATUS_DISCONNECTED,
Expand All @@ -67,7 +67,8 @@
id: null,
everStarted: false,
errors: [],
nextLogIndex: 0
nextLogIndex: 0,
lastInlineImages: {}
}
},
Expand Down Expand Up @@ -167,6 +168,14 @@
return this.currentExecutor.state.downloadableFiles;
},
inlineImages() {
if (!this.currentExecutor) {
return {};
}
return this.currentExecutor.state.inlineImages;
},
inputPromptText() {
if (this.status !== STATUS_EXECUTING) {
return null;
Expand Down Expand Up @@ -290,6 +299,28 @@
this.appendLog(logChunk);
}
}
},
inlineImages: {
handler(newValue, oldValue) {
const logPanel = this.$refs.logPanel;
forEachKeyValue(this.lastInlineImages, (key, value) => {
if (!newValue.hasOwnProperty(key)) {
logPanel.removeInlineImage(key);
} else if (value !== newValue[key]) {
logPanel.setInlineImage(key, value);
}
});
forEachKeyValue(newValue, (key, value) => {
if (!this.lastInlineImages.hasOwnProperty(key)) {
logPanel.setInlineImage(key, value);
}
});
this.lastInlineImages = deepCloneObject(newValue);
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions web-src/js/main-app/store/scriptExecutor.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';
import Vue from 'vue';
import {getWebsocketUrl, isNull, isWebsocketClosed} from '../../common';

export const STATUS_INITIALIZING = 'initializing';
Expand Down Expand Up @@ -31,6 +32,7 @@ export default (id, scriptName, parameterValues) => {
logChunks: [],
inputPromptText: null,
downloadableFiles: [],
inlineImages: {},
parameterValues: parameterValues,
status: STATUS_INITIALIZING,
scriptName: scriptName,
Expand Down Expand Up @@ -159,6 +161,10 @@ export default (id, scriptName, parameterValues) => {
state.downloadableFiles.push(file);
},

ADD_INLINE_IMAGE(state, {output_path, download_url}) {
Vue.set(state.inlineImages, output_path, download_url);
},

SET_STATUS(state, status) {
state.status = status;
},
Expand Down Expand Up @@ -215,6 +221,11 @@ function attachToWebsocket(internalState, state, commit, dispatch) {
'url': data.url,
'filename': data.filename
});
} else if (eventType === 'inline-image') {
commit('ADD_INLINE_IMAGE', {
'output_path': data.output_path,
'download_url': data.download_url
});
}
});

Expand Down
Loading

0 comments on commit 80a8fd5

Please sign in to comment.