unisi 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
unisi/tables.py CHANGED
@@ -1,75 +1,199 @@
1
1
  from .guielements import Gui
2
+ from .common import references, set_defaults, Warning, pretty4
3
+ from .dbelements import Dblist
2
4
 
3
- def accept_cell_value(table, val):
4
- value, position = val
5
+ relation_mark = 'Ⓡ'
6
+ exclude_mark = '✘'
7
+
8
+ def iterate(iter, times):
9
+ for i, val in enumerate(iter):
10
+ if i == times:
11
+ return val
12
+
13
+ def accept_cell_value(table, dval):
14
+ value = dval['value']
5
15
  if not isinstance(value, bool):
6
16
  try:
7
17
  value = float(value)
8
- except ValueError:
9
- return
10
- table.rows[position[0]][position[1]] = value
11
-
12
- def delete_table_row(table, value):
13
- if table.rows:
14
- keyed = len(table.headers) < len(table.rows[0])
15
- table.value = value
16
- if isinstance(value, list):
17
- if keyed:
18
- table.rows = [row for row in table.rows if row[-1] not in value]
19
- else:
20
- value.sort(reverse=True)
21
- for v in value:
22
- del table.rows[v]
23
- table.value = []
18
+ except:
19
+ pass
20
+ if hasattr(table,'id'):
21
+ dbt = table.rows.dbtable
22
+ in_node, field = table.index2node_relation(dval['cell'])
23
+ if in_node:
24
+ table_id = table.id
25
+ row_id = table.rows[dval['delta']][len(dbt.table_fields)]
24
26
  else:
25
- if keyed:
26
- table.rows = [row for row in table.rows if row[-1] != value]
27
- else:
28
- del table.rows[value]
27
+ table_id = table.__link__[2]
28
+ row_id = dval['id']
29
+ dbt.db.update_row(table_id, row_id, {field: value}, in_node)
30
+
31
+ table.rows[dval['delta']][dval['cell']] = value
32
+
33
+ def delete_table_row(table, value):
34
+ if table.selected_list:
35
+ if hasattr(table, 'link') and table.filter:
36
+ link_table, rel_props, rel_name = table.__link__
37
+ if not isinstance(value, list):
38
+ value = [value]
39
+ table.rows.dbtable.delete_links(link_table.id, link_ids = value, index_name = rel_name)
40
+ table.__link_table_selection_changed__(link_table, link_table.value)
41
+ return table
42
+ elif isinstance(value, list):
43
+ value.sort(reverse = True)
44
+ for v in value:
45
+ del table.rows[v]
46
+ table.value = []
47
+ else:
48
+ del table.rows[value]
29
49
  table.value = None
30
50
 
31
- def append_table_row(table, value):
32
- ''' append has to return new row or error string, val is search string in the table'''
33
- new_id_row, search = value #new_id_row == rows count
34
- new_row = [''] * len(table.headers)
35
- if search:
36
- new_row[0] = search
51
+ def append_table_row(table, search_str):
52
+ ''' append has to return new row, value is the search string value in the table'''
53
+ new_row = [None] * len(table.rows.dbtable.table_fields)
54
+ if getattr(table,'id', None):
55
+ id = table.rows.dbtable.list.append(new_row)
56
+ new_row.append(id)
57
+ if hasattr(table, 'link') and table.filter:
58
+ link_table, _, rel_name = table.__link__
59
+ for linked_id in link_table.selected_list:
60
+ relation = table.rows.dbtable.add_link(id, link_table.id, linked_id, link_index_name = rel_name)
61
+ new_row.extend(relation)
62
+ break
37
63
  table.rows.append(new_row)
38
64
  return new_row
39
65
 
66
+ def get_chunk(obj, start_index):
67
+ delta, data = obj.rows.get_delta_chunk(start_index)
68
+ return {'type': 'updates', 'index': delta, 'data': data}
69
+
40
70
  class Table(Gui):
41
71
  def __init__(self, *args, panda = None, **kwargs):
42
72
  if panda is not None:
43
73
  self.mutate(PandaTable(*args, panda=panda, **kwargs))
44
74
  else:
45
- super().__init__(*args, **kwargs)
46
- if not hasattr(self,'headers'):
47
- self.headers = []
48
- if not hasattr(self,'type'):
49
- self.type = 'table'
50
- if not hasattr(self,'value'):
51
- self.value = None
52
- if not hasattr(self,'rows'):
53
- self.rows = []
54
- if not hasattr(self,'dense'):
55
- self.dense = True
75
+ super().__init__(*args, **kwargs)
76
+ set_defaults(self, dict(headers = [], type = 'table', value = None, rows = [], editing = False, dense = True))
77
+ self.__headers__ = self.headers[:]
78
+ if getattr(self,'id', None):
79
+ db = references.context_user().db
80
+ if db:
81
+ db.set_db_list(self)
82
+ else:
83
+ raise AssertionError('Config db_dir is not defined!')
84
+ self.get = get_chunk
85
+ has_link = hasattr(self, 'link')
86
+ set_defaults(self, {'filter': has_link, 'ids': False, 'search': ''})
87
+ if has_link:
88
+ prop_types = {}
89
+ rel_name = ''
90
+ match self.link:
91
+ case [link_table, prop_types, rel_name]: ...
92
+ case [link_table, prop_types]: ...
93
+ case link_table: ...
94
+ rel_name, rel_fields = self.rows.dbtable.get_rel_fields2(link_table.id, prop_types, rel_name)
95
+ if not hasattr(link_table, 'id'):
96
+ raise AttributeError('Linked table has to be persistent!')
97
+ self.__link__ = link_table, list(prop_types.keys()), rel_name
98
+ self.link = rel_fields
99
+
100
+ @references.handle(link_table,'changed')
101
+ def link_table_selection_changed(master_table, val, init = False):
102
+ lstvalue = val if isinstance(val, list) else [val] if val != None else []
103
+ if lstvalue:
104
+ link_ids = [link_table.rows[val][-1] for val in lstvalue]
105
+ link_rows = self.rows.dbtable.calc_linked_rows(rel_name, link_ids, self.filter, self.search)
106
+ else:
107
+ link_rows = Dblist(self.rows.dbtable, cache = [])
108
+ if self.filter:
109
+ self.clean_selection()
110
+ self.rows = link_rows
111
+ else:
112
+ selected_ids = [link_rows[i][-1] for i in range(len(link_rows))]
113
+ self.value = selected_ids
114
+ #restore table rows if they are not rows
115
+ if self.rows.cache is not None:
116
+ self.rows = self.rows.dbtable.get_init_list()
117
+ if not init:
118
+ master_table.accept(val)
119
+ return self
120
+ link_table_selection_changed(link_table, link_table.value, True)
121
+ self.__link_table_selection_changed__ = link_table_selection_changed
122
+
123
+ @references.handle(self,'filter')
124
+ def filter_status_changed(table, value):
125
+ self.filter = value
126
+ link_table_selection_changed(link_table, link_table.value, True)
127
+ self.calc_headers()
128
+ return self
129
+
130
+ @references.handle(self,'changed')
131
+ def changed_selection_causes__changing_links(self, new_value):
132
+ if link_table.value is not None and link_table.value != []:
133
+ #if link table is in multi mode, links are not editable
134
+ if not self.filter and not isinstance(link_table.value, list | tuple):
135
+ if self.editing:
136
+ actual = set(new_value if isinstance(new_value, list) else [] if new_value is None else [new_value])
137
+ old = set(self.value if isinstance(self.value, list) else ([] if self.value is None else [self.value]))
138
+ deselected = old - actual
139
+ if deselected:
140
+ self.rows.dbtable.delete_links(link_table.id, link_table.value, deselected)
141
+ selected = actual - old
142
+ if selected:
143
+ self.rows.dbtable.add_links(link_table.id, selected, link_table.value)
144
+ else:
145
+ return Warning('The linked table is not in edit mode', self)
146
+ return self.accept(new_value)
147
+
148
+ @references.handle(self,'search')
149
+ def search_changed(table, value):
150
+ self.search = value
151
+ if has_link:
152
+ link_table_selection_changed(link_table, link_table.value, True)
153
+ else:
154
+ self.rows = self.rows.dbtable.get_init_list(self.search)
155
+ return self
156
+
157
+ self.calc_headers()
158
+
159
+ elif hasattr(self,'ids'):
160
+ raise ValueError("Only persistent tables can have 'ids' option!")
56
161
 
57
162
  if getattr(self,'edit', True):
58
- if not hasattr(self,'delete'):
59
- self.delete = delete_table_row
60
- if not hasattr(self,'append'):
61
- self.append = append_table_row
62
- if not hasattr(self,'modify'):
63
- self.modify = accept_cell_value
64
-
163
+ set_defaults(self,{'delete': delete_table_row, 'append': append_table_row, 'modify': accept_cell_value})
164
+
165
+ @property
65
166
  def selected_list(self):
66
167
  return [self.value] if self.value != None else [] if type(self.value) == int else self.value
67
168
 
68
- def clean(self):
69
- self.rows = []
70
- self.value = [] if isinstance(self.value,(tuple, list)) else None
169
+ def clean_selection(self):
170
+ self.value = [] if isinstance(self.value,tuple | list) else None
71
171
  return self
172
+
173
+ def calc_headers(self):
174
+ """only for persistent"""
175
+ table_fields = self.rows.dbtable.table_fields
176
+ self.headers = self.__headers__[:] if self.__headers__ else [pretty4(prop)for prop in table_fields]
177
+ only_node_headers = len(self.headers) == len(table_fields)
178
+ if self.ids:
179
+ self.headers.insert(len(table_fields), 'ID')
180
+ elif self.filter:
181
+ self.headers.insert(len(table_fields), exclude_mark + 'ID')
182
+ if self.filter:
183
+ if only_node_headers:
184
+ self.headers.extend([relation_mark + pretty4(link_field) for link_field in self.link])
185
+ if self.ids:
186
+ self.headers.append(relation_mark + 'ID')
72
187
 
188
+ def index2node_relation(self, cell_index):
189
+ """calculate delta to property of node or link for persistent"""
190
+ table_fields = self.rows.dbtable.table_fields
191
+ delta = cell_index - len(table_fields)
192
+ if delta < 0:
193
+ return True, iterate(table_fields, cell_index)
194
+ delta -= 1 #ID field
195
+ return False, iterate(self.link, delta)
196
+
73
197
  def delete_panda_row(table, row_num):
74
198
  df = table.__panda__
75
199
  if row_num < 0 or row_num >= len(df):
@@ -99,17 +223,13 @@ class PandaTable(Table):
99
223
  raise Exception('PandaTable has to get panda = pandaTable as an argument.')
100
224
  self.headers = panda.columns.tolist()
101
225
  if fix_headers:
102
- self.headers = [header.replace('_',' ') for header in self.headers]
226
+ self.headers = [pretty4(header) for header in self.headers]
103
227
  self.rows = panda.values.tolist()
104
228
  self.__panda__ = panda
105
229
 
106
230
  if getattr(self,'edit', True):
107
- if not hasattr(self,'delete'):
108
- self.delete = delete_panda_row
109
- if not hasattr(self,'append'):
110
- self.append = append_panda_row
111
- if not hasattr(self,'modify'):
112
- self.modify = accept_panda_cell
231
+ set_defaults(self,{'delete': delete_panda_row, 'append': append_panda_row,
232
+ 'modify': accept_panda_cell})
113
233
  @property
114
234
  def panda(self):
115
235
  return getattr(self,'__panda__',None)
unisi/users.py CHANGED
@@ -3,9 +3,15 @@ from .guielements import *
3
3
  from .common import *
4
4
  from .containers import Dialog, Screen
5
5
  from .multimon import notify_monitor, logging_lock, run_external_process
6
+ from .kdb import Database
6
7
  import sys, asyncio, logging, importlib
7
8
 
8
- class User:
9
+ class User:
10
+ last_user = None
11
+ toolbar = []
12
+ sessions = {}
13
+ count = 0
14
+
9
15
  def __init__(self, session: str, share = None):
10
16
  self.session = session
11
17
  self.active_dialog = None
@@ -30,12 +36,12 @@ class User:
30
36
 
31
37
  self.monitor(session, share)
32
38
 
33
- async def run_process(self, long_running_task, *args, callback = False):
34
- if callback and notify_monitor and callback != self.progress: #progress notifies the monitor
39
+ async def run_process(self, long_running_task, *args, progress_callback = None, **kwargs):
40
+ if progress_callback and notify_monitor and progress_callback != self.progress: #progress notifies the monitor
35
41
  async def new_callback(value):
36
- asyncio.gather(notify_monitor('e', self.session, self.last_message), callback(value))
37
- callback = new_callback
38
- return await run_external_process(long_running_task, *args, callback = callback)
42
+ asyncio.gather(notify_monitor('e', self.session, self.last_message), progress_callback(value))
43
+ progress_callback = new_callback
44
+ return await run_external_process(long_running_task, *args, progress_callback = progress_callback, **kwargs)
39
45
 
40
46
  async def broadcast(self, message):
41
47
  screen = self.screen_module
@@ -62,16 +68,7 @@ class User:
62
68
  if notify_monitor:
63
69
  await notify_monitor('e', self.session, self.last_message)
64
70
 
65
- def load_screen(self, file):
66
- screen_vars = {
67
- 'icon' : None,
68
- 'prepare' : None,
69
- 'blocks' : [],
70
- 'header' : config.appname,
71
- 'toolbar' : [],
72
- 'order' : 0,
73
- 'reload': config.hot_reload
74
- }
71
+ def load_screen(self, file):
75
72
  name = file[:-3]
76
73
  path = f'{screens_dir}{divpath}{file}'
77
74
  spec = importlib.util.spec_from_file_location(name,path)
@@ -81,8 +78,8 @@ class User:
81
78
  spec.loader.exec_module(module)
82
79
  screen = Screen(getattr(module, 'name', ''))
83
80
  #set system vars
84
- for var in screen_vars:
85
- setattr(screen, var, getattr(module,var,screen_vars[var]))
81
+ for var, val in screen.defaults.items():
82
+ setattr(screen, var, getattr(module, var, val))
86
83
 
87
84
  if screen.toolbar:
88
85
  screen.toolbar += User.toolbar
@@ -122,7 +119,7 @@ class User:
122
119
  self.screens.append(module)
123
120
 
124
121
  if self.screens:
125
- self.screens.sort(key=lambda s: s.order)
122
+ self.screens.sort(key=lambda s: s.screen.order)
126
123
  main = self.screens[0]
127
124
  if 'prepare' in dir(main):
128
125
  main.prepare()
@@ -146,7 +143,6 @@ class User:
146
143
 
147
144
  def set_screen(self,name):
148
145
  return asyncio.run(self.process(ArgObject(block = 'root', element = None, value = name)))
149
-
150
146
  async def result4message(self, message):
151
147
  result = None
152
148
  self.last_message = message
@@ -244,18 +240,20 @@ class User:
244
240
 
245
241
  async def process_element(self, elem, message):
246
242
  event = message.event
247
- query = event == 'complete' or event == 'append'
248
-
243
+ query = event == 'complete' or event == 'append' or event == 'get'
249
244
  handler = self.__handlers__.get((elem, event), None)
250
245
  if handler:
251
246
  return await self.eval_handler(handler, elem, message.value)
252
-
253
- handler = getattr(elem, event, False)
254
- if handler:
255
- result = await self.eval_handler(handler, elem, message.value)
256
- if query:
257
- result = Answer(event, message, result)
258
- return result
247
+
248
+ if hasattr(elem, event):
249
+ attr = getattr(elem, event)
250
+ if is_callable(attr):
251
+ result = await self.eval_handler(attr, elem, message.value)
252
+ if query:
253
+ result = Answer(event, message, result)
254
+ return result
255
+ #set attribute only for declared properties
256
+ setattr(elem, event, message.value)
259
257
  elif event == 'changed':
260
258
  elem.value = message.value
261
259
  else:
@@ -284,12 +282,6 @@ class User:
284
282
  logging.info(str)
285
283
  func(level = logging.WARNING)
286
284
 
287
- User.type = User
288
- User.last_user = None
289
- User.toolbar = []
290
- User.sessions = {}
291
- User.count = 0
292
-
293
285
  def context_user():
294
286
  return context_object(User)
295
287
 
@@ -297,10 +289,19 @@ def context_screen():
297
289
  user = context_user()
298
290
  return user.screen if user else None
299
291
 
292
+ def message_logger(str, type = 'error'):
293
+ user = context_user()
294
+ user.log(str, type)
295
+
296
+ references.context_user = context_user
297
+
298
+ User.db = Database(config.db_dir, message_logger) if config.db_dir else None
299
+ User.type = User
300
+
300
301
  def make_user(request):
301
302
  session = f'{request.remote}-{User.count}'
302
303
  User.count += 1
303
- if requested_connect := request.headers.get('session') if config.share else None:
304
+ if requested_connect := request.query_string if config.share else None:
304
305
  user = User.sessions.get(requested_connect, None)
305
306
  if not user:
306
307
  error = f'Session id "{requested_connect}" is unknown. Connection refused!'
@@ -320,5 +321,14 @@ def make_user(request):
320
321
 
321
322
  def handle(elem, event):
322
323
  def h(fn):
323
- User.last_user.__handlers__[elem, event] = fn
324
- return h
324
+ key = elem, event
325
+ handler_map = User.last_user.__handlers__
326
+ func = handler_map.get(key, None)
327
+ if func:
328
+ handler_map[key] = lambda el, ev: compose_returns(func(el, ev), fn(el, ev))
329
+ else:
330
+ handler_map[key] = fn
331
+ return fn
332
+ return h
333
+
334
+ references.handle = handle
unisi/utils.py CHANGED
@@ -1,9 +1,9 @@
1
- import os, platform, requests, inspect, logging
1
+ import os, platform, requests, logging
2
+ from .common import set_defaults
3
+ from .containers import Screen
2
4
 
3
5
  blocks_dir = 'blocks'
4
6
  screens_dir = 'screens'
5
- UpdateScreen = True
6
- Redesign = 2
7
7
  public_dirs = 'public_dirs'
8
8
  testdir = 'autotest'
9
9
 
@@ -28,40 +28,36 @@ appname = 'Unisi app'
28
28
  print("Config with default parameters is created!")
29
29
 
30
30
  #setting config variables
31
- defaults = {
32
- testdir: False,
33
- 'appname' : 'Unisi app',
34
- 'upload_dir' : 'web',
35
- 'logfile': None,
36
- 'hot_reload' : False,
37
- 'mirror' : False,
38
- 'share' : False,
39
- 'profile' : 0,
40
- 'rag' : None,
41
- 'froze_time': None,
42
- 'monitor_tick' : 0.005,
43
- 'pool' : None
44
- }
45
- for param, value in defaults.items():
46
- if not hasattr(config, param):
47
- setattr(config, param, value)
48
- #froze_time can not be 0
31
+ set_defaults(config, dict(
32
+ autotest= False,
33
+ appname = 'Unisi app',
34
+ upload_dir = 'web',
35
+ logfile= None,
36
+ hot_reload = False,
37
+ mirror = False,
38
+ share = False,
39
+ profile = 0,
40
+ llm = None,
41
+ froze_time= None,
42
+ monitor_tick = 0.005,
43
+ pool = None,
44
+ db_dir = None
45
+ ))
46
+
47
+ Screen.defaults = dict(
48
+ icon = None,
49
+ prepare = None,
50
+ blocks = [],
51
+ header = config.appname,
52
+ toolbar = [],
53
+ order = 0,
54
+ reload = config.hot_reload
55
+ )
56
+
49
57
  if config.froze_time == 0:
58
+ print('froze_time in config.py can not be 0!')
50
59
  config.froze_time = None
51
60
 
52
- def context_object(target_type):
53
- """
54
- Finds the first argument of a specific type in the current function call stack.
55
- """
56
- frame = inspect.currentframe()
57
- while frame:
58
- args, _, _, values = inspect.getargvalues(frame)
59
- if args and isinstance(values[args[0]], target_type):
60
- return values[args[0]]
61
- # Move to the previous frame in the call stack
62
- frame = frame.f_back
63
- return None
64
-
65
61
  def is_screen_switch(message):
66
62
  return message and message.block == 'root' and message.element is None
67
63
 
@@ -93,54 +89,6 @@ def cache_url(url):
93
89
  file.close()
94
90
  return fname
95
91
 
96
- class Message:
97
- def __init__(self, *gui_objects, user = None, type = 'update'):
98
- self.type = type
99
- if gui_objects:
100
- self.updates = [{'data': gui} for gui in gui_objects]
101
- if user:
102
- self.fill_paths4(user)
103
-
104
- def fill_paths4(self, user):
105
- if hasattr(self, 'updates'):
106
- invalid = []
107
- for update in self.updates:
108
- data = update["data"]
109
- path = user.find_path(data)
110
- if path:
111
- update['path'] = path
112
- else:
113
- invalid.append(update)
114
- user.log(f'Invalid element update {data.name}, type {data.type}.\n\
115
- Such element not on the screen!')
116
- for inv in invalid:
117
- self.updates.remove(inv)
118
-
119
- def contains(self, guiobj):
120
- if hasattr(self, 'updates'):
121
- for update in self.updates:
122
- if guiobj is update['data']:
123
- return True
124
-
125
- def TypeMessage(type, value, *data, user = None):
126
- message = Message(*data, user=user, type = type)
127
- message.value = value
128
- return message
129
-
130
- def Warning(text, *data):
131
- return TypeMessage('warning', text, *data)
132
-
133
- def Error(text, *data):
134
- return TypeMessage('error', text, *data)
135
-
136
- def Info(text, *data):
137
- return TypeMessage('info', text, *data)
138
-
139
- def Answer(type, message, result):
140
- ms = TypeMessage(type, result)
141
- ms.message = message
142
- return ms
143
-
144
92
  def start_logging():
145
93
  format = "%(asctime)s - %(levelname)s - %(message)s"
146
94
  logfile = config.logfile
@@ -1 +1 @@
1
- thead tr:first-child th{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:sticky;top:0;z-index:1000}:root{--scrollbar-width-height:10px;--scrollbar-thumb-hover:#2176d2;--scrollbar-thumb-dark:#2176d2;--scrollbar-thumb-hover-dark:#2176d2}::-webkit-scrollbar{height:var(--scrollbar-width-height);width:var(--scrollbar-width-height)}::-webkit-scrollbar-track{box-shadow:inset 0 0 4px var(--scrollbar-track-dark)}::-webkit-scrollbar-corner{background:var(--scrollbar-track-dark)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-dark);border-radius:5px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover-dark)}body[data-v-f48596a0]{display:flex;justify-content:center}.custom-caption[data-v-f48596a0]{padding:5px!important}.web-camera-container[data-v-f48596a0]{align-items:center;border:1px solid #ccc;border-radius:4px;display:flex;flex-direction:column;justify-content:center;margin-bottom:2rem;margin-top:2rem;padding:2rem;width:500px}.web-camera-container .camera-button[data-v-f48596a0]{margin-bottom:2rem}.web-camera-container .camera-box .camera-shutter[data-v-f48596a0]{background-color:#fff;height:337.5px;opacity:0;position:absolute;width:450px}.web-camera-container .camera-box .camera-shutter.flash[data-v-f48596a0]{opacity:1}.web-camera-container .camera-shoot[data-v-f48596a0]{margin:1rem 0}.web-camera-container .camera-shoot button[data-v-f48596a0]{align-items:center;border-radius:100%;display:flex;height:60px;justify-content:center;width:60px}.web-camera-container .camera-shoot button img[data-v-f48596a0]{height:35px;object-fit:cover}.web-camera-container .camera-loading[data-v-f48596a0]{height:100%;margin:3rem 0 0 -1.2rem;min-height:150px;overflow:hidden;position:absolute;width:100%}.web-camera-container .camera-loading ul[data-v-f48596a0]{height:100%;margin:0;position:absolute;width:100%;z-index:999999}.web-camera-container .camera-loading .loader-circle[data-v-f48596a0]{display:block;height:14px;left:100%;margin:0 auto;padding:0;position:absolute;top:50%;transform:translateY(-50%);transform:translateX(-50%);width:100%}.web-camera-container .camera-loading .loader-circle li[data-v-f48596a0]{animation:preload-f48596a0 1s infinite;background:#999;border-radius:100%;display:block;float:left;height:10px;line-height:10px;margin:0 0 0 4px;padding:0;position:relative;top:-50%;width:10px}.web-camera-container .camera-loading .loader-circle li[data-v-f48596a0]:nth-child(2){animation-delay:.2s}.web-camera-container .camera-loading .loader-circle li[data-v-f48596a0]:nth-child(3){animation-delay:.4s}@keyframes preload-f48596a0{0%{opacity:1}50%{opacity:.4}to{opacity:1}}.q-tab__label{font-size:16px;font-weight:700}
1
+ thead tr:first-child th{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:sticky;top:0;z-index:1000}:root{--scrollbar-width-height:10px;--scrollbar-thumb-hover:#2176d2;--scrollbar-thumb-dark:#2176d2;--scrollbar-thumb-hover-dark:#2176d2}::-webkit-scrollbar{height:var(--scrollbar-width-height);width:var(--scrollbar-width-height)}::-webkit-scrollbar-track{box-shadow:inset 0 0 4px var(--scrollbar-track-dark)}::-webkit-scrollbar-corner{background:var(--scrollbar-track-dark)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-dark);border-radius:5px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover-dark)}body[data-v-6eb6b02a]{display:flex;justify-content:center}.custom-caption[data-v-6eb6b02a]{padding:5px!important}.web-camera-container[data-v-6eb6b02a]{align-items:center;border:1px solid #ccc;border-radius:4px;display:flex;flex-direction:column;justify-content:center;margin-bottom:2rem;margin-top:2rem;padding:2rem;width:500px}.web-camera-container .camera-button[data-v-6eb6b02a]{margin-bottom:2rem}.web-camera-container .camera-box .camera-shutter[data-v-6eb6b02a]{background-color:#fff;height:337.5px;opacity:0;position:absolute;width:450px}.web-camera-container .camera-box .camera-shutter.flash[data-v-6eb6b02a]{opacity:1}.web-camera-container .camera-shoot[data-v-6eb6b02a]{margin:1rem 0}.web-camera-container .camera-shoot button[data-v-6eb6b02a]{align-items:center;border-radius:100%;display:flex;height:60px;justify-content:center;width:60px}.web-camera-container .camera-shoot button img[data-v-6eb6b02a]{height:35px;object-fit:cover}.web-camera-container .camera-loading[data-v-6eb6b02a]{height:100%;margin:3rem 0 0 -1.2rem;min-height:150px;overflow:hidden;position:absolute;width:100%}.web-camera-container .camera-loading ul[data-v-6eb6b02a]{height:100%;margin:0;position:absolute;width:100%;z-index:999999}.web-camera-container .camera-loading .loader-circle[data-v-6eb6b02a]{display:block;height:14px;left:100%;margin:0 auto;padding:0;position:absolute;top:50%;transform:translateY(-50%);transform:translateX(-50%);width:100%}.web-camera-container .camera-loading .loader-circle li[data-v-6eb6b02a]{animation:preload-6eb6b02a 1s infinite;background:#999;border-radius:100%;display:block;float:left;height:10px;line-height:10px;margin:0 0 0 4px;padding:0;position:relative;top:-50%;width:10px}.web-camera-container .camera-loading .loader-circle li[data-v-6eb6b02a]:nth-child(2){animation-delay:.2s}.web-camera-container .camera-loading .loader-circle li[data-v-6eb6b02a]:nth-child(3){animation-delay:.4s}@keyframes preload-6eb6b02a{0%{opacity:1}50%{opacity:.4}to{opacity:1}}.q-tab__label{font-size:16px;font-weight:700}
unisi/web/index.html CHANGED
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><title>UNISI</title><meta charset=utf-8><meta name=description content="UNISI on Quasar"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png sizes=128x128 href=icons/favicon-128x128.png><link rel=icon type=image/png sizes=96x96 href=icons/favicon-96x96.png><link rel=icon type=image/png sizes=32x32 href=icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=icons/favicon-16x16.png><link rel=icon type=image/ico href=favicon.ico><script defer src=js/vendor.d6797c01.js></script><script defer src=js/app.636fcf8d.js></script><link href=css/vendor.9ed7638d.css rel=stylesheet><link href=css/app.31d6cfe0.css rel=stylesheet></head><body><div id=q-app></div></body></html>
1
+ <!DOCTYPE html><html><head><base href=/ ><title>UNISI</title><meta charset=utf-8><meta name=description content="UNISI on Quasar"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png sizes=128x128 href=icons/favicon-128x128.png><link rel=icon type=image/png sizes=96x96 href=icons/favicon-96x96.png><link rel=icon type=image/png sizes=32x32 href=icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=icons/favicon-16x16.png><link rel=icon type=image/ico href=favicon.ico><script defer src=/js/vendor.eab68489.js></script><script defer src=/js/app.4b51aa78.js></script><link href=/css/vendor.9ed7638d.css rel=stylesheet><link href=/css/app.31d6cfe0.css rel=stylesheet></head><body><div id=q-app></div></body></html>