unisi 0.1.16__py3-none-any.whl → 0.1.18__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,8 +1,9 @@
1
- from .guielements import Gui
1
+ from .units import Unit
2
2
  from .common import *
3
- from .dbelements import Dblist
3
+ from .dbunits import Dblist, dbupdates
4
4
  from .llmrag import get_property
5
5
  import asyncio
6
+ from collections import OrderedDict
6
7
 
7
8
  relation_mark = 'Ⓡ'
8
9
  exclude_mark = '✘'
@@ -10,14 +11,9 @@ max_len_rows4llm = 30
10
11
 
11
12
  def get_chunk(obj, start_index):
12
13
  delta, data = obj.rows.get_delta_chunk(start_index)
13
- return {'type': 'updates', 'index': delta, 'data': data}
14
+ return {'update': 'updates', 'index': delta, 'data': data}
14
15
 
15
- def iterate(iter, times):
16
- for i, val in enumerate(iter):
17
- if i == times:
18
- return val
19
-
20
- def accept_cell_value(table, dval):
16
+ def accept_cell_value(table, dval):
21
17
  value = dval['value']
22
18
  if not isinstance(value, bool):
23
19
  try:
@@ -25,21 +21,15 @@ def accept_cell_value(table, dval):
25
21
  except:
26
22
  pass
27
23
  if hasattr(table,'id'):
28
- dbt = table.rows.dbtable
29
- in_node, field = table.index2node_relation(dval['cell'])
30
- if in_node:
31
- table_id = table.id
32
- row_id = table.rows[dval['delta']][len(dbt.table_fields)]
33
- else:
34
- table_id = table.__link__[2]
35
- row_id = dval['id']
36
- dbt.db.update_row(table_id, row_id, {field: value}, in_node)
37
- table.rows[dval['delta']][dval['cell']] = value
24
+ dval['value'] = value
25
+ table.rows.update_cell(**dval)['exclude'] = True
26
+ else:
27
+ table.rows[dval['delta']][dval['cell']] = value
38
28
 
39
29
  def delete_table_row(table, value):
40
30
  if table.selected_list:
41
31
  if hasattr(table, 'link') and table.filter:
42
- link_table, rel_props, rel_name = table.__link__
32
+ link_table, rel_props, rel_name = table.rows.link
43
33
  if not isinstance(value, list):
44
34
  value = [value]
45
35
  table.rows.dbtable.delete_links(link_table.id, link_ids = value, index_name = rel_name)
@@ -61,7 +51,7 @@ def append_table_row(table, search_str):
61
51
  id = table.rows.dbtable.list.append(new_row)
62
52
  new_row.append(id)
63
53
  if hasattr(table, 'link') and table.filter:
64
- link_table, _, rel_name = table.__link__
54
+ link_table, _, rel_name = table.rows.link
65
55
  for linked_id in link_table.selected_list:
66
56
  relation = table.rows.dbtable.add_link(id, link_table.id, linked_id, link_index_name = rel_name)
67
57
  new_row.extend(relation)
@@ -69,7 +59,7 @@ def append_table_row(table, search_str):
69
59
  table.rows.append(new_row)
70
60
  return new_row
71
61
 
72
- class Table(Gui):
62
+ class Table(Unit):
73
63
  def __init__(self, *args, panda = None, **kwargs):
74
64
  if panda is not None:
75
65
  self.mutate(PandaTable(*args, panda=panda, **kwargs))
@@ -77,10 +67,9 @@ class Table(Gui):
77
67
  super().__init__(*args, **kwargs)
78
68
  set_defaults(self, dict(headers = [], type = 'table', value = None, rows = [], editing = False, dense = True))
79
69
  self.__headers__ = self.headers[:]
80
- if getattr(self,'id', None):
81
- db = references.context_user().db
82
- if db:
83
- db.set_db_list(self)
70
+ if hasattr(self,'id'):
71
+ if Unishare.db:
72
+ Unishare.db.set_db_list(self)
84
73
  else:
85
74
  raise AssertionError('Config db_dir is not defined!')
86
75
  self.get = get_chunk
@@ -96,10 +85,10 @@ class Table(Gui):
96
85
  rel_name, rel_fields = self.rows.dbtable.get_rel_fields2(link_table.id, prop_types, rel_name)
97
86
  if not hasattr(link_table, 'id'):
98
87
  raise AttributeError('Linked table has to be persistent!')
99
- self.__link__ = link_table, list(prop_types.keys()), rel_name
88
+ self.rows.link = link_table, list(prop_types.keys()), rel_name
100
89
  self.link = rel_fields
101
90
 
102
- @references.handle(link_table,'changed')
91
+ @Unishare.handle(link_table,'changed')
103
92
  def link_table_selection_changed(master_table, val, init = False):
104
93
  lstvalue = val if isinstance(val, list) else [val] if val != None else []
105
94
  if lstvalue:
@@ -112,24 +101,23 @@ class Table(Gui):
112
101
  self.rows = link_rows
113
102
  else:
114
103
  selected_ids = [link_rows[i][-1] for i in range(len(link_rows))]
115
- self.value = selected_ids
116
- #restore table rows if they are not rows
104
+ self.value = selected_ids
117
105
  if self.rows.cache is not None:
118
- self.rows = self.rows.dbtable.get_init_list()
106
+ self.rows = self.rows.dbtable.list
119
107
  if not init:
120
108
  master_table.accept(val)
121
109
  return self
122
110
  link_table_selection_changed(link_table, link_table.value, True)
123
111
  self.__link_table_selection_changed__ = link_table_selection_changed
124
112
 
125
- @references.handle(self,'filter')
113
+ @Unishare.handle(self,'filter')
126
114
  def filter_status_changed(table, value):
127
115
  self.filter = value
128
116
  link_table_selection_changed(link_table, link_table.value, True)
129
117
  self.calc_headers()
130
118
  return self
131
119
 
132
- @references.handle(self,'changed')
120
+ @Unishare.handle(self,'changed')
133
121
  def changed_selection_causes__changing_links(self, new_value):
134
122
  if link_table.value is not None and link_table.value != []:
135
123
  #if link table is in multi mode, links are not editable
@@ -146,8 +134,8 @@ class Table(Gui):
146
134
  else:
147
135
  return Warning('The linked table is not in edit mode', self)
148
136
  return self.accept(new_value)
149
-
150
- @references.handle(self,'search')
137
+ """
138
+ @Unishare.handle(self,'search')
151
139
  def search_changed(table, value):
152
140
  self.search = value
153
141
  if has_link:
@@ -155,7 +143,7 @@ class Table(Gui):
155
143
  else:
156
144
  self.rows = self.rows.dbtable.get_init_list(self.search)
157
145
  return self
158
-
146
+ """
159
147
  self.calc_headers()
160
148
 
161
149
  elif hasattr(self,'ids'):
@@ -165,15 +153,13 @@ class Table(Gui):
165
153
  set_defaults(self,{'delete': delete_table_row, 'append': append_table_row, 'modify': accept_cell_value})
166
154
 
167
155
  @property
168
- def compact_view(self):
156
+ def compact_view(self) -> str:
169
157
  """only selected are sended to llm"""
170
- selected = self.selected_list
171
- result = []
158
+ selected = self.selected_list
172
159
  if not selected and len(self.rows) < max_len_rows4llm:
173
- selected = range(len(self.rows))
174
- for index in selected:
175
- result.append({field: value for field, value in zip(self.headers, self.rows[index])})
176
- return {'name': self.name, 'value': result}
160
+ selected = range(len(self.rows))
161
+ str_rows = ';'.join(','.join(f'{field}: {value}' for field, value in zip(self.headers, self.rows[index])) for index in selected)
162
+ return f'{self.name} : {str_rows}'
177
163
 
178
164
  @property
179
165
  def selected_list(self):
@@ -181,7 +167,7 @@ class Table(Gui):
181
167
 
182
168
  def clean_selection(self):
183
169
  self.value = [] if isinstance(self.value,tuple | list) else None
184
- return self
170
+ return self
185
171
 
186
172
  def calc_headers(self):
187
173
  """only for persistent"""
@@ -197,45 +183,45 @@ class Table(Gui):
197
183
  self.headers.extend([relation_mark + pretty4(link_field) for link_field in self.link])
198
184
  if self.ids:
199
185
  self.headers.append(relation_mark + 'ID')
200
-
201
- def index2node_relation(self, cell_index):
202
- """calculate delta to property of node or link for persistent"""
203
- table_fields = self.rows.dbtable.table_fields
204
- delta = cell_index - len(table_fields)
205
- if delta < 0:
206
- return True, iterate(table_fields, cell_index)
207
- delta -= 1 #ID field
208
- return False, iterate(self.link, delta)
209
186
 
210
187
  async def emit(self, *_):
211
188
  """calcute llm field values for selected rows if they are None"""
212
- if references.llm_model and getattr(self, 'llm', None) is not None:
189
+ if Unishare.llm_model and getattr(self, 'llm', None) is not None:
213
190
  tasks = []
214
191
  for index in self.selected_list:
215
- values = {field: value for field, value in zip(self.headers, self.rows[index])
216
- if value is not None and value != ''}
192
+ values = {field: value for field, value in zip(self.headers, self.rows[index]) if value}
217
193
  for fld, deps in self.__llm_dependencies__.items():
218
- if fld not in values:
219
- context = {}
220
- for dep in deps:
221
- value = values.get(dep, None)
222
- if value is None:
223
- if self.llm: #exact
224
- return #not all fields
225
- else:
226
- if isinstance(dep, str):
227
- context[dep] = value
228
- elif isinstance(dep, Gui):
229
- context[dep.name] = dep.value
230
- else:
231
- raise AttributeError(f'Invalid llm parameter {dep} in {self.name} element!')
194
+ if fld not in values:
195
+ if deps is True:
196
+ context = values
197
+ else:
198
+ context = OrderedDict()
199
+ for dep in deps:
200
+ value = values.get(dep, None)
201
+ if value is None:
202
+ if self.llm: #exact
203
+ continue #not all fields
204
+ else:
205
+ if isinstance(dep, str):
206
+ context[dep] = value
207
+ elif isinstance(dep, Unit):
208
+ context[dep.name] = dep.value
209
+ else:
210
+ raise AttributeError(f'Invalid llm parameter {dep} in {self.name} element!')
232
211
  if context:
233
- async def assign(index, fld, jcontext):
234
- self.rows[index][self.headers.index(fld)] = await get_property(fld, jcontext)
235
- tasks.append(asyncio.create_task(assign(index, fld, toJson(context))))
212
+ async def assign(index, fld, context):
213
+ self.rows[index][self.headers.index(fld)] = await get_property(fld, context)
214
+ context = ','.join(f'{fld}:{val}' for fld, val in context.items())
215
+ tasks.append(asyncio.create_task(assign(index, fld, context)))
236
216
  if tasks:
237
217
  await asyncio.gather(*tasks)
238
218
  return self
219
+ @property
220
+ def is_base_table_list(self):
221
+ """is table in basic view mode"""
222
+ if hasattr(self, 'id'):
223
+ dbtable = self.rows.dbtable
224
+ return dbtable.list is self.rows
239
225
 
240
226
  def delete_panda_row(table, row_num):
241
227
  df = table.__panda__
@@ -1,7 +1,7 @@
1
1
  from .common import *
2
2
  from .llmrag import get_property
3
3
 
4
- class Gui:
4
+ class Unit:
5
5
  def __init__(self, name, *args, **kwargs):
6
6
  self.name = name
7
7
  la = len(args)
@@ -24,20 +24,20 @@ class Gui:
24
24
  self.value = value
25
25
 
26
26
  @property
27
- def compact_view(self):
27
+ def compact_view(self) -> str:
28
28
  """reduce for external (llm) using if required"""
29
- return {self.name : self.value}
29
+ return f'{self.name} : {self.value}'
30
30
 
31
31
  async def emit(self, *_ ):
32
32
  """calcute value by system llm, can be used as a handler"""
33
- if references.llm_model and (exactly := getattr(self, 'llm', None)) is not None:
33
+ if Unishare.llm_model and (exactly := getattr(self, 'llm', None)) is not None:
34
34
  elems = [e.compact_view for e in self.__llm_dependencies__ if e.value != '' and e.value is not None]
35
35
  #exactly is requirment that all elements have to have valid value
36
36
  if not exactly or len(elems) == len(self.__llm_dependencies__):
37
- context = toJson(elems)
37
+ context = ','.join(elems)
38
38
  self.value = await get_property(self.name, context, self.type, options = getattr(self, 'options', None))
39
39
  return self
40
-
40
+
41
41
  def add_changed_handler(self, handler):
42
42
  changed_handler = getattr(self, 'changed', None)
43
43
  if not changed_handler:
@@ -45,7 +45,7 @@ class Gui:
45
45
  obj.value = value
46
46
  self.changed = compose_handlers(changed_handler, handler)
47
47
 
48
- Line = Gui("__Line__", type = 'line')
48
+ Line = Unit("__Line__", type = 'line')
49
49
 
50
50
  def smart_complete(lst, min_input_length = 0, max_output_length = 20):
51
51
  di = {it: it.lower() for it in lst}
@@ -60,7 +60,7 @@ def smart_complete(lst, min_input_length = 0, max_output_length = 20):
60
60
  return [e[1] for e in arr]
61
61
  return complete
62
62
 
63
- class Edit(Gui):
63
+ class Edit(Unit):
64
64
  def __init__(self, name, *args, **kwargs):
65
65
  super().__init__(name, *args, **kwargs)
66
66
  has_value = hasattr(self,'value')
@@ -70,18 +70,18 @@ class Edit(Gui):
70
70
  if type_value == int or type_value == float:
71
71
  self.type = 'number'
72
72
  return
73
- self.type = 'string'
73
+ self.type = 'string'
74
74
  if not has_value:
75
75
  self.value = '' if self.type != 'number' else 0
76
76
 
77
- class Text(Gui):
77
+ class Text(Unit):
78
78
  def __init__(self, name, *args, **kwargs):
79
79
  super().__init__(name, *args, **kwargs)
80
80
  self.value = self.name
81
81
  self.type = 'string'
82
82
  self.edit = False
83
83
 
84
- class Range(Gui):
84
+ class Range(Unit):
85
85
  def __init__(self, name, *args, **kwargs):
86
86
  super().__init__(name, *args, **kwargs)
87
87
  if not hasattr(self, 'value'):
@@ -90,7 +90,7 @@ class Range(Gui):
90
90
  if 'options' not in kwargs:
91
91
  self.options = [self.value - 10, self.value + 10, 1]
92
92
 
93
- class Button(Gui):
93
+ class Button(Unit):
94
94
  def __init__(self, name, handler = None, **kwargs):
95
95
  self.name = name
96
96
  self.value = None
@@ -110,7 +110,7 @@ def UploadButton(name, handler = None,**kwargs):
110
110
  kwargs['width'] = 250.0
111
111
  return Button(name, handler, **kwargs)
112
112
 
113
- class Image(Gui):
113
+ class Image(Unit):
114
114
  '''name is file name or url, label is optional text to draw on the image'''
115
115
  def __init__(self, name, value = False, handler = None, label = '', width = 300, **kwargs):
116
116
  super().__init__(name, [], **kwargs)
@@ -126,7 +126,7 @@ class Image(Gui):
126
126
  if self.url[1] == ':':
127
127
  self.url = f'/{self.url}'
128
128
 
129
- class Video(Gui):
129
+ class Video(Unit):
130
130
  '''has to contain src parameter'''
131
131
  def __init__(self,name, *args, **kwargs):
132
132
  super().__init__(name, *args, **kwargs)
@@ -134,7 +134,7 @@ class Video(Gui):
134
134
  set_defaults(self, {'url': self.name, 'ratio' : None})
135
135
 
136
136
  class Node:
137
- def __init__(self, name = '', color = '', size = 0, id = ''):
137
+ def __init__(self, name = '',id = '', color = '', size = 0):
138
138
  if name:
139
139
  self.name = name
140
140
  if color:
@@ -145,7 +145,7 @@ class Node:
145
145
  self.id = id
146
146
 
147
147
  class Edge:
148
- def __init__(self, source, target, name = '', color = '', size = 0, id = ''):
148
+ def __init__(self, source, target, name = '', id = '', color = '', size = 0):
149
149
  self.source = source
150
150
  self.target = target
151
151
  if name:
@@ -159,32 +159,32 @@ class Edge:
159
159
 
160
160
  graph_default_value = {'nodes' : [], 'edges' : []}
161
161
 
162
- class Graph(Gui):
162
+ class Graph(Unit):
163
163
  '''has to contain nodes, edges, see Readme'''
164
164
  def __init__(self, name, *args, **kwargs):
165
165
  super().__init__(name, *args, **kwargs)
166
166
  self.type='graph'
167
167
  set_defaults(self,{'value': graph_default_value, 'nodes': [], 'edges': []})
168
168
 
169
- class Switch(Gui):
169
+ class Switch(Unit):
170
170
  def __init__(self,name, *args, **kwargs):
171
171
  super().__init__(name, *args, **kwargs)
172
172
  set_defaults(self,{'value': False, 'type': 'switch'})
173
173
 
174
- class Select(Gui):
174
+ class Select(Unit):
175
175
  def __init__(self,name, *args, **kwargs):
176
176
  super().__init__(name, *args, **kwargs)
177
177
  set_defaults(self,{'options': [], 'value': None})
178
178
  if not hasattr(self, 'type'):
179
179
  self.type = 'select' if len(self.options) > 3 else 'radio'
180
180
 
181
- class Tree(Gui):
181
+ class Tree(Unit):
182
182
  def __init__(self,name, *args, **kwargs):
183
183
  super().__init__(name, *args, **kwargs)
184
184
  self.type = 'tree'
185
185
  set_defaults(self,{'options': [], 'value': None})
186
186
 
187
- class TextArea(Gui):
187
+ class TextArea(Unit):
188
188
  def __init__(self,name, *args, **kwargs):
189
189
  super().__init__(name, *args, **kwargs)
190
190
  self.type = 'text'
unisi/users.py CHANGED
@@ -1,20 +1,17 @@
1
1
  from .utils import *
2
- from .guielements import *
2
+ from .units 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
6
  from .kdb import Database
7
+ from .dbunits import dbshare, dbupdates
7
8
  import sys, asyncio, logging, importlib
8
- from collections import defaultdict
9
9
 
10
10
  class User:
11
11
  last_user = None
12
- toolbar = []
13
- sessions = {}
12
+ toolbar = []
14
13
  count = 0
15
- #storage id -> screen name -> [elem name, block name]
16
- dbshare = defaultdict(lambda: defaultdict(lambda: []))
17
-
14
+
18
15
  def __init__(self, session: str, share = None):
19
16
  self.session = session
20
17
  self.active_dialog = None
@@ -53,7 +50,7 @@ class User:
53
50
  await asyncio.gather(*[user.send(message)
54
51
  for user in self.reflections
55
52
  if user is not self and screen is user.screen_module])
56
-
53
+
57
54
  async def reflect(self, message, result):
58
55
  if self.reflections and not is_screen_switch(message):
59
56
  if result:
@@ -92,7 +89,7 @@ class User:
92
89
  return module
93
90
 
94
91
  async def delete(self):
95
- uss = User.sessions
92
+ uss = Unishare.sessions
96
93
  if uss and uss.get(self.session):
97
94
  del uss[self.session]
98
95
 
@@ -124,7 +121,7 @@ class User:
124
121
  if self.screens:
125
122
  self.screens.sort(key=lambda s: s.screen.order)
126
123
  main = self.screens[0]
127
- if 'prepare' in dir(main):
124
+ if hasattr(main, 'prepare'):
128
125
  main.prepare()
129
126
  self.screen_module = main
130
127
  self.update_menu()
@@ -146,6 +143,7 @@ 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)))
146
+
149
147
  async def result4message(self, message):
150
148
  result = None
151
149
  self.last_message = message
@@ -211,21 +209,21 @@ class User:
211
209
  else:
212
210
  if isinstance(raw, Message):
213
211
  raw.fill_paths4(self)
214
- elif isinstance(raw,Gui):
212
+ elif isinstance(raw,Unit):
215
213
  raw = Message(raw, user = self)
216
214
  elif isinstance(raw, (list, tuple)):
217
215
  raw = Message(*raw, user = self)
218
216
  return raw
219
217
 
220
218
  async def process(self, message):
221
- screen_change_message = getattr(message, 'screen',None) and self.screen.name != message.screen
222
- if is_screen_switch(message) or screen_change_message:
219
+ screen_change_message = message.screen and self.screen.name != message.screen
220
+ if screen_change_message or is_screen_switch(message):
223
221
  for s in self.screens:
224
222
  if s.name == message.value:
225
223
  self.screen_module = s
226
224
  if screen_change_message:
227
225
  break
228
- if getattr(s.screen,'prepare', False):
226
+ if getattr(s.screen,'prepare', None):
229
227
  s.screen.prepare()
230
228
  return True
231
229
  else:
@@ -285,6 +283,33 @@ class User:
285
283
  logging.info(str)
286
284
  func(level = logging.WARNING)
287
285
 
286
+ def calc_dbsharing(self):
287
+ """calc connections db and units"""
288
+ dbshare.clear()
289
+ for module in self.screens:
290
+ screen = module.screen
291
+ for block in flatten(screen.blocks):
292
+ for elem in flatten(block.value):
293
+ if hasattr(elem, 'id'):
294
+ dbshare[elem.id][screen.name].append({'element': elem.name, 'block': block.name})
295
+
296
+ async def sync_dbupdates(self):
297
+ sync_calls = []
298
+ for id, updates in dbupdates.items():
299
+ for update in updates:
300
+ if update:
301
+ screen2el_bl = dbshare[id]
302
+ exclude = update.get('exclude', False)
303
+ for user in Unishare.sessions.values():
304
+ if not exclude or user is not self:
305
+ scr_name = user.screen.name
306
+ if scr_name in screen2el_bl:
307
+ for elem_block in screen2el_bl[scr_name]:
308
+ update4user = {**update, **elem_block}
309
+ sync_calls.append(user.send(update4user))
310
+ dbupdates.clear()
311
+ await asyncio.gather(*sync_calls)
312
+
288
313
  def context_user():
289
314
  return context_object(User)
290
315
 
@@ -296,16 +321,17 @@ def message_logger(str, type = 'error'):
296
321
  user = context_user()
297
322
  user.log(str, type)
298
323
 
299
- references.context_user = context_user
300
- references.message_logger = message_logger
301
-
302
- User.db = Database(config.db_dir, message_logger) if config.db_dir else None
324
+ Unishare.context_user = context_user
325
+ Unishare.message_logger = message_logger
303
326
  User.type = User
304
327
 
328
+ if config.db_dir:
329
+ Unishare.db = Database(config.db_dir, message_logger)
330
+
305
331
  def make_user(request):
306
332
  session = f'{request.remote}-{User.count}'
307
333
  if requested_connect := request.query_string if config.share else None:
308
- user = User.sessions.get(requested_connect, None)
334
+ user = Unishare.sessions.get(requested_connect, None)
309
335
  if not user:
310
336
  error = f'Session id "{requested_connect}" is unknown. Connection refused!'
311
337
  with logging_lock:
@@ -319,16 +345,11 @@ def make_user(request):
319
345
  else:
320
346
  user = User.type(session)
321
347
  ok = user.load()
322
- #register in shared db objects for init user
348
+ #register shared db map once
323
349
  if not user.count:
324
- for module in user.screens:
325
- screen = module.screen
326
- for block in flatten(screen.blocks):
327
- for elem in flatten(block.value):
328
- if hasattr(elem, 'id'):
329
- User.dbshare[elem.id][screen.name].append([elem.name, block.name])
350
+ user.calc_dbsharing()
330
351
  User.count += 1
331
- User.sessions[session] = user
352
+ Unishare.sessions[session] = user
332
353
  return user, ok
333
354
 
334
355
  def handle(elem, event):
@@ -343,4 +364,4 @@ def handle(elem, event):
343
364
  return fn
344
365
  return h
345
366
 
346
- references.handle = handle
367
+ Unishare.handle = handle
@@ -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-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}
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-763052dd]{display:flex;justify-content:center}.custom-caption[data-v-763052dd]{padding:5px!important}.web-camera-container[data-v-763052dd]{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-763052dd]{margin-bottom:2rem}.web-camera-container .camera-box .camera-shutter[data-v-763052dd]{background-color:#fff;height:337.5px;opacity:0;position:absolute;width:450px}.web-camera-container .camera-box .camera-shutter.flash[data-v-763052dd]{opacity:1}.web-camera-container .camera-shoot[data-v-763052dd]{margin:1rem 0}.web-camera-container .camera-shoot button[data-v-763052dd]{align-items:center;border-radius:100%;display:flex;height:60px;justify-content:center;width:60px}.web-camera-container .camera-shoot button img[data-v-763052dd]{height:35px;object-fit:cover}.web-camera-container .camera-loading[data-v-763052dd]{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-763052dd]{height:100%;margin:0;position:absolute;width:100%;z-index:999999}.web-camera-container .camera-loading .loader-circle[data-v-763052dd]{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-763052dd]{animation:preload-763052dd 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-763052dd]:nth-child(2){animation-delay:.2s}.web-camera-container .camera-loading .loader-circle li[data-v-763052dd]:nth-child(3){animation-delay:.4s}@keyframes preload-763052dd{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><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.c4786556.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.3ca810a2.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>