From c417577b9cf19e00f92d583f3c82ff95c9e54efe Mon Sep 17 00:00:00 2001 From: emkael Date: Thu, 30 May 2019 00:02:54 +0200 Subject: Error reporting improved --- dealconvert/formats/ber.py | 2 +- dealconvert/formats/bhg.py | 2 +- dealconvert/formats/bri.py | 2 +- dealconvert/formats/dlm.py | 2 +- dealconvert/formats/rzd.py | 4 +++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dealconvert/formats/ber.py b/dealconvert/formats/ber.py index 54ffd37..1ddd6c8 100644 --- a/dealconvert/formats/ber.py +++ b/dealconvert/formats/ber.py @@ -50,7 +50,7 @@ class BERFormat(DealFormat): deal_str[j*13 + self.cards.index(card)] = str(i + 1) except ValueError: raise RuntimeError( - 'invalid card character: %s' % (card)) + 'invalid card character: %s in board %d' % (card, board.number)) if ' ' in deal_str: warnings.warn('not all cards present in board %d' % ( board.number)) diff --git a/dealconvert/formats/bhg.py b/dealconvert/formats/bhg.py index 23a1673..08b9213 100644 --- a/dealconvert/formats/bhg.py +++ b/dealconvert/formats/bhg.py @@ -51,7 +51,7 @@ class BHGFormat(DealFormat): line += chr((65 if card < 26 else 71)+card) except ValueError: raise RuntimeError( - 'invalid suit %s in board #%d' % ( + 'invalid suit %s in board %d' % ( ''.join(suit), deal.number)) lines[deal.number] = line out_file.write('\r\n'.join(lines)) diff --git a/dealconvert/formats/bri.py b/dealconvert/formats/bri.py index 942c4e4..1a4cc0f 100644 --- a/dealconvert/formats/bri.py +++ b/dealconvert/formats/bri.py @@ -67,5 +67,5 @@ class BRIFormat(DealFormat): deal_str += '%02d' % (self.cards.index(card) + 13*i + 1) except ValueError: raise RuntimeError( - 'invalid card character: %s' % (card)) + 'invalid card character: %s in board %d' % (card, deal.number)) return deal_str diff --git a/dealconvert/formats/dlm.py b/dealconvert/formats/dlm.py index db9ac79..f99cde4 100644 --- a/dealconvert/formats/dlm.py +++ b/dealconvert/formats/dlm.py @@ -98,7 +98,7 @@ class DLMFormat(DealFormat): try: values[suit*13+self.cards.index(card)] = i except ValueError: - raise RuntimeError('invalid card: %s' % (card)) + raise RuntimeError('invalid card: %s in board %d' % (card, board)) line = 'Board %02d=' % (board) checksum = board for i in range(0, 26): diff --git a/dealconvert/formats/rzd.py b/dealconvert/formats/rzd.py index 8ca1e97..93dbc45 100644 --- a/dealconvert/formats/rzd.py +++ b/dealconvert/formats/rzd.py @@ -51,11 +51,13 @@ class RZDFormat(DealFormat): try: idx = self.cards.index(card) except ValueError: - raise RuntimeError('invalid card: %s' % (card)) + raise RuntimeError('invalid card: %s in board %d' % (card, deal.number)) values[idx*4+suit] = (i + offset)%4 for i in range(0, 13): byte = 0 for j in range(0, 4): + if values[4*i+j] is None: + raise RuntimeError('missing card: %s%s in board %d' % ('SHDC'[j], self.cards[i], deal.number)) byte *= 4 byte += values[4*i+j] value += chr(byte) -- cgit v1.2.3 From ddc48b5f38de12d28917c71c46c61913e640c774 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 17:06:41 +0200 Subject: Changes that allow providing input from StringIO: - format detection exposed for manual parser creation - optional filename in constructor --- dealconvert/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dealconvert/__init__.py b/dealconvert/__init__.py index 1779976..2f9b5aa 100644 --- a/dealconvert/__init__.py +++ b/dealconvert/__init__.py @@ -1,17 +1,18 @@ from .formats import * class DealConverter(object): - def __init__(self, input_file): + def __init__(self, input_file=None): self.input = input_file self.formats = {} - self.parser = self._detect_format(self.input) + if input_file is not None: + self.parser = self._detect_format(self.input) def output(self, output_files): deal_set = sorted(self.parser.parse(self.input), key=lambda d:d.number) for output in output_files: self._detect_format(output).output(output, deal_set) - def _detect_format(self, filename): + def detect_format(self, filename): for deal_format in globals()['formats'].__all__: if deal_format not in self.formats: self.formats[deal_format] = getattr( -- cgit v1.2.3 From 674e71ef10bee715e4d43d1b5efd714f96c6df1f Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 17:12:15 +0200 Subject: Precautions against empty deal sets --- dealconvert/formats/__init__.py | 2 ++ dealconvert/formats/bhg.py | 2 +- dealconvert/formats/dlm.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dealconvert/formats/__init__.py b/dealconvert/formats/__init__.py index eea9fc6..9c37cf9 100644 --- a/dealconvert/formats/__init__.py +++ b/dealconvert/formats/__init__.py @@ -8,6 +8,8 @@ class DealFormat(object): return self.parse_content(content) def output(self, output_file, deal): + if not len(deal): + raise RuntimeError('Dealset is empty') with open(output_file, 'wb') as out_file: return self.output_content(out_file, deal) diff --git a/dealconvert/formats/bhg.py b/dealconvert/formats/bhg.py index 08b9213..6964ea3 100644 --- a/dealconvert/formats/bhg.py +++ b/dealconvert/formats/bhg.py @@ -40,7 +40,7 @@ class BHGFormat(DealFormat): return deals def output_content(self, out_file, dealset): - lines = [''] * (max([board.number for board in dealset])+2) + lines = [''] * (max([board.number for board in dealset])+2) if len(dealset) else [] for deal in dealset: line = '' for hand in range(0, 4): diff --git a/dealconvert/formats/dlm.py b/dealconvert/formats/dlm.py index f99cde4..56f6e5d 100644 --- a/dealconvert/formats/dlm.py +++ b/dealconvert/formats/dlm.py @@ -66,7 +66,7 @@ class DLMFormat(DealFormat): def output_content(self, out_file, dealset): dealset = dealset[0:99] board_numbers = [deal.number for deal in dealset] - first_board = min(board_numbers) + first_board = min(board_numbers) if len(board_numbers) else 1 board_count = len(dealset) for board in range(first_board, first_board+board_count): if board not in board_numbers: -- cgit v1.2.3 From e09274eb80af691af0feb9c774bd0517bb92e381 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 17:17:59 +0200 Subject: Web interface --- http/dealconvert.js | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ http/index.html | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 http/dealconvert.js create mode 100644 http/index.html diff --git a/http/dealconvert.js b/http/dealconvert.js new file mode 100644 index 0000000..3e5ea85 --- /dev/null +++ b/http/dealconvert.js @@ -0,0 +1,121 @@ +$(document).ready(function() { + $('input[name="output"]').change(function() { + if ($('input[name="output"]:checked').length > 0) { + $('#submit-btn').removeAttr('disabled'); + } else { + $('#submit-btn').attr('disabled', 'disabled'); + } + }); + $('#converter-input').submit(function() { + var that = $(this); + var output = []; + that.find('input[name="output"]:checked').each(function() { + output.push(this.value); + }); + var display = that.find('input[name="display"]').is(':checked'); + var files = {}; + var formFiles = this['input-files'].files; + if (formFiles.length) { + $('.output-group').remove(); + that.css('opacity', 0.3); + that.find('input, button').attr('disabled', 'disabled'); + } + var completed = 0; + for (var i = 0; i < formFiles.length; i++) { + var currentFile = formFiles[i]; + files[currentFile.name] = null; + var reader = new FileReader(); + reader.file = currentFile; + reader.onload = function(e) { + files[e.target.file.name] = btoa(e.target.result); + for (var file in files) { + if (files[file] == null) { + return; + } + } + for (var file in files) { + var paramObj = { + 'name': file, + 'content': files[file], + 'output': output, + 'display_deals': display + }; + $.ajax( + 'api/upload/', + { + data: JSON.stringify(paramObj), + dataType: 'json', + method: 'POST', + success: function(data, status, xhr) { + var outputGroup = $('template#file-output-group').clone().contents().unwrap(); + var warningTemplate = $('template#file-output-warning'); + var errorTemplate = $('template#file-output-error'); + var fileTemplate = $('template#file-output'); + var inputHeader = outputGroup.find('.card-header'); + inputHeader.text(data.name); + var groupBody = outputGroup.find('.card-body'); + if (data.error) { + inputHeader.addClass('bg-danger'); + groupBody.append(errorTemplate.clone().contents().unwrap().text(data.error)); + } else { + inputHeader.addClass('bg-success'); + if (data.warnings.length) { + inputHeader.removeClass('bg-success'); + inputHeader.addClass('bg-warning'); + for (var w = 0; w < data.warnings.length; w++) { + groupBody.append(warningTemplate.clone().contents().unwrap().text(data.warnings[w])); + } + } + for (var f = 0; f < data.files.length; f++) { + var fileContent = fileTemplate.clone().contents().unwrap(); + groupBody.append(fileContent); + fileContent.find('.file-name').text(data.files[f].name); + fileContent.find('.file-status').popover(); + if (data.files[f].error) { + fileContent.find('.file-link').remove(); + fileContent.find('.file-status').addClass('btn-danger').text('⚠️').attr('data-content', data.files[f].error); + fileContent.find('.file-name').addClass('btn-danger'); + inputHeader.removeClass('bg-success'); + inputHeader.addClass('bg-warning'); + } else { + if (data.files[f].warnings.length) { + fileContent.find('.file-status').addClass('btn-warning').text('⚠️').attr( + 'data-content', data.files[f].warnings.join("
")); + fileContent.find('.file-name').addClass('btn-warning'); + inputHeader.removeClass('bg-success'); + inputHeader.addClass('bg-warning'); + } else { + fileContent.find('.file-status').addClass('btn-success').text('✔️'); + fileContent.find('.file-name').addClass('btn-success'); + } + fileContent.find('.file-link').attr( + 'href', 'api/' + data.files[f].link + ); + } + } + } + $('body').append(outputGroup); + completed += 1; + if (completed >= formFiles.length) { + that.css('opacity', ''); + that.find('input, button').removeAttr('disabled'); + } + }, + error: function(xhr, status, error) { + var errorBox = $('
'); + errorBox.append($('template#file-output-error').clone().contents().unwrap().text( + 'Nie udało się wykonać konwersji: ' + xhr.responseText + )); + $('body').append(errorBox); + that.css('opacity', ''); + that.find('input, button').removeAttr('disabled'); + } + } + ); + } + } + reader.readAsBinaryString(currentFile); + } + return false; + }); +}); diff --git a/http/index.html b/http/index.html new file mode 100644 index 0000000..faebaff --- /dev/null +++ b/http/index.html @@ -0,0 +1,122 @@ + + + + DealConvert by mkl + + + + + + + +
+ +
+
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+ + + + + + -- cgit v1.2.3 From 5b81841f420eb948de7f184b588bc75a3227da15 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 17:18:46 +0200 Subject: Web (mod_python) API --- cache/.gitignore | 1 + http/api/.htaccess | 6 ++ http/api/api.py | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 cache/.gitignore create mode 100644 http/api/.htaccess create mode 100644 http/api/api.py diff --git a/cache/.gitignore b/cache/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/cache/.gitignore @@ -0,0 +1 @@ +* diff --git a/http/api/.htaccess b/http/api/.htaccess new file mode 100644 index 0000000..998bfca --- /dev/null +++ b/http/api/.htaccess @@ -0,0 +1,6 @@ +RewriteEngine On +RewriteRule ^.*$ api.py [QSA,L] + +AddHandler mod_python .py +PythonHandler api +PythonDebug On diff --git a/http/api/api.py b/http/api/api.py new file mode 100644 index 0000000..1c1cb0f --- /dev/null +++ b/http/api/api.py @@ -0,0 +1,165 @@ +# coding=utf-8 + +import base64, copy, json, os, random, sys, warnings +from StringIO import StringIO + +from mod_python import apache, Session + +OLDPATH = copy.copy(sys.path) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) +from dealconvert import DealConverter +sys.path = OLDPATH + +CACHEPATH = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + '../../cache')) + +def _get_rand_string(length=30): + return ('%0' + str(length) + 'x') % (random.randrange(16**length)) + +def _get_file_id(): + while True: + output_id = _get_rand_string() + output_path = os.path.join(CACHEPATH, output_id) + if not os.path.exists(output_path): + return output_id, output_path + +def _print_response(response, obj): + response.write(json.dumps(obj)) + +def handle_upload(response, request): + if request.method != 'POST': + response.status = apache.HTTP_METHOD_NOT_ALLOWED + return + try: + params = json.load(request) + except ValueError as e: + response.write(str(e)) + response.status = apache.HTTP_BAD_REQUEST + return + + session = Session.Session(response) + if 'tokens' not in session: + session['tokens'] = {} + + response.content_type = 'application/json' + return_obj = { + 'name': None, + 'warnings': [], + 'error': None, + 'files': [] + } + warnings.simplefilter('always') + warnings.showwarning = lambda msg, *args: return_obj['warnings'].append( + unicode(msg)) + + try: + return_obj['name'] = params['name'] + converter = DealConverter() + parser = converter.detect_format(params['name']) + input_file = StringIO(base64.b64decode(params['content'])) + dealset = parser.parse_content(input_file) + input_file.close() + if not len(dealset): + raise RuntimeError('Dealset is empty') + except RuntimeError as e: + return_obj['error'] = unicode(e) + return _print_response(response, return_obj) + + for output_type in params['output']: + output_return = { + 'name': None, + 'link': None, + 'warnings': [], + 'error': None + } + warnings.showwarning = lambda msg, *args: output_return['warnings'].append( + unicode(msg)) + try: + output_name = '.'.join(params['name'].split('.')[:-1] + [output_type]) + output_return['name'] = output_name + output = converter.detect_format(output_name) + output_id, output_path = _get_file_id() + token = _get_rand_string(16) + output_buffer = StringIO() + output.output_content(output_buffer, dealset) + with file(output_path, 'w') as output_file: + json.dump({ + 'token': token, + 'name': output_name, + 'content': base64.b64encode(output_buffer.getvalue()) + }, output_file) + output_buffer.close() + session['tokens'][output_id] = token + output_return['link'] = 'download/%s' % (output_id) + except RuntimeError as e: + output_return['error'] = unicode(e) + return_obj['files'].append(output_return) + session.save() + _print_response(response, return_obj) + + +def handle_download(response, request, uri_parts=[]): + if not len(uri_parts): + response.status = apache.HTTP_BAD_REQUEST + return + if request.method != 'GET': + response.status = apache.HTTP_METHOD_NOT_ALLOWED + return + + session = Session.Session(response) + + if 'tokens' not in session: + response.status = apache.HTTP_NOT_FOUND + return + + output_id = uri_parts[0] + output_path = os.path.join(CACHEPATH, output_id) + if not os.path.exists(output_path): + response.status = apache.HTTP_NOT_FOUND + return + if output_id not in session['tokens']: + response.status = apache.HTTP_NOT_FOUND + return + with file(output_path) as output_file: + output = json.load(output_file) + if output['token'] != session['tokens'][output_id]: + response.status = apache.HTTP_NOT_FOUND + return + content = base64.b64decode(output['content']) + response.content_type = 'application/octet-stream' + response.headers_out.add( + 'Content-Disposition', 'attachment; filename=%s' % (output['name'])) + response.write(content) + +def handler(req): + # MIME type fix for error messages + req.content_type = 'text/plain' + + # we need to recover original request path, from before rewrite + orig_req = req + while True: + if orig_req.prev: + orig_req = orig_req.prev + else: + break + + uri_parts = [part for part in orig_req.uri.split('/') if part.strip()] + uri_parts = uri_parts[uri_parts.index('api')+1:] + + if not len(uri_parts): + req.status = apache.HTTP_BAD_REQUEST + else: + try: + if uri_parts[0] == 'upload': + handle_upload(req, orig_req) + elif uri_parts[0] == 'download': + handle_download(req, orig_req, uri_parts[1:]) + else: + req.status = apache.HTTP_BAD_REQUEST + except Exception as e: + req.status = apache.HTTP_INTERNAL_SERVER_ERROR + req.write(str(e)) + + return apache.OK -- cgit v1.2.3 From 3cc877cd687a5d0539d7edb62c934b5d920b95fb Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 17:23:30 +0200 Subject: Crontab entry for clearing file cache --- _cron/clear_cache | 1 + 1 file changed, 1 insertion(+) create mode 100644 _cron/clear_cache diff --git a/_cron/clear_cache b/_cron/clear_cache new file mode 100644 index 0000000..ca29bcb --- /dev/null +++ b/_cron/clear_cache @@ -0,0 +1 @@ +* * * * * find $SITEPATH/cache -type f -mmin +15 -not -name .gitignore -delete -- cgit v1.2.3 From 6ddd1c437c56f45c840969ec55361b8d642885a1 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 18:21:55 +0200 Subject: Unrecognized file extensions now raise RuntimeError to be handled the same way all other converter errors --- dealconvert/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dealconvert/__init__.py b/dealconvert/__init__.py index 2f9b5aa..3b5a8ba 100644 --- a/dealconvert/__init__.py +++ b/dealconvert/__init__.py @@ -20,4 +20,4 @@ class DealConverter(object): deal_format.upper() + 'Format')() if self.formats[deal_format].match_file(filename): return self.formats[deal_format] - raise ValueError('Unrecognized file extension: %s' % filename) + raise RuntimeError('Unrecognized file extension: %s' % filename) -- cgit v1.2.3 From e7f7ae45b7dfd45a8b2ac0603a501273a5f10b2d Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 18:22:53 +0200 Subject: FAQ --- http/dealconvert.js | 1 + http/index.html | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/http/dealconvert.js b/http/dealconvert.js index 3e5ea85..ff25444 100644 --- a/http/dealconvert.js +++ b/http/dealconvert.js @@ -1,4 +1,5 @@ $(document).ready(function() { + $('a.faq-btn').popover(); $('input[name="output"]').change(function() { if ($('input[name="output"]:checked').length > 0) { $('#submit-btn').removeAttr('disabled'); diff --git a/http/index.html b/http/index.html index faebaff..fb97c5e 100644 --- a/http/index.html +++ b/http/index.html @@ -1,6 +1,7 @@ + DealConvert by mkl @@ -11,10 +12,62 @@
+
+
+
+ Pytania i odpowiedzi +
+
+
+
Czemu konwerter mi nie zadziałał?
+
+ Nie wiem, prześlij mi problemowy plik z opisem błędu mailem albo od razu do systemu śledzenia błędów.
+ Pliki wszystkich formatów wygenerowane poprawnie i z ciągłą numeracją rozdań od 1 (np. wygenerowane przez BigDeala) powinny konwertować się we wszystkie strony bez problemu. Nie wszystkie formaty plików są udokumentowane w sposób pozwalający obsłużyć je kompleksowo, na przykład formaty DUP i DLM (stary i nowy format Duplimate) zawierają dużo opcji, które konwerter ignoruje, a czasami wręcz wprost przyznaje, że nie wie, jak się zachować. Jeśli masz jakieś informacje, które mogłyby usprawnić wsparcie takich formatów, pisz jak wyżej. +
+
Czemu konwerter nie wspiera formatu X?
+
+ Bo nie obsługiwał go BigDeal w momencie, kiedy konwerter powstawał.
+ Wyjątkiem są "ślepe" formaty Duplimate, które były wyodrębione w BigDeal, ale nie są obsługiwane, a różnią się od "jawnych" formatów Duplimate tylko określonymi opcjami. Oba te formaty są prawidłowo importowane, konwerter zapisuje jednak tylko do plików "jawnych".
+ W przyszłości rozważane jest wsparcie dla formatu LIN. Jeśli chcesz obsługi jakiegoś innego formatu, pisz jak wyżej. +
+
Czemu w pliku PBN nie ma analizy w widne?
+
Nie ma, pracuję nad tym. Możesz ją sobie łatwo (i szybko!) wykonać przy pomocy BCDD.
+
Czemu dostaję mnóstwo ostrzeżeń .xxx file format assumes consequent deal numbers from 1?
+
Niektóre formaty nie przechowują numerów rozdań. W takich sytuacjach, zarówno przy imporcie, jak i eksporcie, konwerter zakłada, że rozdania mają kolejne numery, od 1. Może to doprowadzić do zmiany numeracji rozdań, jeśli oryginalny plik nie zaczynał się od rozdania nr 1 albo numeracja zawierała dziury.
+
Czemu mój PBN nie chce się otworzyć w BigDealu?
+
+ BigDeal zakłada, że numeracja rozdań w pliku PBN zaczyna się od 1. Możliwy jest eksport do PBN plików, które nie spełniają tego warunku (np. formaty Duplimate robią tak powszechnie).
+ Jest łatwy sposób, aby poradzić sobie z tym problemem: +
    +
  1. Wyeksportować rozkłady do plik DLM.
  2. +
  3. Odnaleźć w pliku DLM linię From board=X.
  4. +
  5. Zmienić ją na From board=1.
  6. +
  7. Tak spreparowany plik skonwertować do PBN.
  8. +
  9. Prawdopodobnie konwerter zgłosi błąd sumy kontrolnej, można go ignorować, jeśli nie chce się używać pośredniego pliku DLM.
  10. +
  11. Wynikowy plik PBN rozpoczyna się od rozdania nr 1 i jest uzupełniony rozdaniami z 13-kartowymi kolorami w każdej z rąk.
  12. +
+
Czy to bezpieczne, tak wysyłać rozkłady do Internetu?
+
+ Doskonałe pytanie!
+ Nie jestem oczywiście w stanie zapewnić, że przesyłane w ten sposób rozkłady są w 100% odporne na wyciek, ale ze strony konwertera: +
    +
  • zapewniam szyfrowane połączenie HTTPS
  • +
  • udostępniam kod źródłowy całości narzędzia
  • +
  • nie przechowuję oryginalnych plików wejściowych na serwerze
  • +
  • pliki wynikowe przechowuję przez 15 minut od momentu wygenerowania
  • +
  • pliki wynikowe dostępne są tylko w tej samej sesji przeglądarki, w której zostały wygenerowane
  • +
+ Ogólnie, jeśli zachować elementarne środki ostrożności, tj. niekorzystanie z konwertera w niezaufanych sieciach oraz z niezaufanych komputerów, konwerter niesie dużo mniejsze ryzyko niż np. zostawienie rozkładów na pamięci przenośnej na sali gry czy wysyłanie ich pocztą elektroniczną. +
+
+
+
+
-- cgit v1.2.3 From a9944c178f13ed6fd876a8757a147ca5949d6423 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 20:51:05 +0200 Subject: Board preview feature --- http/api/api.py | 16 +++++++++ http/dealconvert.js | 22 +++++++++++-- http/img/e-ew.png | Bin 0 -> 5657 bytes http/img/e-ns-ew.png | Bin 0 -> 5952 bytes http/img/e-ns.png | Bin 0 -> 5674 bytes http/img/e.png | Bin 0 -> 5267 bytes http/img/n-ew.png | Bin 0 -> 5577 bytes http/img/n-ns-ew.png | Bin 0 -> 5955 bytes http/img/n-ns.png | Bin 0 -> 5731 bytes http/img/n.png | Bin 0 -> 5247 bytes http/img/s-ew.png | Bin 0 -> 5558 bytes http/img/s-ns-ew.png | Bin 0 -> 5936 bytes http/img/s-ns.png | Bin 0 -> 5688 bytes http/img/s.png | Bin 0 -> 5250 bytes http/img/w-ew.png | Bin 0 -> 5595 bytes http/img/w-ns-ew.png | Bin 0 -> 5901 bytes http/img/w-ns.png | Bin 0 -> 5602 bytes http/img/w.png | Bin 0 -> 5207 bytes http/index.html | 89 ++++++++++++++++++++++++++++++++++++++++++++++++--- 19 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 http/img/e-ew.png create mode 100644 http/img/e-ns-ew.png create mode 100644 http/img/e-ns.png create mode 100644 http/img/e.png create mode 100644 http/img/n-ew.png create mode 100644 http/img/n-ns-ew.png create mode 100644 http/img/n-ns.png create mode 100644 http/img/n.png create mode 100644 http/img/s-ew.png create mode 100644 http/img/s-ns-ew.png create mode 100644 http/img/s-ns.png create mode 100644 http/img/s.png create mode 100644 http/img/w-ew.png create mode 100644 http/img/w-ns-ew.png create mode 100644 http/img/w-ns.png create mode 100644 http/img/w.png diff --git a/http/api/api.py b/http/api/api.py index 1c1cb0f..be2635f 100644 --- a/http/api/api.py +++ b/http/api/api.py @@ -63,6 +63,22 @@ def handle_upload(response, request): input_file.close() if not len(dealset): raise RuntimeError('Dealset is empty') + if params['display_deals']: + preview_obj = [] + for board in dealset: + deal_preview = { + 'number': board.number, + 'conditions': 'nesw'[board.dealer], + 'hands': [] + } + for pair in ['ns', 'ew']: + if board.vulnerable[pair.upper()]: + deal_preview['conditions'] += '-' + pair + deal_preview['hands'] = board.hands + preview_obj.append(deal_preview) + return_obj['preview'] = preview_obj + else: + return_obj['preview'] = None except RuntimeError as e: return_obj['error'] = unicode(e) return _print_response(response, return_obj) diff --git a/http/dealconvert.js b/http/dealconvert.js index ff25444..e038b58 100644 --- a/http/dealconvert.js +++ b/http/dealconvert.js @@ -52,9 +52,9 @@ $(document).ready(function() { var warningTemplate = $('template#file-output-warning'); var errorTemplate = $('template#file-output-error'); var fileTemplate = $('template#file-output'); - var inputHeader = outputGroup.find('.card-header'); + var inputHeader = outputGroup.find('.file-header'); inputHeader.text(data.name); - var groupBody = outputGroup.find('.card-body'); + var groupBody = outputGroup.find('.file-body'); if (data.error) { inputHeader.addClass('bg-danger'); groupBody.append(errorTemplate.clone().contents().unwrap().text(data.error)); @@ -94,6 +94,24 @@ $(document).ready(function() { ); } } + if (data.preview) { + var boardTemplate = $('#board-preview'); + var hands = ['north', 'east', 'south', 'west']; + var suits = ['spades', 'hearts', 'diamonds', 'clubs']; + for (var b = 0; b < data.preview.length; b++) { + var board = boardTemplate.clone().contents().unwrap(); + board.find('.board-number').text(data.preview[b].number); + board.find('.board-conditions').attr('src', 'img/' + data.preview[b].conditions + '.png'); + for (var h = 0; h < hands.length; h++) { + for (var s = 0; s < suits.length; s++) { + board.find('.board-' + hands[h] + '-' + suits[s]).text(data.preview[b].hands[h][s].join('')); + } + } + outputGroup.find('.file-boards-panel .board-body').append(board); + } + } else { + outputGroup.find('.file-boards-panel').remove(); + } } $('body').append(outputGroup); completed += 1; diff --git a/http/img/e-ew.png b/http/img/e-ew.png new file mode 100644 index 0000000..bbf899f Binary files /dev/null and b/http/img/e-ew.png differ diff --git a/http/img/e-ns-ew.png b/http/img/e-ns-ew.png new file mode 100644 index 0000000..05a3b6c Binary files /dev/null and b/http/img/e-ns-ew.png differ diff --git a/http/img/e-ns.png b/http/img/e-ns.png new file mode 100644 index 0000000..2e94563 Binary files /dev/null and b/http/img/e-ns.png differ diff --git a/http/img/e.png b/http/img/e.png new file mode 100644 index 0000000..0d9304b Binary files /dev/null and b/http/img/e.png differ diff --git a/http/img/n-ew.png b/http/img/n-ew.png new file mode 100644 index 0000000..8cc1807 Binary files /dev/null and b/http/img/n-ew.png differ diff --git a/http/img/n-ns-ew.png b/http/img/n-ns-ew.png new file mode 100644 index 0000000..d45ef7e Binary files /dev/null and b/http/img/n-ns-ew.png differ diff --git a/http/img/n-ns.png b/http/img/n-ns.png new file mode 100644 index 0000000..a94eb42 Binary files /dev/null and b/http/img/n-ns.png differ diff --git a/http/img/n.png b/http/img/n.png new file mode 100644 index 0000000..cb079c4 Binary files /dev/null and b/http/img/n.png differ diff --git a/http/img/s-ew.png b/http/img/s-ew.png new file mode 100644 index 0000000..44c3e71 Binary files /dev/null and b/http/img/s-ew.png differ diff --git a/http/img/s-ns-ew.png b/http/img/s-ns-ew.png new file mode 100644 index 0000000..8cff6f8 Binary files /dev/null and b/http/img/s-ns-ew.png differ diff --git a/http/img/s-ns.png b/http/img/s-ns.png new file mode 100644 index 0000000..36fe441 Binary files /dev/null and b/http/img/s-ns.png differ diff --git a/http/img/s.png b/http/img/s.png new file mode 100644 index 0000000..a0611ac Binary files /dev/null and b/http/img/s.png differ diff --git a/http/img/w-ew.png b/http/img/w-ew.png new file mode 100644 index 0000000..b66f4f9 Binary files /dev/null and b/http/img/w-ew.png differ diff --git a/http/img/w-ns-ew.png b/http/img/w-ns-ew.png new file mode 100644 index 0000000..448abf2 Binary files /dev/null and b/http/img/w-ns-ew.png differ diff --git a/http/img/w-ns.png b/http/img/w-ns.png new file mode 100644 index 0000000..623a641 Binary files /dev/null and b/http/img/w-ns.png differ diff --git a/http/img/w.png b/http/img/w.png new file mode 100644 index 0000000..6a995ad Binary files /dev/null and b/http/img/w.png differ diff --git a/http/index.html b/http/index.html index fb97c5e..82c3dc8 100644 --- a/http/index.html +++ b/http/index.html @@ -9,7 +9,7 @@ - +
-
@@ -171,5 +178,77 @@ + -- cgit v1.2.3 From 0342bcfb2b5f03229112d8b319e2059b6c9e76f2 Mon Sep 17 00:00:00 2001 From: emkael Date: Fri, 31 May 2019 20:51:46 +0200 Subject: README updated --- README.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.txt b/README.txt index 24002b2..753d94b 100644 --- a/README.txt +++ b/README.txt @@ -12,3 +12,5 @@ optional arguments: Supported formats: BER BHG BRI CDS CSV DGE DLM DUP PBN RZD. Formats are auto-detected based on file extension. To display deals on STDOUT, provide "-" as an output file name. + +Web interface available: https://deal.emkael.info/ -- cgit v1.2.3 From e3e5c088687d73dc492199be071cc12d786ba00d Mon Sep 17 00:00:00 2001 From: emkael Date: Tue, 4 Jun 2019 23:32:43 +0200 Subject: Collapsing/exapnding output option sections after files are selected --- http/dealconvert.js | 3 +++ http/index.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/http/dealconvert.js b/http/dealconvert.js index e038b58..2478fe9 100644 --- a/http/dealconvert.js +++ b/http/dealconvert.js @@ -7,6 +7,9 @@ $(document).ready(function() { $('#submit-btn').attr('disabled', 'disabled'); } }); + $('#input-files').change(function() { + $('#submit-panel, #output-formats').collapse(this.files.length ? 'show' : 'hide'); + }); $('#converter-input').submit(function() { var that = $(this); var output = []; diff --git a/http/index.html b/http/index.html index 82c3dc8..9f40cf1 100644 --- a/http/index.html +++ b/http/index.html @@ -74,7 +74,7 @@
-
+
@@ -139,7 +139,7 @@
-
+
-- cgit v1.2.3