import os import base64 import re import dash from dash import dcc from dash import html from dash import callback_context from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate from input.interface import InputInterface import input.publication from verarbeitung.process_main import Processing app = dash.Dash(__name__) # List of options when inputting data and generating the graph additional_options = ['Update Automatically','Smart Input'] # Reads the contents of info_box.txt. # They can later be displayed by pressing the corresponding button. f = open('info_box.txt', 'r') boxcontent = f.read() f.close() app.layout = html.Div([ # Layer 0: For the Header and Help Function(s) html.Div([ html.Button(id='show-info',children='Show Info',n_clicks=0), html.Div(id='info-box') ]), # Layer 1: For all mandatory Inputs html.Div([ "Input: ", # A simple box for inputting a string. # Value is transmitted upon pressing return or clicking out of the box. dcc.Input(id='input-string', value='', type='text',debounce=True), # Forward recursion. Values between 1 and 10 can be entered. dcc.Input(id='forward-depth',value='1',type='number',min='1',max='10'), # Backward recursion. Values between 1 and 10 can be entered. dcc.Input(id='backward-depth',value='1',type='number',min='1',max='10'), # Upload box. Can be used via drag-and-drop or byclicking on it to open a file viewer. dcc.Upload( id="upload-data", children=html.Div( ["Drag and drop or click to select a file to upload."]), style={ "width": "30%", "height": "60px", "lineHeight": "60px", "borderWidth": "1px", "borderStyle": "dashed", "borderRadius": "5px", "textAlign": "center", "margin": "10px", }) ]), # Layer 2: For the checklist, Remove-/Start-Buttons and input-error-message html.Div([ # All input DOIs are collected in this checklist. # It is initialized to avoid error messages. dcc.Checklist(id='input-checklist',options=[], labelStyle = dict(display='block'),value=[]), # Displays error message if 'Smart Input' is active. html.Div(id='input-err',style={'color':'red'}), # Clears the entire list. html.Button(id='clear-all-button',children='Clear All'), # Clear all selected elements. html.Button(id='clear-selected-button',children='Clear Selected'), # Starts the process that generates a graph. html.Button(id='start-button',children='Generate Graph') ]), # Layer 3: For additional Options (e.g. Topological Sort) html.Div([ html.H4('Additional Options'), # A checklist of all additional options that are listed above. dcc.Checklist(id='additional-options', options=[{'label':k,'value':k} for k in additional_options], value=[]) ]), # Layer 4: For the Graph html.Div( [html.Iframe( src="assets/index.html", style={"height": "600px", "width": "100%"}, ), html.Div(id='test-output') ]) ]) @app.callback( Output('input-checklist','options'), Output('input-checklist','value'), Output('input-string','value'), Output('input-err','children'), Input('input-string','value'), Input('clear-all-button','n_clicks'), Input('clear-selected-button','n_clicks'), Input('upload-data','contents'), State('input-checklist','options'), State('input-checklist','value'), State('additional-options','value') ) def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs, selected_inputs,additional_options): ''' Most important callback function. Updates the checklist that holds all inputs. State of the checklist as input is needed so that previews entries are readded. input-string is required as Output to clear the input box after each input. Different actions are performed depending on which input triggered the callback. The value-attribute of input-checklist must be updates so that the values of deleted elements no longer appear in the list of selected elements. :param input_value: given by dcc.Input :type input_value: string :param btn1: signals pressing of clear-all-button :type btn1: int :param btn2: signals pressing of clear-selected-button :type btn2: int :param filecontents: the contents of an uploaded file :type filecontents: bit-string :param all_inputs: all labels and values from the checklist, regardless if they have been checked or not :type all_inputs: list of dictionaries with 2 entries each :param selected_inputs: values of all checked elements :type selected_inputs: list of strings :param addtitional_options: all checked additional options :type additional_options: list of strings ''' # changed_id is used to determine which Input has triggered the callback changed_id = [p['prop_id'] for p in callback_context.triggered][0] # if clear-all-button was pressed: if 'clear-all-button' in changed_id: os.remove('assets/json_text.json') return list(),list(),'','' # if clear-selected-button was pressed: if 'clear-selected-button' in changed_id: all_inputs = [i for i in all_inputs if i['value'] not in selected_inputs] return all_inputs,list(),'','' # when a new element is added via dcc.Input if 'input-string' in changed_id: # Creates a list of previously added inputs to make sure nothing is added twice currValues = [x['value'] for x in all_inputs] if input_value not in currValues: # if 'Smart Input' is selected, the input will be checked for validity # and a more readable string will be returned if 'Smart Input' in additional_options: try: # Attempts to call get_publication. If unsuccesful, # the DOI is not added and an error message is returned i = InputInterface() pub = i.get_pub_light(input_value) except Exception as err: return all_inputs,selected_inputs,'','{}'.format(err) # Creates a more readable string to display in the checklist rep_str = pub.contributors[0] + ',' + pub.journal + \ ',' + pub.publication_date all_inputs.append({'label':rep_str, 'value':input_value}) # if 'Smart Input' is not selected, the input value is added as is, # without checking for validity. else: all_inputs.append({'label':input_value,'value':input_value}) return all_inputs,selected_inputs,'','' # when a txt-file is uploaded if 'upload-data.contents' in changed_id: if filecontents: # Skips the info portion that is added when a file is uploaded found = base64.b64decode(re.search(',(.+?)$', filecontents).group(1)) # Returns the binary string into a proper text text = found.decode('utf-8') # Creates a list of inputs by splitting the lines list_of_inputs = (text.strip().split('\n')) CurrValues = [x['value'] for x in all_inputs] # For every line the same actions as for a single input are performed for input_value in list_of_inputs: if input_value not in CurrValues: if 'Smart Input' in additional_options: try: i = InputInterface() pub = i.get_pub_light(input_value) except Exception as err: return all_inputs,selected_inputs,'','{}'.format(err) rep_str = pub.contributors[0] + ',' + pub.journal + \ ',' + pub.publication_date all_inputs.append({'label':rep_str, 'value':input_value}) else: all_inputs.append({'label':input_value,'value':input_value}) return all_inputs,selected_inputs,'','' # when the programm is first started: # if this is not done, the input_checklist will be generated # with one element that contains an empty string if input_value == '': return list(),list(),'','' @app.callback( Output('info-box','children'), Input('show-info','n_clicks') ) def show_hide_info_box(n_clicks): ''' This callback shows and hides the (first) info-box by, checking how often the button has been pressed. The text was loaded at the top. :param n_clicks: number of times show-info has been clicked. 'type n_clicks: int ''' if n_clicks % 2 == 0: return '' else: return html.Div(boxcontent, style={'whiteSpace': 'pre-line'}) @app.callback( Output('test-output','children'), Input('start-button','n_clicks'), Input('input-checklist','options'), Input('input-checklist','value'), Input('forward-depth','value'), Input('backward-depth','value'), State('additional-options','value') ) def generate_output(n_clicks,all_inputs,selected_inputs, forward_depth,backward_depth,additional_options): ''' Basic structure for a callback that generates an output. This is only a proof of concept and has noting to do with the intended output yet. :param n_clicks: how often has Generate Graph been clicked :type n_clicks: int :param all_inputs: all labels and values from the checklist, regardless if they have been checked or not :type all_inputs: list of dictionaries with 2 entries each :param selected_inputs: values of all checked elements :type selected_inputs: list of strings :param forward_depth: forward recursion depth :type forward_depth: unsigned int :param backward_depth: backward recursion depth :type backward_depth: unsigned int :param additional_options: value of all selected additional options :type additional_options: list of strings ''' changed_id = [p['prop_id'] for p in callback_context.triggered][0] if n_clicks is None: raise PreventUpdate elif 'Update Automatically' in additional_options \ or 'start-button' in changed_id: input_links = [x['value'] for x in all_inputs] Processing(input_links,int(forward_depth),int(backward_depth),'assets/json_text.json') if __name__ == '__main__': app.run_server(debug=False)