38
resources/client/admin/channels/channel-auto-update-field.tsx
Executable file
38
resources/client/admin/channels/channel-auto-update-field.tsx
Executable file
@@ -0,0 +1,38 @@
|
||||
import {useSettings} from '@common/core/settings/use-settings';
|
||||
import {useFormContext} from 'react-hook-form';
|
||||
import {UpdateChannelPayload} from '@common/admin/channels/requests/use-update-channel';
|
||||
import {channelContentConfig} from '@app/admin/channels/channel-content-config';
|
||||
import {ContentAutoUpdateField} from '@common/admin/channels/channel-editor/controls/content-auto-update-field';
|
||||
import {FormSelect, Option} from '@common/ui/forms/select/select';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
export function ChannelAutoUpdateField({className}: Props) {
|
||||
const {tmdb_is_setup} = useSettings();
|
||||
const {watch} = useFormContext<UpdateChannelPayload>();
|
||||
const methodConfig =
|
||||
channelContentConfig.autoUpdateMethods[watch('config.autoUpdateMethod')!];
|
||||
return (
|
||||
<ContentAutoUpdateField config={channelContentConfig} className={className}>
|
||||
{!methodConfig?.provider && tmdb_is_setup && (
|
||||
<FormSelect
|
||||
selectionMode="single"
|
||||
className="mt-24 flex-auto md:mt-0"
|
||||
name="config.autoUpdateProvider"
|
||||
label={<Trans message="Fetch content from" />}
|
||||
required
|
||||
>
|
||||
<Option value="tmdb">
|
||||
<Trans message="TheMovieDB" />
|
||||
</Option>
|
||||
<Option value="local">
|
||||
<Trans message="Local database" />
|
||||
</Option>
|
||||
</FormSelect>
|
||||
)}
|
||||
</ContentAutoUpdateField>
|
||||
);
|
||||
}
|
||||
254
resources/client/admin/channels/channel-content-config.tsx
Executable file
254
resources/client/admin/channels/channel-content-config.tsx
Executable file
@@ -0,0 +1,254 @@
|
||||
import {message} from '@common/i18n/message';
|
||||
import {
|
||||
MOVIE_MODEL,
|
||||
SERIES_MODEL,
|
||||
Title,
|
||||
TITLE_MODEL,
|
||||
} from '@app/titles/models/title';
|
||||
import {NEWS_ARTICLE_MODEL, NewsArticle} from '@app/titles/models/news-article';
|
||||
import {Channel, CHANNEL_MODEL} from '@common/channels/channel';
|
||||
import {ChannelContentConfig} from '@common/admin/channels/channel-editor/channel-content-config';
|
||||
import {Person, PERSON_MODEL} from '@app/titles/models/person';
|
||||
import {GridViewIcon} from '@common/icons/material/GridView';
|
||||
import {ViewWeekIcon} from '@common/icons/material/ViewWeek';
|
||||
import {ViewListIcon} from '@common/icons/material/ViewList';
|
||||
|
||||
export enum Sort {
|
||||
popular = 'popularity:desc',
|
||||
recent = 'created_at:desc',
|
||||
rating = 'rating:desc',
|
||||
curated = 'channelables.order:asc',
|
||||
name = 'name:asc',
|
||||
birthdayDesc = 'birth_date:desc',
|
||||
birthdayAsc = 'birth_date:asc',
|
||||
budget = 'budget:desc',
|
||||
revenue = 'revenue:desc',
|
||||
}
|
||||
export enum Layout {
|
||||
grid = 'grid',
|
||||
landscapeGrid = 'landscapeGrid',
|
||||
list = 'list',
|
||||
news = 'news',
|
||||
carousel = 'carousel',
|
||||
landscapeCarousel = 'landscapeCarousel',
|
||||
slider = 'slider',
|
||||
}
|
||||
enum Auto {
|
||||
latestVideos = 'latestVideos',
|
||||
mostPopular = 'mostPopular',
|
||||
topRated = 'topRated',
|
||||
upcoming = 'upcoming',
|
||||
nowPlaying = 'nowPlaying',
|
||||
airingToday = 'airingToday',
|
||||
airingThisWeek = 'airingThisWeek',
|
||||
trendingPeople = 'trendingPeople',
|
||||
discover = 'discover',
|
||||
}
|
||||
|
||||
const contentModels: ChannelContentConfig['models'] = {
|
||||
[MOVIE_MODEL]: {
|
||||
label: message('Movies'),
|
||||
sortMethods: [
|
||||
Sort.popular,
|
||||
Sort.recent,
|
||||
Sort.rating,
|
||||
Sort.budget,
|
||||
Sort.revenue,
|
||||
],
|
||||
layoutMethods: [
|
||||
Layout.grid,
|
||||
Layout.landscapeGrid,
|
||||
Layout.list,
|
||||
Layout.carousel,
|
||||
Layout.landscapeCarousel,
|
||||
Layout.slider,
|
||||
],
|
||||
autoUpdateMethods: [
|
||||
Auto.latestVideos,
|
||||
Auto.mostPopular,
|
||||
Auto.topRated,
|
||||
Auto.upcoming,
|
||||
Auto.nowPlaying,
|
||||
Auto.discover,
|
||||
],
|
||||
},
|
||||
[SERIES_MODEL]: {
|
||||
label: message('TV series'),
|
||||
sortMethods: [
|
||||
Sort.popular,
|
||||
Sort.recent,
|
||||
Sort.rating,
|
||||
Sort.budget,
|
||||
Sort.revenue,
|
||||
],
|
||||
layoutMethods: [
|
||||
Layout.grid,
|
||||
Layout.landscapeGrid,
|
||||
Layout.list,
|
||||
Layout.carousel,
|
||||
Layout.landscapeCarousel,
|
||||
Layout.slider,
|
||||
],
|
||||
autoUpdateMethods: [
|
||||
Auto.latestVideos,
|
||||
Auto.mostPopular,
|
||||
Auto.topRated,
|
||||
Auto.airingThisWeek,
|
||||
Auto.airingToday,
|
||||
Auto.discover,
|
||||
],
|
||||
},
|
||||
[TITLE_MODEL]: {
|
||||
label: message('Titles (movies and series)'),
|
||||
sortMethods: [
|
||||
Sort.popular,
|
||||
Sort.recent,
|
||||
Sort.rating,
|
||||
Sort.budget,
|
||||
Sort.revenue,
|
||||
],
|
||||
layoutMethods: [
|
||||
Layout.grid,
|
||||
Layout.landscapeGrid,
|
||||
Layout.list,
|
||||
Layout.carousel,
|
||||
Layout.landscapeCarousel,
|
||||
Layout.slider,
|
||||
],
|
||||
autoUpdateMethods: [Auto.latestVideos],
|
||||
},
|
||||
[NEWS_ARTICLE_MODEL]: {
|
||||
label: message('News articles'),
|
||||
sortMethods: [Sort.recent],
|
||||
layoutMethods: [Layout.news, Layout.landscapeCarousel, Layout.list],
|
||||
},
|
||||
[PERSON_MODEL]: {
|
||||
label: message('People'),
|
||||
sortMethods: [
|
||||
Sort.popular,
|
||||
Sort.recent,
|
||||
Sort.name,
|
||||
Sort.birthdayDesc,
|
||||
Sort.birthdayAsc,
|
||||
],
|
||||
layoutMethods: [Layout.grid, Layout.list, Layout.carousel],
|
||||
autoUpdateMethods: [Auto.trendingPeople],
|
||||
},
|
||||
[CHANNEL_MODEL]: {
|
||||
label: message('Channels'),
|
||||
sortMethods: [],
|
||||
layoutMethods: [Layout.list],
|
||||
},
|
||||
};
|
||||
|
||||
const contentSortingMethods: Record<
|
||||
Sort,
|
||||
ChannelContentConfig['sortingMethods']['any']
|
||||
> = {
|
||||
[Sort.popular]: {
|
||||
label: message('Most popular first'),
|
||||
},
|
||||
[Sort.recent]: {
|
||||
label: message('Recently added first'),
|
||||
},
|
||||
[Sort.rating]: {
|
||||
label: message('Highest rated first'),
|
||||
},
|
||||
[Sort.curated]: {
|
||||
label: message('Curated (reorder below)'),
|
||||
contentTypes: ['manual'],
|
||||
},
|
||||
[Sort.name]: {
|
||||
label: message('Name (A-Z)'),
|
||||
contentTypes: ['manual'],
|
||||
},
|
||||
[Sort.birthdayDesc]: {
|
||||
label: message('Youngest first'),
|
||||
},
|
||||
[Sort.birthdayAsc]: {
|
||||
label: message('Oldest first'),
|
||||
},
|
||||
[Sort.budget]: {
|
||||
label: message('Biggest budget first'),
|
||||
},
|
||||
[Sort.revenue]: {
|
||||
label: message('Biggest revenue first'),
|
||||
},
|
||||
};
|
||||
|
||||
const contentLayoutMethods: Record<
|
||||
Layout,
|
||||
ChannelContentConfig['layoutMethods']['any']
|
||||
> = {
|
||||
[Layout.grid]: {
|
||||
label: message('Grid'),
|
||||
icon: <GridViewIcon />,
|
||||
},
|
||||
[Layout.landscapeGrid]: {
|
||||
label: message('Landscape'),
|
||||
icon: <ViewWeekIcon />,
|
||||
},
|
||||
[Layout.list]: {
|
||||
label: message('List'),
|
||||
icon: <ViewListIcon />,
|
||||
},
|
||||
[Layout.carousel]: {
|
||||
label: message('Carousel (portrait)'),
|
||||
},
|
||||
[Layout.landscapeCarousel]: {
|
||||
label: message('Carousel (landscape)'),
|
||||
},
|
||||
[Layout.slider]: {
|
||||
label: message('Slider'),
|
||||
},
|
||||
[Layout.news]: {
|
||||
label: message('News'),
|
||||
},
|
||||
};
|
||||
|
||||
const contentAutoUpdateMethods: Record<
|
||||
Auto,
|
||||
ChannelContentConfig['autoUpdateMethods']['any']
|
||||
> = {
|
||||
[Auto.discover]: {
|
||||
label: message('Discover (TMDB only)'),
|
||||
provider: 'tmdb',
|
||||
},
|
||||
[Auto.mostPopular]: {
|
||||
label: message('Most popular'),
|
||||
},
|
||||
[Auto.topRated]: {
|
||||
label: message('Top rated'),
|
||||
},
|
||||
[Auto.upcoming]: {
|
||||
label: message('Upcoming'),
|
||||
},
|
||||
[Auto.nowPlaying]: {
|
||||
label: message('In theaters'),
|
||||
},
|
||||
[Auto.airingToday]: {
|
||||
label: message('Airing today'),
|
||||
},
|
||||
[Auto.airingThisWeek]: {
|
||||
label: message('Airing this week'),
|
||||
},
|
||||
[Auto.trendingPeople]: {
|
||||
label: message('Trending people'),
|
||||
},
|
||||
[Auto.latestVideos]: {
|
||||
label: message('Most recently published videos'),
|
||||
provider: 'local',
|
||||
},
|
||||
};
|
||||
export const channelContentConfig: ChannelContentConfig = {
|
||||
models: contentModels,
|
||||
sortingMethods: contentSortingMethods,
|
||||
layoutMethods: contentLayoutMethods,
|
||||
autoUpdateMethods: contentAutoUpdateMethods,
|
||||
userSelectableLayouts: [Layout.grid, Layout.landscapeGrid, Layout.list],
|
||||
};
|
||||
|
||||
export type ChannelContentModel = (Title | NewsArticle | Person | Channel) & {
|
||||
channelable_id?: number;
|
||||
channelable_order?: number;
|
||||
};
|
||||
24
resources/client/admin/channels/channel-content-item-image.tsx
Executable file
24
resources/client/admin/channels/channel-content-item-image.tsx
Executable file
@@ -0,0 +1,24 @@
|
||||
import {NormalizedModel} from '@common/datatable/filters/normalized-model';
|
||||
import {useImageSrc} from '@app/images/use-image-src';
|
||||
import clsx from 'clsx';
|
||||
import {ImageIcon} from '@common/icons/material/Image';
|
||||
|
||||
interface Props {
|
||||
item: NormalizedModel;
|
||||
}
|
||||
export function ChannelContentItemImage({item}: Props) {
|
||||
const src = useImageSrc(item.image, {size: 'sm'});
|
||||
|
||||
const imageClassName = clsx(
|
||||
'aspect-square w-40 rounded object-cover',
|
||||
!src ? 'flex items-center justify-center' : 'block',
|
||||
);
|
||||
|
||||
return src ? (
|
||||
<img className={imageClassName} src={src} alt="" />
|
||||
) : (
|
||||
<span className={imageClassName}>
|
||||
<ImageIcon className="max-w-[60%] text-divider" size="text-6xl" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
140
resources/client/admin/channels/channel-restriction-field.tsx
Executable file
140
resources/client/admin/channels/channel-restriction-field.tsx
Executable file
@@ -0,0 +1,140 @@
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {Item} from '@common/ui/forms/listbox/item';
|
||||
import {FormSelect} from '@common/ui/forms/select/select';
|
||||
import React, {Fragment, useState} from 'react';
|
||||
import {GENRE_MODEL} from '@app/titles/models/genre';
|
||||
import {KEYWORD_MODEL} from '@app/titles/models/keyword';
|
||||
import {PRODUCTION_COUNTRY_MODEL} from '@app/titles/models/production-country';
|
||||
import {useValueLists} from '@common/http/value-lists';
|
||||
import {message} from '@common/i18n/message';
|
||||
import {useTrans} from '@common/i18n/use-trans';
|
||||
import {useFormContext} from 'react-hook-form';
|
||||
import {UpdateChannelPayload} from '@common/admin/channels/requests/use-update-channel';
|
||||
import {MOVIE_MODEL, SERIES_MODEL, TITLE_MODEL} from '@app/titles/models/title';
|
||||
import {InfoDialogTrigger} from '@common/ui/overlays/dialog/info-dialog-trigger/info-dialog-trigger';
|
||||
import clsx from 'clsx';
|
||||
import {ChannelsDocsLink} from '@common/admin/channels/channels-docs-link';
|
||||
|
||||
const supportedModels = [TITLE_MODEL, MOVIE_MODEL, SERIES_MODEL];
|
||||
|
||||
const restrictions = {
|
||||
[GENRE_MODEL]: message('Genre'),
|
||||
[KEYWORD_MODEL]: message('Keyword'),
|
||||
[PRODUCTION_COUNTRY_MODEL]: message('Production country'),
|
||||
};
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
export function ChannelRestrictionField({className}: Props) {
|
||||
const {setValue} = useFormContext<UpdateChannelPayload>();
|
||||
const {watch} = useFormContext<UpdateChannelPayload>();
|
||||
|
||||
if (!supportedModels.includes(watch('config.contentModel'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('items-end gap-14 md:flex', className)}>
|
||||
<FormSelect
|
||||
className="w-full flex-auto"
|
||||
name="config.restriction"
|
||||
selectionMode="single"
|
||||
label={
|
||||
<Fragment>
|
||||
<Trans message="Filter titles by" />
|
||||
<InfoTrigger />
|
||||
</Fragment>
|
||||
}
|
||||
onSelectionChange={() => {
|
||||
setValue('config.restrictionModelId', 'urlParam');
|
||||
}}
|
||||
>
|
||||
<Item value={null}>
|
||||
<Trans message="Don't filter titles" />
|
||||
</Item>
|
||||
{Object.entries(restrictions).map(([value, label]) => (
|
||||
<Item key={value} value={value}>
|
||||
<Trans {...label} />
|
||||
</Item>
|
||||
))}
|
||||
</FormSelect>
|
||||
<RestrictionModelField />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RestrictionModelField() {
|
||||
const {trans} = useTrans();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const {watch} = useFormContext<UpdateChannelPayload>();
|
||||
const {data} = useValueLists(['genres', 'productionCountries'], {
|
||||
type: watch('config.autoUpdateProvider'),
|
||||
});
|
||||
|
||||
const selectedRestriction = watch(
|
||||
'config.restriction',
|
||||
) as keyof typeof restrictions;
|
||||
const selectedKeywordId = watch('config.restrictionModelId');
|
||||
const keywordQuery = useValueLists(['keywords'], {
|
||||
searchQuery: searchValue,
|
||||
selectedValue: selectedKeywordId,
|
||||
type: watch('config.autoUpdateProvider'),
|
||||
});
|
||||
|
||||
if (!selectedRestriction) return null;
|
||||
|
||||
const options = {
|
||||
[GENRE_MODEL]: data?.genres,
|
||||
[KEYWORD_MODEL]: keywordQuery.data?.keywords,
|
||||
[PRODUCTION_COUNTRY_MODEL]: data?.productionCountries,
|
||||
};
|
||||
const restrictionLabel = restrictions[selectedRestriction];
|
||||
|
||||
// allow setting keyword to custom value, because there are too many keywords
|
||||
// to put into autocomplete, ideally it would use async search from backend though
|
||||
|
||||
return (
|
||||
<FormSelect
|
||||
className="w-full flex-auto"
|
||||
name="config.restrictionModelId"
|
||||
selectionMode="single"
|
||||
showSearchField
|
||||
searchPlaceholder={trans(message('Search...'))}
|
||||
isAsync={selectedRestriction === KEYWORD_MODEL}
|
||||
isLoading={
|
||||
selectedRestriction === KEYWORD_MODEL && keywordQuery.isLoading
|
||||
}
|
||||
inputValue={searchValue}
|
||||
onInputValueChange={setSearchValue}
|
||||
label={
|
||||
<Trans
|
||||
message=":restriction name"
|
||||
values={{restriction: trans(restrictionLabel)}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Item value="urlParam">
|
||||
<Trans message="Dynamic (from url)" />
|
||||
</Item>
|
||||
{options[selectedRestriction]?.map(option => (
|
||||
<Item key={option.value} value={option.value}>
|
||||
<Trans message={option.name} />
|
||||
</Item>
|
||||
))}
|
||||
</FormSelect>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoTrigger() {
|
||||
return (
|
||||
<InfoDialogTrigger
|
||||
body={
|
||||
<Fragment>
|
||||
<Trans message="Allows specifying additional condition channel content should be filtered on. " />
|
||||
<ChannelsDocsLink className="mt-20" hash="filter-titles-by" />
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
26
resources/client/admin/channels/channel-seo-fields.tsx
Executable file
26
resources/client/admin/channels/channel-seo-fields.tsx
Executable file
@@ -0,0 +1,26 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {useTrans} from '@common/i18n/use-trans';
|
||||
import {message} from '@common/i18n/message';
|
||||
|
||||
export function ChannelSeoFields() {
|
||||
const {trans} = useTrans();
|
||||
return (
|
||||
<Fragment>
|
||||
<FormTextField
|
||||
name="config.seoTitle"
|
||||
label={<Trans message="SEO title" />}
|
||||
className="mb-24"
|
||||
placeholder={trans(message('Optional'))}
|
||||
/>
|
||||
<FormTextField
|
||||
name="config.seoDescription"
|
||||
label={<Trans message="SEO description" />}
|
||||
inputElementType="textarea"
|
||||
rows={6}
|
||||
placeholder={trans(message('Optional'))}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
98
resources/client/admin/channels/create-channel-page.tsx
Executable file
98
resources/client/admin/channels/create-channel-page.tsx
Executable file
@@ -0,0 +1,98 @@
|
||||
import React, {ReactElement} from 'react';
|
||||
import {CreateChannelPageLayout} from '@common/admin/channels/channel-editor/create-channel-page-layout';
|
||||
import {MOVIE_MODEL} from '@app/titles/models/title';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {ChannelNameField} from '@common/admin/channels/channel-editor/controls/channel-name-field';
|
||||
import {FormSwitch} from '@common/ui/forms/toggle/switch';
|
||||
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {ContentTypeField} from '@common/admin/channels/channel-editor/controls/content-type-field';
|
||||
import {channelContentConfig} from '@app/admin/channels/channel-content-config';
|
||||
import {ContentModelField} from '@common/admin/channels/channel-editor/controls/content-model-field';
|
||||
import {ChannelRestrictionField} from '@app/admin/channels/channel-restriction-field';
|
||||
import {ContentOrderField} from '@common/admin/channels/channel-editor/controls/content-order-field';
|
||||
import {ContentLayoutFields} from '@common/admin/channels/channel-editor/controls/content-layout-fields';
|
||||
import {ChannelPaginationTypeField} from '@common/admin/channels/channel-editor/controls/channel-pagination-type-field';
|
||||
import {ChannelAutoUpdateField} from '@app/admin/channels/channel-auto-update-field';
|
||||
import {ChannelSeoFields} from '@app/admin/channels/channel-seo-fields';
|
||||
import clsx from 'clsx';
|
||||
import {Tabs} from '@common/ui/tabs/tabs';
|
||||
import {TabList} from '@common/ui/tabs/tab-list';
|
||||
import {Tab} from '@common/ui/tabs/tab';
|
||||
import {TabPanel, TabPanels} from '@common/ui/tabs/tab-panels';
|
||||
|
||||
export function CreateChannelPage() {
|
||||
return (
|
||||
<CreateChannelPageLayout
|
||||
defaultValues={{
|
||||
contentModel: MOVIE_MODEL,
|
||||
autoUpdateProvider: 'local',
|
||||
layout: 'grid',
|
||||
nestedLayout: 'carousel',
|
||||
paginationType: 'infiniteScroll',
|
||||
}}
|
||||
>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>
|
||||
<Trans message="Settings" />
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Trans message="SEO" />
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels className="pt-24">
|
||||
<TabPanel>
|
||||
<ChannelNameField />
|
||||
<FormSwitch
|
||||
className="mt-24"
|
||||
name="config.hideTitle"
|
||||
description={
|
||||
<Trans message="Whether title should be shown when displaying this channel on the site." />
|
||||
}
|
||||
>
|
||||
<Trans message="Hide title" />
|
||||
</FormSwitch>
|
||||
<FormTextField
|
||||
name="description"
|
||||
label={<Trans message="Description" />}
|
||||
inputElementType="textarea"
|
||||
rows={2}
|
||||
className="my-24"
|
||||
/>
|
||||
<ContentTypeField config={channelContentConfig} className="mb-24" />
|
||||
<ChannelAutoUpdateField className="mb-24" />
|
||||
<ContentModelField
|
||||
config={channelContentConfig}
|
||||
className="mb-24"
|
||||
/>
|
||||
<ChannelRestrictionField className="mb-24" />
|
||||
<ContentOrderField config={channelContentConfig} />
|
||||
<ContentLayoutFields
|
||||
config={channelContentConfig}
|
||||
className="my-24"
|
||||
/>
|
||||
<ChannelPaginationTypeField
|
||||
config={channelContentConfig}
|
||||
className="mb-24"
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ChannelSeoFields />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</CreateChannelPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
interface TitleProps {
|
||||
children: ReactElement;
|
||||
className?: string;
|
||||
}
|
||||
function Title({children, className}: TitleProps) {
|
||||
return (
|
||||
<h2 className={clsx('mb-20 mt-20 border-t pt-20 text-2xl', className)}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
115
resources/client/admin/channels/edit-channel-page.tsx
Executable file
115
resources/client/admin/channels/edit-channel-page.tsx
Executable file
@@ -0,0 +1,115 @@
|
||||
import {EditChannelPageLayout} from '@common/admin/channels/channel-editor/edit-channel-page-layout';
|
||||
import React, {Fragment} from 'react';
|
||||
import {Accordion, AccordionItem} from '@common/ui/accordion/accordion';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DescriptionIcon} from '@common/icons/material/Description';
|
||||
import {ChannelNameField} from '@common/admin/channels/channel-editor/controls/channel-name-field';
|
||||
import {FormSwitch} from '@common/ui/forms/toggle/switch';
|
||||
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {InfoDialogTrigger} from '@common/ui/overlays/dialog/info-dialog-trigger/info-dialog-trigger';
|
||||
import {SettingsIcon} from '@common/icons/material/Settings';
|
||||
import {ContentTypeField} from '@common/admin/channels/channel-editor/controls/content-type-field';
|
||||
import {channelContentConfig} from '@app/admin/channels/channel-content-config';
|
||||
import {ChannelAutoUpdateField} from '@app/admin/channels/channel-auto-update-field';
|
||||
import {ContentModelField} from '@common/admin/channels/channel-editor/controls/content-model-field';
|
||||
import {ChannelRestrictionField} from '@app/admin/channels/channel-restriction-field';
|
||||
import {ContentOrderField} from '@common/admin/channels/channel-editor/controls/content-order-field';
|
||||
import {DashboardIcon} from '@common/icons/material/Dashboard';
|
||||
import {ContentLayoutFields} from '@common/admin/channels/channel-editor/controls/content-layout-fields';
|
||||
import {ChannelPaginationTypeField} from '@common/admin/channels/channel-editor/controls/channel-pagination-type-field';
|
||||
import {PublicIcon} from '@common/icons/material/Public';
|
||||
import {ChannelSeoFields} from '@app/admin/channels/channel-seo-fields';
|
||||
import {ChannelContentEditor} from '@common/admin/channels/channel-editor/channel-content-editor';
|
||||
import {
|
||||
ChannelContentSearchField,
|
||||
ChannelContentSearchFieldProps,
|
||||
} from '@common/admin/channels/channel-editor/channel-content-search-field';
|
||||
import {ChannelContentItemImage} from '@app/admin/channels/channel-content-item-image';
|
||||
|
||||
export function EditChannelPage() {
|
||||
return (
|
||||
<EditChannelPageLayout>
|
||||
<Fragment>
|
||||
<Accordion variant="outline">
|
||||
<AccordionItem
|
||||
label={<Trans message="Title & description" />}
|
||||
startIcon={<DescriptionIcon />}
|
||||
>
|
||||
<ChannelNameField />
|
||||
<FormSwitch
|
||||
className="mt-24"
|
||||
name="config.hideTitle"
|
||||
description={
|
||||
<Trans message="Whether title should be shown when displaying this channel on the site." />
|
||||
}
|
||||
>
|
||||
<Trans message="Hide title" />
|
||||
</FormSwitch>
|
||||
<FormTextField
|
||||
name="description"
|
||||
label={<Trans message="Description" />}
|
||||
inputElementType="textarea"
|
||||
rows={1}
|
||||
className="mt-24"
|
||||
/>
|
||||
<FormTextField
|
||||
name="config.adminDescription"
|
||||
label={
|
||||
<Fragment>
|
||||
<Trans message="Internal description" />
|
||||
<InfoDialogTrigger
|
||||
body={
|
||||
<Trans message="This describes the purpose of the channel and is only visible in admin area." />
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
inputElementType="textarea"
|
||||
rows={1}
|
||||
className="mt-24"
|
||||
/>
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
label={<Trans message="Content settings" />}
|
||||
startIcon={<SettingsIcon />}
|
||||
>
|
||||
<ContentTypeField config={channelContentConfig} className="mb-24" />
|
||||
<ChannelAutoUpdateField className="mb-24" />
|
||||
<ContentModelField
|
||||
config={channelContentConfig}
|
||||
className="mb-24"
|
||||
/>
|
||||
<ChannelRestrictionField className="mb-24" />
|
||||
<ContentOrderField config={channelContentConfig} />
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
label={<Trans message="Layout" />}
|
||||
startIcon={<DashboardIcon />}
|
||||
>
|
||||
<ContentLayoutFields
|
||||
config={channelContentConfig}
|
||||
className="mb-24"
|
||||
/>
|
||||
<ChannelPaginationTypeField config={channelContentConfig} />
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
label={<Trans message="SEO" />}
|
||||
startIcon={<PublicIcon />}
|
||||
>
|
||||
<ChannelSeoFields />
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<ChannelContentEditor searchField={<SearchField />} />
|
||||
</Fragment>
|
||||
</EditChannelPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchField(props: ChannelContentSearchFieldProps) {
|
||||
return (
|
||||
<ChannelContentSearchField
|
||||
{...props}
|
||||
imgRenderer={item => <ChannelContentItemImage item={item} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user