From 49bdbc4b60b053ff4de9c0f9067cf0a2db2fd40e Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 8 Jan 2024 14:20:20 +0800 Subject: [PATCH] SolidJS Build Infrastructure (#1919) * backport changes from #1913 * Make ESLint play nice * eslint fix --------- Co-authored-by: Brendan Allan --- .gitignore | 1 + apps/landing/src/app/Footer.tsx | 2 +- apps/landing/src/components/Footer.tsx | 2 +- .../src/components/browse/BrowseLocations.tsx | 8 ++-- .../src/components/browse/BrowseTags.tsx | 6 +-- .../src/components/browse/Categories.tsx | 2 +- apps/mobile/src/components/browse/Jobs.tsx | 8 ++-- interface/app/demo.solid.tsx | 19 +++++++++ interface/app/index.tsx | 19 ++++++++- interface/package.json | 1 + packages/client/src/hooks/useFeatureFlag.tsx | 1 + packages/config/base.tsconfig.json | 2 +- packages/config/eslint/base.js | 20 ++------- packages/config/eslint/react.js | 23 +++++++++++ packages/config/eslint/solid.js | 14 +++++++ packages/config/package.json | 7 +++- packages/config/vite/index.ts | 4 +- packages/config/vite/narrowSolidPlugin.ts | 39 ++++++++++++++++++ packages/config/vite/relativeAliasResolver.ts | 1 + pnpm-lock.yaml | Bin 926496 -> 952501 bytes 20 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 interface/app/demo.solid.tsx create mode 100644 packages/config/eslint/react.js create mode 100644 packages/config/eslint/solid.js create mode 100644 packages/config/vite/narrowSolidPlugin.ts diff --git a/.gitignore b/.gitignore index 2fba6111f..443c90e76 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ spacedrive .cargo/config .cargo/config.toml .github/scripts/deps +.vite-inspect diff --git a/apps/landing/src/app/Footer.tsx b/apps/landing/src/app/Footer.tsx index 4f3dd5efe..3c865d5ae 100644 --- a/apps/landing/src/app/Footer.tsx +++ b/apps/landing/src/app/Footer.tsx @@ -25,7 +25,7 @@ export function Footer() { style={{ width: '100%', height: '400px' }} sizes="100vw" /> -
+
Spacedrive logo diff --git a/apps/landing/src/components/Footer.tsx b/apps/landing/src/components/Footer.tsx index a1e882b9a..e270dba29 100644 --- a/apps/landing/src/components/Footer.tsx +++ b/apps/landing/src/components/Footer.tsx @@ -43,7 +43,7 @@ export function Footer() { style={{ width: '100%', height: '400px' }} sizes="100vw" /> -
+
Spacedrive logo diff --git a/apps/mobile/src/components/browse/BrowseLocations.tsx b/apps/mobile/src/components/browse/BrowseLocations.tsx index b6e33c039..658939ddb 100644 --- a/apps/mobile/src/components/browse/BrowseLocations.tsx +++ b/apps/mobile/src/components/browse/BrowseLocations.tsx @@ -41,7 +41,7 @@ const BrowseLocationItem: React.FC = ({ - + @@ -97,17 +97,17 @@ const BrowseLocations = () => { return ( - + Locations - + modalRef.current?.present()}> diff --git a/apps/mobile/src/components/browse/BrowseTags.tsx b/apps/mobile/src/components/browse/BrowseTags.tsx index bbb07c0a1..d5b3553af 100644 --- a/apps/mobile/src/components/browse/BrowseTags.tsx +++ b/apps/mobile/src/components/browse/BrowseTags.tsx @@ -62,17 +62,17 @@ const BrowseTags = () => { return ( - + Tags - + modalRef.current?.present()}> diff --git a/apps/mobile/src/components/browse/Categories.tsx b/apps/mobile/src/components/browse/Categories.tsx index 717f6e605..1b08da4c8 100644 --- a/apps/mobile/src/components/browse/Categories.tsx +++ b/apps/mobile/src/components/browse/Categories.tsx @@ -29,7 +29,7 @@ const CATEGORIES_LIST = [ const Categories = () => { return ( - Library + Library diff --git a/apps/mobile/src/components/browse/Jobs.tsx b/apps/mobile/src/components/browse/Jobs.tsx index a4a5cf990..627e5cd35 100644 --- a/apps/mobile/src/components/browse/Jobs.tsx +++ b/apps/mobile/src/components/browse/Jobs.tsx @@ -10,7 +10,7 @@ import Fade from '../layout/Fade'; const Jobs = () => { return ( - + Jobs @@ -48,15 +48,15 @@ const Job = ({ progress, message, error }: JobProps) => { style={tw`h-fit w-[310px] flex-col rounded-md border border-sidebar-line/50 bg-sidebar-box`} > - Added Memories + Added Memories - + + +
Hello from Solid: {count()}
+
+ ); +} + +export function renderDemo(element: HTMLDivElement): () => void { + return render(Demo, element); +} diff --git a/interface/app/index.tsx b/interface/app/index.tsx index 48c1022c9..5794d8400 100644 --- a/interface/app/index.tsx +++ b/interface/app/index.tsx @@ -1,10 +1,11 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Navigate, Outlet, redirect, useMatches, type RouteObject } from 'react-router-dom'; import { currentLibraryCache, getCachedLibraries, NormalisedCache, - useCachedLibraries + useCachedLibraries, + useFeatureFlag } from '@sd/client'; import { Dialogs, Toaster } from '@sd/ui'; import { RouterErrorBoundary } from '~/ErrorFallback'; @@ -12,11 +13,24 @@ import { useRoutingContext } from '~/RoutingContext'; import { Platform } from '..'; import libraryRoutes from './$libraryId'; +import { renderDemo } from './demo.solid'; import onboardingRoutes from './onboarding'; import { RootContext } from './RootContext'; import './style.scss'; +function RenderSolid() { + const ref = useRef(null); + + useEffect(() => { + let cleanup = () => {}; + if (ref.current) cleanup = renderDemo(ref.current); + return cleanup; + }, []); + + return
; +} + // NOTE: all route `Layout`s below should contain // the `usePlausiblePageViewMonitor` hook, as early as possible (ideally within the layout itself). // the hook should only be included if there's a valid `ClientContext` (so not onboarding) @@ -29,6 +43,7 @@ export const createRoutes = (platform: Platform, cache: NormalisedCache) => return ( + {useFeatureFlag('solidJsDemo') ? : null} diff --git a/interface/package.json b/interface/package.json index 60436017a..e4eba1e33 100644 --- a/interface/package.json +++ b/interface/package.json @@ -58,6 +58,7 @@ "react-use-draggable-scroll": "^0.4.7", "remix-params-helper": "^0.4.10", "rooks": "^7.14.1", + "solid-js": "^1.8.8", "use-count-up": "^3.0.1", "use-debounce": "^9.0.4", "use-resize-observer": "^9.1.0", diff --git a/packages/client/src/hooks/useFeatureFlag.tsx b/packages/client/src/hooks/useFeatureFlag.tsx index 0890885e0..cd352490a 100644 --- a/packages/client/src/hooks/useFeatureFlag.tsx +++ b/packages/client/src/hooks/useFeatureFlag.tsx @@ -10,6 +10,7 @@ export const features = [ 'p2pPairing', 'backups', 'debugRoutes', + 'solidJsDemo', 'hostedLocations' ] as const; diff --git a/packages/config/base.tsconfig.json b/packages/config/base.tsconfig.json index be3ed9350..a7ea1783c 100644 --- a/packages/config/base.tsconfig.json +++ b/packages/config/base.tsconfig.json @@ -3,7 +3,7 @@ "display": "Default", "compilerOptions": { "strict": true, - "jsx": "react-jsx", + "jsx": "preserve", "esModuleInterop": true, "skipLibCheck": true, "preserveWatchOutput": true, diff --git a/packages/config/eslint/base.js b/packages/config/eslint/base.js index c6bfcf360..1d4f11eac 100644 --- a/packages/config/eslint/base.js +++ b/packages/config/eslint/base.js @@ -10,20 +10,13 @@ module.exports = { }, extends: [ 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', 'plugin:@typescript-eslint/recommended', 'turbo', - 'prettier' + 'prettier', + require.resolve('./react.js'), + require.resolve('./solid.js') ], - plugins: ['react'], rules: { - 'react/display-name': 'off', - 'react/prop-types': 'off', - 'react/no-unescaped-entities': 'off', - 'react/react-in-jsx-scope': 'off', - 'react-hooks/rules-of-hooks': 'warn', - 'react-hooks/exhaustive-deps': 'warn', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-explicit-any': 'off', @@ -42,10 +35,5 @@ module.exports = { } ] }, - ignorePatterns: ['dist', '**/*.js', '**/*.cjs', '**/*.json', 'node_modules'], - settings: { - react: { - version: 'detect' - } - } + ignorePatterns: ['dist', '**/*.js', '**/*.json', 'node_modules'] }; diff --git a/packages/config/eslint/react.js b/packages/config/eslint/react.js new file mode 100644 index 000000000..87a85ddc3 --- /dev/null +++ b/packages/config/eslint/react.js @@ -0,0 +1,23 @@ +module.exports = { + plugins: ['react'], + overrides: [ + { + files: ['**/*.tsx'], + excludedFiles: '*.solid.tsx', + extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'], + rules: { + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-hooks/rules-of-hooks': 'warn', + 'react-hooks/exhaustive-deps': 'warn' + } + } + ], + settings: { + react: { + version: 'detect' + } + } +}; diff --git a/packages/config/eslint/solid.js b/packages/config/eslint/solid.js new file mode 100644 index 000000000..519b2fd59 --- /dev/null +++ b/packages/config/eslint/solid.js @@ -0,0 +1,14 @@ +module.exports = { + plugins: ['solid'], + overrides: [ + { + files: ['**/*.solid.tsx'], + extends: ['plugin:solid/recommended'], + rules: { + 'solid/reactivity': 'warn', + 'solid/no-destructure': 'warn', + 'solid/jsx-no-undef': 'error' + } + } + ] +}; diff --git a/packages/config/package.json b/packages/config/package.json index 37d9fa3e2..62ccad256 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -13,6 +13,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", + "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^8.53.0", "eslint-config-next": "^13.5.6", "eslint-config-prettier": "^9.0.0", @@ -20,11 +21,13 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-solid": "^0.13.1", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-utils": "^3.0.0", "regexpp": "^3.2.0", "vite-plugin-html": "^3.2.1", - "vite-plugin-svgr": "^3.3.0", - "@vitejs/plugin-react-swc": "^3.5.0" + "vite-plugin-inspect": "^0.8.1", + "vite-plugin-solid": "^2.8.0", + "vite-plugin-svgr": "^3.3.0" } } diff --git a/packages/config/vite/index.ts b/packages/config/vite/index.ts index e231a2115..24d59934f 100644 --- a/packages/config/vite/index.ts +++ b/packages/config/vite/index.ts @@ -4,11 +4,13 @@ import { createHtmlPlugin } from 'vite-plugin-html'; import svg from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; +import { narrowSolidPlugin } from './narrowSolidPlugin'; + export default defineConfig({ plugins: [ tsconfigPaths(), - // @ts-expect-error react(), + narrowSolidPlugin({ include: '**/*.solid.tsx' }), svg({ svgrOptions: { icon: true } }), createHtmlPlugin({ minify: true diff --git a/packages/config/vite/narrowSolidPlugin.ts b/packages/config/vite/narrowSolidPlugin.ts new file mode 100644 index 000000000..425970ff5 --- /dev/null +++ b/packages/config/vite/narrowSolidPlugin.ts @@ -0,0 +1,39 @@ +// Source: https://github.com/merged-js/react-solid/blob/master/src/narrowSolidPlugin.ts +// +// We vendor it due to: https://github.com/merged-js/react-solid/issues/1 +// + +import { createFilter } from '@rollup/pluginutils'; +import solidPlugin, { Options } from 'vite-plugin-solid'; + +export interface NarrowSolidPluginOptions extends Partial { + include?: string | RegExp | Array | Array; + exclude?: string | RegExp | Array | Array; +} + +export function narrowSolidPlugin({ include, exclude, ...rest }: NarrowSolidPluginOptions = {}) { + const plugin = solidPlugin(rest); + const originalConfig = plugin.config!.bind(plugin); + const filter = createFilter(include, exclude); + plugin.config = (...args) => { + const baseConfig = originalConfig(...args); + return { + ...baseConfig, + esbuild: { + include: exclude, + exclude: include + } + }; + }; + + const originalTransform = (plugin.transform! as any).bind(plugin); + plugin.transform = (source, id, ssr) => { + if (!filter(id)) { + return null; + } + + return originalTransform(source, id, ssr); + }; + + return plugin; +} diff --git a/packages/config/vite/relativeAliasResolver.ts b/packages/config/vite/relativeAliasResolver.ts index bd22ed89d..4af71d5a9 100644 --- a/packages/config/vite/relativeAliasResolver.ts +++ b/packages/config/vite/relativeAliasResolver.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ // BE REALLY DAMN CAREFUL MODIFYING THIS FILE: https://github.com/spacedriveapp/spacedrive/pull/1353 import fs from 'fs/promises'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7984aa0c621c27e725cf3f171a8ba2c082f766e6..e109bc3aa4a1da665cbc6d3b7671c4d0eff08fee 100644 GIT binary patch delta 9105 zcmb_?d3c-EmG|?$)^5pm?3g&4m#h{N?UJp1ms%h|S%xKqsQ5rj zNnrX-XQm}2ag&!MKn!I%4`rz{v?QS z<=Nb-uK9(c+n4vPpFeW6bHb2-CScBT|7^UI}VsyHfu+rCcQ;EC()dH0w+ThbX7 zWxK3eNN6jvbiO*~n+TY!6;)W1mz9+X+oT}O9XDxo#R-X7nAIpt1$$oP3i`$ys+ggY zkEyjay;LbQPFZ5yDOfN~8=6_S?*$AKgDS+l=HUK|So7k3&dlP28hSH#xVyRj&cT^` zkL;i|pMSo)dFCxTys{GSYi^$X7fjYX@zy|daS3`TCHaNmBo;=VC$MkUO73fz!uHyc=@Ixf6o+W{RJG`Iku!zWK|p4ukKXK99*}PQ2BH zwFVY8?PocZQ8`Ztg74DSdjwW}r6~4Jm0c4deNtExc_XrtsZg)xxeb2U9G%ecokpv^ zFz)Az1?reIm>io3l`3QYY)Us(Y8VZQtW+m88O0?xH#1c$mjill3Ql|dc zqrr^>*uoYI@W=pm8b+A-buhaa>wz0)FgkqiF4j6s4yrODYH=}Vp$`&E2QdZ}a-T96 z!&8gqk9~I$_6T-qz2>S_!*{Y45TYem5C^gesM%q*ums!LStWQ7-+IM;uXE7*iT|?< zdlQ4FucwP)FMR`yzB(@ravh*`ode!kh6GFQu#6PR z5IjAM{EZ3_BwnSxevD^CISh!L?xeTF+9t~HX;QE&b8Q4 z*l)x11R7Cb?aBLj7?+$p%R`UVnnWY@&Pwvh2(bh!V1WL&xrbXN*lDC#o7(FU$so|> z*bupiUfwx@eC$C24e!Unt-zMUh#gxGk1DWJmscKt0A~=#mDo+aaBK`)*4CMeV^?gB zPZl9>!GbES=t36**kV#(*MGGmbs&ccG(1ZM&o=BUZE5V;){(~Sjo1=qJr^y83&~swZrg}d zmlUa6m$XiLjW$bFW8+H1?tIo=h&vRMmTFiP5fpuOe^TkR$Ht3h_oT!rvZ%CLt;4S~ zCt|Unr^YWED$@D^;8n5kj^)-^xD zAzuFm7G6LgqoH8Pn+!EqTv!j42eAe4@cr02;@JnWKTu)mw@}pU)uUZ`(-^h2>vH0z zZ(}pd;8(}+UdSElSS0K}bd{g^?Om)DZ7Uci`UvbW-a41~;0X3JO50Ecui)3h?5)UE z@}_YP;XIB#!E9ghdJmler(O6c5q%lEw=?+Q3FP_CxDY4BH?VaS;Jzl}z-n|juOxRChJyfv%3R_L=I)nWya^YvNmE=RNhtp@UQ?R#y4evhGKfhiCqsOrR zX7Bs#-4|E{T6V1K^3+@&U)tP$VUW1x0yajSANLc2-(Y-do7e1zSPud3VIvFwsy8B# zQ@erIeyR_;a5^!|#NX;|FGpUEMZCTkul2R%liP@wD3IHPcg+0RO?cbP?OX9-;v5fO z(gpVlke4BW0=y=;ViwRWgZNyC=P~$9h>x^MZV>$m37wb}<0Cl4#mLFEcKLz0MY5>5 z<7Ga~{Q|{@xg2I{hl@TZeHcl3L38gjz3?IrUq#$2#fuyWjG_bPoJgooj^Zt$2H}?~ zT!v`Td+o)q0f8DHneS;}<5s$kjGM4&A5ySUjJ*(y-)dXmKWp*RAb18Fzcf5bpfMT> zcBICWv_!Wae~bd#&4}e4hjA)#(uhafDh@Y3fMVWV8Kf)+ZTRoo>J5F5;|mC+LGRMx zYcC-i*gMhjUvPfnipvUy@k!`ghY!K+U&YsS)b-K|3R3;^A2Hi;)FTV4$E0a>#Z#7e zJz9apW-w(Y8lkemYF8x6+M-sh)8_NJ@m#T3b!)SEgJ#m4HH2hbN4)42`igE*CYf{Q zHTHN-D>nNxI-A@dBSFx2T5`|bimf6Mz#x*DsKpbjblKH7PGd`9`+p*LKa#{5FnS}t zl5|}6+=xVXZX50(&?ZG6eiCOaMPBAQfwM_ut845!okp>F|E(xWw_3>fx`D9+UrNkA zf=|*BmZL%QZd@_{@|X31Kw5FI9lhaijv~GhCQ<)6H#=@dULCoXjktUR2#?|e#PG9t zfdN~O;&c$dhfm0kZHmRo6@PD$Vff1K+fk3jA!D(`h*502ToU9Wp!0GBCpUG;wJ5KNyb}iH2lU= zR1s3BCu#+=us)%b)nW;^x?r`oUH4y>b_;|dE}7svjbb(_M8xXQ6~aYK_swe_dU6CP zULxTTY44|BYAG*+IQmaGgGvZq!|%jMt0G*l;-MBUC0>6WzXK<>oxuO=A`0>>_~~ip zGMGHu)!jUId^zzCb6tK$Un*KGj!JkUo?ruuH8R3)2A^L*xZ_sFPCPn2DM>5o- zSz4PjxQ*V;gWS6e4$M789Y8mv!s#!m-49!6bmG@%sZTS|)%sxX$ILZI&q&`i2#Ie~ zInZ~Qx*X=-rc#KJUsLz=Hye7gSD**S(+j!<#wU0+`Lww!t6%p}QY`lBM;4Fi$&%$>4=7ZD6`UJJzA? zffuv1e&FP34EUr#o55tn-xq0B7I}|h*m?_XFT{qKYvIvbX#oP?L32%9dFSTr8w-i= zpQiPEt*vBlOX){3IRP^ITiDQKv9>9aO>c_Uas{L{$!rE?+dQ!x&L6Ucz+9h2hunS?7WV?iul{DbPsjDyNpc61bP06XK6vj~XG_@crmt#mfZ&`F5Qh6oyNiI^5;%Ly{LxoCyQ?J8IRc4e zZF?y~Lkr-hLpTlQrg1sstaKWQjo83^Vg6V&wx&&tfC7@j{j8yO_twLRZr69wG6V*s zBqrg&b4L0Kc*sv5To{U`Hjx?~Ep2KQm&nQz*lqMquupS~`dXuq^u#U6y2sw&XMHsf z*DAADCqyA{G2l&wCB|BfEA=O|C249*$4^J&+<=)sWsbX40l9Rd>`|(Wfr3OL@nnP{ zo1l^~UQ+DA=Jwx8*M@>ECQ{{iGK0!#e`aT!N9GM}5#(mD0pfdp`mN`?Hbsl@P?XK} z#QDlXEK|t@<4J3bt94ooI`3F{tURtxX~r#Deb%j#31x<$U6fMVQ<}-KNpC`{RM@Q{ zugh3TnM4CAn&7)a8FN^ab4bLZ6c@R^v4($=pSR|x5>kiN8_;BZ;}KU;pfRdMT6rSx6KGS} zMycv4G^Wt3)~EM{#Yv|s&4&Z#u3`A#NOw2At$=fW3LQrBC=&Gj zghqv%pQ5dS(F3$@Sn>*Oa6DOT88vC|0-m5_?^97E*-hbKF}is~h{i?pF+>2z0(NwK<@(MpIT*X)38#Nv7&n+nA`N9hX@e!m`Vk%nPe3Q=~qb zF;?r^q}C?a2SqNMP~)!I{bq%?)_OMhV}(g2c9fZ)E+g*R&3s{mLV7$n|3{qbadBgUxuQGxNgFzV=@aJad8|rz{I8V(Nu?OKmKBdhxJlDIXl)|q2kO|D@IbJNa3 zKwp$=xz%_~FZDU~W~-_o(W?0QQbB1fD#|vM7gf6xWwRxw3OfWjrzs!GP1d;Xu+baQ z2}z{3h{)1ek5Y!F{(|Ko6d-~ZHgvD|INh#n(4&f(D>*waPZY>LoBV6 z>D(?~tXy&nRK{dgD2U25>2PGi=`g0u&WKW~mWs842~kihoOGeg$Sbb9^jv#ep?7dt zL&ON1bvVinX6mH`3f!YoRF!r-MqB5OI!hs2NNANN>jpt-%Ar+d9TJp&<@nOP%PCiC z8-<1Jw63V%()IKYFHJ87_w>B6%O{ zjxN}9I|{}vAn?xZtP?FAYH2kp4=;xI?_e#xq|2=`@Z})S1XPculhxeySCCw#{YU0t;H*&6k#StYr3nJl(4Lk`pVS1ru7W*B4{b_83{VY)8(kvBIV55*u>$`5^xP z3Kp)02$$^_C6Qkzg*hMp|JQ(zLO&C}bBZ?70SjdJ-%rsxDCExjS)JBj0S33hjW3a9 zmOc1Ju*wUl&Qn}YH%JJ-_Dx1FEZ@ai-rCbLOkG0A9%XTG()IPjxgVo%Hl3a4*To3> zW2{3sNl*OKFssZ=g^M!7ZLSeTSuUEYdefyj)bis_|z|1J2z1a>0~5QjA)yQvV0yKJ7kJ2__ec>_^b(jPO~yaB5*lk{ct1z*8Ar50+84YfyX)rFGo zqCGrO7pAmDl~u)e8yYUI(x*<=rI{Ewj-yOtWItQJTGGKd>6WOk#zUoS31vXV5}aYO zmJko`XU|d@?Ju~aLRj#R-H1C)$tzLxqulP0s>-2&+3JxM8-Ye#>~uJ!7QaA}Ek)Ji<73*Qpe}M$6hXf! zt&B<}UPXiJ$><|+;HQ+8J@em~H;>#TL|84j5?k3Z&PAQ%bA&83a4yr4DZ~DE@j=3M zn0=842OnXq_h=gWjMM2)G!XP1FS*6Gv|izI#C-K&!jPEEiw)JV#j3HIGhC~DDrK|f z?UR=17++#^mF04+yq*h0#wxBxWvURWN8OfWqapGZ$O6G~_}x3~)$si7Y$kETJM4&x zKL4x-9;9+T#h4^1mS-Fyv$`fpi>qq6q|Pmevbns>$B&FR{BeO^J2qLI$OH`DpimoB z=nV>~xSBGH;w4Wa&aD~!ld&oBlqcX4tDRX#H0w~qi3(=}y!Hi+Nj$lh)BIlVl^Hb3 zm_S&dulFRzB5_Byp(#3ymU_%2Ef?bYuuqpSx?Ba5OsuL#1ttEt#VH5{1P)6nQw}B# z4R^w)5XQ@8alWPuhRjK&(yA*bgDAsN#=R3oEBV!Z5%6B+48qn;-E_k63THi=L;h4C z8ZIIIP#_d=|4EL}leMeu-m#plq>e~UkqLf&LY69o%>H~KWN?lN5&?5X7c|C_0sfdH zrd4x`4QJVG2&EheVKyl=Bod0Ts5x0efF&iV#fl=AitEhnBFV1HsEbO+ae}JULGeUp>Xyh(6E{ z!1QCReilmN$O$1&IDM8n44+*`+1Oo77AkGamB0-rIg6%G(pJNw)0|b!?MHfG`x%sC z+;p15TulD_0;LlQp5RX{vv7R&fZH9*4R5L4Uuz-uL?7OV` z$VAAR*f};$4YLoR%JtkHRM9}Fx*sM^x4z4C!&^BT2R>LvVpPeBUt&t1iami(5Y(hqv9SeYae@ucg? z9x8jHBR0VKZ!nFQWV(^qeUf8kL+u=AnE2oVN74o7E^zt?^?z{2ySiI@jJCFDtDGxt zC|S(ed@vdv%S1%cNav_vOj1WzAEO9@yI3(>r|-eRf83 RG%%m!EGF*zE$3my{{ZZ-h?M{U delta 3595 zcmbtWdstOf7XS9X=W*}84=(o#@=7oj5fc#%LQqN45DW-Y3QMjz*u#`7kFc?l8jG5- z#7?%#W)jPxQJ4|us>{a*B9@sNVv5p3B6CvA6cHZ<%ItHFJeqI*nQ#8N-(Bao*4pdw z+iUG@t0$E2zo)v^r9|FS0TIKcL7tuS;%uII?lj_Als{@BTnU$kc(STLBrw}ZZA#Eu zGqen&k}_e7)ieB`=Lu93BEW%WqYd1-RQC99ohn1wb%Rr>{$BDtwR?>OpEMgC8mO4+ z+rTxKTETvlj8h`_9n+=19j5_G`KN*>;*+aJWloL1^6UPQ%Hl>J2+1KMl}(K&p>ZCa z1|fnW$74N`Ou$t~e3d&syH{D*q+zj;m`erag%dHb&X35wcz|O6*GSL8rWm4n^;DgB zgg|!{34pqG66BfC{LKJSnb2I^8@Ls|E0p~_KYhK=phc+cZaJs_k&cW|n!fu|7b;KN zl$;-B<(m+$AXo*Yqz0N*)YiCx&9PU8b6VsGD^1S4ch&yx3cF9 z^Gx}rP|w`6uX_vaEsMNn6R~(!_QX?|HdODa5k9BHj4gyT5wL$r1&9fxLC_!m<-I z0461p82#*s$)r<&x;hd9i7w8$$Vwu?^DAWrf1b)IrcxJ9O(nb3&Q0q_k)vVcw{{M2 zCG&7Klk#NpRiB9iIq-zouZ^t_kmm>lXOd7|{h7?(6F4Fx2WlxM&L#`I;Eqcq2-=_O ztwYn3L>S-+Tp`pO9?I)I_wBr1M=F3E7&$P?x)Ia?*O%}XX;H@H_iC#Eav*e|c54w~ zm{>qc>A=v7ek+$OB{OBXSkLW0eVI&^RMl|k$rpVw;dRnshO#$kIJ6#P4k%eo1az$- zRa(Krq5553;kegH0=~GGoH6K@>TgP}c-K7%K}q%D|szbhS4B;bf5@x4eg9=tb*(b(}w&B?7BmHQEx@?O#M9vqz zVdq5VR2PT09#u-4Xu&bT(q0D~{?#xaH2JY8X{WQ_Manekha8x>+2r$1lO*Hx3du@g z??!32x{qb}!zSr~mZwTIEj)N>qvQ)KDkYDmfs?AFC9KZ@9NI2@2o+!nogI}@oV7SP zH7>0yGNXXUBtdUrrvzH9||k?ey{ z7t6E6{*T!BygX!ptSXsctecu)^NV~a`n@ct2v|`n$D34wI-z6}rw_mTBemU%e7D|B z@z`JF7rw{t<;JUXFsa43B0#GfcECyO{SqI_A zl_tS~>9wZoLCW^`osjme$%He%F>MXOwrOe>J>w*+ z+D8Ubfi(m>gV=@)i7#W@e(Wy`HcC;szFn>1UaTzwC2~T`Au_R4Hk>Q}OEYQtECR z0iO_k;mA2Q5%yeSEqyxRrUw4b>>E{K=U6nnI!g?|w9Cv&ISHIf+3_INUBBu8$HQPl zjlmDccd~W-4XpOXXSNsz4YPP7t1j~dPW{9Jbg|?*V=(3-li}OT%uD-*{F~dCjK|AX z^9{l|J=b4|#yjlhD#|||beK;XRnTF!5v&d}cW_lWddoTt_eYyojzRO|=K9$@L^Gb; zVLs;6$Dl9l@fR$mUmP~$7l$o3!f?qo%X%k07Jy}`w&?~8$*`@t=ifSiNx=XB