# urbi-exhibitions

## Установка

`npm i urbi-exhibitions`

## Использование

Для использования компонентов и сценариев необходимо сделать следующие шаги:

1. Задать в приложении цвета в конфиге `tailwind` как в [примере](../demo/tailwind.config.js);
2. Добавить в конфиг `tailwind` в `content` `'./node_modules/urbi-exhibitions/dist/**/*.{js,mjs,ts,jsx,tsx}'`;
3. Обернуть приложение в необходимые контексты

## Состав библиотеки

### [Типы](./src/types.ts)

Базовые типы, используемые в навигации: `Coordinates, Point, RoutePart, Route`

Пример использования:

```
import type { Point, Coordinates } from "urbi-exhibitions"
```

### [Контексты](./src/contexts)

- **[ExhibitionContext](./src/contexts/exhibitionContext.tsx)** — Общий контекст для проброса параметров между основным приложением и библиотекой;

Пример использования:

```
import { ExhibitionContextProvider } from 'urbi-exhibitions/contexts';

const { theme } = useTheme();
const { t } = useTranslation();

return (
    <ExhibitionContextProvider
      theme={theme}
      t={t} // Либо не передаем вовсе, если в приложении отсутствует мультиязычность
    >
      // code
    </ExhibitionContextProvider>
);
```

- **[MapContext](./src/contexts/mapContext.tsx)** — Хранит в себе объект map c экземпляром карты, gltf плагина и основные параметры состояния карты. До момента загрузки карты map = undefined.

```
interface MapContextState {
	map?: {
		instance: Map
		gltfPlugin: GltfPlugin;
		state: MapState
	}
}
interface MapState {
  zoom: number;
  isDragging: boolean;
  floorLevels: FloorLevel[];
  floorLevelIndex?: number;
  floorLevelName?: string;
  floorPlanId?: string;
}
```

Примеры использования:

```
import { MapProvider } from "urbi-exhibitions/contexts"

function App() {
	return (
		<MapProvider>
			...
		</MapProvider>
	)
}
```

```
import { useMap } from "urbi-exhibitions/contexts"

function SomeComponent() {
    const { map } = useMap()
}
```

- **[GeolocationContext](./src/contexts/geolocationContext.tsx)** — Хранит в себе текущие координаты клиента при включенном отслеживании координат (см. компонент LocationTracker).

```
interface GeolocationContextState {
	coordinates?: GeolocationCoordinates;
}
```

Примеры использования:

```
import { GeolocationProvider } from "urbi-exhibitions/contexts"

function App() {
	return (
		<GeolocationProvider>
			...
		</GeolocationProvider>
	)
}
```

```
import { useGeolocation } from "urbi-exhibitions/contexts"

function SomeComponent() {
    const { coordinates } = useGeolocation()
}
```

- **[RoutingApiContext](./src/contexts/routingApiContext.tsx)** — Используется для передачи в компоненты приложения экземпляра Routing API, созданного при помощи функции createRoutingApi.

Примеры использования:

```
import { routingApi } from "./routingApi.ts"
import { RoutingApiProvider } from "urbi-exhibitions/contexts"

function App() {
	return (
		<RoutingApiProvider routingApi={routingApi}>
			...
		</RoutingApiProvider>
	)
}
```

```
import { useRoutingApi } from "urbi-exhibitions/contexts"

function SomeComponent() {
    const { queries, baseUrl } = useRoutingApi()
}
```

### [Routing API](./src/api/routing)

Для использования Routing API в проекте, необходимо создать его экземпляр:

```
import { createRoutingApi } from "urbi-exhibitions/api"

const routingApi = createRoutingApi({ apiKey: "YOUR_API_KEY" })

```

- **[Get route](./src/api/routing/getRoute/)** — Метод, который строит маршрут из точки А в точку Б. Также есть возможность указания промежуточных точек. [Подробнее](https://docs.2gis.com/ru/api/@balady-tech/navigation/routing/reference/routing) в документации к API.

Пример использования с react-query:

```
const { data } = useQuery(routingApi.queries.getRoute({ points: [...] }))
```

Условный запрос маршрута:

```
const { data } = useQuery({
    ...routingApi.queries.getRoute({ points: [...] }),
    enabled: !someCheck,
    // Также можно переопределять другие параметры хука useQuery (кэшировани и т.д.)
})
```

### [Хуки](./src/hooks)

- **[useChunks](./src/hooks/useChunks/)** — Разбивает массив элементов на несколько массивов указанной длины

- **[useGltfModels](./src/hooks/useGltfModels/)** — При помощи gltf плагина добавляет указанные модели на карту

- **[usePartnerLogos](./src/hooks/usePartherLogos/)** — Добавляет на карту логотипы партнеров, через проп параметр `floorLevelIndexes` можно указать на каких этажах логотипы должны отображаться.

### [Utils](./src/utils)

- **[getRouteKey](./src/hooks/useChunks/)** — Создает ключ, которые используется в react-query для запроса Get route

- **[parseLineStringWKT](./src/utils/parseLineStringWKT.ts)** — Парсит строку типа `LINESTRING(...)` и возвращает массив чисел

- **[toChunks](./src/utils/toChunks.ts)** — Разбивает массив на массивы указанной длины. Используется в основе хука `useChunks`

### [Компоненты](./src/components)

- **[PlayButton](./src/components/playButton)** — Кнопка play;

- **[MapContainer](./src/components/mapContainer/)** — Компонент для отрисовки карты. Отрисовывает карту внутри себя и сохраняет инстанс карты в MapContext. Также осуществляет подписку на некоторые события карты (изменение зума, переключение этажей) для обновления соответствующих данных в MapContext. Есть возможность передать URL локально скрипта для работы карт без подключения к сети. Также можно подписаться на событие загрузки карты, чтобы загрузить стили и т.д.

Пример использования с react-query:

```
import { MapProvider } from "urbi-exhibitions/contexts"
import { MapContainer } from "urbi-exhibitions/components"

import { useCallback } from "react"
import type { Map, MapOptions } from "@2gis/mapgl/types"

const API_KEY = "YOUR_API_KEY"
const DEFAULT_OPTIONS: MapOptions = { zoom: 30, center: [37.617734, 55.751999], floorControl: 'centerRight' }

function App() {
	const onLoad = useCallback(({ instance }: { instance: Map }) => {
		// Тут можно загрузить какие-то стили для карты и др.
	}, [])

	return (
		<MapProvider>
			<MapContainer
				className="fixed inset-0"
				apiKey={API_KEY}
				options={DEFAULT_OPTIONS}
				onLoad={onLoad}
			/>
		</MapProvider>
	)
}

export default App

```

#### Кеширование тайлов

Есть возможность включить кеширование тайлов карты, что позволяет приложению работать в оффлайн-режиме или существенно ускорить загрузку при повторных посещениях. Кэширование реализовано через Service Worker API с поддержкой TTL (времени жизни) для кэшированных тайлов.

Для включения кэширования тайлов:

- Добавьте параметр `tilesCache={true}` в компонент MapContainer
- Настройте Vite-плагин для генерации Service Worker

```
// App.tsx
import { MapProvider } from "urbi-exhibitions/contexts"
import { MapContainer } from "urbi-exhibitions/components"

const API_KEY = "YOUR_API_KEY"

function App() {
  return (
    <MapProvider>
      <MapContainer
				apiKey={API_KEY}
        tilesCache={true} // Включение кеширования
      />
    </MapProvider>
  );
}
```

```
// vite.config.js
import { defineConfig } from 'vite';
import { tilesCacheSwPlugin } from 'urbi-exhibitions/plugins';

export default defineConfig({
  plugins: [
    // ..другие плагины
    tilesCacheSwPlugin({
      cacheName: 'my-tiles-cache',
      // URL адреса запросы которого необходимо кешировать, опционально, по умолчанию '2gis.com'
      requiredRequestUrl: '2gis.com',
      // Время жизни кеша, опционально, по умолчанию 1 день
      ttl: 1000 * 60 * 60 * 24,
    }),
  ]
});
```

Очистка кеша:

```
import { clearTilesCache } from 'urbi-exhibitions/clientSW';

function ClearCacheButton() {
  const handleClearCache = async () => {
    await clearTilesCache();
    console.log('Кэш тайлов очищен');
  };

  return <button onClick={handleClearCache}>Очистить кэш</button>;
}
```

Также можно добавить параметр ?cache=clear в URL приложения для очистки кэша при загрузке страницы.

- **[LocationTracker](./src/components/locationTracker/)** — Компонент, который отслеживает изменение местоположения пользователя и сохраняет его в GeolocationContext. Соответственно, должен использоваться внутри данного контекста. Есть параметры для настройки debounce эффекта, позволяющего регулировать частоту обновления координат в контексте для предотвращения слишком частых перерисовок. На все приложение должно существовать не более одного экземпляра данного компонента. Функционал выполнен в виде компонента для возможности условной отрисовки (отслеживание местоположения пользователя по условию, чтобы запрашивать права на получение местоположения у пользователя только когда это действительно необходимо).

Пример использования:

```
import { LocationTracker } from "urbi-exhibitions/components"


function SomeComponent() {
    return {
        <LocationTracker/>
    }
}
```

- **[FloorControl](./src/components/floorControl/)** — Компонент для переключения этажа.

Пример использования:

```
import { FloorControl } from "urbi-exhibitions/components"
import { useMap } from "urbi-exhibitions/contexts"

const SomeComponent = () => {
  const { map } = useMap()

  return (
    <FloorControl.Root className="">
      {map?.state.floorLevels.map(({ floorLevelIndex, floorLevelName }) => (
        <FloorControl.Button
          key={floorLevelIndex}
          floorLevelIndex={floorLevelIndex}
          className=""
        >
          {floorLevelName}
        </FloorControl.Button>
      ))}
    </FloorControl.Root>
  )
}
```

### [Сценарии](./src/contexts)

- **[ImmersiveScenario](./src/scenarios/immersive)** — [Иммерсивный сценарий](https://urbi-ae.github.io/exhibitions/#/immersive)

Пример использования целиком можно посмотреть [demo](../demo/src/pages/immersive).

- **[QuizScenario](./src/scenarios/quiz)** — [Квиз сценарий](https://urbi-ae.github.io/exhibitions/#/quiz)

Пример использования целиком можно посмотреть [demo](../demo/src/pages/quiz).

#### Модули:

1. **[flyOverImmersiveBuilding](./src/scenarios/immersive/modules/flyOverImmersiveBuilding.ts)** — Полет над зданием;

#### Компоненты:

1. **[ComplexCard](./src/scenarios/immersive/components/complexCard)** — Карточка здания (вне маркера);
2. **[PovPagination](./src/scenarios/immersive/components/povPagination)** — Пагинация для pov'ов здания;

### [Модули](./src/modules)

- **[MapFlight](./src/modules/mapFlight.ts)** — полеты карты;

Пример использования:

```
import { MapFlight } from 'urbi-exhibitions/modules';

const mapFlight = new MapFlight({
  zoom: MAP_ZOOM,
  maxZoom: MAP_MAX_ZOOM,
  minZoom: MAP_MIN_ZOOM,
  center: MAP_CENTER,
  pitch: MAP_PITCH,
  rotation: MAP_ROTATION,
});

mapFlight.runFlight(map, config);
```

- **[convertModelToCS, convertModelParams](./src/modules/convertModel.ts)** — вспомогательные утилиты для конвертации конфигов иммерсивных моделей;

Пример использования:

```
import { convertModelToCS } from 'urbi-exhibitions/modules';

convertModelToCS({
  buildingIds: ['70030076151587939', '70030076299034544'],
  coords: [54.607139, 24.483673],
  scale: 1,
  rotateX: 0,
  rotateZ: -0.10300000000000001,
  rotateY: 0,
  moveX: 0,
  moveY: 0,
  moveZ: 0,
  models: [
    {
      path: getModelUrl('ml/ferrari_gloss.glb'),
      name: 'ferrari',
    },
  ],
});
```
