From f720c82c1837b1c8e2c44f50c3685ef2a090855e Mon Sep 17 00:00:00 2001 From: Sebastiano Giacomini <92300303+Sebastiano-G@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:37:14 +0200 Subject: [PATCH] #10 Min fix; #6 multiple classes enabled #10 : Knowledge Extraction min fix #6 : Template creation form has been updated. It is now possible to specify multiple classes for the same template. Consequently, data retrieval functions have been adapted to this new feature. --- app.py | 17 +- forms.py | 51 +-- mapping.py | 22 +- queries.py | 59 ++-- resource_templates/template_list.json | 2 +- static/css/main.css | 177 +++++++++- static/js/main.js | 484 ++++++++++++++++---------- templates/index.html | 35 +- templates/modify.html | 11 +- templates/record.html | 27 +- templates/review.html | 8 +- templates/template.html | 2 +- templates/view.html | 19 +- 13 files changed, 614 insertions(+), 300 deletions(-) diff --git a/app.py b/app.py index af30bcb..ad03a6b 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ import logging import cgi from importlib import reload +import urllib.parse from urllib.parse import parse_qs import requests import web @@ -254,7 +255,7 @@ def GET(self, res_name): tpl_list = json.load(tpl_file) print(res_name) - res_type = [i['type'] for i in tpl_list if i["short_name"] == res_name][0] + res_type = "; ".join([i['type'] for i in tpl_list if i["short_name"] == res_name][0]) res_full_name = [i['name'] for i in tpl_list if i["short_name"] == res_name][0] # if does not exist create the template json file @@ -411,6 +412,7 @@ def POST(self, page): web.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') actions = web.input() + print(actions) session['ip_address'] = str(web.ctx['ip']) is_git_auth = github_sync.is_git_auth() @@ -519,7 +521,8 @@ def POST(self, page): elif actions.action.startswith('createTemplate'): print('create template') is_git_auth = github_sync.is_git_auth() - res_type = actions.class_uri.strip() if "class_uri" in actions else conf.main_entity + res_type = sorted([ urllib.parse.unquote(actions[class_input].strip()) for class_input in actions if class_input.startswith("uri_class")]) + res_type = conf.main_entity if res_type == [] else res_type res_name = actions.class_name.replace(' ','_').lower() if "class_name" in actions else "not provided" with open(TEMPLATE_LIST,'r') as tpl_file: @@ -963,8 +966,8 @@ def GET(self): filters_by_template[template["name"]] = filtersBrowse return render.records(user=session['username'], data=records_by_template, title='Latest resources', r_base=conf.base, - alll=count_by_template, filters=filters_by_template,is_git_auth=is_git_auth, - project=conf.myProject) + alll=count_by_template, filters=filters_by_template, + is_git_auth=is_git_auth,project=conf.myProject) def POST(self): """ EXPLORE page """ @@ -990,11 +993,13 @@ def GET(self, name): record = base+name res_class = queries.getClass(conf.base+name) data, stage, title, properties, data_labels = None, None, None, None, {} - extractor = queries.retrieve_extractions(conf.base+name) if u.has_extractor(name, modify=True) else False + try: res_template = u.get_template_from_class(res_class) data = dict(queries.getData(record+'/',res_template)) stage = data['stage'][0] if 'stage' in data else 'draft' + previous_extractors = u.has_extractor(res_template, name) + extractions_data = queries.retrieve_extractions(previous_extractors) with open(res_template) as tpl_form: fields = json.load(tpl_form) @@ -1015,7 +1020,7 @@ def GET(self, name): return render.view(user=session['username'], graphdata=data_labels, graphID=name, title=title, stage=stage, base=base,properties=properties, - is_git_auth=is_git_auth,project=conf.myProject,knowledge_extractor=extractor) + is_git_auth=is_git_auth,project=conf.myProject,knowledge_extractor=extractions_data) def POST(self,name): """ Record web page diff --git a/forms.py b/forms.py index 25b5547..be12714 100644 --- a/forms.py +++ b/forms.py @@ -51,7 +51,7 @@ def get_form(json_form, from_dict=False, subtemplate=False): tpl_list = json.load(tpl_file) res_class = [t["type"] for t in tpl_list if t["template"] == json_form] - res_class = res_class[0] if len(res_class) > 0 else "none" + res_class = "; ".join(res_class[0]) if len(res_class) > 0 else "none" for field in fields: if 'hidden' in field and field['hidden'] == 'False': # do not include hidden fields @@ -61,7 +61,6 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre_a = '' prepend = pre_a+html.escape(field['prepend'])+pre_b if 'prepend' in field and len(field['prepend']) > 0 else '' - disabled = 'disabled' if 'disabled' in field and field['disabled'] == "True" else '' classes = field['class'] if 'class' in field and len(field['class']) > 0 else '' if 'skos' in field: for vocabulary in field['skos']: @@ -77,7 +76,7 @@ def get_form(json_form, from_dict=False, subtemplate=False): classes = classes+' vocabularyField' if field['type'] == 'Skos' else classes classes = classes+' oneVocableAccepted' if 'vocables' in field and field['vocables'] == 'oneVocable' else classes classes = classes+' websitePreview' if field['type'] == 'WebsitePreview' else classes - classes = classes+' ('+res_class+') '+disabled + classes = classes+' disabled' if 'disabled' in field and field['disabled'] == "True" else classes classes = classes+ ' ' + field['cardinality'] if 'cardinality' in field else classes autocomplete = field['cache_autocomplete'] if 'cache_autocomplete' in field and len(field['cache_autocomplete']) > 0 else '' mandatory = field['mandatory'] if 'mandatory' in field and field['mandatory'] == 'True' else 'False' @@ -100,8 +99,9 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory, - lang=conf.mainLang) , ) + lang=conf.mainLang, + data_mandatory = mandatory, + data_class=res_class) , ) else: params = params + (form.Textbox(myid, type='text', @@ -111,8 +111,9 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory, - lang=conf.mainLang), ) + lang=conf.mainLang, + data_mandatory = mandatory, + data_class=res_class), ) # Entities, SKOS thesauri, links if field['type'] in ['Skos', 'WebsitePreview'] or (field['type'] == 'Textbox' and field['value'] in ['URL', 'URI', 'Place']): @@ -123,7 +124,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) # Multimedia Link if field['type'] == 'Multimedia': @@ -134,7 +136,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory) , ) + data_mandatory = mandatory, + data_class=res_class) , ) # Text box if field['type'] == 'Textarea': @@ -145,8 +148,9 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory, - lang=conf.mainLang), ) + lang=conf.mainLang, + data_mandatory = mandatory, + data_class=res_class), ) if field['type'] == 'Date': if field['calendar'] == 'Month': @@ -155,14 +159,16 @@ def get_form(json_form, from_dict=False, subtemplate=False): id=myid, pre = prepend, class_= classes, - mandatory=mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) elif field['calendar'] == 'Day': params = params + (form.Date(myid, description = description, id=myid, pre = prepend, class_= classes, - mandatory=mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) elif field['calendar'] == 'Year': params = params + (form.Textbox(myid, description = description, @@ -170,7 +176,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory=mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) if field['type'] == 'Dropdown': params = params + (form.Dropdown(myid, @@ -180,7 +187,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): id=myid, pre = prepend, class_= classes, - mandatory = mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) if field['type'] == 'Checkbox': prepend_title = '
'+description+'
' @@ -192,7 +200,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend_title+prepend, class_= classes+' checkbox_group', checked=False, - mandatory = mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) for value in dropdown_values[1:]: i += 1 @@ -203,7 +212,8 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = '', class_= classes+' checkbox_group following_checkbox', checked=False, - mandatory = mandatory), ) + data_mandatory = mandatory, + data_class=res_class), ) # Subtemplate if field['type'] == 'Subtemplate': @@ -215,9 +225,10 @@ def get_form(json_form, from_dict=False, subtemplate=False): pre = prepend, class_= classes, value=default, - mandatory = mandatory, - subtemplate = resource_class, - subtemplateID = field['import_subtemplate']), ) + get_form(field['import_subtemplate'], subtemplate=True) + data_mandatory = mandatory, + data_class=res_class, + data_subtemplate = resource_class, + data_subtemplateID = field['import_subtemplate']), ) + get_form(field['import_subtemplate'], subtemplate=True) if subtemplate: return params diff --git a/mapping.py b/mapping.py index 41af66d..cc2aa9a 100644 --- a/mapping.py +++ b/mapping.py @@ -142,7 +142,8 @@ def inputToRDF(recordData, userID, stage, graphToClear=None,tpl_form=None): wd.add(( URIRef(base+graph_name+'/'), URIRef('http://dbpedia.org/ontology/currentStatus'), Literal(stage, datatype="http://www.w3.org/2001/XMLSchema#string") )) # GET VALUES FROM FIELDS, MAP THEIR TYPE, TRANSFORM TO RDF - wd.add(( URIRef(base+graph_name), RDF.type, URIRef(resource_class) )) # type of catalogued resource + for res_class in resource_class: + wd.add(( URIRef(base+graph_name), RDF.type, URIRef(res_class) )) # type of catalogued resource # if the user did not specify any disambiguate field is_any_disambiguate = ["yes" for field in fields if field['disambiguate'] == 'True'] @@ -218,27 +219,26 @@ def inputToRDF(recordData, userID, stage, graphToClear=None,tpl_form=None): extractions_array = extractions_dict[recordID] if recordID in extractions_dict else [] for extraction in extractions_array: - extraction_num = next(iter(extraction.keys())) - extraction_type = extraction[extraction_num]['type'] - extraction_url = extraction[extraction_num]['url'] + extraction_num = str(extraction['internalId']) + extraction_type = extraction['metadata']['type'] + extraction_url = extraction['metadata']['url'] extraction_access_keys = False if extraction_type == 'api': - if 'query' in extraction[extraction_num]: + if 'query' in extraction['metadata']: encoded_query = '' add_symbol = '?' - for parameter_key,parameter_value in extraction[extraction_num]['query'].items(): + for parameter_key,parameter_value in extraction['metadata']['query'].items(): encoded_query += add_symbol + parameter_key + '=' + parameter_value add_symbol = '&' extraction_url+=encoded_query - if 'results' in extraction[extraction_num]: - extraction_access_keys = extraction[extraction_num]['results'] + if 'results' in extraction['metadata']: + extraction_access_keys = extraction['metadata']['results'] elif extraction_type == 'sparql': - query = extraction[extraction_num]['query'].replace("'","\"") - print(query) + query = extraction['metadata']['query'].replace("'","\"") encoded_query = urllib.parse.quote(query) extraction_url+="?query="+encoded_query elif extraction_type == 'file': - query = extraction[extraction_num]['query'] + query = extraction['metadata']['query'] query = query.replace("{", "{{ SERVICE {{").replace("}", "}}") if " ." + filter_class_exists = "\n".join([f"FILTER EXISTS {{ ?s a <{cls}> }}" for cls in res_class]) if res_class != None else "" + filter_class_not_exists = f"FILTER (NOT EXISTS {{ ?s a ?other_class FILTER (?other_class NOT IN ({', '.join([f'<{cls}>' for cls in res_class])})) }})" if res_class != None else "" queryRecords = """ PREFIX prov: PREFIX base: <"""+conf.base+"""> - SELECT DISTINCT ?g ?title ?userLabel ?modifierLabel ?date ?stage ?class + SELECT DISTINCT ?g ?title ?userLabel ?modifierLabel ?date ?stage (GROUP_CONCAT(DISTINCT ?class; SEPARATOR="; ") AS ?classes) WHERE { GRAPH ?g { ?s ?p ?o . ?s a ?class . - """ +class_restriction+ """ + """ +filter_class_exists+filter_class_not_exists+ """ OPTIONAL { ?g rdfs:label ?title; prov:wasAttributedTo ?user; prov:generatedAtTime ?date ; ?stage. ?user rdfs:label ?userLabel . OPTIONAL {?g prov:wasInfluencedBy ?modifier. ?modifier rdfs:label ?modifierLabel .} } OPTIONAL {?g rdfs:label ?title; prov:generatedAtTime ?date ; ?stage . } @@ -56,16 +57,19 @@ def getRecords(res_class=None): } FILTER( str(?g) != '"""+conf.base+"""vocabularies/' ) } + GROUP BY ?g ?title ?userLabel ?modifierLabel ?date ?stage """ records = set() sparql = SPARQLWrapper(conf.myEndpoint) sparql.setQuery(queryRecords) sparql.setReturnFormat(JSON) + print("query: \n",queryRecords) results = sparql.query().convert() + print("res: \n",results) for result in results["results"]["bindings"]: - records.add( (result["g"]["value"], result["title"]["value"], result["userLabel"]["value"], result["modifierLabel"]["value"], result["date"]["value"], result["stage"]["value"], result["class"]["value"] )) + records.add( (result["g"]["value"], result["title"]["value"], result["userLabel"]["value"], result["modifierLabel"]["value"], result["date"]["value"], result["stage"]["value"], result["classes"]["value"] )) return records @@ -77,7 +81,7 @@ def getRecordsPagination(page, filterRecords=''): queryRecordsPagination = """ PREFIX prov: PREFIX base: <"""+conf.base+"""> - SELECT DISTINCT ?g ?title ?userLabel ?modifierLabel ?date ?stage ?class + SELECT DISTINCT ?g ?title ?userLabel ?modifierLabel ?date ?stage (GROUP_CONCAT(DISTINCT ?class; SEPARATOR="; ") AS ?classes) WHERE { GRAPH ?g { ?s ?p ?o ; a ?class . @@ -100,6 +104,7 @@ def getRecordsPagination(page, filterRecords=''): FILTER( str(?g) != '"""+conf.base+"""vocabularies/' ) } + GROUP BY ?g ?title ?userLabel ?modifierLabel ?date ?stage ORDER BY DESC(?date) LIMIT """+conf.pagination+""" OFFSET """+offset+""" @@ -111,7 +116,7 @@ def getRecordsPagination(page, filterRecords=''): sparql.setReturnFormat(JSON) results = sparql.query().convert() for result in results["results"]["bindings"]: - records.append( (result["g"]["value"], result["title"]["value"], result["userLabel"]["value"], result["modifierLabel"]["value"], result["date"]["value"], result["stage"]["value"] , result["class"]["value"] )) + records.append( (result["g"]["value"], result["title"]["value"], result["userLabel"]["value"], result["modifierLabel"]["value"], result["date"]["value"], result["stage"]["value"] , result["classes"]["value"].split("; ") )) return records @@ -145,7 +150,9 @@ def getCountings(filterRecords=''): def countAll(res_class=None, exclude_unpublished=False): - class_restriction = "" if res_class is None else "?s a <"+res_class+"> ." + filter_class_exists = "\n".join([f"FILTER EXISTS {{ ?s a <{cls}> }}" for cls in res_class]) if res_class != None else "" + filter_class_not_exists = f"FILTER (NOT EXISTS {{ ?s a ?other_class FILTER (?other_class NOT IN ({', '.join([f'<{cls}>' for cls in res_class])})) }})" if res_class != None else "" + exclude = "" if exclude_unpublished is False \ else "?g ?anyValue . FILTER (isLiteral(?anyValue) && lcase(str(?anyValue))= 'published') ." countall = """ @@ -154,7 +161,7 @@ def countAll(res_class=None, exclude_unpublished=False): SELECT (COUNT(DISTINCT ?g) AS ?count) WHERE { GRAPH ?g { ?s ?p ?o . - """+class_restriction+""" + """+filter_class_exists+filter_class_not_exists+""" } """+exclude+""" FILTER( str(?g) != '"""+conf.base+"""vocabularies/' && !CONTAINS(STR(?g), "/extraction-")) . @@ -194,11 +201,11 @@ def getRecordCreator(graph_name): def getClass(res_uri): """ get the class of a resource given the URI""" - q = """ SELECT DISTINCT ?class WHERE {<"""+res_uri+"""> a ?class}""" + q = """ SELECT DISTINCT (GROUP_CONCAT(DISTINCT ?class; SEPARATOR="; ") AS ?classes) WHERE {<"""+res_uri+"""> a ?class}""" res_class = [] results = hello_blazegraph(q) for result in results["results"]["bindings"]: - res_class.append(result["class"]["value"]) + res_class.append(result["classes"]["value"].split("; ")) return res_class[0] if len(res_class) > 0 else "" def getData(graph,res_template): @@ -237,6 +244,7 @@ def disambiguate_pattern(properties,fields,k): properties_dict = {} patterns = [] res_class = getClass(graph[:-1]) + class_patterns = ".".join(['''?subject a <'''+single_class+'''>''' for single_class in res_class]) # check duplicate properties for field in fields: @@ -269,7 +277,7 @@ def disambiguate_pattern(properties,fields,k): WHERE { <'''+graph+'''> rdfs:label ?graph_title ; ?stage GRAPH <'''+graph+'''> - { ?subject a <'''+res_class+'''> . + { '''+class_patterns+'''. '''+patterns_string+''' OPTIONAL {?subject schema:keywords ?keywords . ?keywords rdfs:label ?keywords_label . } } } @@ -424,7 +432,6 @@ def retrieve_extractions(res_uri_list): """ . OPTIONAL { ?extraction_entity_"""+query_var_id+""" rdfs:comment ?extraction_comment_"""+query_var_id+"""}""" q = """PREFIX schema: PREFIX prov: SELECT DISTINCT * WHERE {""" +query_pattern+ """}""" - print("\n ---------- \n query: \n", q, "\n ---------- \n") results = hello_blazegraph(q) res_dict[uri_id] = [] @@ -451,47 +458,47 @@ def retrieve_extractions(res_uri_list): link = metadata['link'] comment = metadata['comment'] - res_dict[uri_id].append({graph: {}}) + res_dict[uri_id].append({"graph":graph, "metadata": {}}) # Api metadata if comment: - res_dict[uri_id][-1][graph]['results'] = json.loads(comment.replace("'",'"')) - res_dict[uri_id][-1][graph]['type'] = 'api' + res_dict[uri_id][-1]["metadata"]['results'] = json.loads(comment.replace("'",'"')) + res_dict[uri_id][-1]["metadata"]['type'] = 'api' url, parameters = link.rsplit('?', 1) query = {p.split("=")[0]: p.split("=")[1] for p in parameters.split("&")} - res_dict[uri_id][-1][graph]['url'] = url - res_dict[uri_id][-1][graph]['query'] = query + res_dict[uri_id][-1]["metadata"]['url'] = url + res_dict[uri_id][-1]["metadata"]['query'] = query # File metadata elif link.startswith(conf.sparqlAnythingEndpoint): - res_dict[uri_id][-1][graph]['type'] = 'file' + res_dict[uri_id][-1]["metadata"]['type'] = 'file' query = link.split("?query=")[1] url_match = pattern.search(query) if url_match: url = url_match.group(1) else: url = '' - res_dict[uri_id][-1][graph]['url'] = url - res_dict[uri_id][-1][graph]['query'] = query + res_dict[uri_id][-1]["metadata"]['url'] = url + res_dict[uri_id][-1]["metadata"]['query'] = query # Sparql metadata else: - res_dict[uri_id][-1][graph]['type'] = 'sparql' + res_dict[uri_id][-1]["metadata"]['type'] = 'sparql' print("LINK,", link) url, query = link.split("?query=") - res_dict[uri_id][-1][graph]['url'] = url - res_dict[uri_id][-1][graph]['query'] = query + res_dict[uri_id][-1]["metadata"]['url'] = url + res_dict[uri_id][-1]["metadata"]['query'] = query # Retrieve the keywords populating each extraction graph for n in range(len(res_dict[uri_id])): - graph_uri = list(res_dict[uri_id][n].keys())[0] + graph_uri = res_dict[uri_id][n]["graph"] idx = graph_uri.split('/extraction-')[-1][:-1] + res_dict[uri_id][n]["internalId"] = idx retrieve_graph = """PREFIX rdfs: SELECT DISTINCT ?uri ?label WHERE {GRAPH <"""+graph_uri+"""> { ?uri rdfs:label ?label . }}""" graph_results = hello_blazegraph(retrieve_graph)["results"]["bindings"] - res_dict[uri_id][n][graph_uri]['output'] = [{"uri": {"value": urllib.parse.unquote(res["uri"]["value"]), "type":res["uri"]["type"]}, "label": {"value": res["label"]["value"], "type":res["label"]["type"]}} for res in graph_results] - res_dict[uri_id][n][idx] = res_dict[uri_id][n].pop(graph_uri) + res_dict[uri_id][n]["metadata"]['output'] = [{"uri": {"value": urllib.parse.unquote(res["uri"]["value"]), "type":res["uri"]["type"]}, "label": {"value": res["label"]["value"], "type":res["label"]["type"]}} for res in graph_results] print("#### Extractions dictionary: ", res_dict) diff --git a/resource_templates/template_list.json b/resource_templates/template_list.json index bd944f6..e2b3fb6 100644 --- a/resource_templates/template_list.json +++ b/resource_templates/template_list.json @@ -1 +1 @@ -[{"name": "Example template", "short_name": "example_template", "type": "http://example.org/ExampleClass", "hidden": "False", "template": "resource_templates/template-example_template.json"}] \ No newline at end of file +[{"name": "Example template", "short_name": "example_template", "type": ["http://example.org/ExampleClass"], "hidden": "False", "template": "resource_templates/template-example_template.json"}] \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index 5dedf37..beb322d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -119,6 +119,19 @@ button:hover { margin-top: 15px; } +.modal-bg { + z-index: 1021; + display: block; + width: 100%; + left: 0%; + position: fixed; + top:0%; + height: 100%; + background-color: rgba(0, 0, 0); + opacity: 0; + animation: fadeIn 1s forwards; +} + /* button in backend*/ .menu_btn { border: none !important; @@ -538,12 +551,14 @@ article p { } .delete, -.delete_template { +.delete_template, +.discard { background: rgba(255, 0, 0, 1) !important; } .delete:hover, -.delete_template:hover { +.delete_template:hover, +.discard:hover { background-color: rgba(255, 10, 10, 1) !important; } @@ -648,7 +663,6 @@ th { text-transform: uppercase; margin-top: 0.5em; margin-bottom: 5%; - } .buttonsSection { @@ -782,7 +796,6 @@ input[type='radio'] { } input[type='text'], -input[type='textarea'], input[type='date'], input[type='month'], textarea { @@ -796,7 +809,6 @@ textarea { } input[type='text']:focus, -input[type='textarea']:focus, input[type='date']:focus, input[type='month']:focus, textarea:focus { @@ -807,6 +819,22 @@ textarea:focus { padding-left: 0.6em !important; } +input[type='text'].error-input, +textarea.error-input { + animation: border-error 0.5s linear forwards; +} + +@-webkit-keyframes border-error { + from { border-bottom: solid 2px rgba(100, 23, 180, 1); } + to { border-bottom: solid 2px rgb(200, 22, 22); } +} + +@keyframes border-error { + from { border-bottom: solid 2px rgba(100, 23, 180, 1); } + to { border-bottom: solid 2px rgb(200, 22, 22); } +} + + input[value="Save"], input[value="Start review process"] { margin: 2em auto; @@ -839,6 +867,21 @@ input.following_checkbox { padding-right: 20px; } +.error-message { + padding: 0px 10px ; + display: block; + color: rgb(200, 0, 0); + background-color: rgba(253, 168, 168, 0.7); + height: 0px; + animation: upToDown 0.3s ease-in forwards; + font-size: 14px; +} + +i.fa-exclamation-triangle { + color: rgb(200, 0, 0); + padding-right: 5px; +} + i.fa-info-circle { color: rgba(100, 23, 180, 1); display: block; @@ -1576,13 +1619,91 @@ section.dropdown-menu.gradient_dropbox.show input[type='text'] { opacity: 70%; } -#selectTemplateClass .btn-dark { - margin-top: 15% !important; +#selectTemplateClassModal { + display: none; +} + +#selectTemplateClassModal.open-modal { + display: block; + position: fixed; + background-color: #F5F6FA !important; + z-index: 1022; + width: 600px; + height: 550px; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + overflow-y: auto !important; + position: fixed; + max-height: 90vh; /* Altezza massima relativa all'altezza della finestra */ + overflow-y: auto; /* Abilita lo scroll verticale */ +} + +#selectTemplateClassModal > form > section:first-of-type { + width: 100%; + padding: 15px 10px 10px 18px; + border-bottom: 3px solid rgba(100, 23, 180, 1); + color: rgba(100, 23, 180, 1); + font-size: 22px; + background-color: #dbdce3; +} + +#selectTemplateClassModal .modal-input { + width: 86%; + margin-left: 7%; + background-color: inherit; +} + +#selectTemplateClassModal .modal-input .label { + display: inline-block; + margin-bottom: 5px; + font-size: 16px; +} + +#selectTemplateClassModal .modal-input .comment { + font-size: 14px; +} + +#selectTemplateClassModal .modal-input input { + margin-top: 10px; +} + +#selectTemplateClassModal .modal-input:is(:nth-of-type(2), :nth-of-type(3)) { + margin-top: 40px; +} + +#selectTemplateClassModal .fa-times { + position: absolute; + right: 20px; + margin-top: 6px; + color: rgb(100, 23, 180); + font-size: 0.9em; + cursor: pointer; } -#selectTemplateClassButton { +#selectTemplateClass button { border: none; outline: none; + display: inline-block; + margin: 10px 10px 0px 0px !important; + left: 60%; + position: relative; +} + +#uri-container { + height: 100px; + max-height: 100px; + overflow-y: auto; + border-bottom: 1pt #acacac solid; +} + +#uri-container::before { + content: 'press return to add a URI'; + font-size: 0.7em; + color: grey; + display: block; + margin-left: 20px; } button#showTemplates { @@ -2018,8 +2139,32 @@ button#showTemplates { margin-top: 0.7em; } +.add-graph-button { + text-decoration-line: underline; + text-decoration-style: dotted; + color: rgba(100, 23, 180, 0.7); +} + +.add-graph-button:hover { + text-decoration-line: underline; + text-decoration-style: dotted; + color: rgba(100, 23, 180, 0.9); + cursor: pointer; +} + .imported_graphs { margin-top: 0.7em; + list-style-type: none; + color: #333333bd; + padding-left: 20px; +} + +.imported_graphs label { + padding-left: 5px; +} + +.imported_graphs li:not(:last-of-type) { + margin-bottom: 20px; } .import-form + .block_field { @@ -2046,7 +2191,7 @@ button#showTemplates { margin-top: 0.5em; } -.fa-times { +.extraction-form-div .fa-times { position: absolute; right: 50px; margin-top: 12px; @@ -2326,3 +2471,17 @@ input[subtemplate]+.fa-plus-circle { .link_btn, .btn {white-space: nowrap;} .add_fields .link_btn {display: inline-block; margin-bottom: 8px} +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 0.8; + } +} + +@keyframes upToDown { + from {height: 0px; padding-top:0px} + to {height: 32px; padding-top:5px} +} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index 4785aa1..9dd7e68 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -18,6 +18,54 @@ $(document).ready(function() { } }); + // create a new TEMPLATE + $("#selectTemplateClassButton").on('click', function() { + // show modal + $("#selectTemplateClassModal").toggleClass('open-modal'); + $('body').append($("