added new folder and ran tests
This commit is contained in:
+16
-17
@@ -1,21 +1,20 @@
|
|||||||
version: '3.7'
|
|
||||||
services:
|
services:
|
||||||
# app:
|
app:
|
||||||
# image: visual_critical_discourse_analysis:dev
|
image: visual_critical_discourse_analysis:dev
|
||||||
# container_name: visual_critical_discourse_analysis
|
container_name: visual_critical_discourse_analysis
|
||||||
# build:
|
build:
|
||||||
# context: .
|
context: .
|
||||||
# dockerfile: Dockerfile
|
dockerfile: ./web_ui/Dockerfile
|
||||||
# env_file:
|
env_file:
|
||||||
# - local.env
|
- local.env
|
||||||
# environment:
|
environment:
|
||||||
# - ENV=DEV
|
- ENV=DEV
|
||||||
# ports:
|
ports:
|
||||||
# - 8050:8050
|
- 8050:8050
|
||||||
# networks:
|
networks:
|
||||||
# - backend
|
- backend
|
||||||
# depends_on:
|
depends_on:
|
||||||
# - mongo
|
- mongo
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:latest
|
image: mongo:latest
|
||||||
container_name: mongo
|
container_name: mongo
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
version: '3.7'
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: visual_critical_discourse_analysis:dev
|
image: visual_critical_discourse_analysis:dev
|
||||||
container_name: visual_critical_discourse_analysis
|
container_name: visual_critical_discourse_analysis
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: ./web_ui/Dockerfile
|
||||||
env_file:
|
env_file:
|
||||||
- server.env
|
- server.env
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# build stage
|
||||||
|
FROM python:3.12-slim-bookworm as BUILDER
|
||||||
|
|
||||||
|
# set environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PIP_NO_CACHE_DIR=off \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=ON \
|
||||||
|
PIP_DEFAULT_TIMEOUT=100 \
|
||||||
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
POETRY_HOME=/etc/poetry \
|
||||||
|
POETRY_VERSION=1.7.1 \
|
||||||
|
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
||||||
|
POETRY_VIRTUALENVS_CREATE=1 \
|
||||||
|
POETRY_NO_INTERACTION=1 \
|
||||||
|
POETRY_CACHE_DIR=/tmp/poetry_cache \
|
||||||
|
APP_HOME=/home/app
|
||||||
|
|
||||||
|
# update system
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# install poetry
|
||||||
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
ENV PATH="${POETRY_HOME}/bin:$PATH"
|
||||||
|
|
||||||
|
# install runtime dependencies
|
||||||
|
WORKDIR ${APP_HOME}
|
||||||
|
COPY poetry.lock pyproject.toml ./
|
||||||
|
RUN --mount=type=cache,target=${POETRY_CACHE_DIR} poetry install --without model
|
||||||
|
|
||||||
|
# final stage
|
||||||
|
FROM python:3.12-slim-bookworm
|
||||||
|
|
||||||
|
# copy virtualenv made by poetry
|
||||||
|
ENV APP_HOME=/home/app \
|
||||||
|
VIRTUAL_ENV=/home/app/.venv
|
||||||
|
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||||
|
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"
|
||||||
|
|
||||||
|
# create home directory and app user
|
||||||
|
RUN mkdir -p /home/app && \
|
||||||
|
addgroup --system app && \
|
||||||
|
adduser --system --group app
|
||||||
|
|
||||||
|
# add code while changing ownership
|
||||||
|
WORKDIR $APP_HOME
|
||||||
|
COPY --chown=app:app ./core ./core
|
||||||
|
COPY --chown=app:app ./src ./src
|
||||||
|
|
||||||
|
# change to the app user
|
||||||
|
USER app
|
||||||
|
|
||||||
|
ENTRYPOINT [ "gunicorn", "src.main:server", "-b", "0.0.0.0:8050" ]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .app import app
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash import ALL
|
||||||
|
from dash import Dash
|
||||||
|
from dash import Input
|
||||||
|
from dash import Output
|
||||||
|
from dash import State
|
||||||
|
from dash_auth import BasicAuth
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
from .layout import app_layout
|
||||||
|
from core.database import connect
|
||||||
|
from core.database import count_documents
|
||||||
|
from core.database import get_visual_communication
|
||||||
|
from core.database import NoDocumentFoundException
|
||||||
|
from core.database import upsert_annotation
|
||||||
|
from core.database import upsert_visual_communication
|
||||||
|
from core.database import VisualCommunication
|
||||||
|
from core.dto import ModelData
|
||||||
|
|
||||||
|
# setup app
|
||||||
|
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||||
|
app.title = 'visual critical discourse analysis'.title()
|
||||||
|
app.layout = app_layout
|
||||||
|
server = app.server
|
||||||
|
|
||||||
|
# setup authentication
|
||||||
|
AUTH_DICT = {
|
||||||
|
os.getenv('DASH_AUTH_USERNAME'): os.getenv('DASH_AUTH_PASSWORD'),
|
||||||
|
}
|
||||||
|
BasicAuth(app, AUTH_DICT)
|
||||||
|
|
||||||
|
# connect to database
|
||||||
|
collection, db, client = connect()
|
||||||
|
|
||||||
|
|
||||||
|
# define callbacks
|
||||||
|
@app.callback(
|
||||||
|
Output('alert-element', 'is_open'),
|
||||||
|
Output('alert-element', 'children'),
|
||||||
|
Input('alert-message', 'data'),
|
||||||
|
)
|
||||||
|
def show_alert(
|
||||||
|
msg: str | None,
|
||||||
|
):
|
||||||
|
if msg is None or msg == '':
|
||||||
|
return False, ''
|
||||||
|
logging.info(f'updated alert message: {msg}')
|
||||||
|
return True, msg
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('success-element', 'is_open'),
|
||||||
|
Output('success-element', 'children'),
|
||||||
|
Input('success-message', 'data'),
|
||||||
|
)
|
||||||
|
def show_success(
|
||||||
|
msg: str | None,
|
||||||
|
):
|
||||||
|
if msg is None or msg == '':
|
||||||
|
return False, ''
|
||||||
|
logging.info(f"updated success message: {msg}")
|
||||||
|
return True, msg
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('progress-bar', 'value'),
|
||||||
|
Output('progress-bar', 'max'),
|
||||||
|
Output('progress-bar', 'label'),
|
||||||
|
Input('next-button', 'n_clicks'),
|
||||||
|
Input('upload-data', 'filename'),
|
||||||
|
)
|
||||||
|
def update_progress_bar(
|
||||||
|
n_clicks: int,
|
||||||
|
filename_list: list[str] | None,
|
||||||
|
):
|
||||||
|
logging.info('began updating progress bar')
|
||||||
|
global collection
|
||||||
|
# get values from database
|
||||||
|
num_total = count_documents(
|
||||||
|
collection=collection,
|
||||||
|
only_with_annotation=False,
|
||||||
|
)
|
||||||
|
num_handled = count_documents(
|
||||||
|
collection=collection,
|
||||||
|
only_with_annotation=True,
|
||||||
|
)
|
||||||
|
limit = int(num_total/20)
|
||||||
|
label_str = f"{num_handled}/{num_total}" if num_handled >= limit else ''
|
||||||
|
return num_handled, num_total, label_str
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('success-message', 'data', allow_duplicate=True),
|
||||||
|
Output('alert-message', 'data', allow_duplicate=True),
|
||||||
|
Input('upload-data', 'contents'),
|
||||||
|
State('upload-data', 'filename'),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
)
|
||||||
|
def upload_images(
|
||||||
|
content_list: list[str] | None,
|
||||||
|
filename_list: list[str] | None,
|
||||||
|
):
|
||||||
|
logging.info('began uploading images')
|
||||||
|
global collection
|
||||||
|
# stop early if possible
|
||||||
|
if content_list is None or filename_list is None:
|
||||||
|
logging.warning('content_list is None -> nothing to upload')
|
||||||
|
return None, 'nothing selected to upload'.title()
|
||||||
|
# build list of visual communication
|
||||||
|
visual_communication_list = []
|
||||||
|
for content, filename in zip(content_list, filename_list):
|
||||||
|
try:
|
||||||
|
image = VisualCommunication.decode_image(content)
|
||||||
|
vis_com = VisualCommunication(
|
||||||
|
name=filename,
|
||||||
|
image=image,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logging.warning(
|
||||||
|
(
|
||||||
|
'failed creating VisualCommunication object '
|
||||||
|
f"from file {filename}"
|
||||||
|
),
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
visual_communication_list.append(vis_com)
|
||||||
|
# upsert documents
|
||||||
|
success = upsert_visual_communication(
|
||||||
|
collection=collection,
|
||||||
|
visual_communication_list=visual_communication_list,
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
logging.info(f"succesfully uploaded {len(filename_list)} images")
|
||||||
|
return 'successfully uploaded images'.title(), None
|
||||||
|
logging.warning('failed inserting images into database')
|
||||||
|
return None, 'failed uploading images'.title()
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('alert-message', 'data', allow_duplicate=True),
|
||||||
|
Output('vis-com-name', 'data'),
|
||||||
|
Output('image-container', 'src'),
|
||||||
|
Output({'type': 'annotation', 'index': ALL}, 'value'),
|
||||||
|
Input('next-button', 'n_clicks'),
|
||||||
|
State('vis-com-name', 'data'),
|
||||||
|
State('image-container', 'src'),
|
||||||
|
State({'type': 'annotation', 'index': ALL}, 'id'),
|
||||||
|
State({'type': 'annotation', 'index': ALL}, 'value'),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
)
|
||||||
|
def cycle_visual_communication_data(
|
||||||
|
n_clicks: int,
|
||||||
|
vis_com_name: str,
|
||||||
|
image_src: str,
|
||||||
|
annotation_keys: list,
|
||||||
|
annotation_values: list,
|
||||||
|
):
|
||||||
|
logging.info('began cycling visual communication data')
|
||||||
|
global collection
|
||||||
|
# prepare default response
|
||||||
|
response = [
|
||||||
|
'',
|
||||||
|
vis_com_name,
|
||||||
|
image_src,
|
||||||
|
annotation_values,
|
||||||
|
]
|
||||||
|
# check if next-button clicked
|
||||||
|
if n_clicks == 0:
|
||||||
|
logging.info('stopping early: next-button has not yet been clicked')
|
||||||
|
return response
|
||||||
|
# check if visual communication name is set
|
||||||
|
if len(vis_com_name) > 0:
|
||||||
|
logging.info('saving annotations to database: %s', vis_com_name)
|
||||||
|
try:
|
||||||
|
# extract option keys
|
||||||
|
annotation_keys = [
|
||||||
|
elem['index']
|
||||||
|
for elem in annotation_keys
|
||||||
|
]
|
||||||
|
# ensure all options are set
|
||||||
|
logging.info(annotation_keys)
|
||||||
|
for option, value in zip(annotation_keys, annotation_values):
|
||||||
|
if value is None:
|
||||||
|
raise ValueError(f"{option} is not set")
|
||||||
|
# prepare data to save
|
||||||
|
annotation_keys = [
|
||||||
|
elem.replace(' ', '_')
|
||||||
|
for elem
|
||||||
|
in annotation_keys
|
||||||
|
]
|
||||||
|
annotation_values = [
|
||||||
|
elem.replace(' ', '_').lower()
|
||||||
|
for elem
|
||||||
|
in annotation_values
|
||||||
|
]
|
||||||
|
annotation_map = {
|
||||||
|
key: value
|
||||||
|
for key, value
|
||||||
|
in zip(annotation_keys, annotation_values)
|
||||||
|
}
|
||||||
|
# instantiate ModelOutputs object
|
||||||
|
annotations = ModelData.from_annotations(**annotation_map)
|
||||||
|
# save data to database
|
||||||
|
upsert_annotation(
|
||||||
|
collection=collection,
|
||||||
|
vis_com_name=vis_com_name,
|
||||||
|
annotations=annotations,
|
||||||
|
)
|
||||||
|
except (ValueError, ValidationError) as exc:
|
||||||
|
msg = f"failed saving annotation: {exc}"
|
||||||
|
logging.warning(msg)
|
||||||
|
response[0] = msg
|
||||||
|
return tuple(response)
|
||||||
|
# get new visual communication
|
||||||
|
logging.info('trying to get new visual communication')
|
||||||
|
try:
|
||||||
|
# get data
|
||||||
|
vis_com = get_visual_communication(
|
||||||
|
collection=collection,
|
||||||
|
with_annotation=False,
|
||||||
|
)
|
||||||
|
# set variables
|
||||||
|
vis_com_name = vis_com.name
|
||||||
|
image_src = vis_com.webencoded_image()
|
||||||
|
if vis_com.prediction is not None:
|
||||||
|
# TODO: update to use optional predictions
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# reset annotations
|
||||||
|
annotation_values = [None for elem in annotation_values]
|
||||||
|
except NoDocumentFoundException:
|
||||||
|
msg = 'no unannotated data in database'
|
||||||
|
logging.warning(msg)
|
||||||
|
response[0] = msg
|
||||||
|
return tuple(response)
|
||||||
|
else:
|
||||||
|
response[1] = vis_com_name
|
||||||
|
response[2] = image_src
|
||||||
|
response[3] = annotation_values
|
||||||
|
logging.info('finished getting visual communication: %s', vis_com_name)
|
||||||
|
return tuple(response)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .layout import app_layout
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
|
||||||
|
alerts_element = html.Div(
|
||||||
|
children=[
|
||||||
|
dbc.Alert(
|
||||||
|
children='',
|
||||||
|
id='alert-element',
|
||||||
|
dismissable=True,
|
||||||
|
fade=False,
|
||||||
|
is_open=False,
|
||||||
|
),
|
||||||
|
dbc.Alert(
|
||||||
|
children='',
|
||||||
|
id='success-element',
|
||||||
|
is_open=False,
|
||||||
|
duration=1000,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
|
||||||
|
from .image import image_element
|
||||||
|
from .inputs import inputs_element
|
||||||
|
|
||||||
|
|
||||||
|
body_element = dmc.Container(
|
||||||
|
fluid=True,
|
||||||
|
children=[
|
||||||
|
dmc.Grid(
|
||||||
|
grow=True,
|
||||||
|
children=[
|
||||||
|
dmc.Col([image_element], span=5),
|
||||||
|
dmc.Col([inputs_element], span=7),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
from dash import dcc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
title_element = dmc.Center(
|
||||||
|
children=[
|
||||||
|
html.H2(
|
||||||
|
children=[
|
||||||
|
'Visual Critical Discourse Analysis Tool',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
progress_bar_element = dbc.Progress(
|
||||||
|
id='progress-bar',
|
||||||
|
max=100,
|
||||||
|
value=0,
|
||||||
|
color='lime',
|
||||||
|
label='',
|
||||||
|
style={
|
||||||
|
'width': '400px',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
upload_button_element = dcc.Upload(
|
||||||
|
children=[
|
||||||
|
dmc.Button(
|
||||||
|
'upload images'.title(),
|
||||||
|
id='upload-button',
|
||||||
|
color='lime',
|
||||||
|
n_clicks=0,
|
||||||
|
variant='outline',
|
||||||
|
radius='sm',
|
||||||
|
size='md',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
multiple=True,
|
||||||
|
id='upload-data',
|
||||||
|
accept='image/png,image/jpeg,image/jpg',
|
||||||
|
)
|
||||||
|
|
||||||
|
header_element = dmc.Header(
|
||||||
|
height=80,
|
||||||
|
children=[
|
||||||
|
dmc.Group(
|
||||||
|
children=[
|
||||||
|
title_element,
|
||||||
|
progress_bar_element,
|
||||||
|
upload_button_element,
|
||||||
|
],
|
||||||
|
position='apart',
|
||||||
|
style={
|
||||||
|
'margin': '10px',
|
||||||
|
'padding': '10px',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
# read init img
|
||||||
|
init_img_path = Path(__file__).parent / 'init_img.png'
|
||||||
|
with open(init_img_path.absolute(), 'rb') as fh:
|
||||||
|
init_img_enc = b64encode(fh.read()).decode('utf-8')
|
||||||
|
# generate init img string
|
||||||
|
init_img_src = f"data:image/png;base64, {init_img_enc}"
|
||||||
|
|
||||||
|
image_element = dmc.Center(
|
||||||
|
html.Img(
|
||||||
|
style={
|
||||||
|
'width': '100%',
|
||||||
|
},
|
||||||
|
id='image-container',
|
||||||
|
src=init_img_src,
|
||||||
|
),
|
||||||
|
)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
|
||||||
|
from .labels import labels_element
|
||||||
|
|
||||||
|
next_button = dmc.Button(
|
||||||
|
'next'.title(),
|
||||||
|
id='next-button',
|
||||||
|
n_clicks=0,
|
||||||
|
fullWidth=True,
|
||||||
|
color='lime',
|
||||||
|
radius='sm',
|
||||||
|
size='md',
|
||||||
|
style={
|
||||||
|
'height': '50px',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs_element = dmc.SimpleGrid(
|
||||||
|
children=[
|
||||||
|
labels_element,
|
||||||
|
next_button,
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
from dash import dcc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
from core.dto import AngleData
|
||||||
|
from core.dto import ContactData
|
||||||
|
from core.dto import DistanceData
|
||||||
|
from core.dto import FramingData
|
||||||
|
from core.dto import InformationValueData
|
||||||
|
from core.dto import ModalityColorData
|
||||||
|
from core.dto import ModalityDepthData
|
||||||
|
from core.dto import ModalityLightingData
|
||||||
|
from core.dto import PointOfViewData
|
||||||
|
from core.dto import SalienceData
|
||||||
|
from core.dto import VisualSyntaxData
|
||||||
|
|
||||||
|
|
||||||
|
def generate_visual_syntax_options_map():
|
||||||
|
"""Generate map of titles and options for visual syntax labels."""
|
||||||
|
options_map = {}
|
||||||
|
# add experiential labels
|
||||||
|
options_map['visual syntax'] = VisualSyntaxData.list_fields()
|
||||||
|
return options_map
|
||||||
|
|
||||||
|
|
||||||
|
def generate_interpersonal_options_map():
|
||||||
|
"""Generate map of titles and options for interpersonal labels."""
|
||||||
|
options_map = {}
|
||||||
|
# add interpersonal labels
|
||||||
|
options_map['contact'] = ContactData.list_fields()
|
||||||
|
options_map['angle'] = AngleData.list_fields()
|
||||||
|
options_map['point of view'] = PointOfViewData.list_fields()
|
||||||
|
options_map['distance'] = DistanceData.list_fields()
|
||||||
|
options_map['modality lighting'] = ModalityLightingData.list_fields()
|
||||||
|
options_map['modality color'] = ModalityColorData.list_fields()
|
||||||
|
options_map['modality depth'] = ModalityDepthData.list_fields()
|
||||||
|
return options_map
|
||||||
|
|
||||||
|
|
||||||
|
def generate_textual_options_map():
|
||||||
|
"""Generate map of titles and options for textual labels."""
|
||||||
|
options_map = {}
|
||||||
|
# add textual labels
|
||||||
|
options_map['information value'] = InformationValueData.list_fields()
|
||||||
|
options_map['framing'] = FramingData.list_fields()
|
||||||
|
options_map['salience'] = SalienceData.list_fields()
|
||||||
|
return options_map
|
||||||
|
|
||||||
|
|
||||||
|
# prepare experiential container
|
||||||
|
experiential_map = generate_visual_syntax_options_map()
|
||||||
|
experiential_container = dmc.Col(
|
||||||
|
children=[
|
||||||
|
html.H4('experiential'.title()),
|
||||||
|
], span=5,
|
||||||
|
)
|
||||||
|
for title, options in experiential_map.items():
|
||||||
|
id_dict = {'type': 'annotation', 'index': title.replace('_', '-')}
|
||||||
|
experiential_container.children.append(
|
||||||
|
dmc.Container([
|
||||||
|
html.B(title.title()),
|
||||||
|
dcc.RadioItems(
|
||||||
|
options=[text.replace('_', ' ') for text in options],
|
||||||
|
id=id_dict,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
# prepare interpersonal container
|
||||||
|
interpersonal_map = generate_interpersonal_options_map()
|
||||||
|
interpersonal_container = dmc.Col(
|
||||||
|
children=[
|
||||||
|
html.H4('interpersonal'.title()),
|
||||||
|
], span=3,
|
||||||
|
)
|
||||||
|
for title, options in interpersonal_map.items():
|
||||||
|
id_dict = {'type': 'annotation', 'index': title.replace('_', '-')}
|
||||||
|
interpersonal_container.children.append(
|
||||||
|
dmc.Container([
|
||||||
|
html.B(title.title()),
|
||||||
|
dcc.RadioItems(
|
||||||
|
options=[text.replace('_', ' ') for text in options],
|
||||||
|
id=id_dict,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
# prepare textual container
|
||||||
|
textual_map = generate_textual_options_map()
|
||||||
|
textual_container = dmc.Col(
|
||||||
|
children=[
|
||||||
|
html.H4('textual'.title()),
|
||||||
|
], span=4,
|
||||||
|
)
|
||||||
|
for title, options in textual_map.items():
|
||||||
|
id_dict = {'type': 'annotation', 'index': title.replace('_', '-')}
|
||||||
|
textual_container.children.append(
|
||||||
|
dmc.Container([
|
||||||
|
html.B(title.title()),
|
||||||
|
dcc.RadioItems(
|
||||||
|
options=[text.replace('_', ' ') for text in options],
|
||||||
|
id=id_dict,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
labels_element = dmc.Grid(
|
||||||
|
children=[
|
||||||
|
experiential_container,
|
||||||
|
interpersonal_container,
|
||||||
|
textual_container,
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dash_mantine_components as dmc
|
||||||
|
|
||||||
|
from .alerts import alerts_element
|
||||||
|
from .body import body_element
|
||||||
|
from .header import header_element
|
||||||
|
from .stores import stores_element
|
||||||
|
|
||||||
|
|
||||||
|
app_layout = dmc.MantineProvider(
|
||||||
|
theme={
|
||||||
|
'fontFamily': '"Inter", sans-serif',
|
||||||
|
'components': {
|
||||||
|
'NavLink': {
|
||||||
|
'styles': {
|
||||||
|
'label': {
|
||||||
|
'color': '#c2c7d0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children=[
|
||||||
|
stores_element,
|
||||||
|
alerts_element,
|
||||||
|
dmc.Container(
|
||||||
|
children=[
|
||||||
|
header_element,
|
||||||
|
body_element,
|
||||||
|
],
|
||||||
|
fluid=True,
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dash import dcc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
|
||||||
|
stores_element = html.Div(
|
||||||
|
children=[
|
||||||
|
dcc.Store(id='alert-message', data=''),
|
||||||
|
dcc.Store(id='success-message', data=''),
|
||||||
|
dcc.Store(id='vis-com-name', data=''),
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from app import app
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# prepare optional local setup
|
||||||
|
env_path = Path(__file__).parent.parent.parent / 'local.env'
|
||||||
|
assert env_path.exists()
|
||||||
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
# ensure env vars set
|
||||||
|
necesasary_var_list = {
|
||||||
|
'MONGO_HOST',
|
||||||
|
'MONGO_DB',
|
||||||
|
'MONGO_COLLECTION',
|
||||||
|
'MONGO_USER',
|
||||||
|
'MONGO_PASSWORD',
|
||||||
|
'DASH_AUTH_USERNAME',
|
||||||
|
'DASH_AUTH_PASSWORD',
|
||||||
|
}
|
||||||
|
for env_var in necesasary_var_list:
|
||||||
|
# ensure env var set
|
||||||
|
assert (
|
||||||
|
env_var in os.environ
|
||||||
|
), (
|
||||||
|
f"environment variable not set: {env_var}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# setup logging stream handler
|
||||||
|
FMT = (
|
||||||
|
'%(asctime)s | '
|
||||||
|
'%(levelname)s | '
|
||||||
|
'%(filename)s | '
|
||||||
|
'%(funcName)s | '
|
||||||
|
'%(message)s'
|
||||||
|
)
|
||||||
|
DATEFMT = '%Y-%m-%d %H:%M:%S'
|
||||||
|
logging.basicConfig(format=FMT, datefmt=DATEFMT, level=logging.INFO)
|
||||||
|
|
||||||
|
logging.info('initialized app')
|
||||||
|
server = app.server
|
||||||
|
server.config.update(SECRET_KEY=os.urandom(24))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# prepare local env vars
|
||||||
|
os.environ['MONGO_HOST'] = 'localhost'
|
||||||
|
# run app
|
||||||
|
app.run(debug=True)
|
||||||
|
logging.info('started app')
|
||||||
Reference in New Issue
Block a user