Skip to content

Latest commit

 

History

History
99 lines (63 loc) · 22.1 KB

File metadata and controls

99 lines (63 loc) · 22.1 KB

social_platform — ui/widgets

Виджеты — переиспользуемые блоки внутри social_platform. По соглашению виджет используется в одном-двух местах в рамках одной/нескольких страниц (в отличие от примитивов, которые повсюду). Папка projects/social_platform/src/app/ui/widgets/ содержит 20 директорий.

Импорт через @ui/widgets/<name>/<name>.component.

Сводка

Виджет Selector Inputs Outputs Где используется
ChatWindowComponent app-chat-window messages: ChatMessage[] (req, setter), currentUserId (опц.), placeholder, loadingMore submit: ChatMessage, edit: ChatMessage, delete: number, type: void, fetch: void (для пагинации), read: number (id прочитанного) pages/chat/... (direct и project chats)
CookieConsentComponent app-cookie-consent app.component.html (root)
CourseAboutComponent app-course-about description: string (req) pages/courses/detail/info/info.component, pages/courses/detail/course-detail.component
DeatilComponent (sic — опечатка) app-detail — (всё через route.data + DI фасадов) widgets/detail/detail.component обёртка для <app-detail> в pages/projects/detail, pages/program/detail, pages/profile/detail. Универсальный header с инфо + табы + кнопки действий.
FeedFilterComponent app-feed-filter — (читает query params + дёргает фасады напрямую) pages/feed
HeaderComponent app-header invites: Invite[] acceptInvite: number, rejectInvite: number pages/office/office.component (шапка office shell)
InfoCardComponent app-info-card info?: any, type: "invite" | "projects" | "members" | "rating" = "projects", appereance: "my" | "subs" | "base" | "empty" = "base" (sic — опечатка), section: "projects" | "subscriptions" | "other" = "projects", canDelete?, isSubscribed?, profileId?, leaderId?, loggedUserId? onAcceptingInvite: number, onRejectingInvite: number, onCreate: void, onRemoveCollaborator: number Списки: pages/program/detail/list/list.component, pages/members/..., и др.
MessageInputComponent app-message-input placeholder = "", mask = "", replyMessage?: ChatMessage, value (CVA setter) appValueChange: { text, files }, submit: void, resize: void, cancel: void (отмена reply) widgets/chat-window
NewsCardComponent app-news-card feedItem: FeedNews (req), resourceLink: (string | number)[] (req), contentId?: number, isOwner?: boolean delete: number, like: number, edited: FeedNews widgets/news-form, pages/feed, pages/program/detail/main, pages/projects/detail/info, pages/projects/detail/news-detail
NewsFormComponent app-news-form — (внутри своя ReactiveForm) addNews: { text: string; files: string[] } pages/program/detail/main, pages/projects/detail/info
ProgramLinksComponent app-program-links title: string (req), icon: string (req), links: { label, url }[] (req) widgets/detail, pages/program/detail/main
ProjectDirectionCard app-project-direction-card direction!: string, icon!: string, about!: string | any[], type!: string, isOwner!: boolean, profileInfoType?: "skills" | "achievements", projectInfoType?: "goals" | "partners" pages/profile/detail, pages/projects/detail/info
ProjectNavigationComponent app-project-navigation navItems: Navigation[] stepChange: EditStep Step-by-step редакторы: pages/projects/edit, pages/profile/edit
ProjectVacancyCardComponent app-project-vacancy-card vacancy: Vacancy (req), type: "vacancies" | "project" = "project" pages/vacancies/list, pages/projects/detail/vacancies
ProjectsFilterComponent app-projects-filter — (через сервис + query params) pages/projects/list, pages/program/detail/list
SkillsBasketComponent app-skills-basket error = false — (внутри ReactiveForm) Профильный edit, проектный edit
SkillsGroupComponent app-skills-group options: Skill[] (req, setter), selected: Skill[] (req, setter), title: string (req), hasOpenGroups = false, disabled = false groupToggled: boolean, optionToggled: Skill Профильный edit, проектный edit, kanban task-detail
SpecializationsGroupComponent app-specializations-group title: string (req), options: Specialization[] (req), hasOpenGroups = false, disabled = false selectOption: Specialization, groupToggled: boolean Профильный edit
VacancyCardComponent app-vacancy-card vacancy?: Vacancy remove: number, edit: number pages/vacancies/list, pages/projects/detail/vacancies
VacancyFilterComponent app-vacancy-filter searchValue (setter) searchValueChange: string pages/vacancies/list

Заметки по отдельным виджетам

DeatilComponent (app-detail)

Универсальный заголовок-шапка для трёх типов сущностей: проект, программа, профиль. Решение какой режим включить берётся из route.data.listType ("project" \| "program" \| "profile") и из URL (isProjectsPage, isProjectChatPage, isKanbanBoardPage и т. п. — определяются в DetailInfoService).

Внутри:

  • DetailInfoService — общий fasade.
  • DetailProfileInfoService / DetailProjectInfoService / DetailProgramInfoService — специфичные для типа.
  • ProjectAdditionalService, TooltipInfoService — вспомогательные.
  • ChatStateService — для индикатора непрочитанных в чате.

Компонент сам ничего не принимает через @Input() — все данные идут через route.data resolver и инжектируемые сервисы. Поэтому виджет тяжёлый (840+ строк HTML и 130+ строк TS) и фактически "smart" вопреки общей конвенции виджет = dumb. Это исторический долг.

Опечатка в имени класса (DeatilComponent) сохраняется — переименование сломало бы все импорты.

InfoCardComponent (app-info-card)

Универсальная карточка для отображения сущностей в списках. Тип данных переключается через type ("invite" \| "projects" \| "members" \| "rating"). Поле info: any — ввиду полиморфизма; реальный тип зависит от type.

Опечатка appereance (вместо appearance) сохраняется — менять сломает шаблоны-потребители.

ChatWindowComponent + MessageInputComponent

Пара. ChatWindowComponent — собственно окно с сообщениями (виртуальный скролл, типизация, "is typing"). MessageInputComponent — поле ввода с поддержкой replyMessage и upload-файлов (CVA).

Output type: void в ChatWindowComponent имеет коллизию с TypeScript-зарезервированным словом — компилируется, но в IDE может выглядеть странно.

FeedFilterComponent, ProjectsFilterComponent, VacancyFilterComponent

Все три не принимают inputs — каждый дёргает свой *FilterInfoService напрямую и читает/пишет query-параметры через Router + ActivatedRoute. Это противоречит "виджет = dumb"; правильнее было бы переделать на input-driven через декларативные FilterFieldConfig-конфиги (см. domain/other/filter-fields.model.ts), но сейчас работают так.

NewsFormComponent + NewsCardComponent

Пара. NewsFormComponent — форма создания (ReactiveForm с text+files), эмитит addNews. NewsCardComponent — отображение одной новости с редактированием/удалением/лайком.

NewsCardComponent принимает resourceLink: (string | number)[] — массив сегментов URL для router-link навигации к деталям новости. Это путь который собирает страница-родитель (для feed: [], для project-news: ["/office/projects/", projectId, "news"]).

ProgramLinksComponent + CourseAboutComponent

Простые dumb-компоненты. ProgramLinksComponent рисует блок «контакты» / «материалы» программы — с иконкой, заголовком, списком ссылок (через UserLinksPipe). CourseAboutComponent — блок «о курсе» с раскрытием по кнопке.

SkillsGroupComponent + SpecializationsGroupComponent + SkillsBasketComponent

Триада для UX навыков и специализаций:

  • SkillsGroupComponent — рендер одной группы навыков (категория с под-навыками), эмитит выбор отдельного навыка через optionToggled.
  • SpecializationsGroupComponent — то же, но для специализаций.
  • SkillsBasketComponent — корзина выбранных навыков (с возможностью удаления). Внутри подключён к ReactiveForm (через @Input() error для подсветки и DI родительской FormGroup).

ProjectNavigationComponent (app-project-navigation)

Stepper для редактирования (профиля и проекта). Принимает массив navItems: Navigation[] (тип из @core/lib/models/navigation.model.ts) и эмитит stepChange: EditStep при клике. Готовые наборы шагов — в core/consts/navigation/nav-{profile,project}-items.const.ts.

HeaderComponent (app-header)

Шапка office shell. Принимает invites: Invite[] (для бейджа уведомлений) и эмитит acceptInvite / rejectInvite. Использует app-profile-control-panel из @uilib.

CookieConsentComponent

GDPR-баннер, добавлен через коммит e3a36e7c. Лежит в widgets (не в primitives), потому что используется ровно один раз — в app.component.html. Управляет localStorage.cookieConsent, при accepted дёргает AnalyticsService.loadAnalytics() (Yandex Metrika + Mail.ru counter — только на app.procollab.ru, см. docs/cross-cutting.md).