From b668ee8507f977bef48e4828b2a6562774f3c516 Mon Sep 17 00:00:00 2001 From: gitrusprus Date: Thu, 28 May 2026 13:24:48 +0300 Subject: [PATCH] Add network map UX and theme support. Introduce network map view/components with filtering and positioning, expand contacts/map backend fields and migrations, and add a persistent light/dark theme toggle with graph label color updates for readability. Co-authored-by: Cursor --- README.md | 2 +- .../__pycache__/__init__.cpython-311.pyc | Bin 132 -> 132 bytes .../__pycache__/settings.cpython-311.pyc | Bin 1261 -> 1261 bytes .../config/__pycache__/urls.cpython-311.pyc | Bin 316 -> 316 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 134 -> 134 bytes .../__pycache__/models.cpython-311.pyc | Bin 3566 -> 5449 bytes .../__pycache__/serializers.cpython-311.pyc | Bin 4721 -> 4975 bytes .../contacts/__pycache__/urls.cpython-311.pyc | Bin 934 -> 1118 bytes .../__pycache__/views.cpython-311.pyc | Bin 15487 -> 18971 bytes ...importance_contact_life_sphere_and_more.py | 68 + .../0003_contact_include_on_network_map.py | 29 + .../0004_contact_map_default_off.py | 21 + ...tact_map_angle_contact_map_radius_ratio.py | 27 + .../__pycache__/0001_initial.cpython-311.pyc | Bin 3139 -> 3139 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 145 -> 145 bytes backend/contacts/models.py | 56 + backend/contacts/serializers.py | 11 +- backend/contacts/urls.py | 2 + backend/contacts/views.py | 63 +- backend/db.sqlite3 | Bin 159744 -> 204800 bytes frontend/package-lock.json | 2412 +++++++++++++++++ frontend/package.json | 9 +- frontend/src/App.vue | 110 +- frontend/src/api.js | 7 + frontend/src/assets/style.css | 53 +- frontend/src/components/ContactForm.test.js | 25 + frontend/src/components/ContactForm.vue | 117 +- frontend/src/components/GraphHeaderPanel.vue | 43 + frontend/src/components/MapLegendPanel.vue | 87 + .../src/components/NetworkMapTopPanel.vue | 103 + .../src/components/RelationTypeFilters.vue | 29 + .../src/components/SearchableSelect.test.js | 49 + frontend/src/components/SearchableSelect.vue | 250 ++ frontend/src/composables/useGraphData.js | 18 + frontend/src/lib/api/errors.js | 19 + frontend/src/lib/api/errors.test.js | 15 + frontend/src/lib/api/pagination.js | 19 + frontend/src/lib/api/pagination.test.js | 26 + frontend/src/lib/graph/relationColors.js | 8 + frontend/src/lib/map/positioning.js | 81 + frontend/src/lib/map/positioning.test.js | 23 + frontend/src/router/index.js | 5 + frontend/src/stores/contacts.js | 116 +- frontend/src/views/ContactDetailView.vue | 100 +- frontend/src/views/ContactsView.vue | 26 +- frontend/src/views/GraphView.vue | 133 +- frontend/src/views/ImportView.vue | 27 +- frontend/src/views/NetworkMapView.vue | 608 +++++ frontend/vitest.config.js | 11 + 49 files changed, 4679 insertions(+), 129 deletions(-) create mode 100644 backend/contacts/migrations/0002_contact_importance_contact_life_sphere_and_more.py create mode 100644 backend/contacts/migrations/0003_contact_include_on_network_map.py create mode 100644 backend/contacts/migrations/0004_contact_map_default_off.py create mode 100644 backend/contacts/migrations/0005_contact_map_angle_contact_map_radius_ratio.py create mode 100644 frontend/package-lock.json create mode 100644 frontend/src/components/ContactForm.test.js create mode 100644 frontend/src/components/GraphHeaderPanel.vue create mode 100644 frontend/src/components/MapLegendPanel.vue create mode 100644 frontend/src/components/NetworkMapTopPanel.vue create mode 100644 frontend/src/components/RelationTypeFilters.vue create mode 100644 frontend/src/components/SearchableSelect.test.js create mode 100644 frontend/src/components/SearchableSelect.vue create mode 100644 frontend/src/composables/useGraphData.js create mode 100644 frontend/src/lib/api/errors.js create mode 100644 frontend/src/lib/api/errors.test.js create mode 100644 frontend/src/lib/api/pagination.js create mode 100644 frontend/src/lib/api/pagination.test.js create mode 100644 frontend/src/lib/graph/relationColors.js create mode 100644 frontend/src/lib/map/positioning.js create mode 100644 frontend/src/lib/map/positioning.test.js create mode 100644 frontend/src/views/NetworkMapView.vue create mode 100644 frontend/vitest.config.js diff --git a/README.md b/README.md index ef6af7f..1242346 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ```bash git clone cd social-graph -docker-compose up --build +docker compose up --build ``` - Frontend: http://localhost:5173 diff --git a/backend/config/__pycache__/__init__.cpython-311.pyc b/backend/config/__pycache__/__init__.cpython-311.pyc index db475c66cbc21f1ac568e04e63fbdacd53fc0ba9..bc92fc14c1bc9395876ef39fcaf8aefe69eacbfe 100644 GIT binary patch delta 19 ZcmZo+Y+>YH&dbZi00a*W-%sSO1OO^{1or>{ delta 19 ZcmZo+Y+>YH&dbZi00eJyw@&1)1OO`p1qA>A diff --git a/backend/config/__pycache__/settings.cpython-311.pyc b/backend/config/__pycache__/settings.cpython-311.pyc index 26234f0ea3df3073b4c7a14d85de426413e4ecc5..31d9b10be3d6956fa52504b3d2837620fb40915b 100644 GIT binary patch delta 20 acmaFM`IeJ=IWI340}wnke7}+V5eoo8dj@6z delta 20 acmaFM`IeJ=IWI340}yERZQIEGhy?&WeFf0~ diff --git a/backend/config/__pycache__/urls.cpython-311.pyc b/backend/config/__pycache__/urls.cpython-311.pyc index 2396867166f60d3d62428cfaea528c5fc809fe3b..ecbfdceb5c3aa7a965fdcc419eecafc3e3a82cd5 100644 GIT binary patch delta 20 acmdnPw1Vw;&HmgQ$+C>6&wcWJcWkJnDO39M))qmlRgk##7a60JO znQoYNC0q_^Qc@efawOdNb>Y{ex>e7lGtuajj!0_bMM?E)uCv~%;;Rjk^bKzNN;E;L z2~s}6?E`o7|KV;{{i9O6FJ+G5X_}9jV#-|t){ZrZT@k;+c_B_*De3{=h=8R8hU`kWcg(*EqnlAI3n4t){8^#=8zK(gag(;2M7F;rl#{2v` zj4sC;AljJMbGa-l=<7-Lb9*$#e*nWxe3qf;U;MV1-k)PxRZkXDGpWLPy|0#EOn4Kj zZ{y`w_ziv!M!ZXHOwz1sGg>;Eo7OS~y=%1~#FqFiu&)rSc$vmjrl4i=n!c47ukd>q zSq6RAR%?93mt(N#z8F}d`HYXq2l<@BAk{-s3y`=;Wnh58}=Hzf5pyvuD!-a;n2f!BP`n2W(Sn2BEGPh=}v8!>I%DOA}`68Y?Fk$dmh z8TJ-DL@T&buOQxFxiWe>{nW*bvW~uo44@)%6O!*6@BWIKSU(uyc`Sb1#Wq3MR=AN` zv}V^GmS52eSviw^TUJyRmCIXwuE~?tJ^Zj6QmgaiBr>JyOt{pBgj;nXZrzXlmAD-^ zp%oOo2ja_K?wSy`i&agR@zF@!V|ir}lCo_1WEo{tPituVW%(^#Ns}@>d9zH_SSm9q z-olEkj%7KQ)|ryNE)(&LVuN-noEX>XJ)@Yks?%Y&1qgnnen^ZEX3rZoC%~;-CHlGr$ zrZMeYp(2pU7O>C!$A^ao<6Koy!57#SS<{cER=$%lqd9XgKD z){Isl$Rj6HESc7AEl5qKs3|>f1BerCv8aqT7~4riEHteUi&V)vH1f#aPoe}X<9eC9UjDIp{p_An^ZJGS zN+)kVXfz)zH6JViJ zr%Ta3!M4E+_L|Xtu(cCgJF&HUiX&hPzcAlk3hgq3A+xQIxAiS3Mq7WWt)Fju3E!0i z#V!mj1)Za8G~v-Ms|(2BHwBcwK{%Mo zAioVkzmh~o1X!j$Q-%JhLnuQ)0w_-3gULIt=TjLipSN-GB~iw4G62i>R6Oj0bm&#`yXv4;lK@hb0L7&? zYR#%wK<8S`{yILK*Z_E2>wusQc%Nzu0PqIY5WsV-HXHC#8ntfC|@D|tDe)me3|uZWPiZ+ut@hz6?(2@;1+t$++F#E{phURG*kjV4sSNv&W9 z-Bk(-V~)|mjGY9rA=FohvmXe_2)p?TqtegXK*sE7`vUQ^f^i%1Ei+1wjG!?i_vuvd z)*j#NQz3Z)=UU@WBBB8k*=BIp1YfnhJ|wlZ!B>YjTB?o~fP=P_?I+7qc}rS#{>1P1 z*#x@SA3&^@Y6QrI^Er*}M&BwRB1+$d;1gd~P{=vrB9Mey7jD0_od=HMyKwmL$L!Y9 z?$V2cc9W?0Xa%FR)j5^@4mxan;v32`Ej5`rrkxkH6$hOIqlXS09(-ym+Y4^1_0-_; z17pLljmTqfoE#iwGIip=gnW{vassGCheyT+PaQZUn5aE6Iz0A<6{Wur7>-VWHM0G8 z0{*gK$H;`^M8vv1tJaUwD3RB3=eNM=s}|I?pS$+|*wQq2#n~Sj)KKS&Y%X`JqzVW>JEBpOoMz>5G!SsHb=W`T!27pkGU_ zwDIF-cvv>Vaw#kqkD1*&t_+pBcL)W2X0XeQ?#Dbmq@afs^q>fK{X!9>dbgeGT|J?C zx1H);WdQYV*g*yFS=26iBvkaB~=;V`8m{8?X9>gKiIvZpcV zEK5~V^q0e~2s)~y7$~<&?w3s0PN0@9CzqmS|9}IPe=V7vm`ndm{g0jwjyoE|q`pFm at>w1O4)Wl$>FmthJ3ouDN58-!JpDgUdX)VD delta 1267 zcmZ`&&rcIU6rNeS?NVsFZ6P4PtTAd9BSblZ3aKDOMI*hyWmCITShBP*+Y<s2oTn zF=R|MCVJoiF>){{k&BvmFyXcwOvw5N7!zVj;9xu$=WQuygOiX*hS!?`J z>tFQwyoisVBP;rtH0Q65B>CzI(vXG?G=c{(MgqEsG+`QPB?J!(bE>l0l@o=J9~b zp+ru(3zVpN`j8@h2d4_Q-6Ls@7-nKFSSiFXdoFAfKTt3Dm|R-lS;=J+{7j*ZKzMe-lBy}# z^(tW+=6_lN09v+3Rkd_NRcQ&VN~1$@+NBdiqaj91yVr?9wtT)yN zYbC+gd&9jeZM&fo4b1Bguy4L+(fxdvFBRvTSA6Dt87$vlx?-d#Wbf9RrIWJD zJEs@CU1>`6!PF%(;Z)l8L_7MT9Tz(3-ud(h*S7uFJ1=*{dam}WvHokFeY68M%QRvp zs6Gn8Q3aef0_^otr1-imKm#1*)BKIta%cgX>3+E1iPhnH6}Mfg4aZZ1=_c()K2Grp zyxV*;UMM&z^8QO|N~_!z4~foOTE(@;|^05|IP!06dS=q#^BaoR1$r&+|U7 z-|y?kt;yd{guf1j0vsHvaPp2Iej9#DNR<6R=5LCIzOKpU_HB)7cDSlpAJpikT+wO8 z(CmP|wOyl@QmttAh+eH2JE|tvs&Y4NOWC&l09mPSKzbBSs;K&o2^UMRu|LUaF$vSs zQN$RVSjiGn^eqB! zClNCM%rLRe9{%Q>OCy?m=xOpEl(q|yHpq>aLE2S32c=yKjj~I^$KF#|cb5Gk+>J|M z+HO^`6q5%tlc9Nb%QrYH@BvPD$CIFI; zeJ`f6gI?a|OVc)~9E$g=xR5;$1I!12J+8@#QP`j!5?pMk8Dq=A3x!5Hv;Uxx%hz)= zjfwQW(Tw;)VGsr2>gD4|nq!{_Q(oLEI>nv`N1#7`4oYN>{S!>*d#MR-WTH6?w=Eph zKA6*w!ybqE9k?~O0k0JDMr3xg zk z@moY9%Pbk4-tE^teZ1~4sw@Qty2ln|S*=xMnPOF1#Hl>hnyQ($M^iU6lfH-k8;F4> zT0|e>G{>_LykcbdaB@~M^NJv!3EZ!x!-DJ`1 z=0HUiP8El$YMBG5N^|KU=kntZ=%GDxh}3HT2Gp)qAF4=I``)gzNp04vk7wSSee>S; z&HOppJeJ$ZW;Fqxf7jkLK2d+ly(C2AKa&gmmRQN?;9z}-e*R4S{)D7!p|Fpq=};4f zm&x0q8~7@v_+`Vmew5!zy(J@jN}4AH{y>`TLvz(o*Z{hVh$H;9R6g1tEk_(j3?ZTr zBm58POhu;4BoyAyW@K+2_UilwEs^0@4SJp^3d>+LgYTp-Z!#>#kYZC**eP^=0H_-B z;k3}4MwAePh@*%@so``KaT0*pdglO7lHc$7(o-6W`%-~uWnGH}tAb!l_;w1mE$^fa zjCq=0S043Ez%Y<3(>Fa4hUGFF;lC-<Lh{7Aj>z+~4NwqvoR#5mjY z7`~&7DYPL<{6}@6D${DD_xp%?R|)E!cy&tqL!ldS3#VSqXy?d)4AJov{q+stxv(lc z7Va$YFEfwpmbfK-Cw_fpMKDr(LvttuU z#GFtZ4SQkQ#MrHNDk|*4x8*@Tp8M)Vd=4N;odEV91qP9h&tTBjaS?Q!6m-1)thM6=-R2ika=D#R5p|Fd7(HK7GT=Mjau!zP(O%RHMEXP5y-Wg<<@I8hFxKkmp84i#7z@BmPU_!YPe{4hc#boOUBV{Ih&oU##HG3bYlQHB6`Rh0UUH z9sBGl7uQ^C!?EYsr+iz#w3#Omw;L}qyQP^XI$8(bNu9tA?8b|s-NCa>bhHk<(<2zU U8{dAuTRhoBM+-2}xh5*;KgJML(f|Me diff --git a/backend/contacts/__pycache__/urls.cpython-311.pyc b/backend/contacts/__pycache__/urls.cpython-311.pyc index 44a9513cdc758e5d493e35deaacc11e088649b2d..4ea6118aed9e6234eb3d1f013e016add742cbff9 100644 GIT binary patch delta 294 zcmZ3+evdt`ORd)aY delta 176 zcmcb|v5Z}PIWI340}$}#ZOvTH$iVOz#DM{JDC4usMs<5eu5^|t{uJ(D22GyH8yG`n z_={3=5=%1k^K?rp3sQ^qtJpJh3-XIf^!+piCVMlr@bbYF!~+#fW?}Z^k^~wEw1lDf z=HzhZ&3aNZT(8I&Tx2o4!eV%Xg`>fxLvn)a6%oygELvAsv_5dM@-sDXgJ6+3P!j-+ C=PjfF diff --git a/backend/contacts/__pycache__/views.cpython-311.pyc b/backend/contacts/__pycache__/views.cpython-311.pyc index 5efed7a113a7bb34af132d5686b275e59a26f796..9537f7d710617f7d5bee58edcfae93442f759297 100644 GIT binary patch delta 4893 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 diff --git a/backend/contacts/migrations/0002_contact_importance_contact_life_sphere_and_more.py b/backend/contacts/migrations/0002_contact_importance_contact_life_sphere_and_more.py new file mode 100644 index 0000000..c4535d6 --- /dev/null +++ b/backend/contacts/migrations/0002_contact_importance_contact_life_sphere_and_more.py @@ -0,0 +1,68 @@ +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='importance', + field=models.PositiveSmallIntegerField( + default=3, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(5), + ], + verbose_name='Важность (1–5)', + ), + ), + migrations.AddField( + model_name='contact', + name='life_sphere', + field=models.CharField( + choices=[ + ('work', 'Работа'), + ('study', 'Учёба'), + ('hobby', 'Хобби'), + ('family', 'Семья'), + ('health', 'Здоровье'), + ('other', 'Другое'), + ], + default='other', + max_length=32, + verbose_name='Сфера жизни', + ), + ), + migrations.AddField( + model_name='contact', + name='network_circle', + field=models.CharField( + choices=[ + ('support', 'Круг поддержки'), + ('productivity', 'Круг продуктивности'), + ('development', 'Круг развития'), + ], + default='productivity', + max_length=32, + verbose_name='Круг сети', + ), + ), + migrations.AddField( + model_name='relation', + name='interaction_intensity', + field=models.CharField( + choices=[ + ('intense', 'Интенсивные контакты'), + ('sparse', 'Редкие контакты'), + ], + default='intense', + max_length=32, + verbose_name='Интенсивность общения', + ), + ), + ] diff --git a/backend/contacts/migrations/0003_contact_include_on_network_map.py b/backend/contacts/migrations/0003_contact_include_on_network_map.py new file mode 100644 index 0000000..e218c82 --- /dev/null +++ b/backend/contacts/migrations/0003_contact_include_on_network_map.py @@ -0,0 +1,29 @@ +from django.db import migrations, models + + +def set_map_true_for_existing(apps, schema_editor): + Contact = apps.get_model('contacts', 'Contact') + Contact.objects.all().update(include_on_network_map=True) + + +def noop_reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0002_contact_importance_contact_life_sphere_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='include_on_network_map', + field=models.BooleanField( + default=False, + verbose_name='Показывать на карте сети', + ), + ), + migrations.RunPython(set_map_true_for_existing, noop_reverse), + ] diff --git a/backend/contacts/migrations/0004_contact_map_default_off.py b/backend/contacts/migrations/0004_contact_map_default_off.py new file mode 100644 index 0000000..e4616f3 --- /dev/null +++ b/backend/contacts/migrations/0004_contact_map_default_off.py @@ -0,0 +1,21 @@ +from django.db import migrations + + +def set_map_false_for_all(apps, schema_editor): + Contact = apps.get_model('contacts', 'Contact') + Contact.objects.all().update(include_on_network_map=False) + + +def noop_reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0003_contact_include_on_network_map'), + ] + + operations = [ + migrations.RunPython(set_map_false_for_all, noop_reverse), + ] diff --git a/backend/contacts/migrations/0005_contact_map_angle_contact_map_radius_ratio.py b/backend/contacts/migrations/0005_contact_map_angle_contact_map_radius_ratio.py new file mode 100644 index 0000000..eafb1d8 --- /dev/null +++ b/backend/contacts/migrations/0005_contact_map_angle_contact_map_radius_ratio.py @@ -0,0 +1,27 @@ +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0004_contact_map_default_off'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='map_angle', + field=models.FloatField(blank=True, null=True, verbose_name='Угол позиции на карте'), + ), + migrations.AddField( + model_name='contact', + name='map_radius_ratio', + field=models.FloatField( + blank=True, + null=True, + validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)], + verbose_name='Радиус позиции на карте (доля)', + ), + ), + ] diff --git a/backend/contacts/migrations/__pycache__/0001_initial.cpython-311.pyc b/backend/contacts/migrations/__pycache__/0001_initial.cpython-311.pyc index 655ccc775f2a75df122835cb354fdacec5c8a879..e89b4ec80d4d16d7fdb09d414f0516418fd447fa 100644 GIT binary patch delta 20 acmX>saae+TIWI340}wnke7}*~f(HOR4+V|@ delta 20 acmX>saae+TIWI340}wFgZ{5gk!2@HVk=27F+ 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 delta 6382 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 diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..5c70bca --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2412 @@ +{ + "name": "social-graph-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "social-graph-frontend", + "version": "0.1.0", + "dependencies": { + "axios": "^1.6.7", + "pinia": "^2.1.7", + "vis-data": "^7.1.9", + "vis-network": "^9.1.9", + "vue": "^3.4.0", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.3", + "@vue/test-utils": "^2.4.6", + "jsdom": "^25.0.1", + "vite": "^5.1.0", + "vitest": "^2.1.9" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "license": "MIT", + "peer": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.34", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.34", + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.14", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/runtime-core": "3.5.34", + "@vue/shared": "3.5.34", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.34", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.10.tgz", + "integrity": "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.0", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/component-emitter": { + "version": "2.0.0", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/keycharm": { + "version": "0.4.0", + "license": "(Apache-2.0 OR MIT)", + "peer": true + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/pinia": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vis-data": { + "version": "7.1.10", + "license": "(Apache-2.0 OR MIT)", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "vis-util": "^5.0.1" + } + }, + "node_modules/vis-network": { + "version": "9.1.13", + "license": "(Apache-2.0 OR MIT)", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0 || ^2.0.0", + "keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0", + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "vis-data": "^6.3.0 || ^7.0.0", + "vis-util": "^5.0.1" + } + }, + "node_modules/vis-util": { + "version": "5.0.7", + "license": "(Apache-2.0 OR MIT)", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0 || ^2.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.34", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-sfc": "3.5.34", + "@vue/runtime-dom": "3.5.34", + "@vue/server-renderer": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.2.8.tgz", + "integrity": "sha512-9689efAXhN/EV86plgkL/XFiJSXhGtWPG6JDboZ+QnjlUWUUQrQ0ILKQtw4iQsuwIwu5k6Aw+JnehDe7161e7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json index 7c1f718..80fb310 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,9 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "vue": "^3.4.0", @@ -16,7 +18,10 @@ "vis-data": "^7.1.9" }, "devDependencies": { + "@vue/test-utils": "^2.4.6", "@vitejs/plugin-vue": "^5.0.3", - "vite": "^5.1.0" + "jsdom": "^25.0.1", + "vite": "^5.1.0", + "vitest": "^2.1.9" } } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 61c9f2e..198dd82 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ + + diff --git a/frontend/src/api.js b/frontend/src/api.js index d41a0a8..721ca72 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1,8 +1,15 @@ import axios from 'axios' +import { normalizeApiError } from './lib/api/errors' const api = axios.create({ baseURL: '/api', headers: { 'Content-Type': 'application/json' }, + timeout: 12000, }) +api.interceptors.response.use( + (response) => response, + (error) => Promise.reject(normalizeApiError(error)) +) + export default api diff --git a/frontend/src/assets/style.css b/frontend/src/assets/style.css index d30941d..f4ef018 100644 --- a/frontend/src/assets/style.css +++ b/frontend/src/assets/style.css @@ -2,6 +2,7 @@ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { + color-scheme: dark; --bg: #0f1117; --surface: #1a1d27; --surface-alt: #22263a; @@ -21,6 +22,24 @@ --font: 'Inter', system-ui, -apple-system, sans-serif; } +:root[data-theme='light'] { + color-scheme: light; + --bg: #f5f7fb; + --surface: #ffffff; + --surface-alt: #f0f3fa; + --border: #d7deef; + --accent: #2f6feb; + --accent-hover: #1f5bd2; + --accent-dim: rgba(47, 111, 235, 0.12); + --text: #1a2238; + --text-muted: #5e6b8a; + --text-dim: #8b96b0; + --red: #cc3d52; + --green: #1d9c77; + --orange: #cb7a2d; + --shadow: 0 10px 30px rgba(17, 24, 39, 0.08); +} + html, body { height: 100%; background: var(--bg); color: var(--text); font-family: var(--font); font-size: 14px; } #app { display: flex; flex-direction: column; height: 100%; } @@ -79,6 +98,27 @@ a:hover { color: var(--accent-hover); } display: flex; align-items: center; justify-content: space-between; + gap: 12px; +} +.page-header__title { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 12px; +} +.page-header__title h2 { + font-size: 18px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.page-header__actions { + display: flex; + flex-shrink: 0; + align-items: center; + gap: 8px; } .page-header h2 { font-size: 18px; font-weight: 600; } .page-content { padding: 20px 28px; flex: 1; } @@ -136,6 +176,12 @@ textarea.form-control { resize: vertical; min-height: 80px; } .table th { color: var(--text-muted); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; } .table tr:hover td { background: var(--surface-alt); cursor: pointer; } .table tr:last-child td { border-bottom: none; } +.table-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; + justify-content: flex-end; +} /* ===== Badge ===== */ .badge { @@ -155,8 +201,10 @@ textarea.form-control { resize: vertical; min-height: 80px; } /* ===== Modal ===== */ .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); - display: flex; align-items: center; justify-content: center; + display: flex; align-items: flex-start; justify-content: center; z-index: 1000; + overflow-y: auto; + padding: 24px 16px; } .modal { background: var(--surface); @@ -165,7 +213,10 @@ textarea.form-control { resize: vertical; min-height: 80px; } padding: 28px; width: 460px; max-width: 95vw; + max-height: calc(100vh - 48px); + overflow-y: auto; box-shadow: var(--shadow); + margin: auto 0; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-header h3 { font-size: 16px; font-weight: 600; } diff --git a/frontend/src/components/ContactForm.test.js b/frontend/src/components/ContactForm.test.js new file mode 100644 index 0000000..48e536d --- /dev/null +++ b/frontend/src/components/ContactForm.test.js @@ -0,0 +1,25 @@ +import { describe, it, expect, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import ContactForm from './ContactForm.vue' + +vi.mock('../api', () => ({ + default: { + get: vi.fn(async () => ({ + data: { + life_spheres: [{ value: 'work', label: 'Работа' }], + network_circles: [{ value: 'support', label: 'Круг поддержки' }], + }, + })), + }, +})) + +describe('ContactForm', () => { + it('emits submit with normalized payload', async () => { + const wrapper = mount(ContactForm, { props: { initial: { name: 'Иван' } } }) + await wrapper.get('form').trigger('submit.prevent') + const payload = wrapper.emitted('submit')[0][0] + expect(payload.name).toBe('Иван') + expect(payload.importance).toBeDefined() + expect(payload.include_on_network_map).toBe(false) + }) +}) diff --git a/frontend/src/components/ContactForm.vue b/frontend/src/components/ContactForm.vue index 76bd457..2407ecd 100644 --- a/frontend/src/components/ContactForm.vue +++ b/frontend/src/components/ContactForm.vue @@ -24,6 +24,35 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +

На странице «Граф» контакт виден всегда; на «Карте сети» — только с этой отметкой.

+
@@ -36,11 +65,30 @@ + + diff --git a/frontend/src/components/GraphHeaderPanel.vue b/frontend/src/components/GraphHeaderPanel.vue new file mode 100644 index 0000000..54a2222 --- /dev/null +++ b/frontend/src/components/GraphHeaderPanel.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/components/MapLegendPanel.vue b/frontend/src/components/MapLegendPanel.vue new file mode 100644 index 0000000..cdfdfa6 --- /dev/null +++ b/frontend/src/components/MapLegendPanel.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/frontend/src/components/NetworkMapTopPanel.vue b/frontend/src/components/NetworkMapTopPanel.vue new file mode 100644 index 0000000..0edadb2 --- /dev/null +++ b/frontend/src/components/NetworkMapTopPanel.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/frontend/src/components/RelationTypeFilters.vue b/frontend/src/components/RelationTypeFilters.vue new file mode 100644 index 0000000..919c989 --- /dev/null +++ b/frontend/src/components/RelationTypeFilters.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/components/SearchableSelect.test.js b/frontend/src/components/SearchableSelect.test.js new file mode 100644 index 0000000..fba0fdf --- /dev/null +++ b/frontend/src/components/SearchableSelect.test.js @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import SearchableSelect from './SearchableSelect.vue' + +const options = [ + { value: 1, label: 'Иван Петров' }, + { value: 2, label: 'Мария Сидорова' }, +] + +describe('SearchableSelect', () => { + it('filters options while typing', async () => { + const wrapper = mount(SearchableSelect, { + props: { modelValue: '', options, placeholder: 'Поиск' }, + }) + const input = wrapper.get('input') + await input.setValue('мария') + await input.trigger('focus') + + const items = wrapper.findAll('.searchable-select__option') + expect(items).toHaveLength(1) + expect(items[0].text()).toBe('Мария Сидорова') + }) + + it('emits selected value on option click', async () => { + const wrapper = mount(SearchableSelect, { + props: { modelValue: '', options }, + }) + await wrapper.get('input').setValue('иван') + await wrapper.get('input').trigger('focus') + await wrapper.find('.searchable-select__option').trigger('mousedown') + + expect(wrapper.emitted('update:modelValue')).toEqual([[1]]) + }) + + it('shows label for current model value', async () => { + const wrapper = mount(SearchableSelect, { + props: { modelValue: 2, options }, + }) + expect(wrapper.get('input').element.value).toBe('Мария Сидорова') + }) + + it('keeps input empty when no value is selected', async () => { + const wrapper = mount(SearchableSelect, { + props: { modelValue: '', options, placeholder: 'Введите имя' }, + }) + expect(wrapper.get('input').element.value).toBe('') + expect(wrapper.get('input').attributes('placeholder')).toBe('Введите имя') + }) +}) diff --git a/frontend/src/components/SearchableSelect.vue b/frontend/src/components/SearchableSelect.vue new file mode 100644 index 0000000..6cc386f --- /dev/null +++ b/frontend/src/components/SearchableSelect.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/frontend/src/composables/useGraphData.js b/frontend/src/composables/useGraphData.js new file mode 100644 index 0000000..d807a24 --- /dev/null +++ b/frontend/src/composables/useGraphData.js @@ -0,0 +1,18 @@ +import api from '../api' + +export async function fetchGraphBundle(graphEndpoint = '/graph/') { + const [gRes, rtRes] = await Promise.all([ + api.get(graphEndpoint), + api.get('/relation-types/'), + ]) + return { + nodes: gRes.data.nodes || [], + edges: gRes.data.edges || [], + relationTypes: rtRes.data || [], + } +} + +export async function fetchMapChoices() { + const { data } = await api.get('/network-map-choices/') + return data +} diff --git a/frontend/src/lib/api/errors.js b/frontend/src/lib/api/errors.js new file mode 100644 index 0000000..e43b273 --- /dev/null +++ b/frontend/src/lib/api/errors.js @@ -0,0 +1,19 @@ +export function normalizeApiError(error) { + const status = error?.response?.status ?? null + const data = error?.response?.data + let message = 'Произошла ошибка запроса.' + + if (typeof data === 'string' && data.trim()) { + message = data + } else if (typeof data?.error === 'string' && data.error.trim()) { + message = data.error + } else if (typeof error?.message === 'string' && error.message.trim()) { + message = error.message + } + + const normalized = new Error(message) + normalized.status = status + normalized.data = data + normalized.original = error + return normalized +} diff --git a/frontend/src/lib/api/errors.test.js b/frontend/src/lib/api/errors.test.js new file mode 100644 index 0000000..f3ecf0b --- /dev/null +++ b/frontend/src/lib/api/errors.test.js @@ -0,0 +1,15 @@ +import { describe, it, expect } from 'vitest' +import { normalizeApiError } from './errors' + +describe('normalizeApiError', () => { + it('extracts API message from response payload', () => { + const err = normalizeApiError({ response: { status: 400, data: { error: 'Bad request' } } }) + expect(err.message).toBe('Bad request') + expect(err.status).toBe(400) + }) + + it('falls back to generic message', () => { + const err = normalizeApiError({}) + expect(err.message.length).toBeGreaterThan(0) + }) +}) diff --git a/frontend/src/lib/api/pagination.js b/frontend/src/lib/api/pagination.js new file mode 100644 index 0000000..d47a987 --- /dev/null +++ b/frontend/src/lib/api/pagination.js @@ -0,0 +1,19 @@ +/** + * Loads all items from a DRF paginated list endpoint (page query param). + * If the response is a plain array, returns it as-is. + */ +export async function fetchAllPages(requestPage) { + const all = [] + let page = 1 + + while (true) { + const { data } = await requestPage(page) + if (Array.isArray(data)) return data + + all.push(...(data.results ?? [])) + if (!data.next) break + page += 1 + } + + return all +} diff --git a/frontend/src/lib/api/pagination.test.js b/frontend/src/lib/api/pagination.test.js new file mode 100644 index 0000000..408d184 --- /dev/null +++ b/frontend/src/lib/api/pagination.test.js @@ -0,0 +1,26 @@ +import { describe, it, expect, vi } from 'vitest' +import { fetchAllPages } from './pagination' + +describe('fetchAllPages', () => { + it('returns a plain array response unchanged', async () => { + const requestPage = vi.fn().mockResolvedValue({ data: [{ id: 1 }] }) + await expect(fetchAllPages(requestPage)).resolves.toEqual([{ id: 1 }]) + expect(requestPage).toHaveBeenCalledOnce() + }) + + it('concatenates all paginated pages', async () => { + const requestPage = vi + .fn() + .mockResolvedValueOnce({ + data: { results: [{ id: 1 }], next: 'http://example/api/?page=2' }, + }) + .mockResolvedValueOnce({ + data: { results: [{ id: 2 }], next: null }, + }) + + await expect(fetchAllPages(requestPage)).resolves.toEqual([{ id: 1 }, { id: 2 }]) + expect(requestPage).toHaveBeenCalledTimes(2) + expect(requestPage).toHaveBeenNthCalledWith(1, 1) + expect(requestPage).toHaveBeenNthCalledWith(2, 2) + }) +}) diff --git a/frontend/src/lib/graph/relationColors.js b/frontend/src/lib/graph/relationColors.js new file mode 100644 index 0000000..658e48f --- /dev/null +++ b/frontend/src/lib/graph/relationColors.js @@ -0,0 +1,8 @@ +export const RELATION_COLORS = { + colleague: { color: '#4facfe', highlight: '#7ac8ff' }, + friend: { color: '#4ecca3', highlight: '#7edfc0' }, + family: { color: '#f4a261', highlight: '#f7bb8a' }, + business: { color: '#5b8dee', highlight: '#7aa5f5' }, + acquaintance: { color: '#7b82a6', highlight: '#9ba3c5' }, + other: { color: '#555d7a', highlight: '#7b82a6' }, +} diff --git a/frontend/src/lib/map/positioning.js b/frontend/src/lib/map/positioning.js new file mode 100644 index 0000000..136d156 --- /dev/null +++ b/frontend/src/lib/map/positioning.js @@ -0,0 +1,81 @@ +export const SPHERE_ORDER = ['work', 'study', 'hobby', 'family', 'health', 'other'] + +export function ringRadius(networkCircle, layout) { + if (networkCircle === 'support') return layout.rInner + if (networkCircle === 'productivity') return layout.rMid + return layout.rOuter +} + +export function ringByRadius(r, L) { + const t1 = (L.rInner + L.rMid) / 2 + const t2 = (L.rMid + L.rOuter) / 2 + if (r <= t1) return 'support' + if (r <= t2) return 'productivity' + return 'development' +} + +export function sphereByAngle(angle) { + const full = 2 * Math.PI + const norm = ((angle + Math.PI / 2) % full + full) % full + const sectorW = full / SPHERE_ORDER.length + const idx = Math.floor(norm / sectorW) + return SPHERE_ORDER[Math.max(0, Math.min(SPHERE_ORDER.length - 1, idx))] +} + +export function normalizedSphere(n) { + const s = n.life_sphere + return SPHERE_ORDER.includes(s) ? s : 'other' +} + +export function normalizedCircle(n) { + const c = n.network_circle + return c === 'support' || c === 'productivity' || c === 'development' ? c : 'productivity' +} + +export function computePolarPositions(rawNodes, L) { + const groups = new Map() + for (const n of rawNodes) { + const key = `${normalizedSphere(n)}|${normalizedCircle(n)}` + if (!groups.has(key)) groups.set(key, []) + groups.get(key).push(n) + } + const posById = new Map() + const nSectors = SPHERE_ORDER.length + for (const [, group] of groups) { + group.sort((a, b) => Number(a.id) - Number(b.id)) + const sample = group[0] + const sphere = normalizedSphere(sample) + const circle = normalizedCircle(sample) + const idx = SPHERE_ORDER.indexOf(sphere) + const sectorStart = (idx / nSectors) * 2 * Math.PI - Math.PI / 2 + const sectorW = (2 * Math.PI) / nSectors + const pad = sectorW * 0.07 + const usable = Math.max(sectorW - 2 * pad, sectorW * 0.2) + const rBase = ringRadius(circle, L) + const k = group.length + group.forEach((n, i) => { + const angle = k === 1 + ? sectorStart + sectorW / 2 + : sectorStart + pad + ((i + 0.5) / k) * usable + posById.set(n.id, { x: rBase * Math.cos(angle), y: rBase * Math.sin(angle) }) + }) + } + return posById +} + +export function nodeXY(n, layout, posById) { + const ratio = Number(n.map_radius_ratio) + const storedAngle = Number(n.map_angle) + if (Number.isFinite(ratio) && Number.isFinite(storedAngle)) { + const safeRatio = Math.max(0, Math.min(1, ratio)) + const r = safeRatio * layout.rOuter + return { x: r * Math.cos(storedAngle), y: r * Math.sin(storedAngle) } + } + const p = posById.get(n.id) + if (p) return p + const idx = SPHERE_ORDER.indexOf(normalizedSphere(n)) + const sectorStart = (Math.max(0, idx) / SPHERE_ORDER.length) * 2 * Math.PI - Math.PI / 2 + const r = ringRadius(normalizedCircle(n), layout) + const angle = sectorStart + (2 * Math.PI) / SPHERE_ORDER.length / 2 + return { x: r * Math.cos(angle), y: r * Math.sin(angle) } +} diff --git a/frontend/src/lib/map/positioning.test.js b/frontend/src/lib/map/positioning.test.js new file mode 100644 index 0000000..029384f --- /dev/null +++ b/frontend/src/lib/map/positioning.test.js @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest' +import { sphereByAngle, ringByRadius, nodeXY } from './positioning' + +describe('map positioning', () => { + it('maps angle to sphere deterministically', () => { + expect(sphereByAngle(-Math.PI / 2)).toBe('work') + expect(sphereByAngle(0)).toBe('study') + }) + + it('maps radius to ring', () => { + const L = { rInner: 10, rMid: 20, rOuter: 30 } + expect(ringByRadius(5, L)).toBe('support') + expect(ringByRadius(16, L)).toBe('productivity') + expect(ringByRadius(28, L)).toBe('development') + }) + + it('prefers persisted coordinates', () => { + const L = { rInner: 10, rMid: 20, rOuter: 100 } + const p = nodeXY({ map_radius_ratio: 0.5, map_angle: 0 }, L, new Map()) + expect(p.x).toBeCloseTo(50) + expect(p.y).toBeCloseTo(0) + }) +}) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 27cef8c..001a6d0 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -10,6 +10,11 @@ const routes = [ name: 'Graph', component: () => import('../views/GraphView.vue'), }, + { + path: '/map', + name: 'NetworkMap', + component: () => import('../views/NetworkMapView.vue'), + }, { path: '/contacts', name: 'Contacts', diff --git a/frontend/src/stores/contacts.js b/frontend/src/stores/contacts.js index dd8de93..4d211bb 100644 --- a/frontend/src/stores/contacts.js +++ b/frontend/src/stores/contacts.js @@ -1,11 +1,17 @@ import { defineStore } from 'pinia' import api from '../api' +import { fetchAllPages } from '../lib/api/pagination' export const useContactsStore = defineStore('contacts', { state: () => ({ contacts: [], relations: [], + relationTypes: [], + mapChoices: null, loading: false, + contactsLoading: false, + relationsLoading: false, + mapLoading: false, error: null, }), @@ -16,52 +22,116 @@ export const useContactsStore = defineStore('contacts', { }, actions: { - async fetchContacts(search = '') { - this.loading = true + setError(error) { + this.error = error?.message || String(error) + }, + + async withLoading(flagName, fn) { + this[flagName] = true + this.loading = this.contactsLoading || this.relationsLoading || this.mapLoading this.error = null try { - const params = search ? { search } : {} - const { data } = await api.get('/contacts/', { params }) - this.contacts = data.results ?? data + return await fn() } catch (e) { - this.error = e.message + this.setError(e) + throw e } finally { - this.loading = false + this[flagName] = false + this.loading = this.contactsLoading || this.relationsLoading || this.mapLoading } }, + async fetchContacts(search = '') { + return this.withLoading('contactsLoading', async () => { + const params = search ? { search } : {} + this.contacts = await fetchAllPages((page) => + api.get('/contacts/', { params: { ...params, page } }) + ) + }) + }, + + async fetchContactById(id) { + return this.withLoading('contactsLoading', async () => { + const { data } = await api.get(`/contacts/${id}/`) + const idx = this.contacts.findIndex((c) => c.id === id) + if (idx !== -1) this.contacts[idx] = data + return data + }) + }, + async fetchRelations() { - const { data } = await api.get('/relations/') - this.relations = data.results ?? data + return this.withLoading('relationsLoading', async () => { + this.relations = await fetchAllPages((page) => + api.get('/relations/', { params: { page } }) + ) + }) + }, + + async fetchRelationTypes() { + return this.withLoading('mapLoading', async () => { + const { data } = await api.get('/relation-types/') + this.relationTypes = data + return data + }) + }, + + async fetchNetworkMapChoices() { + return this.withLoading('mapLoading', async () => { + const { data } = await api.get('/network-map-choices/') + this.mapChoices = data + return data + }) }, async createContact(payload) { - const { data } = await api.post('/contacts/', payload) - this.contacts.push(data) - return data + return this.withLoading('contactsLoading', async () => { + const { data } = await api.post('/contacts/', payload) + this.contacts.push(data) + return data + }) }, async updateContact(id, payload) { - const { data } = await api.patch(`/contacts/${id}/`, payload) - const idx = this.contacts.findIndex((c) => c.id === id) - if (idx !== -1) this.contacts[idx] = data - return data + return this.withLoading('contactsLoading', async () => { + const { data } = await api.patch(`/contacts/${id}/`, payload) + const idx = this.contacts.findIndex((c) => c.id === id) + if (idx !== -1) this.contacts[idx] = data + return data + }) }, async deleteContact(id) { - await api.delete(`/contacts/${id}/`) - this.contacts = this.contacts.filter((c) => c.id !== id) + return this.withLoading('contactsLoading', async () => { + await api.delete(`/contacts/${id}/`) + this.contacts = this.contacts.filter((c) => c.id !== id) + }) }, async createRelation(payload) { - const { data } = await api.post('/relations/', payload) - this.relations.push(data) - return data + return this.withLoading('relationsLoading', async () => { + const { data } = await api.post('/relations/', payload) + this.relations.push(data) + return data + }) }, async deleteRelation(id) { - await api.delete(`/relations/${id}/`) - this.relations = this.relations.filter((r) => r.id !== id) + return this.withLoading('relationsLoading', async () => { + await api.delete(`/relations/${id}/`) + this.relations = this.relations.filter((r) => r.id !== id) + }) + }, + + async importContacts(file) { + return this.withLoading('mapLoading', async () => { + const fd = new FormData() + fd.append('file', file) + const { data } = await api.post('/import/', fd, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + await this.fetchContacts() + return data + }) }, }, }) diff --git a/frontend/src/views/ContactDetailView.vue b/frontend/src/views/ContactDetailView.vue index 1f22597..14b5fe8 100644 --- a/frontend/src/views/ContactDetailView.vue +++ b/frontend/src/views/ContactDetailView.vue @@ -1,11 +1,13 @@