configured typescript strict mode in the frontend
This commit is contained in:
parent
2d63d2db6d
commit
c02a826aa5
@ -35,15 +35,17 @@ Frontend:
|
|||||||
|
|
||||||
Цель: включить strict TypeScript как обязательный стандарт для frontend и закрыть legacy errors без сохранения JS/CommonJS как допустимого направления. Новый и изменённый код должен быть strict-compatible.
|
Цель: включить strict TypeScript как обязательный стандарт для frontend и закрыть legacy errors без сохранения JS/CommonJS как допустимого направления. Новый и изменённый код должен быть strict-compatible.
|
||||||
|
|
||||||
|
Статус: выполнено 2026-07-06 для strict TypeScript baseline и frontend lint-warning cleanup. Первичный запуск `strict: true` давал 294 ошибки; после исправлений `frontend/tsconfig.json` использует общий `strict: true`, `npm run typecheck` проходит, `npm run lint` проходит без warnings, `npm run test` проходит (307 tests), `npm run test:e2e` проходит (10 tests), `npm run build` проходит.
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
- Посчитать текущий frontend typecheck baseline: сколько ошибок даёт `strict: true`.
|
- [x] Посчитать текущий frontend typecheck baseline: сколько ошибок даёт `strict: true`.
|
||||||
- Включать strictness по шагам: сначала отдельный strict config для selected folders/new code, затем общий `strict: true`, когда baseline закрыт.
|
- [x] Включать strictness по шагам: сначала отдельный strict config для selected folders/new code, затем общий `strict: true`, когда baseline закрыт.
|
||||||
- Для новых/изменённых файлов запрещать новый `any` без явной причины.
|
- [x] Для новых/изменённых файлов запрещать новый `any` без явной причины через `@typescript-eslint/no-explicit-any` warning.
|
||||||
- Запретить новые type assertions/casts (`as`, angle-bracket assertions, non-null `!`) в feature code. Исключения: validated external boundaries, DOM/library interop, discriminated unions после guard; исключение должно быть локальным и объяснённым.
|
- [x] Вернуть guardrails для type assertions/casts (`as`, angle-bracket assertions, non-null `!`) как warnings. Исключения: validated external boundaries, DOM/library interop, discriminated unions после guard; исключение должно быть локальным и объяснённым.
|
||||||
- Вернуть `no-unused-vars`/unused imports как warning, затем поднять до error после cleanup.
|
- [x] Вернуть `no-unused-vars`/unused imports как warning и очистить текущие warnings.
|
||||||
- Добавить frontend `typecheck` script и включить его в minimal checks, когда baseline проходит.
|
- [x] Добавить frontend `typecheck` script и включить его в minimal checks, когда baseline проходит.
|
||||||
- `react-hooks/exhaustive-deps` включать file-by-file после исправления конкретных hooks.
|
- [x] Вернуть `react-hooks/exhaustive-deps` как warning и очистить текущие hook dependency warnings.
|
||||||
|
|
||||||
### Auth storage
|
### Auth storage
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,26 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'react/no-children-prop': 'off',
|
'react/no-children-prop': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
'@typescript-eslint/no-unused-vars': 'off', // Turned off to reduce noise
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-type-assertions': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
assertionStyle: 'as',
|
||||||
|
objectLiteralTypeAssertions: 'allow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||||
'@typescript-eslint/ban-types': 'off',
|
'@typescript-eslint/ban-types': 'off',
|
||||||
'react-hooks/exhaustive-deps': 'off', // Turned off to reduce noise
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
'import/named': 'error',
|
'import/named': 'error',
|
||||||
'import/no-duplicates': 'error',
|
'import/no-duplicates': 'error',
|
||||||
'import/no-unresolved': 'error',
|
'import/no-unresolved': 'error',
|
||||||
|
|||||||
@ -16,7 +16,7 @@ The frontend is a **Next.js 15** application with **React 19**, **TypeScript**,
|
|||||||
|------------|---------|---------|
|
|------------|---------|---------|
|
||||||
| Next.js | 15.3.1 | React framework with Pages Router |
|
| Next.js | 15.3.1 | React framework with Pages Router |
|
||||||
| React | 19.0.0 | UI library |
|
| React | 19.0.0 | UI library |
|
||||||
| TypeScript | 5.x | Type safety |
|
| TypeScript | 5.x | Strict type safety |
|
||||||
| Redux Toolkit | 2.1.0 | State management |
|
| Redux Toolkit | 2.1.0 | State management |
|
||||||
| MUI X DataGrid | 7.0.0 | Data tables |
|
| MUI X DataGrid | 7.0.0 | Data tables |
|
||||||
| Tailwind CSS | 3.4.1 | Utility-first styling |
|
| Tailwind CSS | 3.4.1 | Utility-first styling |
|
||||||
|
|||||||
@ -6,6 +6,8 @@ The types module provides **TypeScript type definitions** used throughout the fr
|
|||||||
|
|
||||||
**Location:** `frontend/src/types/`
|
**Location:** `frontend/src/types/`
|
||||||
|
|
||||||
|
**Strictness:** Frontend TypeScript runs with `strict: true` in `frontend/tsconfig.json`. `npm run typecheck` is the strict type gate for changed frontend code. ESLint also reports explicit `any`, type assertions/non-null assertions, unused variables/imports, and React hook dependency issues as warnings; the current frontend lint baseline has no warnings.
|
||||||
|
|
||||||
**Statistics:**
|
**Statistics:**
|
||||||
- **20 files** (~2,205 LOC total)
|
- **20 files** (~2,205 LOC total)
|
||||||
- **6 categories**: Domain Entities, Runtime/Presentation, Infrastructure, Forms/Filters, Specialized, Module Declarations
|
- **6 categories**: Domain Entities, Runtime/Presentation, Infrastructure, Forms/Filters, Specialized, Module Declarations
|
||||||
|
|||||||
27
frontend/package-lock.json
generated
27
frontend/package-lock.json
generated
@ -45,7 +45,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.61.1",
|
"@playwright/test": "^1.61.1",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/node": "18.7.16",
|
"@types/node": "18.7.16",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
||||||
"@typescript-eslint/parser": "^8.62.1",
|
"@typescript-eslint/parser": "^8.62.1",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
@ -2573,6 +2576,13 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/file-saver": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/hoist-non-react-statics": {
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
|
||||||
@ -2592,6 +2602,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
|
||||||
|
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.7.16",
|
"version": "18.7.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
|
||||||
@ -2621,6 +2638,16 @@
|
|||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.12",
|
"version": "4.4.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
|
|||||||
@ -57,7 +57,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.61.1",
|
"@playwright/test": "^1.61.1",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/node": "18.7.16",
|
"@types/node": "18.7.16",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
||||||
"@typescript-eslint/parser": "^8.62.1",
|
"@typescript-eslint/parser": "^8.62.1",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
|
|||||||
@ -105,6 +105,9 @@ export const getButtonColor = (
|
|||||||
info: 'border-blue-600 border-blue-600 dark:border-pavitra-blue',
|
info: 'border-blue-600 border-blue-600 dark:border-pavitra-blue',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
white: 'text-black dark:text-white',
|
||||||
|
whiteDark: 'text-black dark:text-white',
|
||||||
|
lightDark: 'text-black dark:text-white',
|
||||||
contrast: 'dark:text-slate-100',
|
contrast: 'dark:text-slate-100',
|
||||||
success: 'text-emerald-600 dark:text-pavitra-blue',
|
success: 'text-emerald-600 dark:text-pavitra-blue',
|
||||||
danger: 'text-red-600 dark:text-red-500',
|
danger: 'text-red-600 dark:text-red-500',
|
||||||
@ -112,6 +115,9 @@ export const getButtonColor = (
|
|||||||
info: 'text-blue-600 dark:text-pavitra-blue',
|
info: 'text-blue-600 dark:text-pavitra-blue',
|
||||||
},
|
},
|
||||||
outlineHover: {
|
outlineHover: {
|
||||||
|
white: 'hover:bg-gray-100 hover:dark:bg-dark-800',
|
||||||
|
whiteDark: 'hover:bg-gray-100 hover:dark:bg-dark-800',
|
||||||
|
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
|
||||||
contrast:
|
contrast:
|
||||||
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
||||||
success:
|
success:
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { AccessLog } from '../../types/entities';
|
import type { AccessLog } from '../../types/entities';
|
||||||
@ -47,7 +44,7 @@ const CardAccess_logs = ({
|
|||||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||||
>
|
>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
access_logs.map((item, index) => (
|
access_logs.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import React from 'react';
|
|
||||||
import CardBox from '../CardBox';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import CardBox from '../CardBox';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { AccessLog } from '../../types/entities';
|
import type { AccessLog } from '../../types/entities';
|
||||||
@ -32,9 +29,6 @@ const ListAccess_logs = ({
|
|||||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS');
|
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS');
|
||||||
|
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
|
||||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { createTableComponent } from '../Factory/createTableComponent';
|
|
||||||
import {
|
import {
|
||||||
fetch,
|
|
||||||
update,
|
|
||||||
deleteItem,
|
deleteItem,
|
||||||
setRefetch,
|
|
||||||
deleteItemsByIds,
|
deleteItemsByIds,
|
||||||
|
fetch,
|
||||||
|
setRefetch,
|
||||||
|
update,
|
||||||
} from '../../stores/access_logs/access_logsSlice';
|
} from '../../stores/access_logs/access_logsSlice';
|
||||||
import { loadColumns } from './configureAccess_logsCols';
|
|
||||||
import type { AccessLog } from '../../types/entities';
|
import type { AccessLog } from '../../types/entities';
|
||||||
|
import { createTableComponent } from '../Factory/createTableComponent';
|
||||||
|
import { loadColumns } from './configureAccess_logsCols';
|
||||||
|
|
||||||
const TableAccess_logs = createTableComponent<AccessLog>({
|
const TableAccess_logs = createTableComponent<AccessLog>({
|
||||||
entityName: 'access_logs',
|
entityName: 'access_logs',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createColumnLoader,
|
|
||||||
ColumnMetadata,
|
ColumnMetadata,
|
||||||
|
createColumnLoader,
|
||||||
} from '../DataGrid/configBuilderFactory';
|
} from '../DataGrid/configBuilderFactory';
|
||||||
|
|
||||||
const ACCESS_LOGS_COLUMNS: ColumnMetadata[] = [
|
const ACCESS_LOGS_COLUMNS: ColumnMetadata[] = [
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import { MenuAsideItem } from '../types/menu';
|
import { MenuAsideItem } from '../types/menu';
|
||||||
import AsideMenuLayer from './AsideMenuLayer';
|
import AsideMenuLayer from './AsideMenuLayer';
|
||||||
import OverlayLayer from './OverlayLayer';
|
import OverlayLayer from './OverlayLayer';
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { mdiMinus, mdiPlus } from '@mdi/js';
|
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||||
import BaseIcon from './BaseIcon';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getButtonColor } from '../colors';
|
|
||||||
import AsideMenuList from './AsideMenuList';
|
|
||||||
import { MenuAsideItem } from '../types/menu';
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { getButtonColor } from '../colors';
|
||||||
|
import { useAppSelector } from '../stores/hooks';
|
||||||
|
import { MenuAsideItem } from '../types/menu';
|
||||||
|
import AsideMenuList from './AsideMenuList';
|
||||||
|
import BaseIcon from './BaseIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuAsideItem;
|
item: MenuAsideItem;
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
|
import { mdiClose } from '@mdi/js';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mdiLogout, mdiClose } from '@mdi/js';
|
|
||||||
import BaseIcon from './BaseIcon';
|
|
||||||
import AsideMenuList from './AsideMenuList';
|
|
||||||
import { MenuAsideItem } from '../types/menu';
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
import { useAppSelector } from '../stores/hooks';
|
||||||
import Link from 'next/link';
|
import { MenuAsideItem } from '../types/menu';
|
||||||
|
import AsideMenuList from './AsideMenuList';
|
||||||
|
import BaseIcon from './BaseIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
menu: MenuAsideItem[];
|
menu: MenuAsideItem[];
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import { hasPermission } from '../helpers/userPermissions';
|
||||||
|
import { useAppSelector } from '../stores/hooks';
|
||||||
import { MenuAsideItem } from '../types/menu';
|
import { MenuAsideItem } from '../types/menu';
|
||||||
import AsideMenuItem from './AsideMenuItem';
|
import AsideMenuItem from './AsideMenuItem';
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
import { hasPermission } from '../helpers/userPermissions';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
menu: MenuAsideItem[];
|
menu: MenuAsideItem[];
|
||||||
@ -22,7 +21,9 @@ export default function AsideMenuList({
|
|||||||
return (
|
return (
|
||||||
<ul className={className}>
|
<ul className={className}>
|
||||||
{menu.map((item, index) => {
|
{menu.map((item, index) => {
|
||||||
if (!hasPermission(currentUser, item.permissions)) return null;
|
if (item.permissions && !hasPermission(currentUser, item.permissions)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { AssetVariant } from '../../types/entities';
|
import type { AssetVariant } from '../../types/entities';
|
||||||
@ -50,7 +47,7 @@ const CardAsset_variants = ({
|
|||||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||||
>
|
>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
asset_variants.map((item, index) => (
|
asset_variants.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import React from 'react';
|
|
||||||
import CardBox from '../CardBox';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import CardBox from '../CardBox';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { AssetVariant } from '../../types/entities';
|
import type { AssetVariant } from '../../types/entities';
|
||||||
@ -35,9 +32,6 @@ const ListAsset_variants = ({
|
|||||||
'UPDATE_ASSET_VARIANTS',
|
'UPDATE_ASSET_VARIANTS',
|
||||||
);
|
);
|
||||||
|
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
|
||||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { createTableComponent } from '../Factory/createTableComponent';
|
|
||||||
import {
|
import {
|
||||||
fetch,
|
|
||||||
update,
|
|
||||||
deleteItem,
|
deleteItem,
|
||||||
setRefetch,
|
|
||||||
deleteItemsByIds,
|
deleteItemsByIds,
|
||||||
|
fetch,
|
||||||
|
setRefetch,
|
||||||
|
update,
|
||||||
} from '../../stores/asset_variants/asset_variantsSlice';
|
} from '../../stores/asset_variants/asset_variantsSlice';
|
||||||
import { loadColumns } from './configureAsset_variantsCols';
|
|
||||||
import type { AssetVariant } from '../../types/entities';
|
import type { AssetVariant } from '../../types/entities';
|
||||||
|
import { createTableComponent } from '../Factory/createTableComponent';
|
||||||
|
import { loadColumns } from './configureAsset_variantsCols';
|
||||||
|
|
||||||
const TableAsset_variants = createTableComponent<AssetVariant>({
|
const TableAsset_variants = createTableComponent<AssetVariant>({
|
||||||
entityName: 'asset_variants',
|
entityName: 'asset_variants',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createColumnLoader,
|
|
||||||
ColumnMetadata,
|
ColumnMetadata,
|
||||||
|
createColumnLoader,
|
||||||
} from '../DataGrid/configBuilderFactory';
|
} from '../DataGrid/configBuilderFactory';
|
||||||
|
|
||||||
const ASSET_VARIANTS_COLUMNS: ColumnMetadata[] = [
|
const ASSET_VARIANTS_COLUMNS: ColumnMetadata[] = [
|
||||||
|
|||||||
@ -92,7 +92,7 @@ const AssetSectionCard: React.FC<AssetSectionCardProps> = ({
|
|||||||
<input
|
<input
|
||||||
type='file'
|
type='file'
|
||||||
multiple
|
multiple
|
||||||
accept={section.accept}
|
accept={section.accept ?? undefined}
|
||||||
className='w-full border border-gray-300 rounded px-2 py-2 mb-3 bg-white dark:bg-dark-800'
|
className='w-full border border-gray-300 rounded px-2 py-2 mb-3 bg-white dark:bg-dark-800'
|
||||||
disabled={isUploading || disabled || !hasCreatePermission}
|
disabled={isUploading || disabled || !hasCreatePermission}
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { Asset } from '../../types/entities';
|
import type { Asset } from '../../types/entities';
|
||||||
@ -47,7 +44,7 @@ const CardAssets = ({
|
|||||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||||
>
|
>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
assets.map((item, index) => (
|
assets.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
className={`overflow-hidden ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
||||||
|
|||||||
@ -5,13 +5,13 @@
|
|||||||
* Validates embed URLs against a trusted domain allowlist on the backend.
|
* Validates embed URLs against a trusted domain allowlist on the backend.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { create, deleteItem } from '../../stores/assets/assetsSlice';
|
|
||||||
import { queryKeys } from '../../lib/queryClient';
|
import { queryKeys } from '../../lib/queryClient';
|
||||||
import CardBox from '../CardBox';
|
import { create, deleteItem } from '../../stores/assets/assetsSlice';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||||
import BaseButton from '../BaseButton';
|
import BaseButton from '../BaseButton';
|
||||||
|
import CardBox from '../CardBox';
|
||||||
import type { Asset } from './AssetSectionCard';
|
import type { Asset } from './AssetSectionCard';
|
||||||
|
|
||||||
interface EmbedAssetSectionProps {
|
interface EmbedAssetSectionProps {
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import React from 'react';
|
|
||||||
import CardBox from '../CardBox';
|
|
||||||
import ImageField from '../ImageField';
|
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
import { saveFile } from '../../helpers/fileSaver';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
import { Pagination } from '../Pagination';
|
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import { useAppSelector } from '../../stores/hooks';
|
||||||
|
import CardBox from '../CardBox';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
import { Pagination } from '../Pagination';
|
||||||
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import type { Asset } from '../../types/entities';
|
import type { Asset } from '../../types/entities';
|
||||||
@ -32,9 +29,6 @@ const ListAssets = ({
|
|||||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS');
|
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS');
|
||||||
|
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
|
||||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { createTableComponent } from '../Factory/createTableComponent';
|
|
||||||
import {
|
import {
|
||||||
fetch,
|
|
||||||
update,
|
|
||||||
deleteItem,
|
deleteItem,
|
||||||
setRefetch,
|
|
||||||
deleteItemsByIds,
|
deleteItemsByIds,
|
||||||
|
fetch,
|
||||||
|
setRefetch,
|
||||||
|
update,
|
||||||
} from '../../stores/assets/assetsSlice';
|
} from '../../stores/assets/assetsSlice';
|
||||||
import { loadColumns } from './configureAssetsCols';
|
|
||||||
import type { Asset } from '../../types/entities';
|
import type { Asset } from '../../types/entities';
|
||||||
|
import { createTableComponent } from '../Factory/createTableComponent';
|
||||||
|
import { loadColumns } from './configureAssetsCols';
|
||||||
|
|
||||||
const TableAssets = createTableComponent<Asset>({
|
const TableAssets = createTableComponent<Asset>({
|
||||||
entityName: 'assets',
|
entityName: 'assets',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createColumnLoader,
|
|
||||||
ColumnMetadata,
|
ColumnMetadata,
|
||||||
|
createColumnLoader,
|
||||||
} from '../DataGrid/configBuilderFactory';
|
} from '../DataGrid/configBuilderFactory';
|
||||||
|
|
||||||
const ASSETS_COLUMNS: ColumnMetadata[] = [
|
const ASSETS_COLUMNS: ColumnMetadata[] = [
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { logger } from '../../lib/logger';
|
||||||
|
import {
|
||||||
|
isAudioMimeType,
|
||||||
|
isVideoMimeType,
|
||||||
|
probeMediaDuration,
|
||||||
|
} from '../../lib/mediaDuration';
|
||||||
import FileUploader from '../Uploaders/UploadService';
|
import FileUploader from '../Uploaders/UploadService';
|
||||||
import type { AssetSection } from './AssetSectionCard';
|
import type { AssetSection } from './AssetSectionCard';
|
||||||
import type { UploadQueueItem } from './UploadProgressList';
|
import type { UploadQueueItem } from './UploadProgressList';
|
||||||
import { logger } from '../../lib/logger';
|
|
||||||
import {
|
|
||||||
probeMediaDuration,
|
|
||||||
isVideoMimeType,
|
|
||||||
isAudioMimeType,
|
|
||||||
} from '../../lib/mediaDuration';
|
|
||||||
|
|
||||||
interface UseAssetUploaderOptions {
|
interface UseAssetUploaderOptions {
|
||||||
selectedProjectId: string;
|
selectedProjectId: string;
|
||||||
|
|||||||
@ -11,11 +11,11 @@
|
|||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
interface BackdropItem {
|
interface BackdropItem {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import React from 'react';
|
||||||
import { getButtonColor } from '../colors';
|
import { getButtonColor } from '../colors';
|
||||||
import BaseIcon from './BaseIcon';
|
|
||||||
import type { ColorButtonKey } from '../types/ui';
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
import { useAppSelector } from '../stores/hooks';
|
||||||
|
import type { ColorButtonKey } from '../types/ui';
|
||||||
|
import BaseIcon from './BaseIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Children, cloneElement, ReactElement } from 'react';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import { Children, cloneElement, isValidElement } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type?: string;
|
type?: string;
|
||||||
@ -24,13 +24,15 @@ const BaseButtons = ({
|
|||||||
noWrap ? 'flex-nowrap' : 'flex-wrap'
|
noWrap ? 'flex-nowrap' : 'flex-wrap'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{Children.map(children, (child: ReactElement) =>
|
{Children.map(children, (child) => {
|
||||||
child
|
if (!isValidElement<{ className?: string }>(child)) {
|
||||||
? cloneElement(child as ReactElement<{ className?: string }>, {
|
return child;
|
||||||
className: `${classAddon} ${(child.props as { className?: string }).className || ''}`,
|
}
|
||||||
})
|
|
||||||
: null,
|
return cloneElement(child, {
|
||||||
)}
|
className: `${classAddon} ${child.props.className || ''}`,
|
||||||
|
});
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
import { useAppSelector } from '../stores/hooks';
|
||||||
type Props = {
|
type Props = {
|
||||||
navBar?: boolean;
|
navBar?: boolean;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
* Includes delay to avoid flashing for quick operations.
|
* Includes delay to avoid flashing for quick operations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { PRELOAD_CONFIG } from '../config/preload.config';
|
import { PRELOAD_CONFIG } from '../config/preload.config';
|
||||||
|
|
||||||
interface CanvasLoadingSpinnerProps {
|
interface CanvasLoadingSpinnerProps {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
import { useAppSelector } from '../stores/hooks';
|
||||||
import CardBoxComponentBody from './CardBoxComponentBody';
|
import CardBoxComponentBody from './CardBoxComponentBody';
|
||||||
import CardBoxComponentFooter from './CardBoxComponentFooter';
|
import CardBoxComponentFooter from './CardBoxComponentFooter';
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
rounded?: string;
|
rounded?: string;
|
||||||
@ -20,7 +20,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function CardBox({
|
export default function CardBox({
|
||||||
rounded = 'rounded',
|
|
||||||
flex = 'flex-col',
|
flex = 'flex-col',
|
||||||
className = '',
|
className = '',
|
||||||
hasComponentLayout = false,
|
hasComponentLayout = false,
|
||||||
@ -28,7 +27,6 @@ export default function CardBox({
|
|||||||
hasTable = false,
|
hasTable = false,
|
||||||
isHoverable = false,
|
isHoverable = false,
|
||||||
isList = false,
|
isList = false,
|
||||||
isModal = false,
|
|
||||||
children,
|
children,
|
||||||
footer,
|
footer,
|
||||||
id = '',
|
id = '',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
noPadding?: boolean;
|
noPadding?: boolean;
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const CardBoxComponentEmpty = () => {
|
const CardBoxComponentEmpty = () => {
|
||||||
return (
|
return (
|
||||||
<div className='text-center py-24 text-gray-500 dark:text-slate-400'>
|
<div className='text-center py-24 text-gray-500 dark:text-slate-400'>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mdiClose } from '@mdi/js';
|
import { mdiClose } from '@mdi/js';
|
||||||
import { ReactNode } from 'react';
|
import type { MouseEvent, ReactNode } from 'react';
|
||||||
import type { ColorButtonKey } from '../types/ui';
|
import type { ColorButtonKey } from '../types/ui';
|
||||||
import BaseButton from './BaseButton';
|
import BaseButton from './BaseButton';
|
||||||
import BaseButtons from './BaseButtons';
|
import BaseButtons from './BaseButtons';
|
||||||
@ -32,12 +32,20 @@ const CardBoxModal = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleConfirm = (_event: MouseEvent) => {
|
||||||
|
onConfirm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (_event: MouseEvent) => {
|
||||||
|
onCancel?.();
|
||||||
|
};
|
||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
label={buttonLabel}
|
label={buttonLabel}
|
||||||
color={buttonColor}
|
color={buttonColor}
|
||||||
onClick={onConfirm}
|
onClick={handleConfirm}
|
||||||
disabled={isConfirmDisabled}
|
disabled={isConfirmDisabled}
|
||||||
/>
|
/>
|
||||||
{!!onCancel && (
|
{!!onCancel && (
|
||||||
@ -45,7 +53,7 @@ const CardBoxModal = ({
|
|||||||
label='Cancel'
|
label='Cancel'
|
||||||
color={buttonColor}
|
color={buttonColor}
|
||||||
outline
|
outline
|
||||||
onClick={onCancel}
|
onClick={handleCancel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
@ -53,7 +61,7 @@ const CardBoxModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayLayer
|
<OverlayLayer
|
||||||
onClick={onCancel}
|
onClick={handleCancel}
|
||||||
className={onCancel ? 'cursor-pointer' : ''}
|
className={onCancel ? 'cursor-pointer' : ''}
|
||||||
>
|
>
|
||||||
<CardBox
|
<CardBox
|
||||||
@ -66,7 +74,7 @@ const CardBoxModal = ({
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
icon={mdiClose}
|
icon={mdiClose}
|
||||||
color='whiteDark'
|
color='whiteDark'
|
||||||
onClick={onCancel}
|
onClick={handleCancel}
|
||||||
small
|
small
|
||||||
roundedFull
|
roundedFull
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import React, {
|
import {
|
||||||
|
MutableRefObject,
|
||||||
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
ReactNode,
|
|
||||||
MutableRefObject,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
interface ClickOutsideProps {
|
interface ClickOutsideProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
onClickOutside: () => void;
|
onClickOutside: () => void;
|
||||||
excludedElements: MutableRefObject<any>[];
|
excludedElements: MutableRefObject<HTMLElement | null>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClickOutside = ({
|
const ClickOutside = ({
|
||||||
@ -17,19 +17,21 @@ const ClickOutside = ({
|
|||||||
onClickOutside,
|
onClickOutside,
|
||||||
excludedElements,
|
excludedElements,
|
||||||
}: ClickOutsideProps) => {
|
}: ClickOutsideProps) => {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const handleClickOutside = useCallback(
|
const handleClickOutside = useCallback(
|
||||||
(event) => {
|
(event: MouseEvent) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (!(target instanceof Node)) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
wrapperRef.current &&
|
wrapperRef.current &&
|
||||||
!wrapperRef.current.contains(event.target) &&
|
!wrapperRef.current.contains(target) &&
|
||||||
!excludedElements.some((el) => el.current.contains(event.target))
|
!excludedElements.some((el) => el.current?.contains(target))
|
||||||
) {
|
) {
|
||||||
onClickOutside();
|
onClickOutside();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[wrapperRef, onClickOutside, ...excludedElements],
|
[excludedElements, onClickOutside],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { AssetOption } from './types';
|
|
||||||
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
||||||
|
import type { AssetOption } from './types';
|
||||||
|
|
||||||
interface AssetSelectCompactProps {
|
interface AssetSelectCompactProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
||||||
import type {
|
import type {
|
||||||
AssetOption,
|
AssetOption,
|
||||||
VideoPlaybackSettings,
|
|
||||||
AudioPlaybackSettings,
|
AudioPlaybackSettings,
|
||||||
|
VideoPlaybackSettings,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
|
||||||
|
|
||||||
interface BackgroundSettingsEditorProps {
|
interface BackgroundSettingsEditorProps {
|
||||||
type: 'image' | 'video' | 'embed' | 'audio';
|
type: 'image' | 'video' | 'embed' | 'audio';
|
||||||
|
|||||||
@ -6,21 +6,20 @@
|
|||||||
* Supports custom video playback settings (autoplay, loop, muted, start/end time).
|
* Supports custom video playback settings (autoplay, loop, muted, start/end time).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useBackgroundVideoPlayback } from '../../hooks/useBackgroundVideoPlayback';
|
|
||||||
import { useBackgroundAudioPlayback } from '../../hooks/useBackgroundAudioPlayback';
|
|
||||||
import PreviousBackgroundOverlay from '../PreviousBackgroundOverlay';
|
|
||||||
import { baseURLApi } from '../../config';
|
import { baseURLApi } from '../../config';
|
||||||
|
import { useBackgroundAudioPlayback } from '../../hooks/useBackgroundAudioPlayback';
|
||||||
|
import { useBackgroundVideoPlayback } from '../../hooks/useBackgroundVideoPlayback';
|
||||||
import { buildChromeFreeEmbedUrl } from '../../lib/embedUrl';
|
import { buildChromeFreeEmbedUrl } from '../../lib/embedUrl';
|
||||||
import CanvasBackgroundAudioLayer from './CanvasBackgroundAudioLayer';
|
import PreviousBackgroundOverlay from '../PreviousBackgroundOverlay';
|
||||||
import CanvasBackgroundEmbedLayer from './CanvasBackgroundEmbedLayer';
|
|
||||||
import {
|
import {
|
||||||
getActiveCanvasVideoUrl,
|
getActiveCanvasVideoUrl,
|
||||||
getCanvasVideoSrc,
|
getCanvasVideoSrc,
|
||||||
isBlobUrl,
|
|
||||||
scheduleAfterPaint,
|
scheduleAfterPaint,
|
||||||
shouldUseNativeVideoLoop,
|
shouldUseNativeVideoLoop,
|
||||||
} from './CanvasBackground.helpers';
|
} from './CanvasBackground.helpers';
|
||||||
|
import CanvasBackgroundAudioLayer from './CanvasBackgroundAudioLayer';
|
||||||
|
import CanvasBackgroundEmbedLayer from './CanvasBackgroundEmbedLayer';
|
||||||
import CanvasBackgroundImageLayer from './CanvasBackgroundImageLayer';
|
import CanvasBackgroundImageLayer from './CanvasBackgroundImageLayer';
|
||||||
import CanvasBackgroundVideoLayer from './CanvasBackgroundVideoLayer';
|
import CanvasBackgroundVideoLayer from './CanvasBackgroundVideoLayer';
|
||||||
import { useCanvasBackgroundImageReady } from './useCanvasBackgroundImageReady';
|
import { useCanvasBackgroundImageReady } from './useCanvasBackgroundImageReady';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
|
||||||
import NextImage from 'next/image';
|
import NextImage from 'next/image';
|
||||||
|
import React from 'react';
|
||||||
import { isBlobUrl } from './CanvasBackground.helpers';
|
import { isBlobUrl } from './CanvasBackground.helpers';
|
||||||
|
|
||||||
interface CanvasBackgroundImageLayerProps {
|
interface CanvasBackgroundImageLayerProps {
|
||||||
|
|||||||
@ -8,20 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import UiElementRenderer from '../UiElements/UiElementRenderer';
|
|
||||||
import { useElementEffects } from '../../hooks/useElementEffects';
|
|
||||||
import { useAudioEffects } from '../../hooks/useAudioEffects';
|
import { useAudioEffects } from '../../hooks/useAudioEffects';
|
||||||
|
import { useElementEffects } from '../../hooks/useElementEffects';
|
||||||
|
import type { PreloadCacheProvider } from '../../hooks/video';
|
||||||
import {
|
import {
|
||||||
buildTransitionStyle,
|
|
||||||
buildAppearAnimationStyle,
|
buildAppearAnimationStyle,
|
||||||
|
buildTransitionStyle,
|
||||||
hasAnyEffects,
|
hasAnyEffects,
|
||||||
type ElementEffectProperties,
|
type ElementEffectProperties,
|
||||||
} from '../../lib/elementEffects';
|
} from '../../lib/elementEffects';
|
||||||
|
import { normalizeZIndexValue } from '../../lib/elementStyles';
|
||||||
|
import { isInfoPanelElementType } from '../../lib/elementTypeGuards';
|
||||||
import type { CanvasElement as CanvasElementType } from '../../types/constructor';
|
import type { CanvasElement as CanvasElementType } from '../../types/constructor';
|
||||||
import type { ResolvedTransitionSettings } from '../../types/transition';
|
import type { ResolvedTransitionSettings } from '../../types/transition';
|
||||||
import type { PreloadCacheProvider } from '../../hooks/video';
|
import UiElementRenderer from '../UiElements/UiElementRenderer';
|
||||||
import { isInfoPanelElementType } from '../../lib/elementTypeGuards';
|
|
||||||
import { normalizeZIndexValue } from '../../lib/elementStyles';
|
|
||||||
|
|
||||||
interface CanvasElementProps {
|
interface CanvasElementProps {
|
||||||
element: CanvasElementType;
|
element: CanvasElementType;
|
||||||
@ -180,6 +180,7 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
|
|
||||||
// Check if we need the inner wrapper for effects
|
// Check if we need the inner wrapper for effects
|
||||||
const needsEffectWrapper = !isEditMode && hasAnyEffects(effectProperties);
|
const needsEffectWrapper = !isEditMode && hasAnyEffects(effectProperties);
|
||||||
|
const { onMouseDown: eventMouseDown, ...outerEventHandlers } = eventHandlers;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -188,11 +189,17 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
data-constructor-element-id={element.id}
|
data-constructor-element-id={element.id}
|
||||||
className='absolute cursor-pointer'
|
className='absolute cursor-pointer'
|
||||||
style={positionStyle}
|
style={positionStyle}
|
||||||
onMouseDown={isEditMode ? onMouseDown : undefined}
|
onMouseDown={
|
||||||
|
isEditMode
|
||||||
|
? onMouseDown
|
||||||
|
: !needsEffectWrapper
|
||||||
|
? eventMouseDown
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
aria-disabled={isDisabled}
|
aria-disabled={isDisabled}
|
||||||
{...(!isEditMode && !needsEffectWrapper ? eventHandlers : {})}
|
{...(!isEditMode && !needsEffectWrapper ? outerEventHandlers : {})}
|
||||||
>
|
>
|
||||||
{needsEffectWrapper ? (
|
{needsEffectWrapper ? (
|
||||||
// Inner wrapper handles hover/focus/active effects independently from animation
|
// Inner wrapper handles hover/focus/active effects independently from animation
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { mdiPlus } from '@mdi/js';
|
import { mdiPlus } from '@mdi/js';
|
||||||
import type { CSSProperties, MouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent } from 'react';
|
||||||
import BaseButton from '../BaseButton';
|
import type { PreloadCacheProvider } from '../../hooks/video';
|
||||||
import CanvasElementComponent from './CanvasElement';
|
import { isElementFlagEnabled } from '../../lib/elementFlags';
|
||||||
|
import {
|
||||||
|
isInfoPanelElementType,
|
||||||
|
isNavigationElementType,
|
||||||
|
} from '../../lib/elementTypeGuards';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { ResolvedTransitionSettings } from '../../types/transition';
|
import type { ResolvedTransitionSettings } from '../../types/transition';
|
||||||
import type { PreloadCacheProvider } from '../../hooks/video';
|
import BaseButton from '../BaseButton';
|
||||||
import {
|
import CanvasElementComponent from './CanvasElement';
|
||||||
isNavigationElementType,
|
|
||||||
isInfoPanelElementType,
|
|
||||||
} from '../../lib/elementTypeGuards';
|
|
||||||
import { isElementFlagEnabled } from '../../lib/elementFlags';
|
|
||||||
import { shouldRenderConstructorCanvasElement } from './constructorPage.helpers';
|
import { shouldRenderConstructorCanvasElement } from './constructorPage.helpers';
|
||||||
|
|
||||||
type ConstructorCanvasElementsLayerProps = {
|
type ConstructorCanvasElementsLayerProps = {
|
||||||
@ -22,7 +22,7 @@ type ConstructorCanvasElementsLayerProps = {
|
|||||||
letterboxStyles?: CSSProperties;
|
letterboxStyles?: CSSProperties;
|
||||||
pageTransitionSettings: ResolvedTransitionSettings;
|
pageTransitionSettings: ResolvedTransitionSettings;
|
||||||
preloadCache: PreloadCacheProvider;
|
preloadCache: PreloadCacheProvider;
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
isElementVisible: (element: CanvasElement) => boolean;
|
isElementVisible: (element: CanvasElement) => boolean;
|
||||||
isInfoPanelOpen: (elementId: string) => boolean;
|
isInfoPanelOpen: (elementId: string) => boolean;
|
||||||
onCreateFirstPage: () => void;
|
onCreateFirstPage: () => void;
|
||||||
@ -31,7 +31,7 @@ type ConstructorCanvasElementsLayerProps = {
|
|||||||
onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void;
|
onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void;
|
||||||
onCarouselButtonPositionChange: (
|
onCarouselButtonPositionChange: (
|
||||||
elementId: string,
|
elementId: string,
|
||||||
button: 'prev' | 'next' | 'back',
|
button: 'prev' | 'next',
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { TransitionPreviewState } from '../../types/presentation';
|
||||||
import {
|
import {
|
||||||
shouldShowConstructorCanvasElements,
|
shouldShowConstructorCanvasElements,
|
||||||
shouldShowConstructorCanvasSpinner,
|
shouldShowConstructorCanvasSpinner,
|
||||||
} from './ConstructorCanvasStage.helpers';
|
} from './ConstructorCanvasStage.helpers';
|
||||||
import type { TransitionPreviewState } from '../../types/presentation';
|
|
||||||
|
|
||||||
const transitionPreview = {
|
const transitionPreview = {
|
||||||
videoUrl: 'assets/transition.mp4',
|
videoUrl: 'assets/transition.mp4',
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
import type { CSSProperties, MouseEvent, PointerEvent, RefObject } from 'react';
|
import type { CSSProperties, MouseEvent, PointerEvent, RefObject } from 'react';
|
||||||
|
|
||||||
import { BackdropPortalProvider } from '../BackdropPortal';
|
|
||||||
import CanvasLoadingSpinner from '../CanvasLoadingSpinner';
|
|
||||||
import RuntimeControls from '../Runtime/RuntimeControls';
|
|
||||||
import CanvasBackground from './CanvasBackground';
|
|
||||||
import ConstructorCanvasElementsLayer from './ConstructorCanvasElementsLayer';
|
|
||||||
import ElementEditorPanel from './ElementEditorPanel';
|
|
||||||
import TransitionBlackOverlay from '../TransitionBlackOverlay';
|
|
||||||
import { isSafari } from '../../lib/browserUtils';
|
|
||||||
import type { PreloadCacheProvider } from '../../hooks/video';
|
import type { PreloadCacheProvider } from '../../hooks/video';
|
||||||
|
import { isSafari } from '../../lib/browserUtils';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
import type { TransitionPreviewState } from '../../types/presentation';
|
import type { TransitionPreviewState } from '../../types/presentation';
|
||||||
@ -17,10 +10,17 @@ import type {
|
|||||||
ResolvedUiControlsSettings,
|
ResolvedUiControlsSettings,
|
||||||
SystemUiControlType,
|
SystemUiControlType,
|
||||||
} from '../../types/uiControls';
|
} from '../../types/uiControls';
|
||||||
|
import { BackdropPortalProvider } from '../BackdropPortal';
|
||||||
|
import CanvasLoadingSpinner from '../CanvasLoadingSpinner';
|
||||||
|
import RuntimeControls from '../Runtime/RuntimeControls';
|
||||||
|
import TransitionBlackOverlay from '../TransitionBlackOverlay';
|
||||||
|
import CanvasBackground from './CanvasBackground';
|
||||||
|
import ConstructorCanvasElementsLayer from './ConstructorCanvasElementsLayer';
|
||||||
import {
|
import {
|
||||||
shouldShowConstructorCanvasElements,
|
shouldShowConstructorCanvasElements,
|
||||||
shouldShowConstructorCanvasSpinner,
|
shouldShowConstructorCanvasSpinner,
|
||||||
} from './ConstructorCanvasStage.helpers';
|
} from './ConstructorCanvasStage.helpers';
|
||||||
|
import ElementEditorPanel from './ElementEditorPanel';
|
||||||
|
|
||||||
type Position = {
|
type Position = {
|
||||||
x: number;
|
x: number;
|
||||||
@ -71,11 +71,11 @@ type ConstructorCanvasStageProps = {
|
|||||||
videoAutoplay: boolean;
|
videoAutoplay: boolean;
|
||||||
videoLoop: boolean;
|
videoLoop: boolean;
|
||||||
videoMuted: boolean;
|
videoMuted: boolean;
|
||||||
videoStartTime?: number;
|
videoStartTime?: number | null;
|
||||||
videoEndTime?: number;
|
videoEndTime?: number | null;
|
||||||
audioLoop: boolean;
|
audioLoop: boolean;
|
||||||
audioStartTime?: number;
|
audioStartTime?: number | null;
|
||||||
audioEndTime?: number;
|
audioEndTime?: number | null;
|
||||||
};
|
};
|
||||||
soundControl: {
|
soundControl: {
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
@ -110,7 +110,7 @@ type ConstructorCanvasStageProps = {
|
|||||||
onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void;
|
onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void;
|
||||||
onCarouselButtonPositionChange: (
|
onCarouselButtonPositionChange: (
|
||||||
elementId: string,
|
elementId: string,
|
||||||
button: 'prev' | 'next' | 'back',
|
button: 'prev' | 'next',
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import CardBoxModal from '../CardBoxModal';
|
import CardBoxModal from '../CardBoxModal';
|
||||||
import CreatePageModal from './CreatePageModal';
|
|
||||||
import { getConstructorDeletePageName } from './ConstructorPageModals.helpers';
|
import { getConstructorDeletePageName } from './ConstructorPageModals.helpers';
|
||||||
|
import CreatePageModal from './CreatePageModal';
|
||||||
|
|
||||||
type ConstructorPageModalsProps = {
|
type ConstructorPageModalsProps = {
|
||||||
isCreatePageModalActive: boolean;
|
isCreatePageModalActive: boolean;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { InfoPanelImage } from '../../types/infoPanel';
|
||||||
import {
|
import {
|
||||||
getConstructorInfoPanelDetailImage,
|
getConstructorInfoPanelDetailImage,
|
||||||
shouldRenderConstructorImageDetailPanel,
|
shouldRenderConstructorImageDetailPanel,
|
||||||
} from './ConstructorRuntimeOverlays.helpers';
|
} from './ConstructorRuntimeOverlays.helpers';
|
||||||
import type { InfoPanelImage } from '../../types/infoPanel';
|
|
||||||
|
|
||||||
const image = { id: 'image-1', url: 'assets/image.jpg' } as InfoPanelImage;
|
const image = { id: 'image-1', url: 'assets/image.jpg' } as InfoPanelImage;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const getConstructorInfoPanelDetailImage = ({
|
|||||||
activeDetailImages,
|
activeDetailImages,
|
||||||
panelId,
|
panelId,
|
||||||
}: {
|
}: {
|
||||||
activeDetailImages: Record<string, InfoPanelImage | undefined>;
|
activeDetailImages: Record<string, InfoPanelImage | null | undefined>;
|
||||||
panelId: string;
|
panelId: string;
|
||||||
}) => activeDetailImages[panelId];
|
}) => activeDetailImages[panelId];
|
||||||
|
|
||||||
@ -12,6 +12,6 @@ export const shouldRenderConstructorImageDetailPanel = ({
|
|||||||
image,
|
image,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
}: {
|
}: {
|
||||||
image?: InfoPanelImage;
|
image?: InfoPanelImage | null;
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
}) => Boolean(image) || isEditMode;
|
}) => Boolean(image) || isEditMode;
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { Fragment, type CSSProperties } from 'react';
|
import { Fragment, type CSSProperties } from 'react';
|
||||||
|
|
||||||
import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay';
|
|
||||||
import ImageDetailPanel from '../UiElements/ImageDetailPanel';
|
|
||||||
import InfoPanelOverlay from '../UiElements/InfoPanelOverlay';
|
|
||||||
import type {
|
import type {
|
||||||
CanvasElement,
|
CanvasElement,
|
||||||
GalleryCarouselMediaItem,
|
GalleryCarouselMediaItem,
|
||||||
} from '../../types/constructor';
|
} from '../../types/constructor';
|
||||||
import type { InfoPanelImage } from '../../types/infoPanel';
|
import type { InfoPanelImage } from '../../types/infoPanel';
|
||||||
import type { ResolvedTransitionSettings } from '../../types/transition';
|
import type { ResolvedTransitionSettings } from '../../types/transition';
|
||||||
|
import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay';
|
||||||
|
import ImageDetailPanel from '../UiElements/ImageDetailPanel';
|
||||||
|
import InfoPanelOverlay from '../UiElements/InfoPanelOverlay';
|
||||||
import {
|
import {
|
||||||
getConstructorInfoPanelDetailImage,
|
getConstructorInfoPanelDetailImage,
|
||||||
shouldRenderConstructorImageDetailPanel,
|
shouldRenderConstructorImageDetailPanel,
|
||||||
@ -32,7 +32,7 @@ type ConstructorRuntimeOverlaysProps = {
|
|||||||
activeInfoPanelGalleryElement: CanvasElement | null;
|
activeInfoPanelGalleryElement: CanvasElement | null;
|
||||||
shouldShowInfoPanelOverlays: boolean;
|
shouldShowInfoPanelOverlays: boolean;
|
||||||
infoPanelElementsToRender: CanvasElement[];
|
infoPanelElementsToRender: CanvasElement[];
|
||||||
activeDetailImages: Record<string, InfoPanelImage | undefined>;
|
activeDetailImages: Record<string, InfoPanelImage | null | undefined>;
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
letterboxStyles: CSSProperties;
|
letterboxStyles: CSSProperties;
|
||||||
cssVars: CSSProperties;
|
cssVars: CSSProperties;
|
||||||
@ -42,7 +42,7 @@ type ConstructorRuntimeOverlaysProps = {
|
|||||||
onCloseInfoPanelGallery: () => void;
|
onCloseInfoPanelGallery: () => void;
|
||||||
onCloseAllInfoPanels: () => void;
|
onCloseAllInfoPanels: () => void;
|
||||||
onCloseInfoPanel: (panelId: string) => void;
|
onCloseInfoPanel: (panelId: string) => void;
|
||||||
onSetDetailImage: (panelId: string, image: InfoPanelImage) => void;
|
onSetDetailImage: (panelId: string, image: InfoPanelImage | null) => void;
|
||||||
onCloseDetailImage: (panelId: string) => void;
|
onCloseDetailImage: (panelId: string) => void;
|
||||||
onOpenInfoPanelGallery: (
|
onOpenInfoPanelGallery: (
|
||||||
panelId: string,
|
panelId: string,
|
||||||
@ -220,7 +220,7 @@ const ConstructorRuntimeOverlays = ({
|
|||||||
}) && (
|
}) && (
|
||||||
<ImageDetailPanel
|
<ImageDetailPanel
|
||||||
element={infoPanelElementToRender}
|
element={infoPanelElementToRender}
|
||||||
image={panelDetailImage}
|
image={panelDetailImage ?? null}
|
||||||
onClose={() => onCloseDetailImage(infoPanelElementToRender.id)}
|
onClose={() => onCloseDetailImage(infoPanelElementToRender.id)}
|
||||||
resolveUrl={resolveUrl}
|
resolveUrl={resolveUrl}
|
||||||
letterboxStyles={letterboxStyles}
|
letterboxStyles={letterboxStyles}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import type { TourPage } from './types';
|
|
||||||
import {
|
import {
|
||||||
getCollapsedToolbarPageName,
|
getCollapsedToolbarPageName,
|
||||||
getConstructorToolbarMaxWidth,
|
|
||||||
getConstructorToolbarActionState,
|
getConstructorToolbarActionState,
|
||||||
|
getConstructorToolbarMaxWidth,
|
||||||
sortToolbarPages,
|
sortToolbarPages,
|
||||||
} from './ConstructorToolbar.helpers';
|
} from './ConstructorToolbar.helpers';
|
||||||
|
import type { TourPage } from './types';
|
||||||
|
|
||||||
const makePage = (id: string, name: string, sortOrder?: number): TourPage =>
|
const makePage = (id: string, name: string, sortOrder?: number): TourPage =>
|
||||||
({
|
({
|
||||||
|
|||||||
@ -5,19 +5,19 @@
|
|||||||
* Glassmorphism styling with draggable positioning.
|
* Glassmorphism styling with draggable positioning.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef, useEffect, forwardRef } from 'react';
|
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
|
import { forwardRef, useEffect, useRef, useState } from 'react';
|
||||||
import BaseIcon from '../BaseIcon';
|
import BaseIcon from '../BaseIcon';
|
||||||
import InteractionModeToggle from './InteractionModeToggle';
|
|
||||||
import ConstructorToolbarCollapsed from './ConstructorToolbarCollapsed';
|
|
||||||
import ConstructorToolbarElementActions from './ConstructorToolbarElementActions';
|
|
||||||
import ConstructorToolbarPageActions from './ConstructorToolbarPageActions';
|
|
||||||
import ConstructorToolbarSaveControls from './ConstructorToolbarSaveControls';
|
|
||||||
import {
|
import {
|
||||||
getConstructorToolbarActionState,
|
getConstructorToolbarActionState,
|
||||||
getConstructorToolbarMaxWidth,
|
getConstructorToolbarMaxWidth,
|
||||||
type ToolbarDropdown,
|
type ToolbarDropdown,
|
||||||
} from './ConstructorToolbar.helpers';
|
} from './ConstructorToolbar.helpers';
|
||||||
|
import ConstructorToolbarCollapsed from './ConstructorToolbarCollapsed';
|
||||||
|
import ConstructorToolbarElementActions from './ConstructorToolbarElementActions';
|
||||||
|
import ConstructorToolbarPageActions from './ConstructorToolbarPageActions';
|
||||||
|
import ConstructorToolbarSaveControls from './ConstructorToolbarSaveControls';
|
||||||
|
import InteractionModeToggle from './InteractionModeToggle';
|
||||||
import type { ConstructorToolbarProps } from './types';
|
import type { ConstructorToolbarProps } from './types';
|
||||||
|
|
||||||
const ConstructorToolbar = forwardRef<HTMLDivElement, ConstructorToolbarProps>(
|
const ConstructorToolbar = forwardRef<HTMLDivElement, ConstructorToolbarProps>(
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import { mdiChevronRight, mdiDotsVertical } from '@mdi/js';
|
||||||
import type { ForwardedRef, MouseEvent } from 'react';
|
import type { ForwardedRef, MouseEvent } from 'react';
|
||||||
import BaseIcon from '../BaseIcon';
|
import BaseIcon from '../BaseIcon';
|
||||||
import { mdiChevronRight, mdiDotsVertical } from '@mdi/js';
|
|
||||||
import type { Position, TourPage } from './types';
|
|
||||||
import { getCollapsedToolbarPageName } from './ConstructorToolbar.helpers';
|
import { getCollapsedToolbarPageName } from './ConstructorToolbar.helpers';
|
||||||
|
import type { Position, TourPage } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position: Position;
|
position: Position;
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { RefObject } from 'react';
|
|
||||||
import {
|
import {
|
||||||
mdiChevronDown,
|
mdiChevronDown,
|
||||||
mdiContentDuplicate,
|
mdiContentDuplicate,
|
||||||
@ -12,10 +11,11 @@ import {
|
|||||||
mdiVideo,
|
mdiVideo,
|
||||||
mdiViewCarousel,
|
mdiViewCarousel,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
import type { CanvasElementType } from '../../types/constructor';
|
||||||
import BaseIcon from '../BaseIcon';
|
import BaseIcon from '../BaseIcon';
|
||||||
import ClickOutside from '../ClickOutside';
|
import ClickOutside from '../ClickOutside';
|
||||||
import MenuActionButton from './MenuActionButton';
|
import MenuActionButton from './MenuActionButton';
|
||||||
import type { CanvasElementType } from '../../types/constructor';
|
|
||||||
import type { NavigationElementType } from './types';
|
import type { NavigationElementType } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
import {
|
import {
|
||||||
findConstructorToolbarPage,
|
findConstructorToolbarPage,
|
||||||
getConstructorExitHref,
|
getConstructorExitHref,
|
||||||
shouldShowConstructorToolbar,
|
shouldShowConstructorToolbar,
|
||||||
} from './ConstructorToolbarLayer.helpers';
|
} from './ConstructorToolbarLayer.helpers';
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
|
|
||||||
test('shouldShowConstructorToolbar requires pages and normal constructor mode', () => {
|
test('shouldShowConstructorToolbar requires pages and normal constructor mode', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
|
|
||||||
|
import type { NavigationElementType } from '../../context/ConstructorContext';
|
||||||
|
import type {
|
||||||
|
CanvasElementType,
|
||||||
|
EditorMenuItem,
|
||||||
|
} from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
import ConstructorToolbar from './ConstructorToolbar';
|
import ConstructorToolbar from './ConstructorToolbar';
|
||||||
import {
|
import {
|
||||||
findConstructorToolbarPage,
|
findConstructorToolbarPage,
|
||||||
getConstructorExitHref,
|
getConstructorExitHref,
|
||||||
shouldShowConstructorToolbar,
|
shouldShowConstructorToolbar,
|
||||||
} from './ConstructorToolbarLayer.helpers';
|
} from './ConstructorToolbarLayer.helpers';
|
||||||
import type {
|
|
||||||
CanvasElementType,
|
|
||||||
EditorMenuItem,
|
|
||||||
} from '../../types/constructor';
|
|
||||||
import type { NavigationElementType } from '../../context/ConstructorContext';
|
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
import type { ConstructorInteractionMode, Position } from './types';
|
import type { ConstructorInteractionMode, Position } from './types';
|
||||||
|
|
||||||
type ConstructorToolbarLayerProps = {
|
type ConstructorToolbarLayerProps = {
|
||||||
@ -19,7 +19,7 @@ type ConstructorToolbarLayerProps = {
|
|||||||
position: Position;
|
position: Position;
|
||||||
isElementEditMode: boolean;
|
isElementEditMode: boolean;
|
||||||
pages: TourPage[];
|
pages: TourPage[];
|
||||||
activePageId: string;
|
activePageId: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
isReorderingPages: boolean;
|
isReorderingPages: boolean;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
@ -100,7 +100,7 @@ const ConstructorToolbarLayer = ({
|
|||||||
position={position}
|
position={position}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
pages={pages}
|
pages={pages}
|
||||||
activePageId={activePageId}
|
activePageId={activePageId || ''}
|
||||||
onPageChange={(pageId) => {
|
onPageChange={(pageId) => {
|
||||||
const page = findConstructorToolbarPage(pages, pageId);
|
const page = findConstructorToolbarPage(pages, pageId);
|
||||||
if (page) onSwitchToPage(page);
|
if (page) onSwitchToPage(page);
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { RefObject } from 'react';
|
|
||||||
import {
|
import {
|
||||||
mdiChevronDown,
|
mdiChevronDown,
|
||||||
mdiChevronUp,
|
mdiChevronUp,
|
||||||
@ -10,11 +9,12 @@ import {
|
|||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiVideo,
|
mdiVideo,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
import type { EditorMenuItem } from '../../types/constructor';
|
||||||
import BaseIcon from '../BaseIcon';
|
import BaseIcon from '../BaseIcon';
|
||||||
import ClickOutside from '../ClickOutside';
|
import ClickOutside from '../ClickOutside';
|
||||||
import MenuActionButton from './MenuActionButton';
|
import MenuActionButton from './MenuActionButton';
|
||||||
import PageSelector from './PageSelector';
|
import PageSelector from './PageSelector';
|
||||||
import type { EditorMenuItem } from '../../types/constructor';
|
|
||||||
import type { ConstructorToolbarProps } from './types';
|
import type { ConstructorToolbarProps } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { mdiChevronLeft, mdiExitToApp } from '@mdi/js';
|
import { mdiChevronLeft, mdiExitToApp } from '@mdi/js';
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
import BaseButton from '../BaseButton';
|
import BaseButton from '../BaseButton';
|
||||||
import BaseIcon from '../BaseIcon';
|
import BaseIcon from '../BaseIcon';
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
* Slug is auto-generated from the page name behind the scenes.
|
* Slug is auto-generated from the page name behind the scenes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { buildUniqueSlug, sanitizeSlug } from '../../lib/slugHelpers';
|
||||||
import CardBoxModal from '../CardBoxModal';
|
import CardBoxModal from '../CardBoxModal';
|
||||||
import { sanitizeSlug, buildUniqueSlug } from '../../lib/slugHelpers';
|
|
||||||
|
|
||||||
interface CreatePageModalProps {
|
interface CreatePageModalProps {
|
||||||
/** Whether the modal is visible */
|
/** Whether the modal is visible */
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import {
|
import {
|
||||||
StyleSettingsSectionCompact,
|
StyleSettingsSectionCompact,
|
||||||
extractNumericValue,
|
extractNumericValue,
|
||||||
} from '../ElementSettings';
|
} from '../ElementSettings';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import { buildElementCssPatch } from './elementEditorPanel.helpers';
|
import { buildElementCssPatch } from './elementEditorPanel.helpers';
|
||||||
|
|
||||||
interface ElementEditorCommonCssSectionProps {
|
interface ElementEditorCommonCssSectionProps {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { EffectsSettingsSectionCompact } from '../ElementSettings';
|
|
||||||
import type { AssetOption, CanvasElement } from '../../types/constructor';
|
import type { AssetOption, CanvasElement } from '../../types/constructor';
|
||||||
|
import { EffectsSettingsSectionCompact } from '../ElementSettings';
|
||||||
|
|
||||||
interface ElementEditorEffectsTabProps {
|
interface ElementEditorEffectsTabProps {
|
||||||
selectedElement: CanvasElement;
|
selectedElement: CanvasElement;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { GallerySectionStyleInputs } from '../ElementSettings';
|
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
import { GallerySectionStyleInputs } from '../ElementSettings';
|
||||||
|
|
||||||
interface ElementEditorGalleryCssSectionProps {
|
interface ElementEditorGalleryCssSectionProps {
|
||||||
selectedElement: CanvasElement;
|
selectedElement: CanvasElement;
|
||||||
|
|||||||
@ -1,28 +1,3 @@
|
|||||||
import {
|
|
||||||
CommonSettingsSectionCompact,
|
|
||||||
DescriptionSettingsSectionCompact,
|
|
||||||
MediaSettingsSectionCompact,
|
|
||||||
GallerySettingsSectionCompact,
|
|
||||||
CarouselSettingsSectionCompact,
|
|
||||||
GalleryCarouselSettingsSectionCompact,
|
|
||||||
InfoPanelSettingsSectionCompact,
|
|
||||||
} from '../ElementSettings';
|
|
||||||
import NavigationSettingsSectionCompact from '../ElementSettings/NavigationSettingsSectionCompact';
|
|
||||||
import {
|
|
||||||
normalizeAppearDelaySec,
|
|
||||||
normalizeAppearDurationSec,
|
|
||||||
} from '../../lib/elementDefaults';
|
|
||||||
import {
|
|
||||||
isNavigationElementType,
|
|
||||||
isDescriptionElementType,
|
|
||||||
isGalleryElementType,
|
|
||||||
isCarouselElementType,
|
|
||||||
isMediaElementType,
|
|
||||||
isVideoPlayerElementType,
|
|
||||||
isInfoPanelElementType,
|
|
||||||
} from '../../lib/elementTypeGuards';
|
|
||||||
import type { AssetOption, CanvasElement } from '../../types/constructor';
|
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
import type {
|
import type {
|
||||||
CarouselSlideOperations,
|
CarouselSlideOperations,
|
||||||
GalleryCardOperations,
|
GalleryCardOperations,
|
||||||
@ -30,6 +5,31 @@ import type {
|
|||||||
InfoPanelSectionOperations,
|
InfoPanelSectionOperations,
|
||||||
NavigationElementType,
|
NavigationElementType,
|
||||||
} from '../../context/ConstructorContext';
|
} from '../../context/ConstructorContext';
|
||||||
|
import {
|
||||||
|
normalizeAppearDelaySec,
|
||||||
|
normalizeAppearDurationSec,
|
||||||
|
} from '../../lib/elementDefaults';
|
||||||
|
import {
|
||||||
|
isCarouselElementType,
|
||||||
|
isDescriptionElementType,
|
||||||
|
isGalleryElementType,
|
||||||
|
isInfoPanelElementType,
|
||||||
|
isMediaElementType,
|
||||||
|
isNavigationElementType,
|
||||||
|
isVideoPlayerElementType,
|
||||||
|
} from '../../lib/elementTypeGuards';
|
||||||
|
import type { AssetOption, CanvasElement } from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
|
import {
|
||||||
|
CarouselSettingsSectionCompact,
|
||||||
|
CommonSettingsSectionCompact,
|
||||||
|
DescriptionSettingsSectionCompact,
|
||||||
|
GalleryCarouselSettingsSectionCompact,
|
||||||
|
GallerySettingsSectionCompact,
|
||||||
|
InfoPanelSettingsSectionCompact,
|
||||||
|
MediaSettingsSectionCompact,
|
||||||
|
} from '../ElementSettings';
|
||||||
|
import NavigationSettingsSectionCompact from '../ElementSettings/NavigationSettingsSectionCompact';
|
||||||
|
|
||||||
interface ElementEditorGeneralTabProps {
|
interface ElementEditorGeneralTabProps {
|
||||||
selectedElement: CanvasElement;
|
selectedElement: CanvasElement;
|
||||||
|
|||||||
@ -9,33 +9,31 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
useConstructorContext,
|
|
||||||
useConstructorElements,
|
|
||||||
useConstructorBackground,
|
|
||||||
useConstructorAssets,
|
useConstructorAssets,
|
||||||
|
useConstructorBackground,
|
||||||
useConstructorCollectionOps,
|
useConstructorCollectionOps,
|
||||||
useConstructorDuration,
|
useConstructorDuration,
|
||||||
useConstructorNavigation,
|
|
||||||
useConstructorEditorTab,
|
useConstructorEditorTab,
|
||||||
|
useConstructorElements,
|
||||||
useConstructorMenu,
|
useConstructorMenu,
|
||||||
|
useConstructorNavigation,
|
||||||
} from '../../context/ConstructorContext';
|
} from '../../context/ConstructorContext';
|
||||||
|
import {
|
||||||
|
isGalleryElementType,
|
||||||
|
isInfoPanelElementType,
|
||||||
|
} from '../../lib/elementTypeGuards';
|
||||||
|
import type { SystemUiControlSettings } from '../../types/uiControls';
|
||||||
import {
|
import {
|
||||||
ElementSettingsTabsCompact,
|
ElementSettingsTabsCompact,
|
||||||
GallerySectionStyleInputs,
|
GallerySectionStyleInputs,
|
||||||
InfoPanelStyleInputs,
|
InfoPanelStyleInputs,
|
||||||
} from '../ElementSettings';
|
} from '../ElementSettings';
|
||||||
import ElementEditorHeader from './ElementEditorHeader';
|
|
||||||
import {
|
|
||||||
isGalleryElementType,
|
|
||||||
isCarouselElementType,
|
|
||||||
isInfoPanelElementType,
|
|
||||||
} from '../../lib/elementTypeGuards';
|
|
||||||
import type { SystemUiControlSettings } from '../../types/uiControls';
|
|
||||||
import { ElementEditorBackgroundSettings } from './ElementEditorBackgroundSettings';
|
import { ElementEditorBackgroundSettings } from './ElementEditorBackgroundSettings';
|
||||||
import { ElementEditorCommonCssSection } from './ElementEditorCommonCssSection';
|
import { ElementEditorCommonCssSection } from './ElementEditorCommonCssSection';
|
||||||
import { ElementEditorEffectsTab } from './ElementEditorEffectsTab';
|
import { ElementEditorEffectsTab } from './ElementEditorEffectsTab';
|
||||||
import { ElementEditorGalleryCssSection } from './ElementEditorGalleryCssSection';
|
import { ElementEditorGalleryCssSection } from './ElementEditorGalleryCssSection';
|
||||||
import { ElementEditorGeneralTab } from './ElementEditorGeneralTab';
|
import { ElementEditorGeneralTab } from './ElementEditorGeneralTab';
|
||||||
|
import ElementEditorHeader from './ElementEditorHeader';
|
||||||
import { InfoPanelMediaStyleSection } from './InfoPanelMediaStyleSection';
|
import { InfoPanelMediaStyleSection } from './InfoPanelMediaStyleSection';
|
||||||
import { InfoPanelTextStyleSection } from './InfoPanelTextStyleSection';
|
import { InfoPanelTextStyleSection } from './InfoPanelTextStyleSection';
|
||||||
import { SystemControlSettingsEditor } from './SystemControlSettingsEditor';
|
import { SystemControlSettingsEditor } from './SystemControlSettingsEditor';
|
||||||
@ -74,7 +72,6 @@ export function ElementEditorPanel({
|
|||||||
// Get state from context
|
// Get state from context
|
||||||
const {
|
const {
|
||||||
selectedElement,
|
selectedElement,
|
||||||
selectedElementId,
|
|
||||||
updateSelectedElement,
|
updateSelectedElement,
|
||||||
removeSelectedElement,
|
removeSelectedElement,
|
||||||
selectedSystemControl,
|
selectedSystemControl,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import { FONT_OPTIONS } from '../../lib/fonts';
|
import { FONT_OPTIONS } from '../../lib/fonts';
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
|
||||||
interface InfoPanelMediaStyleSectionProps {
|
interface InfoPanelMediaStyleSectionProps {
|
||||||
selectedElement: CanvasElement;
|
selectedElement: CanvasElement;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import { FONT_OPTIONS } from '../../lib/fonts';
|
import { FONT_OPTIONS } from '../../lib/fonts';
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
|
||||||
type TextAlignValue = 'left' | 'center' | 'right';
|
type TextAlignValue = 'left' | 'center' | 'right';
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import {
|
|
||||||
getSystemControlAnchorBounds,
|
|
||||||
type SystemUiControlSettings,
|
|
||||||
} from '../../types/uiControls';
|
|
||||||
import type { AssetOption } from '../../types/constructor';
|
|
||||||
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
||||||
import {
|
import {
|
||||||
opacityToPercentInput,
|
opacityToPercentInput,
|
||||||
percentInputToOpacityNumber,
|
percentInputToOpacityNumber,
|
||||||
} from '../../lib/opacityPercent';
|
} from '../../lib/opacityPercent';
|
||||||
|
import type { AssetOption } from '../../types/constructor';
|
||||||
|
import {
|
||||||
|
DEFAULT_UI_CONTROL_SETTINGS,
|
||||||
|
getSystemControlAnchorBounds,
|
||||||
|
type SystemUiControlAnchor,
|
||||||
|
type SystemUiControlSettings,
|
||||||
|
} from '../../types/uiControls';
|
||||||
|
|
||||||
interface SystemControlSettingsEditorProps {
|
interface SystemControlSettingsEditorProps {
|
||||||
settings: SystemUiControlSettings;
|
settings: SystemUiControlSettings;
|
||||||
@ -61,9 +63,13 @@ export function SystemControlSettingsEditor({
|
|||||||
canvasAspectRatio,
|
canvasAspectRatio,
|
||||||
onChange,
|
onChange,
|
||||||
}: SystemControlSettingsEditorProps) {
|
}: SystemControlSettingsEditorProps) {
|
||||||
|
const effectiveSettings = {
|
||||||
|
...DEFAULT_UI_CONTROL_SETTINGS.fullscreen,
|
||||||
|
...settings,
|
||||||
|
};
|
||||||
const bounds = getSystemControlAnchorBounds(
|
const bounds = getSystemControlAnchorBounds(
|
||||||
settings.anchor,
|
effectiveSettings.anchor,
|
||||||
settings.buttonSizePercent,
|
effectiveSettings.buttonSizePercent,
|
||||||
canvasAspectRatio,
|
canvasAspectRatio,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -79,7 +85,7 @@ export function SystemControlSettingsEditor({
|
|||||||
min={bounds.minX}
|
min={bounds.minX}
|
||||||
max={bounds.maxX}
|
max={bounds.maxX}
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={settings.xPercent}
|
value={effectiveSettings.xPercent}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onChange({
|
onChange({
|
||||||
xPercent: clamp(
|
xPercent: clamp(
|
||||||
@ -100,7 +106,7 @@ export function SystemControlSettingsEditor({
|
|||||||
min={bounds.minY}
|
min={bounds.minY}
|
||||||
max={bounds.maxY}
|
max={bounds.maxY}
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={settings.yPercent}
|
value={effectiveSettings.yPercent}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onChange({
|
onChange({
|
||||||
yPercent: clamp(
|
yPercent: clamp(
|
||||||
@ -118,25 +124,24 @@ export function SystemControlSettingsEditor({
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={settings.anchor}
|
value={effectiveSettings.anchor}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const anchor = event.target
|
const anchor = event.target.value as SystemUiControlAnchor;
|
||||||
.value as SystemUiControlSettings['anchor'];
|
|
||||||
const nextBounds = getSystemControlAnchorBounds(
|
const nextBounds = getSystemControlAnchorBounds(
|
||||||
anchor,
|
anchor,
|
||||||
settings.buttonSizePercent,
|
effectiveSettings.buttonSizePercent,
|
||||||
canvasAspectRatio,
|
canvasAspectRatio,
|
||||||
);
|
);
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
anchor,
|
anchor,
|
||||||
xPercent: clamp(
|
xPercent: clamp(
|
||||||
settings.xPercent,
|
effectiveSettings.xPercent,
|
||||||
nextBounds.minX,
|
nextBounds.minX,
|
||||||
nextBounds.maxX,
|
nextBounds.maxX,
|
||||||
),
|
),
|
||||||
yPercent: clamp(
|
yPercent: clamp(
|
||||||
settings.yPercent,
|
effectiveSettings.yPercent,
|
||||||
nextBounds.minY,
|
nextBounds.minY,
|
||||||
nextBounds.maxY,
|
nextBounds.maxY,
|
||||||
),
|
),
|
||||||
@ -237,13 +242,13 @@ export function SystemControlSettingsEditor({
|
|||||||
<input
|
<input
|
||||||
type='number'
|
type='number'
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={settings[key]}
|
value={effectiveSettings[key]}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const value = Number(event.target.value) || 0;
|
const value = Number(event.target.value) || 0;
|
||||||
|
|
||||||
if (key === 'buttonSizePercent') {
|
if (key === 'buttonSizePercent') {
|
||||||
const nextBounds = getSystemControlAnchorBounds(
|
const nextBounds = getSystemControlAnchorBounds(
|
||||||
settings.anchor,
|
effectiveSettings.anchor,
|
||||||
value,
|
value,
|
||||||
canvasAspectRatio,
|
canvasAspectRatio,
|
||||||
);
|
);
|
||||||
@ -251,12 +256,12 @@ export function SystemControlSettingsEditor({
|
|||||||
onChange({
|
onChange({
|
||||||
buttonSizePercent: value,
|
buttonSizePercent: value,
|
||||||
xPercent: clamp(
|
xPercent: clamp(
|
||||||
settings.xPercent,
|
effectiveSettings.xPercent,
|
||||||
nextBounds.minX,
|
nextBounds.minX,
|
||||||
nextBounds.maxX,
|
nextBounds.maxX,
|
||||||
),
|
),
|
||||||
yPercent: clamp(
|
yPercent: clamp(
|
||||||
settings.yPercent,
|
effectiveSettings.yPercent,
|
||||||
nextBounds.minY,
|
nextBounds.minY,
|
||||||
nextBounds.maxY,
|
nextBounds.maxY,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
* - Then hides instantly (no CSS transition) since last video frame = new bg
|
* - Then hides instantly (no CSS transition) since last video frame = new bg
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import CanvasLoadingSpinner from '../CanvasLoadingSpinner';
|
import CanvasLoadingSpinner from '../CanvasLoadingSpinner';
|
||||||
|
|
||||||
interface TransitionPreviewOverlayProps {
|
interface TransitionPreviewOverlayProps {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import {
|
import {
|
||||||
removeConstructorContextElementById,
|
removeConstructorContextElementById,
|
||||||
updateConstructorContextElementById,
|
updateConstructorContextElementById,
|
||||||
} from './constructorContextValue.helpers';
|
} from './constructorContextValue.helpers';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{ id: 'first', type: 'description', label: 'First', xPercent: 10 },
|
{ id: 'first', type: 'description', label: 'First', xPercent: 10 },
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import type { TourPage, User } from '../../types/entities';
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import { presentationHasAudio } from '../../lib/presentationAudio';
|
import { presentationHasAudio } from '../../lib/presentationAudio';
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
import type { TourPage, User } from '../../types/entities';
|
||||||
|
|
||||||
export const getConstructorPageElementsListHref = (projectId: string) =>
|
export const getConstructorPageElementsListHref = (projectId: string) =>
|
||||||
projectId
|
projectId
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import {
|
import {
|
||||||
getCarouselButtonPositionPatch,
|
getCarouselButtonPositionPatch,
|
||||||
getGalleryCarouselButtonPositionPatch,
|
getGalleryCarouselButtonPositionPatch,
|
||||||
updateElementById,
|
updateElementById,
|
||||||
} from './constructorGalleryCarousel.helpers';
|
} from './constructorGalleryCarousel.helpers';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
|
|
||||||
test('getGalleryCarouselButtonPositionPatch maps overlay buttons to their coordinate fields', () => {
|
test('getGalleryCarouselButtonPositionPatch maps overlay buttons to their coordinate fields', () => {
|
||||||
assert.deepEqual(getGalleryCarouselButtonPositionPatch('prev', 10, 20), {
|
assert.deepEqual(getGalleryCarouselButtonPositionPatch('prev', 10, 20), {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import {
|
import {
|
||||||
applyNavigationTransitionDurations,
|
applyNavigationTransitionDurations,
|
||||||
getConstructorDurationNotes,
|
getConstructorDurationNotes,
|
||||||
} from './constructorMediaDuration.helpers';
|
} from './constructorMediaDuration.helpers';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
|
|
||||||
test('getConstructorDurationNotes maps media notes into editor shape', () => {
|
test('getConstructorDurationNotes maps media notes into editor shape', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export const resolveConstructorPreloadedUrl = ({
|
|||||||
}: {
|
}: {
|
||||||
url: string | undefined;
|
url: string | undefined;
|
||||||
getReadyBlobUrl: (url: string) => string | null;
|
getReadyBlobUrl: (url: string) => string | null;
|
||||||
resolveAssetUrl: (url: string) => string;
|
resolveAssetUrl: (url: string | undefined) => string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
|
import type { TransitionPreviewState } from '../../types/presentation';
|
||||||
import {
|
import {
|
||||||
areConstructorElementIconsReady,
|
areConstructorElementIconsReady,
|
||||||
buildConstructorTransitionPlaybackConfig,
|
|
||||||
buildConstructorIconPreloadTargets,
|
buildConstructorIconPreloadTargets,
|
||||||
getDeleteFallbackPageId,
|
buildConstructorTransitionPlaybackConfig,
|
||||||
getConstructorBackgroundSources,
|
getConstructorBackgroundSources,
|
||||||
getConstructorEditorTitle,
|
getConstructorEditorTitle,
|
||||||
getConstructorNavigationErrorMessage,
|
getConstructorNavigationErrorMessage,
|
||||||
|
getDeleteFallbackPageId,
|
||||||
getExistingPageSlugs,
|
getExistingPageSlugs,
|
||||||
getFirstQueryParam,
|
getFirstQueryParam,
|
||||||
getLastProjectSaveAt,
|
getLastProjectSaveAt,
|
||||||
@ -18,9 +21,6 @@ import {
|
|||||||
shouldRenderConstructorCanvasElement,
|
shouldRenderConstructorCanvasElement,
|
||||||
sortTourPagesForDisplay,
|
sortTourPagesForDisplay,
|
||||||
} from './constructorPage.helpers';
|
} from './constructorPage.helpers';
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import type { TransitionPreviewState } from '../../types/presentation';
|
|
||||||
|
|
||||||
const page = (
|
const page = (
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { TourPage } from '../../types/entities';
|
import type { TransitionConfig } from '../../hooks/useTransitionPlayback';
|
||||||
import type {
|
import type {
|
||||||
CanvasElement,
|
CanvasElement,
|
||||||
CanvasElementType,
|
CanvasElementType,
|
||||||
EditorMenuItem,
|
EditorMenuItem,
|
||||||
} from '../../types/constructor';
|
} from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
import type { TransitionPreviewState } from '../../types/presentation';
|
import type { TransitionPreviewState } from '../../types/presentation';
|
||||||
import type { SystemUiControlType } from '../../types/uiControls';
|
import type { SystemUiControlType } from '../../types/uiControls';
|
||||||
import type { TransitionConfig } from '../../hooks/useTransitionPlayback';
|
|
||||||
|
|
||||||
export type ConstructorInteractionMode = 'edit' | 'interact';
|
export type ConstructorInteractionMode = 'edit' | 'interact';
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ export const getDeleteFallbackPageId = ({
|
|||||||
|
|
||||||
export const buildConstructorIconPreloadTargets = (
|
export const buildConstructorIconPreloadTargets = (
|
||||||
elements: CanvasElement[],
|
elements: CanvasElement[],
|
||||||
resolveUrl: (url: string) => string,
|
resolveUrl: (url: string | undefined) => string,
|
||||||
) => {
|
) => {
|
||||||
const urls = elements
|
const urls = elements
|
||||||
.filter(
|
.filter(
|
||||||
@ -146,7 +146,7 @@ export const isConstructorElementIconReady = ({
|
|||||||
}: {
|
}: {
|
||||||
element: CanvasElement;
|
element: CanvasElement;
|
||||||
preloadedIconUrlMap: Record<string, boolean>;
|
preloadedIconUrlMap: Record<string, boolean>;
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
}) => {
|
}) => {
|
||||||
const isPreloadableIconElement =
|
const isPreloadableIconElement =
|
||||||
ICON_PRELOADABLE_TYPES.includes(element.type) && Boolean(element.iconUrl);
|
ICON_PRELOADABLE_TYPES.includes(element.type) && Boolean(element.iconUrl);
|
||||||
@ -166,7 +166,7 @@ export const areConstructorElementIconsReady = ({
|
|||||||
}: {
|
}: {
|
||||||
elements: CanvasElement[];
|
elements: CanvasElement[];
|
||||||
preloadedIconUrlMap: Record<string, boolean>;
|
preloadedIconUrlMap: Record<string, boolean>;
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
}) =>
|
}) =>
|
||||||
elements.every((element) =>
|
elements.every((element) =>
|
||||||
isConstructorElementIconReady({
|
isConstructorElementIconReady({
|
||||||
@ -214,7 +214,7 @@ export const getConstructorBackgroundSources = ({
|
|||||||
embedUrl: string;
|
embedUrl: string;
|
||||||
audioUrl: string;
|
audioUrl: string;
|
||||||
};
|
};
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
}) => ({
|
}) => ({
|
||||||
imageUrl:
|
imageUrl:
|
||||||
isEditMode && edit.imageUrl
|
isEditMode && edit.imageUrl
|
||||||
@ -243,7 +243,7 @@ export const buildConstructorTransitionPlaybackConfig = ({
|
|||||||
transitionPreview: TransitionPreviewState | null;
|
transitionPreview: TransitionPreviewState | null;
|
||||||
isVideoElementReady: boolean;
|
isVideoElementReady: boolean;
|
||||||
pendingNavigationPageId: string;
|
pendingNavigationPageId: string;
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
}): TransitionConfig | null => {
|
}): TransitionConfig | null => {
|
||||||
if (!transitionPreview || !isVideoElementReady) {
|
if (!transitionPreview || !isVideoElementReady) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { buildUniqueSlug } from '../../lib/slugHelpers';
|
||||||
import type { EditorMenuItem } from '../../types/constructor';
|
import type { EditorMenuItem } from '../../types/constructor';
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
import { buildUniqueSlug } from '../../lib/slugHelpers';
|
|
||||||
import {
|
import {
|
||||||
getDeleteFallbackPageId,
|
getDeleteFallbackPageId,
|
||||||
getReorderedPageIds,
|
getReorderedPageIds,
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
import {
|
import {
|
||||||
normalizeForcedNavigationElementTypes,
|
normalizeForcedNavigationElementTypes,
|
||||||
shouldInitializeConstructorPageBackground,
|
shouldInitializeConstructorPageBackground,
|
||||||
toConstructorNavigablePage,
|
toConstructorNavigablePage,
|
||||||
} from './constructorPageNavigationLifecycle.helpers';
|
} from './constructorPageNavigationLifecycle.helpers';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
|
|
||||||
test('toConstructorNavigablePage keeps only navigation background fields', () => {
|
test('toConstructorNavigablePage keeps only navigation background fields', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import type { NavigationElementType } from '../../context/ConstructorContext';
|
||||||
|
import type { NavigablePage } from '../../hooks/usePageNavigationState';
|
||||||
import { isNavigationElementType } from '../../lib/elementTypeGuards';
|
import { isNavigationElementType } from '../../lib/elementTypeGuards';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
import type { NavigationElementType } from '../../context/ConstructorContext';
|
|
||||||
import type { NavigablePage } from '../../hooks/usePageNavigationState';
|
|
||||||
|
|
||||||
export const toConstructorNavigablePage = (
|
export const toConstructorNavigablePage = (
|
||||||
page: TourPage | null,
|
page: TourPage | null,
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
import { getNavigationButtonKind } from '../../lib/elementDefaultConstants';
|
||||||
import {
|
import {
|
||||||
mergeElementWithDefaults,
|
mergeElementWithDefaults,
|
||||||
normalizeAppearDelaySec,
|
normalizeAppearDelaySec,
|
||||||
normalizeAppearDurationSec,
|
normalizeAppearDurationSec,
|
||||||
} from '../../lib/elementDefaults';
|
} from '../../lib/elementDefaults';
|
||||||
import { clamp, createLocalId } from '../../lib/elementInstance.helpers';
|
import { clamp, createLocalId } from '../../lib/elementInstance.helpers';
|
||||||
import { getNavigationButtonKind } from '../../lib/elementDefaultConstants';
|
|
||||||
import {
|
import {
|
||||||
isNavigationElementType,
|
isNavigationElementType,
|
||||||
isVideoPlayerElementType,
|
isVideoPlayerElementType,
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import {
|
|
||||||
buildUpdatedSystemControlSettings,
|
|
||||||
clampConstructorSystemControlPosition,
|
|
||||||
} from './constructorSystemControls.helpers';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_UI_CONTROL_SETTINGS,
|
DEFAULT_UI_CONTROL_SETTINGS,
|
||||||
type ResolvedUiControlsSettings,
|
type ResolvedUiControlsSettings,
|
||||||
} from '../../types/uiControls';
|
} from '../../types/uiControls';
|
||||||
|
import {
|
||||||
|
buildUpdatedSystemControlSettings,
|
||||||
|
clampConstructorSystemControlPosition,
|
||||||
|
} from './constructorSystemControls.helpers';
|
||||||
|
|
||||||
test('buildUpdatedSystemControlSettings merges a control patch without dropping existing controls', () => {
|
test('buildUpdatedSystemControlSettings merges a control patch without dropping existing controls', () => {
|
||||||
const next = buildUpdatedSystemControlSettings({
|
const next = buildUpdatedSystemControlSettings({
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { TransitionPreviewState } from '../../types/presentation';
|
||||||
import {
|
import {
|
||||||
getConstructorTransitionPreviewBuffering,
|
getConstructorTransitionPreviewBuffering,
|
||||||
shouldCloseConstructorTransitionPreview,
|
shouldCloseConstructorTransitionPreview,
|
||||||
} from './constructorTransitionPreviewPlayback.helpers';
|
} from './constructorTransitionPreviewPlayback.helpers';
|
||||||
import type { TransitionPreviewState } from '../../types/presentation';
|
|
||||||
|
|
||||||
const preview = {
|
const preview = {
|
||||||
videoUrl: 'assets/transition.mp4',
|
videoUrl: 'assets/transition.mp4',
|
||||||
|
|||||||
@ -5,19 +5,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
AssetOption,
|
||||||
CanvasElement,
|
CanvasElement,
|
||||||
CanvasElementType,
|
CanvasElementType,
|
||||||
ConstructorAsset,
|
|
||||||
AssetOption,
|
|
||||||
GalleryCard,
|
|
||||||
CarouselSlide,
|
CarouselSlide,
|
||||||
NavigationButtonKind,
|
ConstructorAsset,
|
||||||
EditorMenuItem,
|
EditorMenuItem,
|
||||||
EditorTab,
|
EditorTab,
|
||||||
|
GalleryCard,
|
||||||
|
NavigationButtonKind,
|
||||||
} from '../../types/constructor';
|
} from '../../types/constructor';
|
||||||
import type {
|
import type {
|
||||||
PageBackgroundVideoSettings,
|
|
||||||
PageBackgroundAudioSettings,
|
PageBackgroundAudioSettings,
|
||||||
|
PageBackgroundVideoSettings,
|
||||||
} from '../../types/pageBackground';
|
} from '../../types/pageBackground';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,11 +313,11 @@ export interface TransitionPreviewOverlayProps {
|
|||||||
|
|
||||||
// Re-export types from constructor.ts
|
// Re-export types from constructor.ts
|
||||||
export type {
|
export type {
|
||||||
|
AssetOption,
|
||||||
CanvasElement,
|
CanvasElement,
|
||||||
CanvasElementType,
|
CanvasElementType,
|
||||||
ConstructorAsset,
|
|
||||||
AssetOption,
|
|
||||||
GalleryCard,
|
|
||||||
CarouselSlide,
|
CarouselSlide,
|
||||||
|
ConstructorAsset,
|
||||||
|
GalleryCard,
|
||||||
NavigationButtonKind,
|
NavigationButtonKind,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { ELEMENT_TYPE_LABELS } from '../../lib/elementDefaultConstants';
|
||||||
import { parseJsonObject } from '../../lib/parseJson';
|
import { parseJsonObject } from '../../lib/parseJson';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
type ElementDefaultsByType,
|
type ElementDefaultsByType,
|
||||||
type RawConstructorSchema,
|
type RawConstructorSchema,
|
||||||
} from './constructorSchema.helpers';
|
} from './constructorSchema.helpers';
|
||||||
import { ELEMENT_TYPE_LABELS } from '../../lib/elementDefaultConstants';
|
|
||||||
|
|
||||||
export function useActiveConstructorPageHydration({
|
export function useActiveConstructorPageHydration({
|
||||||
activePage,
|
activePage,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import type { NextRouter } from 'next/router';
|
import type { NextRouter } from 'next/router';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export function useConstructorAuthRedirect({
|
export function useConstructorAuthRedirect({
|
||||||
router,
|
router,
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
import { flushSync } from 'react-dom';
|
|
||||||
import { useCallback, type MouseEvent, type PointerEvent } from 'react';
|
import { useCallback, type MouseEvent, type PointerEvent } from 'react';
|
||||||
import type { CanvasElement, EditorMenuItem } from '../../types/constructor';
|
import { flushSync } from 'react-dom';
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
import type { NavigationContext } from '../../lib/navigationHelpers';
|
|
||||||
import type {
|
|
||||||
SystemUiControlSettings,
|
|
||||||
SystemUiControlType,
|
|
||||||
} from '../../types/uiControls';
|
|
||||||
import type { ElementTransitionSettings } from '../../types/transition';
|
|
||||||
import type { NavigationElementType } from '../../context/ConstructorContext';
|
import type { NavigationElementType } from '../../context/ConstructorContext';
|
||||||
|
import { resolveAssetPlaybackUrl } from '../../lib/assetUrl';
|
||||||
|
import { isElementFlagEnabled } from '../../lib/elementFlags';
|
||||||
|
import { isNavigationElementType } from '../../lib/elementTypeGuards';
|
||||||
|
import { logger } from '../../lib/logger';
|
||||||
|
import type { NavigationContext } from '../../lib/navigationHelpers';
|
||||||
import {
|
import {
|
||||||
getNavigationDirection,
|
getNavigationDirection,
|
||||||
hasPlayableTransition,
|
hasPlayableTransition,
|
||||||
isBackNavigation,
|
isBackNavigation,
|
||||||
resolveNavigationTarget,
|
resolveNavigationTarget,
|
||||||
} from '../../lib/navigationHelpers';
|
} from '../../lib/navigationHelpers';
|
||||||
import { isNavigationElementType } from '../../lib/elementTypeGuards';
|
|
||||||
import { isElementFlagEnabled } from '../../lib/elementFlags';
|
|
||||||
import { resolveAssetPlaybackUrl } from '../../lib/assetUrl';
|
|
||||||
import { downloadManager } from '../../lib/offline/DownloadManager';
|
import { downloadManager } from '../../lib/offline/DownloadManager';
|
||||||
import { logger } from '../../lib/logger';
|
import type { CanvasElement, EditorMenuItem } from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
|
import type { ElementTransitionSettings } from '../../types/transition';
|
||||||
import { extractElementTransitionSettings } from '../../types/transition';
|
import { extractElementTransitionSettings } from '../../types/transition';
|
||||||
|
import type {
|
||||||
|
ResolvedUiControlsSettings,
|
||||||
|
SystemUiControlType,
|
||||||
|
} from '../../types/uiControls';
|
||||||
import { getConstructorNavigationErrorMessage } from './constructorPage.helpers';
|
import { getConstructorNavigationErrorMessage } from './constructorPage.helpers';
|
||||||
|
|
||||||
type ConstructorCanvasInteractionsOptions = {
|
type ConstructorCanvasInteractionsOptions = {
|
||||||
@ -35,10 +35,7 @@ type ConstructorCanvasInteractionsOptions = {
|
|||||||
downlink?: number;
|
downlink?: number;
|
||||||
rtt?: number;
|
rtt?: number;
|
||||||
};
|
};
|
||||||
resolvedUiControlsSettings: Record<
|
resolvedUiControlsSettings: ResolvedUiControlsSettings;
|
||||||
SystemUiControlType,
|
|
||||||
SystemUiControlSettings
|
|
||||||
>;
|
|
||||||
getNavigationContext: () => NavigationContext;
|
getNavigationContext: () => NavigationContext;
|
||||||
getReadyTransitionBlobUrl: (url: string) => string | null;
|
getReadyTransitionBlobUrl: (url: string) => string | null;
|
||||||
selectElementForEdit: (elementId: string) => void;
|
selectElementForEdit: (elementId: string) => void;
|
||||||
|
|||||||
@ -2,10 +2,6 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { TourPage, User } from '../../types/entities';
|
import type { TourPage, User } from '../../types/entities';
|
||||||
import {
|
|
||||||
getExistingPageSlugs,
|
|
||||||
getLastProjectSaveAt,
|
|
||||||
} from './constructorPage.helpers';
|
|
||||||
import {
|
import {
|
||||||
canCurrentUserDeleteConstructorPage,
|
canCurrentUserDeleteConstructorPage,
|
||||||
getConstructorPageElementsListHref,
|
getConstructorPageElementsListHref,
|
||||||
@ -13,6 +9,10 @@ import {
|
|||||||
hasConstructorFullWidthCarousel,
|
hasConstructorFullWidthCarousel,
|
||||||
hasConstructorPresentationAudio,
|
hasConstructorPresentationAudio,
|
||||||
} from './constructorDerivedState.helpers';
|
} from './constructorDerivedState.helpers';
|
||||||
|
import {
|
||||||
|
getExistingPageSlugs,
|
||||||
|
getLastProjectSaveAt,
|
||||||
|
} from './constructorPage.helpers';
|
||||||
|
|
||||||
type UseConstructorDerivedStateOptions = {
|
type UseConstructorDerivedStateOptions = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
type SetStateAction,
|
type SetStateAction,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { ActiveConstructorGalleryCarousel } from './ConstructorRuntimeOverlays';
|
import type { ActiveConstructorGalleryCarousel } from './ConstructorRuntimeOverlays';
|
||||||
import {
|
import {
|
||||||
getCarouselButtonPositionPatch,
|
getCarouselButtonPositionPatch,
|
||||||
@ -14,7 +15,6 @@ import {
|
|||||||
type CarouselButton,
|
type CarouselButton,
|
||||||
type GalleryCarouselButton,
|
type GalleryCarouselButton,
|
||||||
} from './constructorGalleryCarousel.helpers';
|
} from './constructorGalleryCarousel.helpers';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
|
||||||
|
|
||||||
export const useConstructorGalleryCarousel = ({
|
export const useConstructorGalleryCarousel = ({
|
||||||
elements,
|
elements,
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
|
||||||
buildInfoPanelGalleryState,
|
|
||||||
getExternalHref,
|
|
||||||
getInfoPanelBackgroundSources,
|
|
||||||
} from '../UiElements/infoPanelMedia.helpers';
|
|
||||||
import { findDefaultOpenInfoPanelElements } from '../../lib/elementFlags';
|
import { findDefaultOpenInfoPanelElements } from '../../lib/elementFlags';
|
||||||
import { isInfoPanelElementType } from '../../lib/elementTypeGuards';
|
import { isInfoPanelElementType } from '../../lib/elementTypeGuards';
|
||||||
import type {
|
import type {
|
||||||
@ -11,8 +6,13 @@ import type {
|
|||||||
EditorMenuItem,
|
EditorMenuItem,
|
||||||
GalleryCarouselMediaItem,
|
GalleryCarouselMediaItem,
|
||||||
} from '../../types/constructor';
|
} from '../../types/constructor';
|
||||||
import type { InfoPanelImage } from '../../types/infoPanel';
|
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
|
import type { InfoPanelImage } from '../../types/infoPanel';
|
||||||
|
import {
|
||||||
|
buildInfoPanelGalleryState,
|
||||||
|
getExternalHref,
|
||||||
|
getInfoPanelBackgroundSources,
|
||||||
|
} from '../UiElements/infoPanelMedia.helpers';
|
||||||
|
|
||||||
interface UseConstructorInfoPanelsOptions {
|
interface UseConstructorInfoPanelsOptions {
|
||||||
elements: CanvasElement[];
|
elements: CanvasElement[];
|
||||||
@ -26,7 +26,7 @@ interface UseConstructorInfoPanelsOptions {
|
|||||||
setSelectedMenuItem: (item: EditorMenuItem) => void;
|
setSelectedMenuItem: (item: EditorMenuItem) => void;
|
||||||
setErrorMessage: (message: string) => void;
|
setErrorMessage: (message: string) => void;
|
||||||
currentAudioUrl: string;
|
currentAudioUrl: string;
|
||||||
resolveAssetUrl: (url: string) => string;
|
resolveAssetUrl: (url: string | undefined) => string;
|
||||||
setBackgroundDirectly: (
|
setBackgroundDirectly: (
|
||||||
imageUrl?: string,
|
imageUrl?: string,
|
||||||
videoUrl?: string,
|
videoUrl?: string,
|
||||||
@ -53,7 +53,7 @@ export function useConstructorInfoPanels({
|
|||||||
const defaultInfoPanelPageIdRef = useRef<string | null>(null);
|
const defaultInfoPanelPageIdRef = useRef<string | null>(null);
|
||||||
const [activeInfoPanelIds, setActiveInfoPanelIds] = useState<string[]>([]);
|
const [activeInfoPanelIds, setActiveInfoPanelIds] = useState<string[]>([]);
|
||||||
const [activeDetailImages, setActiveDetailImages] = useState<
|
const [activeDetailImages, setActiveDetailImages] = useState<
|
||||||
Record<string, InfoPanelImage | undefined>
|
Record<string, InfoPanelImage | null | undefined>
|
||||||
>({});
|
>({});
|
||||||
const [activeInfoPanelGallery, setActiveInfoPanelGallery] = useState<{
|
const [activeInfoPanelGallery, setActiveInfoPanelGallery] = useState<{
|
||||||
panelId: string;
|
panelId: string;
|
||||||
@ -117,7 +117,7 @@ export function useConstructorInfoPanels({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setDetailImage = useCallback(
|
const setDetailImage = useCallback(
|
||||||
(panelId: string, image: InfoPanelImage) => {
|
(panelId: string, image: InfoPanelImage | null) => {
|
||||||
setActiveDetailImages((current) => ({
|
setActiveDetailImages((current) => ({
|
||||||
...current,
|
...current,
|
||||||
[panelId]: image,
|
[panelId]: image,
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import { useCallback, useMemo } from 'react';
|
|||||||
import { useIconPreload } from '../../hooks/useIconPreload';
|
import { useIconPreload } from '../../hooks/useIconPreload';
|
||||||
import type { PreloadCacheProvider } from '../../hooks/video';
|
import type { PreloadCacheProvider } from '../../hooks/video';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
|
import { resolveConstructorPreloadedUrl } from './constructorMediaPreload.helpers';
|
||||||
import {
|
import {
|
||||||
areConstructorElementIconsReady,
|
areConstructorElementIconsReady,
|
||||||
buildConstructorIconPreloadTargets,
|
buildConstructorIconPreloadTargets,
|
||||||
getConstructorBackgroundSources,
|
getConstructorBackgroundSources,
|
||||||
} from './constructorPage.helpers';
|
} from './constructorPage.helpers';
|
||||||
import { resolveConstructorPreloadedUrl } from './constructorMediaPreload.helpers';
|
|
||||||
|
|
||||||
type ConstructorPreloadOrchestrator = {
|
type ConstructorPreloadOrchestrator = {
|
||||||
getReadyBlob: (url: string) => Blob | null;
|
getReadyBlob: (url: string) => Blob | null;
|
||||||
@ -22,7 +22,7 @@ type UseConstructorMediaPreloadStateOptions = {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
preloadOrchestrator: ConstructorPreloadOrchestrator;
|
preloadOrchestrator: ConstructorPreloadOrchestrator;
|
||||||
resolveAssetUrl: (url: string) => string;
|
resolveAssetUrl: (url: string | undefined) => string;
|
||||||
editBackground: {
|
editBackground: {
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
videoUrl?: string;
|
videoUrl?: string;
|
||||||
@ -81,11 +81,7 @@ export function useConstructorMediaPreloadState({
|
|||||||
getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl,
|
getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl,
|
||||||
resolveAssetUrl,
|
resolveAssetUrl,
|
||||||
}),
|
}),
|
||||||
[
|
[preloadOrchestrator.getReadyBlobUrl, resolveAssetUrl],
|
||||||
preloadOrchestrator.getReadyBlobUrl,
|
|
||||||
preloadOrchestrator.readyUrlsVersion,
|
|
||||||
resolveAssetUrl,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const backgroundSources = useMemo(
|
const backgroundSources = useMemo(
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useCallback, useState } from 'react';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { EditorMenuItem } from '../../types/constructor';
|
import { useCallback, useState } from 'react';
|
||||||
import type { TourPage } from '../../types/entities';
|
|
||||||
import { logger } from '../../lib/logger';
|
import { logger } from '../../lib/logger';
|
||||||
import { queryClient, queryKeys } from '../../lib/queryClient';
|
import { queryClient, queryKeys } from '../../lib/queryClient';
|
||||||
|
import type { EditorMenuItem } from '../../types/constructor';
|
||||||
|
import type { TourPage } from '../../types/entities';
|
||||||
import {
|
import {
|
||||||
deleteConstructorPage,
|
deleteConstructorPage,
|
||||||
duplicateConstructorPage,
|
duplicateConstructorPage,
|
||||||
@ -78,7 +78,6 @@ export function useConstructorPageManagement({
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
activePage,
|
activePage,
|
||||||
activePage?.environment,
|
|
||||||
activePageId,
|
activePageId,
|
||||||
handleReload,
|
handleReload,
|
||||||
isReorderingPages,
|
isReorderingPages,
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import {
|
|||||||
type SetStateAction,
|
type SetStateAction,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import type { NavigationElementType } from '../../context/ConstructorContext';
|
||||||
import type { UsePageNavigationStateResult } from '../../hooks/usePageNavigationState';
|
import type { UsePageNavigationStateResult } from '../../hooks/usePageNavigationState';
|
||||||
import type { CanvasElement } from '../../types/constructor';
|
import type { CanvasElement } from '../../types/constructor';
|
||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
import type { NavigationElementType } from '../../context/ConstructorContext';
|
|
||||||
import {
|
import {
|
||||||
normalizeForcedNavigationElementTypes,
|
normalizeForcedNavigationElementTypes,
|
||||||
shouldInitializeConstructorPageBackground,
|
shouldInitializeConstructorPageBackground,
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import type { CanvasElement, EditorMenuItem } from '../../types/constructor';
|
|||||||
import type { TourPage } from '../../types/entities';
|
import type { TourPage } from '../../types/entities';
|
||||||
import type { PageBackgroundState } from '../../types/pageBackground';
|
import type { PageBackgroundState } from '../../types/pageBackground';
|
||||||
import type { UiControlsSettings } from '../../types/uiControls';
|
import type { UiControlsSettings } from '../../types/uiControls';
|
||||||
import { useConstructorPageManagement } from './useConstructorPageManagement';
|
|
||||||
import { shouldRestoreConstructorPageAfterReload } from './constructorPageWorkflow.helpers';
|
import { shouldRestoreConstructorPageAfterReload } from './constructorPageWorkflow.helpers';
|
||||||
|
import { useConstructorPageManagement } from './useConstructorPageManagement';
|
||||||
|
|
||||||
type ConstructorWorkflowProject = {
|
type ConstructorWorkflowProject = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -21,7 +21,7 @@ type UseConstructorPageWorkflowOptions = {
|
|||||||
project?: ConstructorWorkflowProject | null;
|
project?: ConstructorWorkflowProject | null;
|
||||||
pages: TourPage[];
|
pages: TourPage[];
|
||||||
activePage: TourPage | null;
|
activePage: TourPage | null;
|
||||||
activePageId: string;
|
activePageId: string | null;
|
||||||
elements: CanvasElement[];
|
elements: CanvasElement[];
|
||||||
getElements: () => CanvasElement[];
|
getElements: () => CanvasElement[];
|
||||||
pageBackground: PageBackgroundState;
|
pageBackground: PageBackgroundState;
|
||||||
@ -65,7 +65,9 @@ export function useConstructorPageWorkflow({
|
|||||||
pages,
|
pages,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
setActivePageId(currentPageId);
|
if (currentPageId) {
|
||||||
|
setActivePageId(currentPageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [activePageId, pages, refetchData, setActivePageId]);
|
}, [activePageId, pages, refetchData, setActivePageId]);
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ export function useConstructorPageWorkflow({
|
|||||||
project,
|
project,
|
||||||
pages,
|
pages,
|
||||||
activePage,
|
activePage,
|
||||||
activePageId,
|
activePageId: activePageId || '',
|
||||||
elements,
|
elements,
|
||||||
getElements,
|
getElements,
|
||||||
pageBackground,
|
pageBackground,
|
||||||
@ -99,7 +101,7 @@ export function useConstructorPageWorkflow({
|
|||||||
projectId,
|
projectId,
|
||||||
pages,
|
pages,
|
||||||
activePage,
|
activePage,
|
||||||
activePageId,
|
activePageId: activePageId || '',
|
||||||
existingSlugs,
|
existingSlugs,
|
||||||
refetchData,
|
refetchData,
|
||||||
handleReload,
|
handleReload,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import type { AppDispatch } from '../../stores/store';
|
|
||||||
import { fetch as fetchGlobalTransitionDefaults } from '../../stores/global_transition_defaults/globalTransitionDefaultsSlice';
|
import { fetch as fetchGlobalTransitionDefaults } from '../../stores/global_transition_defaults/globalTransitionDefaultsSlice';
|
||||||
import { fetch as fetchGlobalUiControlDefaults } from '../../stores/global_ui_control_defaults/globalUiControlDefaultsSlice';
|
import { fetch as fetchGlobalUiControlDefaults } from '../../stores/global_ui_control_defaults/globalUiControlDefaultsSlice';
|
||||||
import { fetchByProjectAndEnv as fetchProjectTransitionSettings } from '../../stores/project_transition_settings/projectTransitionSettingsSlice';
|
import { fetchByProjectAndEnv as fetchProjectTransitionSettings } from '../../stores/project_transition_settings/projectTransitionSettingsSlice';
|
||||||
import { fetchByProjectAndEnv as fetchProjectUiControlSettings } from '../../stores/project_ui_control_settings/projectUiControlSettingsSlice';
|
import { fetchByProjectAndEnv as fetchProjectUiControlSettings } from '../../stores/project_ui_control_settings/projectUiControlSettingsSlice';
|
||||||
|
import type { AppDispatch } from '../../stores/store';
|
||||||
|
|
||||||
interface UseConstructorSettingsFetchOptions {
|
interface UseConstructorSettingsFetchOptions {
|
||||||
dispatch: AppDispatch;
|
dispatch: AppDispatch;
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const useConstructorTransitionPreviewPlayback = ({
|
|||||||
resetNavigationToIdle: () => void;
|
resetNavigationToIdle: () => void;
|
||||||
onTransitionEnded: () => void;
|
onTransitionEnded: () => void;
|
||||||
onVideoBufferStateChange: (isBuffering: boolean) => void;
|
onVideoBufferStateChange: (isBuffering: boolean) => void;
|
||||||
resolveUrl: (url: string) => string;
|
resolveUrl: (url: string | undefined) => string;
|
||||||
}) => {
|
}) => {
|
||||||
const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
|
const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
const [isTransitionVideoElementReady, setIsTransitionVideoElementReady] =
|
const [isTransitionVideoElementReady, setIsTransitionVideoElementReady] =
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
|
||||||
import axios from 'axios';
|
|
||||||
import {
|
import {
|
||||||
GridColDef,
|
GridColDef,
|
||||||
GridRenderCellParams,
|
GridRenderCellParams,
|
||||||
GridSingleSelectColDef,
|
GridSingleSelectColDef,
|
||||||
} from '@mui/x-data-grid';
|
} from '@mui/x-data-grid';
|
||||||
|
import axios from 'axios';
|
||||||
|
import React from 'react';
|
||||||
import dataFormatter from '../../helpers/dataFormatter';
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
|
||||||
import { hasPermission } from '../../helpers/userPermissions';
|
import { hasPermission } from '../../helpers/userPermissions';
|
||||||
import { logger } from '../../lib/logger';
|
import { logger } from '../../lib/logger';
|
||||||
|
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||||
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
|
||||||
export interface ColumnMetadata {
|
export interface ColumnMetadata {
|
||||||
field: string;
|
field: string;
|
||||||
@ -163,9 +163,14 @@ function buildColumn(
|
|||||||
|
|
||||||
singleSelectColumn.type = 'singleSelect';
|
singleSelectColumn.type = 'singleSelect';
|
||||||
singleSelectColumn.sortable = false;
|
singleSelectColumn.sortable = false;
|
||||||
singleSelectColumn.getOptionValue = (value: { id?: string }) => value?.id;
|
singleSelectColumn.getOptionValue = (value: unknown) =>
|
||||||
singleSelectColumn.getOptionLabel = (value: { label?: string }) =>
|
typeof value === 'object' && value !== null && 'id' in value
|
||||||
value?.label;
|
? String(value.id ?? '')
|
||||||
|
: String(value ?? '');
|
||||||
|
singleSelectColumn.getOptionLabel = (value: unknown) =>
|
||||||
|
typeof value === 'object' && value !== null && 'label' in value
|
||||||
|
? String(value.label ?? '')
|
||||||
|
: String(value ?? '');
|
||||||
singleSelectColumn.valueOptions = valueOptions;
|
singleSelectColumn.valueOptions = valueOptions;
|
||||||
singleSelectColumn.valueGetter = (
|
singleSelectColumn.valueGetter = (
|
||||||
value: { id?: string; label?: string; name?: string } | string | null,
|
value: { id?: string; label?: string; name?: string } | string | null,
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
|
import { MenuItem, Select, type SelectChangeEvent } from '@mui/material';
|
||||||
import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid';
|
import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid';
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { MenuItem, Select } from '@mui/material';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
entityName: string;
|
entityName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SelectOption {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => {
|
const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => {
|
||||||
const { id, value, field, entityName } = props;
|
const { id, value, field, entityName } = props;
|
||||||
const apiRef = useGridApiContext();
|
const apiRef = useGridApiContext();
|
||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState<SelectOption[]>([]);
|
||||||
|
|
||||||
async function callApi(entityName: string) {
|
async function callApi(entityName: string) {
|
||||||
const data = await axios(`/${entityName}/autocomplete?limit=50`);
|
const data = await axios.get<SelectOption[]>(
|
||||||
|
`/${entityName}/autocomplete?limit=50`,
|
||||||
|
);
|
||||||
return data.data;
|
return data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,18 +28,24 @@ const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => {
|
|||||||
callApi(entityName).then((data) => {
|
callApi(entityName).then((data) => {
|
||||||
setOptions(data);
|
setOptions(data);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [entityName]);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event: SelectChangeEvent<unknown>) => {
|
||||||
const eventValue = event.target.value; // The new value entered by the user
|
const eventValue = event.target.value; // The new value entered by the user
|
||||||
|
|
||||||
const newValue =
|
const newValue =
|
||||||
typeof eventValue === 'string' ? value.split(',') : eventValue;
|
typeof eventValue === 'string'
|
||||||
|
? eventValue.split(',')
|
||||||
|
: Array.isArray(eventValue)
|
||||||
|
? eventValue
|
||||||
|
: [];
|
||||||
|
|
||||||
apiRef.current.setEditCellValue({
|
apiRef.current.setEditCellValue({
|
||||||
id,
|
id,
|
||||||
field,
|
field,
|
||||||
value: newValue.filter((x) => x !== ''),
|
value: newValue.filter(
|
||||||
|
(x): x is string => typeof x === 'string' && x !== '',
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
|
||||||
import BaseIcon from './BaseIcon';
|
|
||||||
import { mdiFileUploadOutline } from '@mdi/js';
|
import { mdiFileUploadOutline } from '@mdi/js';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import BaseIcon from './BaseIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
file: File | null;
|
file: File | null;
|
||||||
setFile: (file: File) => void;
|
setFile: (file: File | null) => void;
|
||||||
formats?: string;
|
formats?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
||||||
const [highlight, setHighlight] = useState(false);
|
const [highlight, setHighlight] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const fileInput = React.createRef<HTMLInputElement>();
|
const fileInput = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!file && fileInput) fileInput.current.value = '';
|
if (!file && fileInput.current) fileInput.current.value = '';
|
||||||
}, [file, fileInput]);
|
}, [file, fileInput]);
|
||||||
|
|
||||||
function onFilesAdded(files: FileList | null) {
|
function onFilesAdded(files: FileList | null) {
|
||||||
if (files && files[0]) {
|
if (files && files[0]) {
|
||||||
const newFile = files[0];
|
const newFile = files[0];
|
||||||
const fileExtension = newFile.name.split('.').pop().toLowerCase();
|
const fileExtension = newFile.name.split('.').pop()?.toLowerCase() || '';
|
||||||
|
|
||||||
if (formats.includes(fileExtension) || !formats) {
|
if (formats.includes(fileExtension) || !formats) {
|
||||||
setFile(newFile);
|
setFile(newFile);
|
||||||
@ -31,7 +31,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragOver(e) {
|
function onDragOver(e: React.DragEvent<HTMLDivElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setHighlight(true);
|
setHighlight(true);
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
|||||||
setHighlight(false);
|
setHighlight(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDrop(e) {
|
function onDrop(e: React.DragEvent<HTMLDivElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
@ -49,11 +49,6 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
|||||||
setHighlight(false);
|
setHighlight(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClear = () => {
|
|
||||||
setFile(null);
|
|
||||||
setErrorMessage('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='flex items-center justify-center w-full mb-4'
|
className='flex items-center justify-center w-full mb-4'
|
||||||
|
|||||||
@ -5,13 +5,13 @@
|
|||||||
* Manages carousel slides with images and captions.
|
* Manages carousel slides with images and captions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { mdiPlus, mdiTrashCan } from '@mdi/js';
|
import { mdiPlus, mdiTrashCan } from '@mdi/js';
|
||||||
|
import React from 'react';
|
||||||
|
import { FONT_OPTIONS } from '../../lib/fonts';
|
||||||
import BaseButton from '../BaseButton';
|
import BaseButton from '../BaseButton';
|
||||||
import CardBox from '../CardBox';
|
import CardBox from '../CardBox';
|
||||||
import FormField from '../FormField';
|
import FormField from '../FormField';
|
||||||
import type { CarouselSettingsSectionProps } from './types';
|
import type { CarouselSettingsSectionProps } from './types';
|
||||||
import { FONT_OPTIONS } from '../../lib/fonts';
|
|
||||||
|
|
||||||
const CarouselSettingsSection: React.FC<CarouselSettingsSectionProps> = ({
|
const CarouselSettingsSection: React.FC<CarouselSettingsSectionProps> = ({
|
||||||
carouselPrevIconUrl,
|
carouselPrevIconUrl,
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { CarouselSlide, AssetOption } from '../../types/constructor';
|
|
||||||
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
||||||
import { FONT_OPTIONS } from '../../lib/fonts';
|
import { FONT_OPTIONS } from '../../lib/fonts';
|
||||||
|
import type { AssetOption, CarouselSlide } from '../../types/constructor';
|
||||||
|
|
||||||
interface CarouselSettingsSectionCompactProps {
|
interface CarouselSettingsSectionCompactProps {
|
||||||
carouselSlides: CarouselSlide[];
|
carouselSlides: CarouselSlide[];
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user