![ظاهر برنامه](https://quera.org/qbox/view/IK2wmfLBBR/ezgif.com-gif-maker%20(1).gif)
پارسا که با پنل ادمین دیجیکالا کار میکرد، متوجه شد بعضی از عکسهای محصولات وضعیت مناسبی ندارند و برای درست دیده شدن آنها لازم است تا تغییراتی روی آنها اعمال شود. پارسا که از قضا خیلی هم صبور نبود، برای این کار از رامین خواست که خیلی فوری در صفحهی عکس محصولات، ابزاری برای او فراهم کند که بتواند با آنها عکس کالا را بچرخاند، برگرداند (*flip* کند) و مقدار روشنایی آن را تنظیم کند.
از طرفی، پارسا از بچگی به عکسهای هنری هم علاقه داشت. به همین دلیل، به رامین گفت که برایش چند فیلتر هم آماده کند که بتواند عکسهای سایت را هنری کند!
رامین که خیلی قلب مهربانی داشت، درخواست پارسا را قبول کرد، اما چون باید در جلسهی تیم فرانتاند شرکت میکرد، تنها فرصت کرد ظاهر صفحهی عکس کالا را پیادهسازی کند. حال، از شما میخواهیم که تا قبل از تمام شدن جلسهی رامین، با کامل کردن عملکرد این صفحه به او کمک کنید تا بتواند موارد درخواستی پارسا را به موقع به دستش برساند.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/39252/download_problem_initial_project/135513/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
image-editor
├── assets
│ ├── css
│ │ ├── editor.css
│ │ └── style.css
│ ├── font
│ │ └── Vazir.ttf
│ └── img
│ ├── favicon.ico
│ ├── flip.png
│ └── template.jpg
├── index.html
├── package.json
└── script.js
```
</details>
- برای پیادهسازی هر یک از ابزارهای خواسته شده، یک `div` خاص در نظر گرفته شده است که بهصورت زیر هستند:
- `preview`: برای چرخش، تغییرات روشنایی و اعمال فیلترها
- `preview-scale`: برای بزرگنمایی با موس
- `preview-flip`: برای *flip* عمودی و افقی
- در این سؤال، شما باید ۷ فیلتر را پیادهسازی کنید که در جدول زیر به آنها اشاره شده است:
| نوع فیلتر | شدت فیلتر |
|-------|------------|
| grayscale | 1 |
| sepia | 1 |
| invert | 1 |
| blur | 2px |
| saturate | 2 |
| contrast | 2 |
| hue-rotate | 90deg |
برای پیادهسازی آنها، کافی است از فیلتر معادل آنها در *CSS* به همراه مقدار ذکر شده برای آن استفاده کنید. برای مثال، فیلتر `grayscale` به صورت `grayscale(1)` خواهد بود.
- با کلیک کردن روی گزینهی `none` در لیست فیلترها، اگر فیلتری روی عکس اعمال شده باشد، باید حذف شود.
- میزان چرخش عکس **از ۰ تا ۳۶۰ درجه** و میزان روشنایی عکس **از ۰ تا ۲** میتواند تغییر کند.
- هنگام چرخاندن تصویر، باید دقت شود که کادر مربعی تصویر (`preview-container`) نباید خالی شود. برای این کار لازم است به همراه چرخش مقداری **بزرگنمایی** نیز انجام شود. این بزرگنمایی به ازای هر $\theta$ درجه چرخش، $|sin(\theta)| + |cos(\theta)|$ خواهد بود. تصویر زیر، این مورد را واضحتر بیان میکند:
![rotate and scale](https://quera.org/qbox/view/yv6dfGr9cp/image_editor_pic.png)
- از بین فیلترها تنها یک مورد در لحظه قابل اعمال است و با هر انتخاب فیلتر، قبلی جایگزین میشود.
- باید امکان استفاده از فیلتر، تغییر مقدار روشنایی، *flip* کردن افقی و عمودی و چرخاندن به صورت همزمان وجود داشته باشد.
- با دوباره زدن روی دکمههای *flip* باید تصویر به حالت قبلی خود برگردد.
- با بردن موس روی عکس، باید عکس با **ضریب ۲** بزرگنمایی شده و با حرکت موس در کادر تصویر، تصویر نیز جابهجا شود. در نهایت با خارج شدن موس از کادر، مقدار بزرگنمایی به حالت قبلی خود بازگردد. برای جابهجا شدن تصویر، میبایست مقدار `transform-origin` را با توجه به موقعیت نشانگر موس **روی تصویر** (یا به طور دقیقتر، روی `preview-scale`) تغییر دهید. برای این کار باید ابتدا موقعیت موس در صفحهی نمایش را با استفاده از `pageX` و `pageY` (که از ایونت `e` ورودی تابع `handleMouseMove` قابل دسترسیاند) به دست آورید. سپس با محاسبهی فاصلهی `preview-scale` از سمت چپ و بالای صفحه نمایش به کمک `offsetLeft` و `offsetTop`، موقعیت موس روی `preview-scale` را محاسبه کنید.
- هنگامی که موس روی تصویر حرکت میکند، سایر فیلترهایی که اعمال شدهاند باید بدون تغییر باقی بمانند.
- پس از چرخاندن یا *flip* کردن، نباید *scale* شدن و حرکت موس روی تصویر دچار مشکل شده یا کادر مربعی تصویر (`preview-container`) خالی شود.
# نکات
- محاسبات ریاضی را تا حداکثر **۵ رقم اعشار** انجام دهید. برای این کار، میتوانید از تابع `toFixed` جاوااسکریپت استفاده کنید.
- تصویر در ابعاد مربعی خواهد بود.
- شما تنها مجاز به اعمال تغییرات در فایل `script.js` هستید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `script.js` را آپلود کنید.
ویرایشگر عکس
![ظاهر برنامه](https://quera.org/qbox/view/h6UyYGFnYU/dk_js_router_org.gif)
*شکرستون* یک شهرِ به شدت در حال رشد است و میزان خرید شهروندانش بسیار بالا رفته، اما از آنجایی که مشکلات زیرساختی زیادی درونش وجود دارد، روی تجربهی کاربری خیلی از محصولاتش تأثیر گذاشته است. مهدی یک مهندس نرمافزار در دیجیکالای *شکرستون* است و قصد دارد طی یه حرکت خفن، تجربهی کاربری این سایت را بهتر کند!
یکی از مواردی که از نظر مهدی خیلی روی تجربهی کاربری سایت تأثیرگذار است، *refresh* شدن صفحات پس از کلیک کردن روی لینکها است. پس از کمی سرچ و مشورت، او متوجه شد که راهحل مشکلش، استفاده از کتابخانهی `react-router` است اما متأسفانه دیجیکالا در *شکرستون* با جاوااسکریپت خام نوشته شده!
مهدی تصمیم گرفته که وارد میدان شود و با کمک شما، یک کتابخانهی *client router* بسازد. از آنجایی که تجربهی کاربر برای مهدی خیلی مهم است، جابهجایی بین صفحات نباید باعث *refresh* شدن صفحه شود تا سرعت برنامه بالاتر رود. همچنین زمانی که کاربر روی دکمههای جلو و عقب مرورگر کلیک میکند، باید صفحهی قبلی یا بعدی بدون تأخیر نمایش داده شود.
مهدی به تنهایی نمیتواند کل این کتابخانه را پیادهسازی کند. او از شما خواسته که در پیادهسازی این کتابخانه به او کمک کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/39252/download_problem_initial_project/135563/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
client-router
├── assets
│ ├── css
│ │ └── style.css
│ ├── fonts
│ │ └── vazir.woff2
│ └── product_data.json
├── clientRouter.js
├── index.html
├── main.js
└── package.json
```
</details>
<details class="brown">
<summary>
راهاندازی پروژه
</summary>
برای اجرای پروژه، باید *Node.js* و *npm* را از قبل نصب کرده باشید.
- پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- در پوشهی `client-router` دستور `npm install` را برای نصب نیازمندیها اجرا کنید.
- در همین پوشه، دستور `npm start` را برای راهاندازی پروژه اجرا کنید.
</details>
یکی از جذابترین مواردی که مهدی در `react-router` دیده بود، قابلیت *route params* است. مهدی نیز تصمیم گرفته این قابلیت را به کتابخانهاش اضافه کند. این قابلیت به اینصورت کار میکند که هنگام تعریف *route* ، اگر قبل از یکی از پارامترها `:` گذاشته شود، آن بخش از *URL* هنگام *render* به فانکشنش پاس داده میشود. مثال:
%align_right_start%
### *route*:
%align_end%
```
/product/:id/comments/:user
```
%align_right_start%
### *render function*:
%align_end%
```javascript
({id, user}) => { `<div>${id}-${user}</div>` }
```
پروژه شامل دو فایل `main.js` و `clientRouter.js` است.
فایل `main.js` شامل صفحات برنامه است. هر صفحه یک فانکشن است که *route params* را بهعنوان ورودی دریافت کرده و یک رشتهی *HTML* را در جواب برمیگرداند.
در انتها نیز کتابخانهی `clientRouter` صدا زده شده و صفحات بهعنوان یک پارامتر به آن پاس داده میشوند.
شما باید فایل `clientRouter.js` را طوری پیادهسازی کنید که برنامه همانند ویدیوی موجود در ابتدای صورت سؤال کار کند.
فایل `clientRouter.js` شامل توابع زیر است:
- تابع `processRoutes`: این تابع پس از هر تغییر در صفحه صدا زده میشود و وظیفهی پردازش و مقایسهی *route* ها را برعهده دارد.
- تابع `handleLinks`: این تابع وظیفهی اضافه کردن *event listener* ها را به لینکهای موجود در صفحه برعهده دارد.
- تابع `handleRouteChange`: این تابع وظیفهی گوش کردن به تغییرات *URL* و اجرای توابع بالا بر حسب نیاز را برعهده دارد و از قبل پیادهسازی شده است.
# نکات
- فایل `clientRouter` باید به گونهای طراحی شود که تنها با اجرای تابع `initializeRouter` و پاس دادن ورودی های مناسب، برنامه بهصورت یک *SPA* شروع به کار کند.
- صفحات در قالب توابعی که یک رشتهی *HTML* برمیگردانند تعریف میشوند و *route param* ها بهعنوان یک آبجکت به این توابع پاس داده میشوند.
- اگر `clientRouter` نتوانست با هیچکدام از صفحات مچ شود، باید صفحه ی `404` به کاربر نشان داده شود.
- لینکهایی که توسط *client router* هندل می شوند، به جای استفاده از اتریبیوت `href`، از اتریبیوت `data-href` استفاده میکنند تا از لینک های معمولی متمایز شوند.
نمونه:
```html
<a data-href="/products">test link</a>
```
- هنگام هندل کردن *event* های مربوط به لینکها، به جای استفاده از `event.target`، از `event.currentTarget` استفاده کنید تا هنگام کلیک روی *element* های زیرمجموعه لینک شما، بتوانید به *element* والد دسترسی داشته باشید. [جزئیات بیشتر](https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget)
- شما تنها مجاز به اعمال تغییرات در فایل `clientRouter.js` هستید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `clientRouter.js` را آپلود کنید.
روتر جاوااسکریپتی
![ظاهر برنامه](https://quera.org/qbox/view/CSjowTqHPD/purge-classnames-gif.gif)
امین تازه در دیجیکالا جذب شده و از آنجایی که خیلی به چالش های فنی علاقهمند است، بابک و فرزاد تصمیم گرفتهاند از او بخواهند راهحلی برای یک مشکل پیدا کند. به امین در یافتن راهحل کمک کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/39252/download_problem_initial_project/135523/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
purge-classnames
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── sample.spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ ├── commands.js
│ └── index.js
├── cypress.json
├── index.html
├── package.json
├── script.js
└── styles.css
```
</details>
امین باید تابعی با نام `purgeClassNames` پیادهسازی کنه که تعداد آرگومانهاش متغیر است. مثال:
```javascript
purgeClassNames('m-5', 'd-block', 'mr-5')
purgeClassNames('p-3', 'oy-12')
```
این تابع باید کلاسهای مشابه را بر اساس اولویت حذف کند، یعنی کلاسهایی که زودتر به تابع پاس داده شدهاند اولویت کمتری دارند. مثلاً خروجی مثال بالا باید بهصورت زیر باشد:
```javascript
purgeClassNames('m-5', 'd-block', 'm-12') === 'd-block m-12' // true
```
در مثال فوق، کلاس `m-5` با کلاس `m-12` جایگزین میشود، زیرا `m-12` دیرتر آمده و اولویت بیشتری دارد. کلاس `d-block` بدون هیچ تغییری به خروجی منتقل میشود.
## توضیح کلاسها
### کلاس `margin`
- کلاس `m-n`: مارجین از همهی جهات به اندازهی $n$
- کلاس `ml-n`: مارجین از چپ
- کلاس `mr-n`: مارجین از راست
- کلاس `mb-n`: مارجین از پایین
- کلاس `mt-n`: مارجین از بالا
- کلاس `my-n`: مارجین از بالا و پایین
- کلاس `mx-n`: مارجین از چپ و راست
در توضیحات بالا، $n$ میتواند هر عددی باشد. مثال:
`m-10` `mr-999` `mx-11`
#### چند نمونه تست کیس برای کلاس `margin`:
**ورودی ۱:**
```javascript
purgeClassNames('m-5', 'my-2')
```
**خروجی ۱:**
my-2 mx-5
*کلاس `m-5` میتواند به `my-5 mx-5` تجزیه شود. بنابراین `my-2` جایگزین `my-5` شده و خروجی برابر با `my-2 mx-5` خواهد بود.*
**ورودی ۲:**
```javascript
purgeClassNames('mx-3', 'mr-1')
```
**خروجی ۲:**
mr-1 ml-3
**ورودی ۳:**
```javascript
purgeClassNames('m-3', 'mt-1')
```
**خروجی ۳:**
mt-1 mx-3 mb-3
**نکته:** `m-n` میتواند به `mx-n` و `my-n` تجزیه شود. کلاس `mx-n` نیز میتواند به `mr-n` و `ml-n` تجزیه شود. به همین ترتیب، کلاس `my-n` نیز شامل `mt-n` و `mb-n` میشود. در این مثالها، $n$ هر عددی میتواند باشد.
### کلاس `padding`
قواعد کلاس `margin` برای این کلاس هم وجود دارند. مثال:
`pr-6` یا `p-5` یا `py-3` و...
### کلاس `display`
ساختار کلاس به صورت `d-OPTION` است که `OPTION` میتواند یکی از مقادیر زیر باشد:
- `flex`
- `inline`
- `inline-flex`
- `block`
- `inline-block`
مثال:
`d-block` `d-inline` `d-flex`
#### یک نمونه تست کیس برای کلاس `display`:
**ورودی:**
```javascript
purgeClassNames('d-block', 'd-flex')
```
**خروجی:**
d-flex
### کلاس `overflow`
ساختار کلاس `overflow` بهصورت زیر است:
- `overflow-OPTION` در تمامی جهات
- `overflow-y-OPTION` درجهت محور y
- `overflow-x-OPTION` در جهت محور x
در مثالهای بالا، `OPTION` میتواند یکی از مقادیر زیر باشد:
- `hidden`
- `visible`
- `scroll`
- `auto`
- `none`
#### چند نمونه تست کیس برای کلاس `overflow`:
**ورودی ۱:**
```javascript
purgeClassNames('overflow-auto', 'overflow-x-none')
```
**خروجی ۱:**
overflow-x-none overflow-y-auto
**ورودی ۲:**
```javascript
purgeClassNames('overflow-x-auto', 'overflow-y-auto')
```
**خروجی ۲:**
overflow-auto
**ورودی ۳:**
```javascript
purgeClassNames('overflow-x-visible', 'overflow-none')
```
**خروجی ۳:**
overflow-none
**نکته:** کلاس `overflow-OPTION` میتواند به کلاسهای `overflow-x-OPTION` و `overflow-y-OPTION` تجزیه شود.
### نقطه شکستها (*Breakpoints*)
نقطه شکستها اندازه های تعریف شدهای هستند که به ما این امکان را میدهند تا یک کلاس را فقط در یک اندازهی مشخص اعمال کنیم.
هر کلاس میتواند فقط در یک *breakpoint* خاص فعال باشد، برای مثال: `lg:mr-2` فقط در *breakpoint lg* فعال است. تمامی کلاس هایی که بالا معرفی شدند باید قابلیت استفاده با *breakpoints* را داشته باشند.
لیست نقطه شکست های موجود:
- `xs`
- `sm`
- `md`
- `lg`
#### چند نمونه تست کیس برای *breakpoints*:
**ورودی ۱:**
```javascript
purgeClassNames('m-5', 'lg:m-2')
```
**خروجی ۱:**
m-5 lg:m-2
همانطور که مشاهده میکنید، کلاسهایی که دارای *breakpoint* هستند، بر روی کلاس های معمولی اثری ندارند.
**ورودی ۲:**
```javascript
purgeClassNames('sm:m-5', 'lg:m-2')
```
**خروجی ۲:**
sm:m-5 lg:m-2
همانطور که مشاهده میکنید، *breakpoint* های متفاوت نیز بر روی یکدیگر تاثیر ندارند.
# نکات
- ترتیب کلاس های خروجی داده شده توسط تابع `purgeClassNames` اهمیتی ندارد.
- خروجی تابع `purgeClassNames` باید یک رشته شامل کلاسها باشد که با استفاده از فاصله (*space*) از هم جدا شدهاند.
- تضمین میشود که فقط و فقط یک کلاس به ازای هر آرگومان به تابع داده خواهد شد.
- دقت کنید که تعداد آرگومان های تابع متغیر خواهد بود.
- حداقل یک آرگومان به تابع پاس داده خواهد شد.
- اگر کلاسی به `purgeClassNames` داده شود که در لیست کلاسهای بالا تعریف نشده بود، شما باید آن کلاس را بدون تغییر و به صورت مستقیم به خروجی ارسال کنید.
- شما تنها مجاز به اعمال تغییرات در فایل `script.js` هستید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `script.js` را آپلود کنید.
تطهیر کلاسها
**در این سؤال، از کتابخانهی `react-router-dom@6` استفاده شده است.**
---
![ظاهر برنامه](https://quera.org/qbox/view/RYLyFlB1MJ/useFilter_DK.gif)
در دیجیکالا تعداد زیادی داده وجود دارد که برخی اوقات لازم است آنها را فیلتر کنیم. حال، میخوایم راهی پیدا کنیم که این فیلتر اعمالشده را به راحتی با بقیه به اشتراک بگذاریم. برای این کار، تصمیم گرفتهایم یک هوک بنویسیم که کارش هماهنگ کردن و اتصال *state* فرم فیلتر با آدرس صفحه است. بنابراین شما باید در کامل کردن این هوک (که آن را `useFilter` نامیدهایم) به ما کمک کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/39252/download_problem_initial_project/135711/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
useFilter
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ ├── css
│ │ │ └── index.css
│ │ └── font
│ │ └── Vazir.ttf
│ ├── components
│ │ ├── chips
│ │ │ ├── chips.css
│ │ │ └── index.jsx
│ │ ├── formInput
│ │ │ ├── Checkbox
│ │ │ │ ├── Checkbox.css
│ │ │ │ └── index.jsx
│ │ │ ├── CheckboxGroup
│ │ │ │ ├── CheckboxGroup.css
│ │ │ │ └── index.jsx
│ │ │ ├── Dropdown
│ │ │ │ └── index.jsx
│ │ │ ├── RangeInput
│ │ │ │ ├── RangeInput.css
│ │ │ │ └── index.jsx
│ │ │ ├── TextInput
│ │ │ │ └── index.jsx
│ │ │ ├── Textarea
│ │ │ │ └── index.jsx
│ │ │ ├── FilterForm.jsx
│ │ │ ├── FormClear.jsx
│ │ │ └── FormItem.jsx
│ │ └── Location.jsx
│ ├── constants
│ │ └── FormType.js
│ ├── data
│ │ └── formData.js
│ ├── hooks
│ │ └── useFilter.js
│ ├── App.jsx
│ └── index.jsx
└── package.json
```
</details>
<details class="brown">
<summary>
راهاندازی پروژه
</summary>
برای اجرای پروژه، باید *Node.js* و *npm* را از قبل نصب کرده باشید.
- پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- در پوشهی `useFilter`، دستور `npm install` را برای نصب نیازمندیها اجرا کنید.
- در همین پوشه، دستور `npm start` را برای راهاندازی پروژه اجرا کنید.
</details>
دادهی لازم برای ساخت فرم بهصورت یک آرایه به هوک `useFilter` پاس داده میشود. هر عضو این آرایه معادل یک المان (*input element*) از فرم است. آبجکت زیر شامل تمامی *prop* ها است که به المانها داده میشود. بعضی از این *prop* ها مربوط به نوع خاصی از المانها هستند. برای مثال، `options` فقط مربوط به `dropdown` و `checkbox group` است.
```json
{
"type": "text"|"number"|"dropdown"|"textarea"|"checkbox"|"checkbox-group",
"name": string,
"label": string,
"children"?: string[],
"parent"?: string,
"options": [{ value: string|number, title: string|number }],
}
```
### فیلد `options`
همانطور که گفته شد، این فیلد مختص `dropdown` و `checkbox group` است.
### فیلد `parent` و `children`
بعضی از المانهای فرم ممکن است دارای وابستگی به المان دیگری باشند. این وابستگی در فرزند با فیلدی به نام `parent` که یک رشته شامل نام پدر است، نمایش داده میشود و در المان پدر با فیلدی به نام `children` که شامل آرایهای از رشتهها شامل نام (فیلد `name`) فرزندان است.
### اتصال (*binding*) دوطرفه
هوک خواستهشده باید یک اتصال (*binding*) دوطرفه بین پارامترهای جستجوی آدرس (*query params*) و فیلدهای فرم داشته باشد، به این معنی که با هر تغییر در المانهای فرم، بلافاصله آدرس به روزرسانی شود و برعکس. این هوک باید یک آبجکت شامل کلیدهای زیر برگرداند:
```json
{
"filterState": {
"name": value
}
"setFilterState": (filterState)=>void
"onChange": (e, name, type)=>void
"onClear": (name)=>void
"onClearAll": ()=>void
}
```
آرایهای که فرم از روی آن ساخته میشود (متغیر `formData`) بهعنوان ورودی به هوک داده میشود.
### متغیر `filterState` و تابع `setFilterState`
این دو مورد، عملکردی معادل متغیر و تابع از هوک معروف `useState` را دارند. متغیر یک آبجکت است که هر کلید آن باید برابر با نام (فیلد `name`) از المان فرم باشد و مقدار آن هم مقدار آن المان است (فیلد `value`).
### تابع `onChange`
این تابع هنگام تغییر هر المان فرم صدا زده میشود.
### تابع `onClear`
در پایین صفحه، دکمههایی برای پاک کردن مقادیر المانهای فرم وجود دارد. با کلیک کردن بر روی این دکمهها، باید مقدار المان مورد نظر از فرم پاک شود و همزمان این مقدار از آدرس هم پاک شود. این تابع به عنوان ورودی نام (فیلد `name`) المان موردنظر را دریافت میکند.
**نکته:** در صورتی که مقدار المان پدر پاک شود و یا تغییر کند، مقدار المان فرزند حتماً باید ریست شود، یعنی هم از آدرس پاک شود و هم مقدار `input` مربوطه از فرم خالی شود.
### تابع `onClearAll`
در صورتی که روی دکمهی پاک کردن همه کلیک شود، مسلماً باید تمامی مقادیر المانها پاک شود و آدرس به حالت `/` برود.
### تجزیه و ترکیب آدرس (*url parser & url stringify*)
همانطور که میدانید، در حالت کلی پارامترهای جستجوی آدرس (*query parameter*) بهصورت زیر ساخته میشوند:
?name1=value1&name2=value2&name3=v1,v2,v3
اما به دلایلی نیاز داریم از فرمت متفاوتی برای ساخت این آدرس استفاده کنیم؛ به اینصورت که برای پشت سر هم قرار دادن پارامترها به جای کارکتر`&` از کارکتر`+`و برای جدا کردن *key* و *value* بهجای کارکتر`=` از `~` استفاده میکنیم. همچنین در مواردی که یک آرایه از مقادیر داریم (این اتفاق فقط در المان `checkbox group` رخ میدهد)، برای جدا کردن آنها از هم به جای `,` از کارکتر `--` استفاده میکنیم (ترتیب مقادیر مهم نیست). برای مثال، این پارامترهای رایج آدرس:
?name1=value1&name2=v1,v2,v3
به این صورت تبدیل میشوند:
?name1~value1+name2~v1--v2--v3
و در صورت تجزیه (*parse*) این پارامترها به آبجکت زیر میرسیم:
```json
{
"name1": "value1",
"name2": ["v1", "v2", "v3"]
}
```
# نکات
- فیلدی که مقداری ندارد **نباید** در پارامترهای جستجو (*query params*) آدرس ظاهر شود.
- در صورتی که روی دکمههای پاک کردن (*clear*) پایین صفحه کلیک شود، باید پارامتر موردنظر از آدرس مربوطه پاک شود و همزمان مقدار `input` متصل به آن فیلد پاک شود.
- در صورتی که یک فیلد دارای `children` پاک شود، در اینصورت باید تمامی فرزندانش نیز از آدرس پاک شوند.
- فیلدهای فیلتر بایستی مقداردهی اولیه شوند، بهطوری که در زمان بارگذاری صفحه، در صورتی که آدرس دارای پارامترهای جستجو (*query params*) باشد، باید بلافاصله این مقادیر با فرم هماهنگ شوند و فیلدهای فرم با مقادیر آدرس پر شوند.
- شما تنها مجاز به اعمال تغییرات در فایل `hooks/useFilter.js` هستید.
- تنها مجاز به استفاده از کتابخانههای ذکر شده در صورت سوال هستید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `useFilter.js` را آپلود کنید.
هوک فیلتر
![ظاهر برنامه](https://quera.org/qbox/view/daklf8x7dv/shamsolah_metaverse.gif)
با *trend* شدن زمینهای متاورس و خرید آنها توسط مردم، شمسالله تصمیم گرفته که به فروش زمینهای متاورسی روی بیاورد. او که جدیداً با وبسوکت آشنا شده است، میخواهد این پروژه را با وبسوکت پیادهسازی کند. برای این کار، او سرور این پروژه را ساخته و از شما میخواهد که در قسمت فرانتاند پروژه به او کمک کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/39252/download_problem_initial_project/135559/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
metaverse-lands
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── components
│ │ └── Block.jsx
│ ├── data
│ │ └── index.js
│ ├── server
│ │ ├── data
│ │ │ ├── MapData.js
│ │ │ └── OwnerData.js
│ │ └── index.js
│ ├── styles
│ │ └── styles.css
│ ├── App.jsx
│ ├── index.js
│ └── setupTests.js
└── package.json
```
</details>
<details class="brown">
<summary>
راهاندازی پروژه
</summary>
برای اجرای پروژه، باید *Node.js* و *npm* را از قبل نصب کرده باشید.
- پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- در پوشهی `metaverse-lands`، دستور `npm install` را برای نصب نیازمندیها اجرا کنید.
- در همین پوشه، دستور `npm start` را برای راهاندازی پروژه اجرا کنید.
</details>
<details class="brown">
<summary>
راهاندازی سرور
</summary>
- ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- در پوشهی `metaverse-lands`، دستور `npm install` را برای نصب نیازمندیها اجرا کنید.
- در همین پوشه، دستور `npm run server` را برای راهاندازی سرور پروژه اجرا کنید.
</details>
در این پروژه، ارتباط شما با سرور بر بستر وبسوکت است و شما تمام دادهها را از این طریق سرور دریافت میکنید و از همین طریق برای سرور میفرستید. در همان لحظه که به وبسوکت وصل شوید، شما دیتای مپ یا نقشه و همچنین دیتای دارندگان هر نقطه از نقشه را دریافت میکنید.
ساختار دادهی ارسالی سرور بهشکل زیر است:
```json
{
"type": "MAP" | "OWNER",
"result": "Array",
}
```
که `type` میتواند یکی از دو مقدار `MAP` و `OWNER` را داشته باشد.
- تایپ `MAP` مشخصکنندهی دادههای نقشه اصلی است.
- تایپ `OWNER` مشخصکنندهی دادههای دارندگان هر نقطه از نقشه است.
دادههای `MAP` بهشکل زیر هستند:
```json
{
"id": "string",
"type": "LAND" | "WALL",
"x": "number",
"y": "number",
}
```
بدین ترتیب مختصات تمام نقاط نقشه را سرور برای شما میفرستد. دقت کنید دادههای نقشه مرتب نیست و تمام مختصات در یک آرایه قرار دارد. همچنین اگر `type` یک نقطه برابر `LAND` بود، بدین معنا است که آن نقطه زمین است و اگر برابر `WALL` بود، بدین معنا است که آن نقطه دیوار است. در صورتی که یک نقطه `LAND` بود، شما باید آن نقطه را به رنگ `DEFAULT_COLORS.LAND` و اگر `WALL` بود، باید به رنگ `DEFAULT_COLORS.WALL` در بیاورید.
دادههای `OWNER` بهشکل زیر است:
```json
{
"compony": "string",
"color": "string",
"startCord": {
"x": "number",
"y": "number"
},
"endCord": {
"x": "number",
"y": "number"
}
}
```
در دادههای بالا، `startCord` نشاندهندهی شروع مختصات نقاطی است که تحت مالکیت فردی با رنگ اتخصاصی `color` است. همچنین `endCord` نشاندهندهی پایان مختصات است. تمام نقاط بین `startCord` و `endCord` تحت مالکیت این فرد یا شرکت است و رنگ این نقط باید به رنگ `color` باشد. با این فرض این نقاط همیشه به شکل مربع یا مستطیل هستند. شما باید بر اساس این دادهها، نقشه را ترسیم کنید. پس از آن، در هر لحظه ممکن است سرور دادههای دارندگان جدید را برای شما بفرستد. شما باید بر اساس دادههای جدید، نقشه را بهروزرسانی کنید.
همچنین کاربر فعلی میتواند با انتخاب هر نقطه از نقشه، آن نقطه را برای خود خریداری کند.
برای این کار، شما باید ساختار دادهای مانند ساختار دادهی زیر را برای سرور تحت سوکت بفرستید:
```json
{
"x": "number",
"y": "number"
}
```
سرور پس دریافت این نقاط، دادههای مالک این نقطه را برای شما میفرستد و شما باید مانند قبل، نقشه را بهروزرسانی کنید.
# نکات
- در ابتدا باید شما به سوکت سرور وصل شوید. در صورتی که به سوکت وصل نباشید، دیتایی دریافت نمیکنید.
- پس دریافت دادههای نقشه و دارندگان هر نقطه باید نقشه را بسازید. برای این منظور به کامنت داخل فایل `App.jsx` توجه کنید.
- شما باید برای نقطه از نقشه از کامپوننت `Block` استفاده کنید.
- رنگ هر نقطه از نقشه باید متناسب با نوع آن نقطه باشد؛ یعنی در صورتی که زمین و دیوار بود، باید از رنگهای دادهشده و در صورتی که تحت مالکیت بود، از رنگ ارسالی سرور استفاده کنید.
- نقطات زمین ممکن است به مالکیت فرد دیگری در بیاید.
- هر فرد میتوانند چندین نقطه داشته باشد، اما دادههای داخل هر ابجکت دیتای `OWNER` پیوسته است.
- دیتای مربوط به نقشه فقط در لحظه اتصال به سوکت ارسال میشود.
- برای مشاهدهی بهتر دیتا، میتوانید پوشه `server` را بررسی کنید.
- سوکت در پورت *3001* لوکال هاست اجرا میشود. (`"ws://localhost:3001"`)
- برای اطلاعات بیشتر در مورد وبسوکت میتوانید از [لینک](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications) استفاده کنید.
- تمام دادههای ثابت در فایل `data/index.js` قرار داده شده است.
- شما تنها مجاز به اعمال تغییرات در فایل `App.jsx` هستید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `App.jsx` را آپلود کنید.