unisi 0.1.11__py3-none-any.whl → 0.1.13__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/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from .utils import *
2
2
  from .guielements import *
3
- from .users import User, handle
3
+ from .users import User, handle, context_user, context_screen
4
4
  from .server import start
5
5
  from .tables import *
6
6
  from .containers import *
unisi/autotest.py CHANGED
@@ -9,32 +9,9 @@ from .jsoncomparison import Compare, NO_DIFF
9
9
  def obj2json(obj):
10
10
  return json.loads(toJson(obj))
11
11
 
12
- #setting config variables
13
- testdir = 'autotest'
14
- if not hasattr(config, testdir):
15
- config.autotest = False
16
- if not hasattr(config, 'port'):
17
- config.port = 8000
18
- if not hasattr(config, 'upload_dir'):
19
- config.upload_dir = 'web'
20
- if not hasattr(config, 'logfile'):
21
- config.logfile = None
22
- if not hasattr(config, 'hot_reload'):
23
- config.hot_reload = False
24
- if not hasattr(config, 'appname'):
25
- config.appname = 'Unisi app'
26
- if not hasattr(config, 'mirror'):
27
- config.mirror = False
28
-
29
12
  if not os.path.exists(config.upload_dir):
30
13
  os.makedirs(config.upload_dir)
31
14
 
32
- #start logging
33
- format = "%(asctime)s - %(levelname)s - %(message)s"
34
- logfile = config.logfile
35
- handlers = [logging.FileHandler(logfile), logging.StreamHandler()] if logfile else []
36
- logging.basicConfig(level = logging.WARNING, format = format, handlers = handlers)
37
-
38
15
  comparator = Compare(rules = {'toolbar': '*'}).check
39
16
 
40
17
  class Recorder:
@@ -202,7 +179,7 @@ def check_module(module):
202
179
  def run_tests():
203
180
  if not os.path.exists(testdir):
204
181
  os.makedirs(testdir)
205
- user = User.type('autotest')
182
+ user = User.type(testdir)
206
183
  user.load()
207
184
  errors = []
208
185
  for module in user.screens:
unisi/common.py CHANGED
@@ -9,16 +9,15 @@ def flatten(*arr):
9
9
 
10
10
  class ArgObject:
11
11
  def __init__(self, **kwargs):
12
- for key, value in kwargs.items():
13
- setattr(self, key, value)
12
+ self.__dict__.update(kwargs)
14
13
 
15
14
  class ReceivedMessage:
16
15
  def __init__(self, data):
17
- self.screen = data.get('screen')
18
- self.block = data.get('block')
19
- self.element = data.get('element')
20
- self.event = data.get('event')
21
- self.value = data.get('value')
16
+ self.__dict__.update(data)
17
+ self.screen = data.get('screen')
18
+ self.value = data.get('value')
19
+ def __str__(self):
20
+ return f'{self.block}/{self.element}->{self.event}({self.value})'
22
21
 
23
22
  def toJson(obj):
24
23
  return jsonpickle.encode(obj,unpicklable = False)
unisi/containers.py CHANGED
@@ -103,8 +103,7 @@ class Dialog:
103
103
 
104
104
  class Screen:
105
105
  def __init__(self, name, **kwargs):
106
- self.name = name
106
+ self.name = name
107
+ self.__dict__.update(kwargs)
107
108
  self.type = 'screen'
108
- for key, value in kwargs.items():
109
- setattr(self, key, value)
110
109
 
unisi/guielements.py CHANGED
@@ -9,8 +9,7 @@ class Gui:
9
9
  self.add(kwargs)
10
10
 
11
11
  def add(self, kwargs):
12
- for key, value in kwargs.items():
13
- setattr(self, key, value)
12
+ self.__dict__.update(kwargs)
14
13
 
15
14
  def mutate(self, obj):
16
15
  self.__dict__ = obj.__dict__
@@ -25,12 +24,12 @@ Line = Gui("Line", type = 'line')
25
24
 
26
25
  def smart_complete(lst, min_input_length = 0, max_output_length = 20):
27
26
  di = {it: it.lower() for it in lst}
28
- def complete(gui, ustr):
27
+ def complete(_, ustr):
29
28
  if len(ustr) < min_input_length:
30
29
  return []
31
30
  ustr = ustr.lower()
32
31
  arr = [(itlow.find(ustr), it, itlow) for it, itlow in di.items() if itlow.find(ustr) != -1]
33
- arr.sort(key=lambda e: (e[0], e[2]))
32
+ arr.sort(key = lambda e: (e[0], e[2]))
34
33
  if len(arr) > max_output_length:
35
34
  arr = arr[: max_output_length]
36
35
  return [e[1] for e in arr]
unisi/multimon.py ADDED
@@ -0,0 +1,101 @@
1
+ import multiprocessing, time, asyncio, logging, inspect
2
+ from .utils import start_logging
3
+ from config import froze_time, monitor_tick, profile, pool
4
+
5
+ def write_string_to(shared_array, input_string):
6
+ input_bytes = input_string.encode()
7
+ shared_array[:len(input_bytes)] = input_bytes
8
+
9
+ def read_string_from(shared_array):
10
+ return shared_array[:].decode().rstrip('\x00')
11
+
12
+ _multiprocessing_pool = None
13
+
14
+ def multiprocessing_pool():
15
+ global _multiprocessing_pool
16
+ if not _multiprocessing_pool:
17
+ _multiprocessing_pool = multiprocessing.Pool(pool)
18
+ return _multiprocessing_pool
19
+
20
+ async def run_external_process(long_running_task, *args, queue = None, progress_callback = None, **kwargs):
21
+ if progress_callback:
22
+ if queue is None:
23
+ queue = multiprocessing.Manager().Queue()
24
+ if args[-1] is None:
25
+ args = *args[:-1], queue
26
+ result = multiprocessing_pool().apply_async(long_running_task, args, kwargs)
27
+ if progress_callback:
28
+ while not result.ready() or not queue.empty():
29
+ message = queue.get()
30
+ if message is None:
31
+ break
32
+ await asyncio.gather(progress_callback(message), asyncio.sleep(monitor_tick))
33
+ return result.get()
34
+
35
+ logging_lock = multiprocessing.Lock()
36
+
37
+ splitter = '~'
38
+
39
+ def monitor_process(monitor_shared_arr):
40
+ timer = None
41
+ session_status = {}
42
+ sname = None
43
+ start_logging()
44
+ while True:
45
+ #Wait for data in the shared array
46
+ while monitor_shared_arr[0] == b'\x00':
47
+ time.sleep(0.005)
48
+ if timer is not None:
49
+ timer -= monitor_tick
50
+ if timer < 0:
51
+ timer = None
52
+ arr = list(session_status.items())
53
+ arr.sort(key = lambda s: s[1][1], reverse=True)
54
+ ct = time.time()
55
+ message = "Hangout is detected! Sessions in a queue and time waiting:" +\
56
+ ''.join(f'\n {s[0]}, {s[1][0]}, {ct - s[1][1]} s' for s in arr)
57
+ with logging_lock:
58
+ logging.warning(message)
59
+ timer = None
60
+ # Read and process the data
61
+ status = read_string_from(monitor_shared_arr).split(splitter)
62
+ #free
63
+ monitor_shared_arr[0] = b'\x00'
64
+ sname = status[1]
65
+ match status[0]:
66
+ case '+' | 'e': #exit external process
67
+ session_status[sname] = [status[2], time.time()]
68
+ timer = froze_time
69
+ case '-':
70
+ event, tstart = session_status.get(sname, (None, 0))
71
+ if event:
72
+ duration = time.time() - tstart
73
+ if profile and duration > profile:
74
+ with logging_lock:
75
+ logging.warning(f'Event handler {event} was executed for {duration} seconds!')
76
+ del session_status[sname]
77
+ timer = None
78
+ case 'p': #call external process
79
+ session_status[sname] = [status[2], time.time()]
80
+ timer = None
81
+
82
+ if froze_time or profile:
83
+ # Create a shared memory array
84
+ monitor_shared_arr = multiprocessing.Array('c', 200)
85
+ monitor_shared_arr[0] != b'\x00'
86
+
87
+ async def notify_monitor(status, session, event):
88
+ s = f'{status}{splitter}{session}{splitter}{event}'
89
+ # Wait for the shared array to be empty
90
+ while monitor_shared_arr[0] != b'\x00':
91
+ await asyncio.sleep(monitor_tick)
92
+ write_string_to(monitor_shared_arr, s)
93
+
94
+ monitor_process = multiprocessing.Process(target=monitor_process, args=(monitor_shared_arr,))
95
+ monitor_process.start()
96
+ else:
97
+ notify_monitor = None
98
+
99
+
100
+
101
+
unisi/proxy.py CHANGED
@@ -30,7 +30,9 @@ class Proxy:
30
30
  addr_port = f'{wss_header if ssl else ws_header}{host_port}'
31
31
  addr_port = f'{addr_port}{"" if addr_port.endswith("/") else "/"}{ws_path}'
32
32
  self.host_port = f'{"https" if ssl else "http"}://{host_port}'
33
- self.conn = create_connection(addr_port, timeout = timeout, header = {'session' : session})
33
+ if session:
34
+ addr_port = f'{addr_port}?{session}'
35
+ self.conn = create_connection(addr_port, timeout = timeout)
34
36
  self.screen = None
35
37
  self.screens = {}
36
38
  self.dialog = None
unisi/server.py CHANGED
@@ -49,8 +49,6 @@ async def websocket_handler(request):
49
49
  if not user:
50
50
  await ws.send_str(toJson(status))
51
51
  else:
52
- user.transport = ws._writer.transport if divpath != '/' else None
53
-
54
52
  async def send(res):
55
53
  if type(res) != str:
56
54
  res = toJson(user.prepare_result(res))
@@ -80,31 +78,18 @@ async def websocket_handler(request):
80
78
  if message:
81
79
  if recorder.record_file:
82
80
  recorder.accept(message, user.prepare_result (result))
83
- await user.reflect(message, result)
84
- """ if user.reflections and not is_screen_switch(message):
85
- if result:
86
- await broadcast(result, user)
87
- msg_object = user.find_element(message)
88
- if not isinstance(result, Message) or not result.contains(msg_object):
89
- await broadcast(toJson(user.prepare_result(msg_object)), user) """
81
+ await user.reflect(message, result)
90
82
  elif msg.type == WSMsgType.ERROR:
91
83
  user.log('ws connection closed with exception %s' % ws.exception())
92
- except:
93
- user.log(traceback.format_exc())
84
+ except BaseException as e:
85
+ if not isinstance(e, ConnectionResetError):
86
+ user.log(traceback.format_exc())
94
87
 
95
- uss = User.sessions
96
- if uss and uss.get(user.session):
97
- del uss[user.session]
98
-
99
- if user.reflections: #reflections is common array
100
- if len(user.reflections) == 2:
101
- user.reflections.clear() #1 element in user.reflections has no sense
102
- else:
103
- user.reflections.remove(user)
104
- return ws #?<->
88
+ await user.delete()
89
+ return ws
105
90
 
106
91
  def start(appname = None, user_type = User, http_handlers = []):
107
- if appname is not None:
92
+ if appname:
108
93
  config.appname = appname
109
94
 
110
95
  User.type = user_type
unisi/users.py CHANGED
@@ -2,6 +2,7 @@ from .utils import *
2
2
  from .guielements import *
3
3
  from .common import *
4
4
  from .containers import Dialog, Screen
5
+ from .multimon import notify_monitor, logging_lock, run_external_process
5
6
  import sys, asyncio, logging, importlib
6
7
 
7
8
  class User:
@@ -25,13 +26,22 @@ class User:
25
26
  self.screens = []
26
27
  self.reflections = []
27
28
  self.screen_module = None
28
- self.__handlers__ = {}
29
+ self.__handlers__ = {}
29
30
 
30
- def sync_send(self, obj):
31
- asyncio.run(self.send(obj))
31
+ self.monitor(session, share)
32
+
33
+ async def run_process(self, long_running_task, *args, progress_callback = None, queue = None, **kwargs):
34
+ if progress_callback and notify_monitor and progress_callback != self.progress: #progress notifies the monitor
35
+ async def new_callback(value):
36
+ asyncio.gather(notify_monitor('e', self.session, self.last_message), progress_callback(value))
37
+ progress_callback = new_callback
38
+ return await run_external_process(long_running_task, *args, progress_callback = progress_callback,
39
+ queue = queue, **kwargs)
32
40
 
33
41
  async def broadcast(self, message):
34
42
  screen = self.screen_module
43
+ if type(message) != str:
44
+ message = toJson(self.prepare_result(message))
35
45
  await asyncio.gather(*[user.send(message)
36
46
  for user in self.reflections
37
47
  if user is not self and screen is user.screen_module])
@@ -43,13 +53,15 @@ class User:
43
53
  if message:
44
54
  msg_object = self.find_element(message)
45
55
  if not isinstance(result, Message) or not result.contains(msg_object):
46
- await self.broadcast(toJson(self.prepare_result(msg_object)))
56
+ await self.broadcast(msg_object)
47
57
 
48
58
  async def progress(self, str, *updates):
49
59
  """open or update progress window if str != null else close it """
50
60
  if not self.testing:
51
61
  msg = TypeMessage('progress', str, *updates, user = self)
52
62
  await asyncio.gather(self.send(msg), self.reflect(None, msg))
63
+ if notify_monitor:
64
+ await notify_monitor('e', self.session, self.last_message)
53
65
 
54
66
  def load_screen(self, file):
55
67
  screen_vars = {
@@ -65,9 +77,8 @@ class User:
65
77
  path = f'{screens_dir}{divpath}{file}'
66
78
  spec = importlib.util.spec_from_file_location(name,path)
67
79
  module = importlib.util.module_from_spec(spec)
80
+ module.user = self
68
81
 
69
- module.user = self
70
-
71
82
  spec.loader.exec_module(module)
72
83
  screen = Screen(getattr(module, 'name', ''))
73
84
  #set system vars
@@ -78,9 +89,22 @@ class User:
78
89
  screen.toolbar += User.toolbar
79
90
  else:
80
91
  screen.toolbar = User.toolbar
81
-
82
- module.screen = screen
92
+ module.screen = screen
83
93
  return module
94
+
95
+ async def delete(self):
96
+ uss = User.sessions
97
+ if uss and uss.get(self.session):
98
+ del uss[self.session]
99
+
100
+ if self.reflections: #reflections is common array
101
+ if len(self.reflections) == 2:
102
+ self.reflections.clear() #1 element in user.reflections has no sense
103
+ else:
104
+ self.reflections.remove(self)
105
+
106
+ if notify_monitor:
107
+ await notify_monitor('-', self.session, self.last_message)
84
108
 
85
109
  def set_clean(self):
86
110
  #remove user modules from sys
@@ -99,7 +123,7 @@ class User:
99
123
  self.screens.append(module)
100
124
 
101
125
  if self.screens:
102
- self.screens.sort(key=lambda s: s.screen.order)
126
+ self.screens.sort(key=lambda s: s.order)
103
127
  main = self.screens[0]
104
128
  if 'prepare' in dir(main):
105
129
  main.prepare()
@@ -115,7 +139,7 @@ class User:
115
139
 
116
140
  @property
117
141
  def testing(self):
118
- return self.session == 'autotest'
142
+ return self.session == testdir
119
143
 
120
144
  @property
121
145
  def screen(self):
@@ -125,16 +149,14 @@ class User:
125
149
  return asyncio.run(self.process(ArgObject(block = 'root', element = None, value = name)))
126
150
 
127
151
  async def result4message(self, message):
128
- result = None
129
- dialog = self.active_dialog
130
- if dialog:
131
- if message.element is None: #button pressed
152
+ result = None
153
+ self.last_message = message
154
+ if dialog := self.active_dialog:
155
+ if message.element is None: #dialog command button is pressed
132
156
  self.active_dialog = None
133
157
  if self.reflections:
134
- await self.broadcast(TypeMessage('action', 'close'))
135
- handler = dialog.changed
136
- result = (await handler(dialog, message.value)) if asyncio.iscoroutinefunction(handler)\
137
- else handler(dialog, message.value)
158
+ await self.broadcast(TypeMessage('action', 'close'))
159
+ result = await self.eval_handler(dialog.changed, dialog, message.value)
138
160
  else:
139
161
  el = self.find_element(message)
140
162
  if el:
@@ -145,6 +167,15 @@ class User:
145
167
  self.active_dialog = result
146
168
  return result
147
169
 
170
+ async def eval_handler(self, handler, gui, value):
171
+ if notify_monitor:
172
+ await notify_monitor('+', self.session, self.last_message)
173
+ result = (await handler(gui, value)) if asyncio.iscoroutinefunction(handler)\
174
+ else handler(gui, value)
175
+ if notify_monitor:
176
+ await notify_monitor('-', self.session, None)
177
+ return result
178
+
148
179
  @property
149
180
  def blocks(self):
150
181
  return [self.active_dialog] if self.active_dialog and \
@@ -188,8 +219,7 @@ class User:
188
219
  raw = Message(*raw, user = self)
189
220
  return raw
190
221
 
191
- async def process(self, message):
192
- self.last_message = message
222
+ async def process(self, message):
193
223
  screen_change_message = getattr(message, 'screen',None) and self.screen.name != message.screen
194
224
  if is_screen_switch(message) or screen_change_message:
195
225
  for s in self.screens:
@@ -214,18 +244,16 @@ class User:
214
244
  return Error(error)
215
245
 
216
246
  async def process_element(self, elem, message):
217
- event = message.event
247
+ event = message.event
218
248
  query = event == 'complete' or event == 'append'
219
249
 
220
250
  handler = self.__handlers__.get((elem, event), None)
221
251
  if handler:
222
- return (await handler(elem, message.value)) if asyncio.iscoroutinefunction(handler)\
223
- else handler(elem, message.value)
252
+ return await self.eval_handler(handler, elem, message.value)
224
253
 
225
254
  handler = getattr(elem, event, False)
226
255
  if handler:
227
- result = (await handler(elem, message.value)) if asyncio.iscoroutinefunction(handler)\
228
- else handler(elem, message.value)
256
+ result = await self.eval_handler(handler, elem, message.value)
229
257
  if query:
230
258
  result = Answer(event, message, result)
231
259
  return result
@@ -235,14 +263,27 @@ class User:
235
263
  error = f"{message.block}/{message.element} doesn't contain '{event}' method type!"
236
264
  self.log(error)
237
265
  return Error(error)
266
+
267
+ def monitor(self, session, share):
268
+ if config.share and session != testdir:
269
+ self.log(f'User is connected, session: {session}, share: {share.session if share else None}', type = 'info')
270
+
271
+ def sync_send(self, obj):
272
+ asyncio.run(self.send(obj))
238
273
 
239
274
  def log(self, str, type = 'error'):
240
275
  scr = self.screen.name if self.screens else 'void'
241
276
  str = f"session: {self.session}, screen: {scr}, message: {self.last_message}\n {str}"
242
- if type == 'error':
243
- logging.error(str)
244
- else:
245
- logging.warning(str)
277
+ with logging_lock:
278
+ if type == 'error':
279
+ logging.error(str)
280
+ elif type == 'warning':
281
+ logging.warning(str)
282
+ else:
283
+ func = logging.getLogger().setLevel
284
+ func(level = logging.INFO)
285
+ logging.info(str)
286
+ func(level = logging.WARNING)
246
287
 
247
288
  User.type = User
248
289
  User.last_user = None
@@ -250,15 +291,22 @@ User.toolbar = []
250
291
  User.sessions = {}
251
292
  User.count = 0
252
293
 
294
+ def context_user():
295
+ return context_object(User)
296
+
297
+ def context_screen():
298
+ user = context_user()
299
+ return user.screen if user else None
300
+
253
301
  def make_user(request):
254
302
  session = f'{request.remote}-{User.count}'
255
303
  User.count += 1
256
- requested_connect = request.headers.get('session')
257
- if requested_connect:
304
+ if requested_connect := request.query_string if config.share else None:
258
305
  user = User.sessions.get(requested_connect, None)
259
306
  if not user:
260
307
  error = f'Session id "{requested_connect}" is unknown. Connection refused!'
261
- logging.error(error)
308
+ with logging_lock:
309
+ logging.error(error)
262
310
  return None, Error(error)
263
311
  user = User.type(session, user)
264
312
  ok = user.screens
unisi/utils.py CHANGED
@@ -1,10 +1,11 @@
1
- import os, platform, requests
1
+ import os, platform, requests, inspect, logging
2
2
 
3
3
  blocks_dir = 'blocks'
4
4
  screens_dir = 'screens'
5
5
  UpdateScreen = True
6
6
  Redesign = 2
7
7
  public_dirs = 'public_dirs'
8
+ testdir = 'autotest'
8
9
 
9
10
  divpath = '\\' if platform.system() == 'Windows' else '/'
10
11
  libpath = os.path.dirname(os.path.realpath(__file__))
@@ -26,6 +27,42 @@ appname = 'Unisi app'
26
27
  import config
27
28
  print("Config with default parameters is created!")
28
29
 
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
+ 'llm' : 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
+
49
+ if config.froze_time == 0:
50
+ print('froze_time in config.py can not be 0!')
51
+ config.froze_time = None
52
+
53
+ def context_object(target_type):
54
+ """
55
+ Finds the first argument of a specific type in the current function call stack.
56
+ """
57
+ frame = inspect.currentframe()
58
+ while frame:
59
+ args, _, _, values = inspect.getargvalues(frame)
60
+ if args and isinstance(values[args[0]], target_type):
61
+ return values[args[0]]
62
+ # Move to the previous frame in the call stack
63
+ frame = frame.f_back
64
+ return None
65
+
29
66
  def is_screen_switch(message):
30
67
  return message and message.block == 'root' and message.element is None
31
68
 
@@ -105,4 +142,13 @@ def Answer(type, message, result):
105
142
  ms.message = message
106
143
  return ms
107
144
 
145
+ def start_logging():
146
+ format = "%(asctime)s - %(levelname)s - %(message)s"
147
+ logfile = config.logfile
148
+ handlers = [logging.FileHandler(logfile), logging.StreamHandler()] if logfile else []
149
+ logging.basicConfig(level = logging.WARNING, format = format, handlers = handlers)
150
+
151
+ start_logging()
152
+
153
+
108
154
 
@@ -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.cf197a5a.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>