Merge pull request #7 from kappa118/main

React UI is live! and other changes...
This commit is contained in:
kappa118 2025-02-27 08:07:14 -05:00 committed by GitHub
commit e4d2f59c70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
811 changed files with 11234 additions and 236676 deletions

View file

@ -66,6 +66,13 @@ class ChannelSerializer(serializers.ModelSerializer):
required=False
)
stream_profile_id = serializers.PrimaryKeyRelatedField(
queryset=StreamProfile.objects.all(),
source='stream_profile',
allow_null=True,
required=False
)
# Possibly show streams inline, or just by ID
# streams = StreamSerializer(many=True, read_only=True)

View file

@ -50,8 +50,10 @@ class EPGGridAPIView(APIView):
responses={200: ProgramDataSerializer(many=True)}
)
def get(self, request, format=None):
now = timezone.now()
twelve_hours_later = now + timedelta(hours=12)
# Get current date and reset time to midnight (00:00)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
twelve_hours_later = now + timedelta(hours=24)
logger.debug(f"EPGGridAPIView: Querying programs between {now} and {twelve_hours_later}.")
# Use select_related to prefetch EPGData and Channel data
programs = ProgramData.objects.select_related('epg__channel').filter(

View file

@ -49,7 +49,10 @@ ROOT_URLCONF = 'dispatcharr.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'DIRS': [
os.path.join(BASE_DIR, 'frontend/build'),
BASE_DIR / "templates"
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -98,8 +101,14 @@ USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles' # Directory where static files will be collected
# Adjust STATICFILES_DIRS to include the paths to the directories that contain your static files.
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'frontend/build/static'), # React build static files
BASE_DIR / 'static', # Django custom static files (if any)
]
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.User'
@ -116,16 +125,15 @@ SERVER_IP = "127.0.0.1"
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
if os.getenv('REACT_UI', False):
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False, # Optional: Whether to rotate refresh tokens
'BLACKLIST_AFTER_ROTATION': True, # Optional: Whether to blacklist refresh tokens
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False, # Optional: Whether to rotate refresh tokens
'BLACKLIST_AFTER_ROTATION': True, # Optional: Whether to blacklist refresh tokens
}

View file

@ -2,7 +2,7 @@ from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.views.generic import RedirectView
from django.views.generic import TemplateView
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
@ -21,39 +21,30 @@ schema_view = get_schema_view(
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('', RedirectView.as_view(pattern_name='dashboard:dashboard'), name='home'),
# API Routes
path('api/', include(('apps.api.urls', 'api'), namespace='api')),
# Admin
path('admin/', admin.site.urls),
path('accounts/', include(('apps.accounts.urls', 'accounts'), namespace='accounts')),
#path('streams/', include(('apps.streams.urls', 'streams'), namespace='streams')),
#path('hdhr/', include(('apps.hdhr.urls', 'hdhr'), namespace='hdhr')),
path('m3u/', include(('apps.m3u.urls', 'm3u'), namespace='m3u')),
path('epg/', include(('apps.epg.urls', 'epg'), namespace='epg')),
path('channels/', include(('apps.channels.urls', 'channels'), namespace='channels')),
path('settings/', include(('core.urls', 'settings'), namespace='settings')),
#path('backup/', include(('apps.backup.urls', 'backup'), namespace='backup')),
path('dashboard/', include(('apps.dashboard.urls', 'dashboard'), namespace='dashboard')),
path('output/', include('apps.output.urls', namespace='output')),
# Swagger UI:
# Swagger UI
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
# ReDoc UI:
# ReDoc UI
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
# Optionally, you can also serve the raw JSON:
# Optionally, serve the raw Swagger JSON
path('swagger.json', schema_view.without_ui(cache_timeout=0), name='schema-json'),
# Catch-all route to serve React's index.html for non-API, non-admin paths
path('', TemplateView.as_view(template_name='index.html')), # React entry point
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Serve static files for development (React's JS, CSS, etc.)
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += [path('<path:unused_path>', TemplateView.as_view(template_name='index.html'))]

View file

@ -6,6 +6,7 @@ services:
container_name: dispatcharr_web
ports:
- "9191:9191"
- "3031:3031"
depends_on:
- db
- redis
@ -25,11 +26,10 @@ services:
ui:
image: alpine
container_name: dispatcharr_ui
network_mode: service:web
volumes:
- ../frontend:/app
entrypoint: ["/bin/sh", "/app/entrypoint.sh"]
ports:
- 3031:3031
celery:
build:

View file

@ -14,6 +14,7 @@
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"eslint": "^8.57.1",
"formik": "^2.4.6",
"material-react-table": "^3.2.0",
@ -23,6 +24,7 @@
"react-dom": "^19.0.0",
"react-router-dom": "^7.2.0",
"react-scripts": "5.0.1",
"react-window": "^1.8.11",
"web-vitals": "^2.1.4",
"yup": "^1.6.1",
"zustand": "^5.0.3"
@ -6511,6 +6513,12 @@
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@ -11183,6 +11191,12 @@
"node": ">= 4.0.0"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@ -13783,6 +13797,23 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react-window": {
"version": "1.8.11",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz",
"integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
},
"engines": {
"node": ">8.0.0"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View file

@ -9,6 +9,7 @@
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"eslint": "^8.57.1",
"formik": "^2.4.6",
"material-react-table": "^3.2.0",
@ -18,6 +19,7 @@
"react-dom": "^19.0.0",
"react-router-dom": "^7.2.0",
"react-scripts": "5.0.1",
"react-window": "^1.8.11",
"web-vitals": "^2.1.4",
"yup": "^1.6.1",
"zustand": "^5.0.3"
@ -46,5 +48,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"proxy": "http://127.0.0.1:9191"
}

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />

View file

@ -11,9 +11,6 @@ import Channels from './pages/Channels';
import M3U from './pages/M3U';
import { ThemeProvider } from '@mui/material/styles'; // Import theme tools
import {
AppBar,
Toolbar,
Typography,
Box,
CssBaseline,
Drawer,
@ -28,7 +25,7 @@ import EPG from './pages/EPG';
import Guide from './pages/Guide';
import StreamProfiles from './pages/StreamProfiles';
import useAuthStore from './store/auth';
import API from './api';
import logo from './images/logo.png';
const drawerWidth = 240;
const miniDrawerWidth = 60;
@ -104,7 +101,7 @@ const App = () => {
pb: 0,
}}
>
<img src="/images/logo.png" width="33x" />
<img src={logo} width="33x" />
{open && (
<ListItemText primary="Dispatcharr" sx={{ paddingLeft: 3 }} />
)}

View file

@ -1,4 +1,3 @@
import Axios from 'axios';
import useAuthStore from './store/auth';
import useChannelsStore from './store/channels';
import useUserAgentsStore from './store/userAgents';
@ -11,7 +10,7 @@ import useStreamProfilesStore from './store/streamProfiles';
// withCredentials: true,
// });
const host = 'http://192.168.1.151:9191';
const host = '';
const getAuthToken = async () => {
const token = await useAuthStore.getState().getToken(); // Assuming token is stored in Zustand store
@ -602,7 +601,6 @@ export default class API {
});
const retval = await response.json();
console.log(retval);
return retval;
return retval.data;
}
}

View file

@ -31,6 +31,7 @@ import {
} from 'material-react-table';
import ChannelGroupForm from './ChannelGroup';
import usePlaylistsStore from '../../store/playlists';
import logo from '../../images/logo.png';
const Channel = ({ channel = null, isOpen, onClose }) => {
const channelGroups = useChannelsStore((state) => state.channelGroups);
@ -38,8 +39,8 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
const { profiles: streamProfiles } = useStreamProfilesStore();
const { playlists } = usePlaylistsStore();
const [logo, setLogo] = useState(null);
const [logoPreview, setLogoPreview] = useState('/images/logo.png');
const [logoFile, setLogoFile] = useState(null);
const [logoPreview, setLogoPreview] = useState(logo);
const [channelStreams, setChannelStreams] = useState([]);
const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false);
@ -58,7 +59,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
const handleLogoChange = (e) => {
const file = e.target.files[0];
if (file) {
setLogo(file);
setLogoFile(file);
setLogoPreview(URL.createObjectURL(file));
}
};
@ -83,20 +84,20 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
await API.updateChannel({
id: channel.id,
...values,
logo_file: logo,
logo_file: logoFile,
streams: channelStreams.map((stream) => stream.id),
});
} else {
await API.addChannel({
...values,
logo_file: logo,
logo_file: logoFile,
streams: channelStreams.map((stream) => stream.id),
});
}
resetForm();
setLogo(null);
setLogoPreview('/images/logo.png');
setLogoFile(null);
setLogoPreview(logo);
setSubmitting(false);
onClose();
},

View file

@ -31,7 +31,7 @@ const Stream = ({ stream = null, isOpen, onClose }) => {
validationSchema: Yup.object({
name: Yup.string().required('Name is required'),
url: Yup.string().required('URL is required').min(0),
stream_profile_id: Yup.string().required('Stream profile is required'),
// stream_profile_id: Yup.string().required('Stream profile is required'),
}),
onSubmit: async (values, { setSubmitting, resetForm }) => {
if (stream?.id) {

View file

@ -28,6 +28,7 @@ import ChannelForm from '../forms/Channel';
import { TableHelper } from '../../helpers';
import utils from '../../utils';
import { ContentCopy } from '@mui/icons-material';
import logo from '../../images/logo.png';
const Example = () => {
const [channel, setChannel] = useState(null);
@ -71,7 +72,7 @@ const Example = () => {
alignItems: 'center',
}}
>
<img src={info.getValue() || '/images/logo.png'} width="20" />
<img src={info.getValue() || logo} width="20" />
</Grid2>
),
meta: {
@ -175,7 +176,6 @@ const Example = () => {
...TableHelper.defaultProperties,
columns,
data: channels,
// enableGlobalFilterModes: true,
enablePagination: false,
// enableRowNumbers: true,
enableRowVirtualization: true,
@ -194,13 +194,14 @@ const Example = () => {
},
enableRowActions: true,
renderRowActions: ({ row }) => (
<>
<Box sx={{ justifyContent: 'right' }}>
<IconButton
size="small" // Makes the button smaller
color="warning" // Red color for delete actions
onClick={() => {
editChannel(row.original);
}}
sx={{ p: 0 }}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -208,14 +209,15 @@ const Example = () => {
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deleteChannel(row.original.id)}
sx={{ p: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
</>
</Box>
),
muiTableContainerProps: {
sx: {
height: 'calc(100vh - 90px)', // Subtract padding to avoid cutoff
height: 'calc(100vh - 75px)', // Subtract padding to avoid cutoff
overflowY: 'auto', // Internal scrolling for the table
},
},
@ -266,13 +268,13 @@ const Example = () => {
marginLeft: 1,
}}
>
<Button variant="contained" onClick={copyHDHRUrl}>
<Button variant="contained" size="small" onClick={copyHDHRUrl}>
HDHR URL
</Button>
<Button variant="contained" onClick={copyM3UUrl}>
<Button variant="contained" size="small" onClick={copyM3UUrl}>
M3U URL
</Button>
<Button variant="contained" onClick={copyEPGUrl}>
<Button variant="contained" size="small" onClick={copyEPGUrl}>
EPG
</Button>
</ButtonGroup>

View file

@ -1,10 +1,10 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
MaterialReactTable,
MRT_ShowHideColumnsButton,
MRT_ToggleFullScreenButton,
useMaterialReactTable,
} from "material-react-table";
} from 'material-react-table';
import {
Box,
Grid2,
@ -16,8 +16,8 @@ import {
Select,
MenuItem,
Snackbar,
} from "@mui/material";
import API from "../../api";
} from '@mui/material';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
@ -26,16 +26,16 @@ import {
Check as CheckIcon,
Close as CloseIcon,
Refresh as RefreshIcon,
} from "@mui/icons-material";
import useEPGsStore from "../../store/epgs";
import EPGForm from "../forms/EPG";
import { TableHelper } from "../../helpers";
} from '@mui/icons-material';
import useEPGsStore from '../../store/epgs';
import EPGForm from '../forms/EPG';
import { TableHelper } from '../../helpers';
const EPGsTable = () => {
const [epg, setEPG] = useState(null);
const [epgModalOpen, setEPGModalOpen] = useState(false);
const [rowSelection, setRowSelection] = useState([]);
const [snackbarMessage, setSnackbarMessage] = useState("");
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
const epgs = useEPGsStore((state) => state.epgs);
@ -44,21 +44,19 @@ const EPGsTable = () => {
//column definitions...
() => [
{
header: "Name",
size: 10,
accessorKey: "name",
header: 'Name',
accessorKey: 'name',
},
{
header: "Source Type",
accessorKey: "source_type",
size: 50,
header: 'Source Type',
accessorKey: 'source_type',
},
{
header: "URL / API Key",
accessorKey: "max_streams",
header: 'URL / API Key',
accessorKey: 'max_streams',
},
],
[],
[]
);
//optionally access the underlying virtualizer instance
@ -82,12 +80,12 @@ const EPGsTable = () => {
const refreshEPG = async (id) => {
await API.refreshEPG(id);
setSnackbarMessage("EPG refresh initiated");
setSnackbarMessage('EPG refresh initiated');
setSnackbarOpen(true);
};
useEffect(() => {
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
setIsLoading(false);
}
}, []);
@ -118,7 +116,7 @@ const EPGsTable = () => {
rowVirtualizerInstanceRef, //optional
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
initialState: {
density: "compact",
density: 'compact',
},
enableRowActions: true,
renderRowActions: ({ row }) => (
@ -127,6 +125,7 @@ const EPGsTable = () => {
size="small" // Makes the button smaller
color="info" // Red color for delete actions
onClick={() => editEPG(row.original)}
sx={{ pt: 0, pb: 0 }}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -134,6 +133,7 @@ const EPGsTable = () => {
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deleteEPG(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -141,6 +141,7 @@ const EPGsTable = () => {
size="small" // Makes the button smaller
color="info" // Red color for delete actions
onClick={() => refreshEPG(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<RefreshIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -148,14 +149,14 @@ const EPGsTable = () => {
),
muiTableContainerProps: {
sx: {
height: "calc(42vh - 0px)",
height: 'calc(43vh - 0px)',
},
},
renderTopToolbarCustomActions: ({ table }) => (
<Stack
direction="row"
sx={{
alignItems: "center",
alignItems: 'center',
}}
>
<Typography>EPGs</Typography>
@ -176,7 +177,7 @@ const EPGsTable = () => {
return (
<Box
sx={{
padding: 2,
padding: 1,
}}
>
<MaterialReactTable table={table} />
@ -188,7 +189,7 @@ const EPGsTable = () => {
/>
<Snackbar
anchorOrigin={{ vertical: "top", horizontal: "right" }}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={snackbarOpen}
autoHideDuration={5000}
onClose={closeSnackbar}

View file

@ -1,10 +1,10 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
MaterialReactTable,
MRT_ShowHideColumnsButton,
MRT_ToggleFullScreenButton,
useMaterialReactTable,
} from "material-react-table";
} from 'material-react-table';
import {
Box,
Grid2,
@ -15,8 +15,8 @@ import {
Checkbox,
Select,
MenuItem,
} from "@mui/material";
import API from "../../api";
} from '@mui/material';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
@ -25,16 +25,16 @@ import {
Check as CheckIcon,
Close as CloseIcon,
Refresh as RefreshIcon,
} from "@mui/icons-material";
import usePlaylistsStore from "../../store/playlists";
import M3UForm from "../forms/M3U";
import { TableHelper } from "../../helpers";
} from '@mui/icons-material';
import usePlaylistsStore from '../../store/playlists';
import M3UForm from '../forms/M3U';
import { TableHelper } from '../../helpers';
const Example = () => {
const [playlist, setPlaylist] = useState(null);
const [playlistModalOpen, setPlaylistModalOpen] = useState(false);
const [rowSelection, setRowSelection] = useState([]);
const [activeFilterValue, setActiveFilterValue] = useState("all");
const [activeFilterValue, setActiveFilterValue] = useState('all');
const playlists = usePlaylistsStore((state) => state.playlists);
@ -42,28 +42,28 @@ const Example = () => {
//column definitions...
() => [
{
header: "Name",
accessorKey: "name",
header: 'Name',
accessorKey: 'name',
},
{
header: "URL / File",
accessorKey: "server_url",
header: 'URL / File',
accessorKey: 'server_url',
},
{
header: "Max Streams",
accessorKey: "max_streams",
header: 'Max Streams',
accessorKey: 'max_streams',
size: 200,
},
{
header: "Active",
accessorKey: "is_active",
header: 'Active',
accessorKey: 'is_active',
size: 100,
sortingFn: "basic",
sortingFn: 'basic',
muiTableBodyCellProps: {
align: "left",
align: 'left',
},
Cell: ({ cell }) => (
<Box sx={{ display: "flex", justifyContent: "center" }}>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{cell.getValue() ? (
<CheckIcon color="success" />
) : (
@ -92,11 +92,11 @@ const Example = () => {
),
filterFn: (row, _columnId, activeFilterValue) => {
if (!activeFilterValue) return true; // Show all if no filter
return String(row.getValue("is_active")) === activeFilterValue;
return String(row.getValue('is_active')) === activeFilterValue;
},
},
],
[],
[]
);
//optionally access the underlying virtualizer instance
@ -126,7 +126,7 @@ const Example = () => {
};
useEffect(() => {
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
setIsLoading(false);
}
}, []);
@ -157,7 +157,7 @@ const Example = () => {
rowVirtualizerInstanceRef, //optional
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
initialState: {
density: "compact",
density: 'compact',
},
enableRowActions: true,
renderRowActions: ({ row }) => (
@ -168,6 +168,7 @@ const Example = () => {
onClick={() => {
editPlaylist(row.original);
}}
sx={{ pt: 0, pb: 0 }}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -175,6 +176,7 @@ const Example = () => {
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deletePlaylist(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -183,6 +185,7 @@ const Example = () => {
color="info" // Red color for delete actions
variant="contained"
onClick={() => refreshPlaylist(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<RefreshIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -190,14 +193,14 @@ const Example = () => {
),
muiTableContainerProps: {
sx: {
height: "calc(42vh - 0px)",
height: 'calc(43vh - 0px)',
},
},
renderTopToolbarCustomActions: ({ table }) => (
<Stack
direction="row"
sx={{
alignItems: "center",
alignItems: 'center',
}}
>
<Typography>M3U Accounts</Typography>
@ -218,7 +221,7 @@ const Example = () => {
return (
<Box
sx={{
padding: 2,
padding: 1,
}}
>
<MaterialReactTable table={table} />

View file

@ -1,10 +1,10 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
MaterialReactTable,
MRT_ShowHideColumnsButton,
MRT_ToggleFullScreenButton,
useMaterialReactTable,
} from "material-react-table";
} from 'material-react-table';
import {
Box,
Grid2,
@ -15,8 +15,8 @@ import {
Checkbox,
Select,
MenuItem,
} from "@mui/material";
import API from "../../api";
} from '@mui/material';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
@ -25,19 +25,19 @@ import {
Check as CheckIcon,
Close as CloseIcon,
Refresh as RefreshIcon,
} from "@mui/icons-material";
import useEPGsStore from "../../store/epgs";
import StreamProfileForm from "../forms/StreamProfile";
import useStreamProfilesStore from "../../store/streamProfiles";
import { TableHelper } from "../../helpers";
} from '@mui/icons-material';
import useEPGsStore from '../../store/epgs';
import StreamProfileForm from '../forms/StreamProfile';
import useStreamProfilesStore from '../../store/streamProfiles';
import { TableHelper } from '../../helpers';
const StreamProfiles = () => {
const [profile, setProfile] = useState(null);
const [profileModalOpen, setProfileModalOpen] = useState(false);
const [rowSelection, setRowSelection] = useState([]);
const [snackbarMessage, setSnackbarMessage] = useState("");
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [activeFilterValue, setActiveFilterValue] = useState("all");
const [activeFilterValue, setActiveFilterValue] = useState('all');
const streamProfiles = useStreamProfilesStore((state) => state.profiles);
@ -45,27 +45,27 @@ const StreamProfiles = () => {
//column definitions...
() => [
{
header: "Name",
accessorKey: "profile_name",
header: 'Name',
accessorKey: 'profile_name',
},
{
header: "Command",
accessorKey: "command",
header: 'Command',
accessorKey: 'command',
},
{
header: "Parameters",
accessorKey: "parameters",
header: 'Parameters',
accessorKey: 'parameters',
},
{
header: "Active",
accessorKey: "is_active",
header: 'Active',
accessorKey: 'is_active',
size: 100,
sortingFn: "basic",
sortingFn: 'basic',
muiTableBodyCellProps: {
align: "left",
align: 'left',
},
Cell: ({ cell }) => (
<Box sx={{ display: "flex", justifyContent: "center" }}>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{cell.getValue() ? (
<CheckIcon color="success" />
) : (
@ -93,12 +93,12 @@ const StreamProfiles = () => {
</Box>
),
filterFn: (row, _columnId, filterValue) => {
if (filterValue == "all") return true; // Show all if no filter
return String(row.getValue("is_active")) === filterValue;
if (filterValue == 'all') return true; // Show all if no filter
return String(row.getValue('is_active')) === filterValue;
},
},
],
[],
[]
);
//optionally access the underlying virtualizer instance
@ -117,7 +117,7 @@ const StreamProfiles = () => {
};
useEffect(() => {
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
setIsLoading(false);
}
}, []);
@ -148,7 +148,7 @@ const StreamProfiles = () => {
rowVirtualizerInstanceRef, //optional
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
initialState: {
density: "compact",
density: 'compact',
},
enableRowActions: true,
renderRowActions: ({ row }) => (
@ -157,6 +157,7 @@ const StreamProfiles = () => {
size="small" // Makes the button smaller
color="warning" // Red color for delete actions
onClick={() => editStreamProfile(row.original)}
sx={{ pt: 0, pb: 0 }}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -164,6 +165,7 @@ const StreamProfiles = () => {
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deleteStreamProfile(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -171,15 +173,15 @@ const StreamProfiles = () => {
),
muiTableContainerProps: {
sx: {
height: "calc(100vh - 100px)", // Subtract padding to avoid cutoff
overflowY: "auto", // Internal scrolling for the table
height: 'calc(100vh - 73px)', // Subtract padding to avoid cutoff
overflowY: 'auto', // Internal scrolling for the table
},
},
renderTopToolbarCustomActions: ({ table }) => (
<Stack
direction="row"
sx={{
alignItems: "center",
alignItems: 'center',
}}
>
<Typography>Stream Profiles</Typography>
@ -200,7 +202,7 @@ const StreamProfiles = () => {
return (
<Box
sx={{
padding: 2,
padding: 1,
}}
>
<MaterialReactTable table={table} />

View file

@ -116,7 +116,6 @@ const Example = () => {
columns,
data: streams,
// enableGlobalFilterModes: true,
enablePagination: false,
enableRowVirtualization: true,
enableRowSelection: true,
@ -132,39 +131,36 @@ const Example = () => {
enableRowActions: true,
renderRowActions: ({ row }) => (
<>
<Tooltip
title={
row.original.m3u_account ? 'M3U streams locked' : 'Edit Stream'
}
<IconButton
size="small" // Makes the button smaller
color="warning" // Red color for delete actions
onClick={() => editStream(row.original)}
disabled={row.original.m3u_account}
sx={{ p: 0 }}
>
<IconButton
size="small" // Makes the button smaller
color="warning" // Red color for delete actions
onClick={() => editStream(row.original)}
disabled={row.original.m3u_account}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
</Tooltip>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deleteStream(row.original.id)}
sx={{ p: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
<DeleteIcon fontSize="small" />
</IconButton>
<IconButton
size="small" // Makes the button smaller
color="success" // Red color for delete actions
onClick={() => createChannelFromStream(row.original)}
sx={{ p: 0 }}
>
<AddIcon fontSize="small" /> {/* Small icon size */}
<AddIcon fontSize="small" />
</IconButton>
</>
),
muiTableContainerProps: {
sx: {
height: 'calc(100vh - 90px)', // Subtract padding to avoid cutoff
height: 'calc(100vh - 75px)', // Subtract padding to avoid cutoff
overflowY: 'auto', // Internal scrolling for the table
},
},
@ -199,6 +195,7 @@ const Example = () => {
<Button
variant="contained"
onClick={createChannelsFromStreams}
size="small"
// disabled={rowSelection.length === 0}
sx={{
marginLeft: 1,

View file

@ -1,10 +1,10 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
MaterialReactTable,
MRT_ShowHideColumnsButton,
MRT_ToggleFullScreenButton,
useMaterialReactTable,
} from "material-react-table";
} from 'material-react-table';
import {
Box,
Grid2,
@ -14,24 +14,24 @@ import {
Tooltip,
Select,
MenuItem,
} from "@mui/material";
import API from "../../api";
} from '@mui/material';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
Check as CheckIcon,
Close as CloseIcon,
} from "@mui/icons-material";
import useUserAgentsStore from "../../store/userAgents";
import UserAgentForm from "../forms/UserAgent";
import { TableHelper } from "../../helpers";
} from '@mui/icons-material';
import useUserAgentsStore from '../../store/userAgents';
import UserAgentForm from '../forms/UserAgent';
import { TableHelper } from '../../helpers';
const UserAgentsTable = () => {
const [userAgent, setUserAgent] = useState(null);
const [userAgentModalOpen, setUserAgentModalOpen] = useState(false);
const [rowSelection, setRowSelection] = useState([]);
const [activeFilterValue, setActiveFilterValue] = useState("all");
const [activeFilterValue, setActiveFilterValue] = useState('all');
const userAgents = useUserAgentsStore((state) => state.userAgents);
@ -39,29 +39,27 @@ const UserAgentsTable = () => {
//column definitions...
() => [
{
header: "Name",
size: 10,
accessorKey: "user_agent_name",
header: 'Name',
accessorKey: 'user_agent_name',
},
{
header: "User-Agent",
accessorKey: "user_agent",
size: 50,
header: 'User-Agent',
accessorKey: 'user_agent',
},
{
header: "Desecription",
accessorKey: "description",
header: 'Desecription',
accessorKey: 'description',
},
{
header: "Active",
accessorKey: "is_active",
header: 'Active',
accessorKey: 'is_active',
size: 100,
sortingFn: "basic",
sortingFn: 'basic',
muiTableBodyCellProps: {
align: "left",
align: 'left',
},
Cell: ({ cell }) => (
<Box sx={{ display: "flex", justifyContent: "center" }}>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{cell.getValue() ? (
<CheckIcon color="success" />
) : (
@ -89,12 +87,12 @@ const UserAgentsTable = () => {
</Box>
),
filterFn: (row, _columnId, activeFilterValue) => {
if (activeFilterValue == "all") return true; // Show all if no filter
return String(row.getValue("is_active")) === activeFilterValue;
if (activeFilterValue == 'all') return true; // Show all if no filter
return String(row.getValue('is_active')) === activeFilterValue;
},
},
],
[],
[]
);
//optionally access the underlying virtualizer instance
@ -117,7 +115,7 @@ const UserAgentsTable = () => {
};
useEffect(() => {
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
setIsLoading(false);
}
}, []);
@ -148,7 +146,7 @@ const UserAgentsTable = () => {
rowVirtualizerInstanceRef, //optional
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
initialState: {
density: "compact",
density: 'compact',
},
enableRowActions: true,
renderRowActions: ({ row }) => (
@ -159,6 +157,7 @@ const UserAgentsTable = () => {
onClick={() => {
editUserAgent(row.original);
}}
sx={{ pt: 0, pb: 0 }}
>
<EditIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -166,6 +165,7 @@ const UserAgentsTable = () => {
size="small" // Makes the button smaller
color="error" // Red color for delete actions
onClick={() => deleteUserAgent(row.original.id)}
sx={{ pt: 0, pb: 0 }}
>
<DeleteIcon fontSize="small" /> {/* Small icon size */}
</IconButton>
@ -173,14 +173,14 @@ const UserAgentsTable = () => {
),
muiTableContainerProps: {
sx: {
height: "calc(42vh - 10px)",
height: 'calc(42vh + 5px)',
},
},
renderTopToolbarCustomActions: ({ table }) => (
<Stack
direction="row"
sx={{
alignItems: "center",
alignItems: 'center',
}}
>
<Typography>User-Agents</Typography>
@ -202,7 +202,7 @@ const UserAgentsTable = () => {
<>
<Box
sx={{
padding: 2,
padding: 1,
}}
>
<MaterialReactTable table={table} />

View file

@ -1,14 +1,33 @@
export default {
defaultProperties: {
enableGlobalFilter: false,
enableBottomToolbar: false,
enableDensityToggle: false,
enableFullScreenToggle: false,
positionToolbarAlertBanner: "none",
columnFilterDisplayMode: "popover",
positionToolbarAlertBanner: 'none',
columnFilterDisplayMode: 'popover',
enableRowNumbers: false,
positionActionsColumn: "last",
positionActionsColumn: 'last',
initialState: {
density: "compact",
density: 'compact',
},
muiTableBodyCellProps: {
sx: {
padding: 0,
},
},
muiTableHeadCellProps: {
sx: {
padding: 0,
},
},
muiTableBodyProps: {
sx: {
//stripe the rows, make odd rows a darker color
'& tr:nth-of-type(odd) > td': {
backgroundColor: '#f5f5f5',
},
},
},
},
};

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
import React from "react";
import ChannelsTable from "../components/tables/ChannelsTable";
import StreamsTable from "../components/tables/StreamsTable";
import { Grid2, Box } from "@mui/material";
import React from 'react';
import ChannelsTable from '../components/tables/ChannelsTable';
import StreamsTable from '../components/tables/StreamsTable';
import { Grid2, Box } from '@mui/material';
const ChannelsPage = () => {
return (
@ -9,13 +9,13 @@ const ChannelsPage = () => {
<Grid2 size={6}>
<Box
sx={{
height: "100vh", // Full viewport height
paddingTop: "20px", // Top padding
paddingBottom: "20px", // Bottom padding
paddingRight: "10px",
paddingLeft: "20px",
boxSizing: "border-box", // Include padding in height calculation
overflow: "hidden", // Prevent parent scrolling
height: '100vh', // Full viewport height
paddingTop: 1, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 0.5,
paddingLeft: 1,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
}}
>
<ChannelsTable />
@ -24,13 +24,13 @@ const ChannelsPage = () => {
<Grid2 size={6}>
<Box
sx={{
height: "100vh", // Full viewport height
paddingTop: "20px", // Top padding
paddingBottom: "20px", // Bottom padding
paddingRight: "20px",
paddingLeft: "10px",
boxSizing: "border-box", // Include padding in height calculation
overflow: "hidden", // Prevent parent scrolling
height: '100vh', // Full viewport height
paddingTop: 1, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 1,
paddingLeft: 0.5,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
}}
>
<StreamsTable />

View file

@ -1,91 +1,325 @@
import React from 'react';
import { useEpg, Epg, Layout } from 'planby';
import React, { useMemo, useState, useEffect, useRef } from 'react';
import { Box, Typography, Avatar, Paper, Tooltip, Stack } from '@mui/material';
import dayjs from 'dayjs';
import API from '../api';
import useChannelsStore from '../store/channels';
import logo from '../images/logo.png';
function App() {
const [channels, setChannels] = React.useState([]);
const [epg, setEpg] = React.useState([]);
const CHANNEL_WIDTH = 100;
const PROGRAM_HEIGHT = 80;
const HOUR_WIDTH = 300;
const fetchChannels = async () => {
const channels = await API.getChannels();
const retval = [];
for (const channel of channels) {
if (!channel.tvg_id) {
continue;
}
console.log(channel);
retval.push({
uuid: channel.tvg_id,
type: 'channel',
title: channel.channel_name,
country: 'USA',
provider: channel.channel_group?.name || 'Default',
logo: channel.logo_url || '/images/logo.png',
year: 2025,
});
}
const TVChannelGuide = ({ startDate, endDate }) => {
const { channels } = useChannelsStore();
setChannels(retval);
return retval;
};
const [programs, setPrograms] = useState([]);
const [guideChannels, setGuideChannels] = useState([]);
const [now, setNow] = useState(dayjs());
const fetchEpg = async () => {
const programs = await API.getGrid();
const retval = [];
console.log(programs);
for (const program of programs.data) {
retval.push({
id: program.id,
channelUuid: 'Nickelodeon (East).us',
description: program.description,
title: program.title,
since: program.start_time,
till: program.end_time,
});
}
const guideRef = useRef(null);
setEpg(retval);
return retval;
};
const fetchData = async () => {
const channels = await fetchChannels();
const epg = await fetchEpg();
setChannels(channels);
setEpg(epg);
};
if (channels.length === 0) {
fetchData();
if (!channels || channels.length === 0) {
console.warn('No channels provided or empty channels array');
}
const formatDate = (date) => date.toISOString().split('T')[0] + 'T00:00:00';
useEffect(() => {
const fetchPrograms = async () => {
const programs = await API.getGrid();
const programIds = [...new Set(programs.map((prog) => prog.tvg_id))];
console.log(programIds);
const filteredChannels = channels.filter((ch) =>
programIds.includes(ch.tvg_id)
);
console.log(filteredChannels);
setGuideChannels(filteredChannels);
setPrograms(programs);
};
const today = new Date();
const tomorrow = new Date(today);
fetchPrograms();
}, [channels]);
const {
getEpgProps,
getLayoutProps,
onScrollToNow,
onScrollLeft,
onScrollRight,
} = useEpg({
epg,
channels,
startDate: '2025-02-25T11:00:00', // or 2022-02-02T00:00:00
width: '100%',
height: 600,
});
const latestHalfHour = new Date();
// Round down the minutes to the nearest half hour
const minutes = latestHalfHour.getMinutes();
const roundedMinutes = minutes < 30 ? 0 : 30;
latestHalfHour.setMinutes(roundedMinutes);
latestHalfHour.setSeconds(0);
latestHalfHour.setMilliseconds(0);
const todayMidnight = dayjs().startOf('day');
const start = dayjs(startDate || todayMidnight);
const end = endDate ? dayjs(endDate) : start.add(24, 'hour');
const timeline = useMemo(() => {
// console.log('Generating timeline...');
const hours = [];
let current = start;
while (current.isBefore(end)) {
hours.push(current);
current = current.add(1, 'hour');
}
// console.log('Timeline generated:', hours);
return hours;
}, [start, end]);
useEffect(() => {
if (guideRef.current) {
const nowOffset = dayjs().diff(start, 'minute');
const scrollPosition = (nowOffset / 60) * HOUR_WIDTH - HOUR_WIDTH;
guideRef.current.scrollLeft = Math.max(scrollPosition, 0);
}
}, [programs, start]);
const renderProgram = (program, channelStart) => {
const programStart = dayjs(program.start_time);
const programEnd = dayjs(program.end_time);
const startOffset = programStart.diff(channelStart, 'minute');
const duration = programEnd.diff(programStart, 'minute');
const now = dayjs();
const isLive =
dayjs(program.start_time).isBefore(now) &&
dayjs(program.end_time).isAfter(now);
return (
// <Tooltip title={`${program.title} - ${program.description}`} arrow>
<Box
sx={{
position: 'absolute',
left: (startOffset / 60) * HOUR_WIDTH + 2,
width: (duration / 60) * HOUR_WIDTH - 4,
top: 2,
height: PROGRAM_HEIGHT - 4,
padding: 1,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
// borderLeft: '1px solid black',
borderRight: '1px solid black',
borderRadius: '8px',
color: 'primary.contrastText',
background: isLive
? 'linear-gradient(to right, #1a202c, #1a202c, #002eb3)'
: 'linear-gradient(to right, #1a202c, #1a202c)',
'&:hover': {
background: 'linear-gradient(to right, #051937, #002360)',
},
}}
>
<Typography
variant="body2"
noWrap
sx={{
fontWeight: 'bold',
}}
>
{program.title}
</Typography>
<Typography variant="overline" noWrap>
{programStart.format('h:mma')} - {programEnd.format('h:mma')}
</Typography>
</Box>
// </Tooltip>
);
};
useEffect(() => {
const interval = setInterval(() => {
setNow(dayjs());
}, 60000); // Update every minute
return () => clearInterval(interval);
}, []);
const nowPosition = useMemo(() => {
if (now.isBefore(start) || now.isAfter(end)) return -1;
const totalMinutes = end.diff(start, 'minute');
const minutesSinceStart = now.diff(start, 'minute');
return (minutesSinceStart / totalMinutes) * (timeline.length * HOUR_WIDTH);
}, [now, start, end, timeline.length]);
return (
<div>
<Epg {...getEpgProps()}>
<Layout {...getLayoutProps()} />
</Epg>
</div>
);
}
<Box
sx={{
overflow: 'hidden',
width: '100%',
height: '100%',
backgroundColor: '#171923',
}}
>
<Typography variant="h6">
Channels length: {guideChannels?.length ?? 0}
</Typography>
export default App;
<Stack direction="row">
<Box>
{/* Channel Column */}
<Box
sx={{
width: CHANNEL_WIDTH,
height: '40px',
}}
/>
{guideChannels.map((channel, index) => {
return (
<Box
key={index}
sx={{
display: 'flex',
// borderTop: '1px solid #ccc',
height: PROGRAM_HEIGHT + 1,
alignItems: 'center',
}}
>
<Box
sx={{
width: CHANNEL_WIDTH,
display: 'flex',
padding: 1,
justifyContent: 'center',
}}
>
<Avatar
src={channel.logo_url || logo}
alt={channel.channel_name}
/>
{/* <Typography variant="body2" sx={{ marginLeft: 1 }}>
{channel.channel_name}
</Typography> */}
</Box>
</Box>
);
})}
</Box>
{/* Timeline and Lineup */}
<Box ref={guideRef} sx={{ overflowY: 'auto', height: '100%' }}>
<Box
sx={{
display: 'flex',
position: 'sticky',
top: 0,
zIndex: 10,
backgroundColor: '#fff',
}}
>
<Box sx={{ flex: 1, display: 'flex' }}>
{timeline.map((time, index) => (
<Box
key={time.format()}
sx={{
width: HOUR_WIDTH,
// borderLeft: '1px solid #ddd',
padding: 1,
backgroundColor: '#171923',
color: 'primary.contrastText',
height: '40px',
alignItems: 'center',
position: 'relative',
padding: 0,
}}
>
<Typography
component="span"
variant="body2"
sx={{
color: '#a0aec0',
position: 'absolute',
left: index == 0 ? 0 : '-18px',
}}
>
{time.format('h:mma')}
</Typography>
<Box
sx={{
height: '100%',
width: '100%',
display: 'grid',
alignItems: 'end',
'grid-template-columns': 'repeat(4, 1fr)',
}}
>
<Box
sx={{
width: '1px',
height: '10px',
marginRight: HOUR_WIDTH / 4 + 'px',
background: '#718096',
}}
></Box>
<Box
sx={{
width: '1px',
height: '10px',
marginRight: HOUR_WIDTH / 4 + 'px',
background: '#718096',
}}
></Box>
<Box
sx={{
width: '1px',
height: '10px',
marginRight: HOUR_WIDTH / 4 + 'px',
background: '#718096',
}}
></Box>
<Box
sx={{
width: '1px',
height: '10px',
marginRight: HOUR_WIDTH / 4 + 'px',
background: '#718096',
}}
></Box>
</Box>
</Box>
))}
</Box>
</Box>
<Box sx={{ position: 'relative' }}>
{nowPosition > 0 && (
<Box
className="now-position"
sx={{
position: 'absolute',
left: nowPosition,
top: 0,
bottom: 0,
width: '3px',
backgroundColor: 'rgb(44, 122, 123)',
zIndex: 15,
}}
/>
)}
{guideChannels.map((channel, index) => {
const channelPrograms = programs.filter(
(p) => p.tvg_id === channel.tvg_id
);
return (
<Box key={index} sx={{ display: 'flex' }}>
<Box
sx={{
flex: 1,
position: 'relative',
minHeight: PROGRAM_HEIGHT,
}}
>
{channelPrograms.map((program) =>
renderProgram(program, start)
)}
</Box>
</Box>
);
})}
</Box>
</Box>
</Stack>
</Box>
);
};
export default TVChannelGuide;

View file

@ -1,38 +1,36 @@
export default {
Limiter: (concurrency, promiseList) => {
if (!promiseList || promiseList.length === 0) {
return Promise.resolve([]); // Return a resolved empty array if no promises
Limiter: (n, list) => {
if (!list || !list.length) {
return;
}
let index = 0; // Keeps track of the current promise to be processed
const results = []; // Stores the results of all promises
const totalPromises = promiseList.length;
var tail = list.splice(n);
var head = list;
var resolved = [];
var processed = 0;
// Helper function to process promises one by one, respecting concurrency
const processNext = () => {
// If we've processed all promises, resolve with the results
if (index >= totalPromises) {
return Promise.all(results);
}
// Execute the current promise and store the result
const currentPromise = promiseList[index]();
results.push(currentPromise);
// Once the current promise resolves, move on to the next one
return currentPromise.then(() => {
index++; // Move to the next promise
return processNext(); // Process the next promise
return new Promise(function (resolve) {
head.forEach(function (x) {
var res = x();
resolved.push(res);
res.then(function (y) {
runNext();
return y;
});
});
};
// Start processing promises up to the given concurrency
const concurrencyPromises = [];
for (let i = 0; i < concurrency && i < totalPromises; i++) {
concurrencyPromises.push(processNext());
}
// Wait for all promises to resolve
return Promise.all(concurrencyPromises).then(() => results);
}
}
function runNext() {
if (processed == tail.length) {
resolve(Promise.all(resolved));
} else {
resolved.push(
tail[processed]().then(function (x) {
runNext();
return x;
})
);
processed++;
}
}
});
},
};

View file

@ -1,11 +0,0 @@
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11

View file

@ -1,28 +0,0 @@
{
"files": [
{
"path": "./dist/css/adminlte.css",
"maxSize": "43 kB"
},
{
"path": "./dist/css/adminlte.min.css",
"maxSize": "40.25 kB"
},
{
"path": "./dist/css/adminlte.rtl.css",
"maxSize": "43 kB"
},
{
"path": "./dist/css/adminlte.rtl.min.css",
"maxSize": "40.25 kB"
},
{
"path": "./dist/js/adminlte.js",
"maxSize": "4.5 kB"
},
{
"path": "./dist/js/adminlte.min.js",
"maxSize": "3 kB"
}
]
}

View file

@ -1,14 +0,0 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

View file

@ -1,7 +0,0 @@
**/*.min.js
**/plugins/
/.temp/
/dist/
/docs/
/docs_html/
**/env.d.ts

View file

@ -1,120 +0,0 @@
{
"root": true,
"extends": [
"plugin:import/errors",
"plugin:import/warnings",
"plugin:unicorn/recommended",
"xo",
"xo/browser"
],
"rules": {
"arrow-body-style": "off",
"capitalized-comments": "off",
"comma-dangle": [
"error",
"never"
],
"indent": [
"error",
2,
{
"MemberExpression": "off",
"SwitchCase": 1
}
],
"max-params": [
"warn",
5
],
"multiline-ternary": [
"error",
"always-multiline"
],
"new-cap": [
"error",
{
"properties": false
}
],
"no-console": "error",
"no-negated-condition": "off",
"object-curly-spacing": [
"error",
"always"
],
"operator-linebreak": [
"error",
"after"
],
"semi": [
"error",
"never"
],
"unicorn/explicit-length-check": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-array-for-each": "off",
"unicorn/no-array-method-this-argument": "off",
"unicorn/no-null": "off",
"unicorn/no-unused-properties": "error",
"unicorn/prefer-array-flat": "off",
"unicorn/prefer-dom-node-dataset": "off",
"unicorn/prefer-export-from": "off",
"unicorn/prefer-module": "off",
"unicorn/prefer-query-selector": "off",
"unicorn/prefer-spread": "off",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prevent-abbreviations": "off"
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"extends": [
"plugin:import/typescript",
"xo-typescript"
],
"rules": {
"@typescript-eslint/comma-dangle": [
"error",
"never"
],
"@typescript-eslint/indent": [
"error",
2,
{
"MemberExpression": "off",
"SwitchCase": 1
}
],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"format": ["camelCase", "StrictPascalCase", "UPPER_CASE"]
}
],
"@typescript-eslint/object-curly-spacing": [
"error",
"always"
],
"@typescript-eslint/semi": [
"error",
"never"
]
}
},
{
"files": ["src/config/**"],
"env": {
"browser": false,
"node": true
},
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-console": "off",
"unicorn/prefer-top-level-await": "off"
}
}
]
}

View file

@ -1,6 +0,0 @@
# Enforce Unix newlines
* text=auto eol=lf
# Ignores for analysis is used to produce the language stats bar which displays the languages percentages
plugins/* linguist-vendored=true
docs/* linguist-vendored=true

View file

@ -1,34 +0,0 @@
# Contributing to AdminLTE
Contributions are always **welcome and recommended**! Here is how for beginner's: [Get started with open source click here](https://youtu.be/GbqSvJs-6W4)
1. Contribution Requirements :
* When you contribute, you agree to give a non-exclusive license to AdminLTE.io to use that contribution in any context as we (AdminLTE.io) see appropriate.
* If you use content provided by another party, it must be appropriately licensed using an [open source](https://opensource.org/licenses) license.
* Contributions are only accepted through GitHub pull requests.
* Finally, contributed code must work in all supported browsers (see above for browser support).
2. Installation :
* Fork the repository ([here is the guide](https://help.github.com/articles/fork-a-repo/)).
* Clone to your machine
```bash
git clone https://github.com/YOUR_USERNAME/AdminLTE.git
```
* Create a new branch from `master`
3. Compile dist files (Development) :
* To compile the dist files you need Node.js 18 or higher/npm (node package manager)
* `npm install` (install npm deps)
* `npm run dev` (developer mode, autocompile with browsersync support for live demo)
* Make your changes only in `./src` Folder OR `package.json` in any files which are necessary for contribution
* Do not make changes in `./dist/**` Because it contains compiled files and do not include in PR (Pull Request)
* `npm run production` (compile css/js files and test all pages are perfectly working fine, before creating a pull request)
4. Create a pull request to `master` branch
## Online one-click setup for contributing
You can use [Codespace](https://docs.github.com/en/codespaces) an online IDE which is free for Open Source for working on issues or making PRs (Pull Requests). With a single click it will launch a workspace and automatically:
- clone the `AdminLTE` repo.
- Open with [Codespace](https://docs.github.com/en/codespaces) or [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)
- install the dependencies.
- run `npm run dev` to start the server.

View file

@ -1,2 +0,0 @@
github: danny007in
custom: ["https://www.paypal.me/daniel007in"]

View file

@ -1,32 +0,0 @@
---
name: Bug report for AdminLTE v3.x
about: Create a report to help us improve AdminLTE v3.x
title: "[BUG]"
labels: type:bug, version:3.1.x
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- AdminLTE Version: [e.g. v3.0.0]
- Operating System: [e.g. macOS Catalina]
- Browser (Version): [e.g. Chrome (Latest)]
**Additional context**
Add any other context about the problem here.

View file

@ -1,20 +0,0 @@
---
name: Feature request for AdminLTE v4.x
about: Suggest an idea for this project
title: "[FEATURE]"
labels: type:enhancement, version:4.x
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,4 +0,0 @@
name: "CodeQL config"
paths-ignore:
- dist

View file

@ -1,35 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: monthly
time: "03:00"
groups:
eslint:
patterns:
- "eslint"
- "eslint-*"
- "@typescript-eslint/*"
stylelint:
patterns:
- "stylelint"
- "stylelint-*"
prettier:
patterns:
- "prettier"
- "prettier-*"
rollup:
patterns:
- "rollup"
- "@rollup/*"
postcss:
patterns:
- "postcss"
- "postcss-*"
astro:
patterns:
- "astro"
- "@astrojs/*"
open-pull-requests-limit: 10
versioning-strategy: increase

View file

@ -1,43 +0,0 @@
name: Bundlewatch
on:
push:
branches-ignore:
- "dependabot/**"
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: 2
NODE: 18
permissions:
contents: read
jobs:
bundlewatch:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "${{ env.NODE }}"
cache: npm
- name: Install npm dependencies
run: npm ci
- name: Run dist
run: npm run compile
- name: Run bundlewatch
run: npm run bundlewatch
env:
BUNDLEWATCH_GITHUB_TOKEN: "${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}"
CI_BRANCH_BASE: master

View file

@ -1,44 +0,0 @@
name: "CodeQL"
on:
push:
branches:
- master
- "!dependabot/**"
pull_request:
# The branches below must be a subset of the branches above
branches:
- master
- "!dependabot/**"
schedule:
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: "javascript"
config-file: ./.github/codeql/codeql-config.yml
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:javascript"

View file

@ -1,37 +0,0 @@
name: Docs
on:
push:
branches-ignore:
- "dependabot/**"
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: 2
NODE: 18
permissions:
contents: read
jobs:
bundlewatch:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "${{ env.NODE }}"
cache: npm
- name: Install npm dependencies
run: npm ci
- name: Build docs
run: npm run docs-compile

View file

@ -1,37 +0,0 @@
name: Lint
on:
push:
branches-ignore:
- "dependabot/**"
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: 2
NODE: 18
permissions:
contents: read
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "${{ env.NODE }}"
cache: npm
- name: Install npm dependencies
run: npm ci
- name: Run lint
run: npm run lint

View file

@ -1,54 +0,0 @@
name: CSS (node-sass)
on:
push:
branches-ignore:
- "dependabot/**"
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: 2
NODE: 18
permissions:
contents: read
jobs:
css:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "${{ env.NODE }}"
cache: npm
- name: Install npm dependencies
run: npm ci
- name: Build CSS with node-sass
run: |
npx --package node-sass@latest node-sass --version
npx --package node-sass@latest node-sass --include-path=node_modules --output-style expanded --source-map true --source-map-contents true --precision 6 src/scss/ -o dist-sass/css/
ls -Al dist-sass/css
# Check that there are no Sass variables (`$`)
- name: Check built CSS files
shell: bash
run: |
SASS_VARS_FOUND=$(find "dist-sass/css/" -type f -name "*.css" -print0 | xargs -0 --no-run-if-empty grep -F "\$" || true)
if [[ -z "$SASS_VARS_FOUND" ]]; then
echo "All good, no Sass variables found!"
exit 0
else
echo "Found $(echo "$SASS_VARS_FOUND" | wc -l | bc) Sass variables:"
echo "$SASS_VARS_FOUND"
exit 1
fi

View file

@ -1,50 +0,0 @@
name: Release & Publish
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "${{ env.NODE }}"
cache: npm
- name: Install npm dependencies
run: npm ci
- name: Build distribution files
run: npm run production
- name: Zip distribution files
uses: montudor/action-zip@v1
with:
args: "zip -qq admin-lte-${{env.RELEASE_VERSION}}.zip -d dist"
- name: Create changelog text
id: changelog
uses: endaft/action-changelog@v0.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
exclude_types: other,doc,chore
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
body: ${{ steps.changelog.outputs.changelog }}
files: |
admin-lte.${{env.RELEASE_VERSION}}.zip

View file

@ -1,40 +0,0 @@
# generated types
.astro/
# unwanted types
**/env.d.ts
# System / Log files
*.DS_Store
*.log
# Archives
*.zip
# Sass Cache
.sass-cache
# Project files
.idea
nbproject
nbproject/private
.vscode/
.vs/
# Node / Bower
node_modules/
bower_components/
# Docs
/docs/_site/
/docs/vendor/
/docs/.bundle/
/docs_html/
.jekyll-cache/
.jekyll-metadata
# ETC
TODO
test.html
ad.js
/.cache/

View file

@ -1,6 +0,0 @@
tasks:
- init: npm install
command: npm run dev
ports:
- port: 3000
onOpen: open-preview

View file

@ -1,15 +0,0 @@
# https://lgtm.com/help/lgtm/customizing-file-classification
path_classifiers:
plugins:
- plugins/
extraction:
javascript:
# https://lgtm.com/help/lgtm/javascript-extraction#customizing-index
# The `index` step extracts information from the files in the codebase.
index:
# Specify a list of files and folders to exclude from extraction.
exclude:
- bower_components/
- docs/assets/js/plugins/
- plugins/

View file

@ -1,25 +0,0 @@
/docs/
/docs_html/
/plugins/*
!/plugins/flot-old/
/.github/
/.temp/
/.lgtm.yml
/.cache/
/.idea/
/.browserlistrc
/.bundlewatch.config.json
/.editorconfig
/.eslintignore
/.eslintrc.json
/.gitattributes
/.gitignore
/.gitpod.yml
/.stylelintignore
/.stylelintrc.json
/composer.json
/tsconfig.json
/src/assets/
/src/config/
/src/html/
/src/utils/

View file

@ -1,6 +0,0 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}

View file

@ -1,8 +0,0 @@
**/*.html
**/*.md
**/*.min.css
**/.temp/
**/dist/
**/docs_html/
**/plugins/
**/.cache/

View file

@ -1,47 +0,0 @@
{
"extends": [
"stylelint-config-twbs-bootstrap"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"overrides": [
{
"files": ["**/*.scss"],
"rules": {
"declaration-no-important": null,
"declaration-property-value-disallowed-list": {
"border": "none",
"outline": "none"
},
"function-disallowed-list": [
"calc",
"lighten",
"darken"
],
"keyframes-name-pattern": null,
"property-disallowed-list": [
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"transition"
],
"scss/dollar-variable-default": [
true,
{
"ignore": "local"
}
],
"scss/selector-no-union-class-name": true,
"selector-max-class": null,
"selector-max-combinators": null,
"selector-max-compound-selectors": null,
"selector-max-id": null,
"selector-max-specificity": null,
"selector-max-type": null,
"selector-no-qualifying-type": null
}
}
]
}

View file

@ -1,43 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at https://colorlib.com/wp/contact-us/. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct/>

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014-2023 ColorlibHQ
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,63 +0,0 @@
# [AdminLTE - Bootstrap 5 Admin Dashboard](https://adminlte.io)
[![npm version](https://img.shields.io/npm/v/admin-lte/latest.svg)](https://www.npmjs.com/package/admin-lte)
[![Packagist](https://img.shields.io/packagist/v/almasaeed2010/adminlte.svg)](https://packagist.org/packages/almasaeed2010/adminlte)
[![cdn version](https://data.jsdelivr.com/v1/package/npm/admin-lte/badge)](https://www.jsdelivr.com/package/npm/admin-lte)
[![Discord Invite](https://img.shields.io/badge/discord-join%20now-green)](https://discord.gg/jfdvjwFqfz)
[![Netlify Status](https://api.netlify.com/api/v1/badges/1277b36b-08f3-43fa-826a-4b4d24614b3c/deploy-status)](https://app.netlify.com/sites/adminlte-v4/deploys)
**AdminLTE** is a fully responsive administration template. Based on **[Bootstrap 5](https://getbootstrap.com/)** framework and also the JavaScript plugins.
Highly customizable and easy to use. Fits many screen resolutions from small mobile devices to large desktops.
## Looking for Premium Templates?
AdminLTE.io just opened a new premium templates page. Hand picked to ensure the best quality and the most affordable
prices. Visit <https://adminlte.io/premium> for more information.
!["AdminLTE Presentation"](https://adminlte.io/AdminLTE3.png "AdminLTE Presentation")
**AdminLTE** has been carefully coded with clear comments in all of its JS, SCSS and HTML files.
SCSS has been used to increase code customizability.
## Quick start
### Compile dist files
To compile the dist files you need Node.js/npm, clone/download the repo then:
1. `npm install` (install npm deps)
2. _Optional:_ `npm run dev` (developer mode, autocompile with browsersync support for live demo)
3. `npm run production` (compile css/js files)
## Contributing
- Highly welcome.
- For your extra reference check [AdminLTE v4 Contribution Guide](https://github.com/ColorlibHQ/AdminLTE#contributing)
- First thing first, you should have bit knowledge about NodeJS.
- Github Knowledge.
- Install NodeJS LTS version.
- Clone this Repository to your machine and change to `master` branch.
- Go to Cloned Folder.
- In cli/bash run `npm install` it will install dependency from `package.json`.
- After installation completes, run `npm run dev`
- Cool, Send urs changes in PR to `master` branch.
## Sponsorship
Support AdminLTE development by becoming a sponsor.
[Github Sponsors](https://github.com/sponsors/danny007in) or
[PayPal](https://www.paypal.me/daniel007in)
## License
AdminLTE is an open source project by [AdminLTE.io](https://adminlte.io) that is licensed under [MIT](https://opensource.org/licenses/MIT).
AdminLTE.io reserves the right to change the license of future releases.
## Image Credits
- [Pixeden](http://www.pixeden.com/psd-web-elements/flat-responsive-showcase-psd)
- [Graphicsfuel](https://www.graphicsfuel.com/2013/02/13-high-resolution-blur-backgrounds/)
- [Pickaface](https://pickaface.net/)
- [Unsplash](https://unsplash.com/)
- [Uifaces](http://uifaces.com/)

View file

@ -1,25 +0,0 @@
{
"name": "almasaeed2010/adminlte",
"description": "AdminLTE - admin control panel and dashboard that's based on Bootstrap 4",
"homepage": "https://adminlte.io/",
"keywords": [
"css",
"js",
"less",
"responsive",
"back-end",
"template",
"theme",
"web",
"admin"
],
"authors": [
{
"name": "Colorlib"
}
],
"license": "MIT",
"support": {
"issues": "https://github.com/ColorlibHQ/AdminLTE/issues"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,715 +0,0 @@
/*!
* AdminLTE v4.0.0-beta3 (https://adminlte.io)
* Copyright 2014-2024 Colorlib <https://colorlib.com>
* Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {}));
})(this, (function (exports) { 'use strict';
const domContentLoadedCallbacks = [];
const onDOMContentLoaded = (callback) => {
if (document.readyState === 'loading') {
// add listener on the first call when the document is in loading state
if (!domContentLoadedCallbacks.length) {
document.addEventListener('DOMContentLoaded', () => {
for (const callback of domContentLoadedCallbacks) {
callback();
}
});
}
domContentLoadedCallbacks.push(callback);
}
else {
callback();
}
};
/* SLIDE UP */
const slideUp = (target, duration = 500) => {
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = `${duration}ms`;
target.style.boxSizing = 'border-box';
target.style.height = `${target.offsetHeight}px`;
target.style.overflow = 'hidden';
window.setTimeout(() => {
target.style.height = '0';
target.style.paddingTop = '0';
target.style.paddingBottom = '0';
target.style.marginTop = '0';
target.style.marginBottom = '0';
}, 1);
window.setTimeout(() => {
target.style.display = 'none';
target.style.removeProperty('height');
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
/* SLIDE DOWN */
const slideDown = (target, duration = 500) => {
target.style.removeProperty('display');
let { display } = window.getComputedStyle(target);
if (display === 'none') {
display = 'block';
}
target.style.display = display;
const height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = '0';
target.style.paddingTop = '0';
target.style.paddingBottom = '0';
target.style.marginTop = '0';
target.style.marginBottom = '0';
window.setTimeout(() => {
target.style.boxSizing = 'border-box';
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = `${duration}ms`;
target.style.height = `${height}px`;
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
}, 1);
window.setTimeout(() => {
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
/**
* --------------------------------------------
* @file AdminLTE layout.ts
* @description Layout for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
const CLASS_NAME_APP_LOADED = 'app-loaded';
/**
* Class Definition
* ====================================================
*/
class Layout {
constructor(element) {
this._element = element;
}
holdTransition() {
let resizeTimer;
window.addEventListener('resize', () => {
document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
}, 400);
});
}
}
onDOMContentLoaded(() => {
const data = new Layout(document.body);
data.holdTransition();
setTimeout(() => {
document.body.classList.add(CLASS_NAME_APP_LOADED);
}, 400);
});
/**
* --------------------------------------------
* @file AdminLTE push-menu.ts
* @description Push menu for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const DATA_KEY$4 = 'lte.push-menu';
const EVENT_KEY$4 = `.${DATA_KEY$4}`;
const EVENT_OPEN = `open${EVENT_KEY$4}`;
const EVENT_COLLAPSE = `collapse${EVENT_KEY$4}`;
const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
const CLASS_NAME_MENU_OPEN$1 = 'menu-open';
const SELECTOR_APP_SIDEBAR = '.app-sidebar';
const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
const SELECTOR_NAV_ITEM$1 = '.nav-item';
const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
const SELECTOR_APP_WRAPPER = '.app-wrapper';
const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
const Defaults = {
sidebarBreakpoint: 992
};
/**
* Class Definition
* ====================================================
*/
class PushMenu {
constructor(element, config) {
this._element = element;
this._config = Object.assign(Object.assign({}, Defaults), config);
}
// TODO
menusClose() {
const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
navTreeview.forEach(navTree => {
navTree.style.removeProperty('display');
navTree.style.removeProperty('height');
});
const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
const navItem = navSidebar === null || navSidebar === void 0 ? void 0 : navSidebar.querySelectorAll(SELECTOR_NAV_ITEM$1);
if (navItem) {
navItem.forEach(navI => {
navI.classList.remove(CLASS_NAME_MENU_OPEN$1);
});
}
}
expand() {
const event = new Event(EVENT_OPEN);
document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
this._element.dispatchEvent(event);
}
collapse() {
const event = new Event(EVENT_COLLAPSE);
document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
this._element.dispatchEvent(event);
}
addSidebarBreakPoint() {
var _a, _b, _c;
const sidebarExpandList = (_b = (_a = document.querySelector(SELECTOR_SIDEBAR_EXPAND)) === null || _a === void 0 ? void 0 : _a.classList) !== null && _b !== void 0 ? _b : [];
const sidebarExpand = (_c = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND))) !== null && _c !== void 0 ? _c : '';
const sidebar = document.getElementsByClassName(sidebarExpand)[0];
const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content');
this._config = Object.assign(Object.assign({}, this._config), { sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) });
if (window.innerWidth <= this._config.sidebarBreakpoint) {
this.collapse();
}
else {
if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
this.expand();
}
if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
this.collapse();
}
}
}
toggle() {
if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
this.expand();
}
else {
this.collapse();
}
}
init() {
this.addSidebarBreakPoint();
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
onDOMContentLoaded(() => {
var _a;
const sidebar = document === null || document === void 0 ? void 0 : document.querySelector(SELECTOR_APP_SIDEBAR);
if (sidebar) {
const data = new PushMenu(sidebar, Defaults);
data.init();
window.addEventListener('resize', () => {
data.init();
});
}
const sidebarOverlay = document.createElement('div');
sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
(_a = document.querySelector(SELECTOR_APP_WRAPPER)) === null || _a === void 0 ? void 0 : _a.append(sidebarOverlay);
sidebarOverlay.addEventListener('touchstart', event => {
event.preventDefault();
const target = event.currentTarget;
const data = new PushMenu(target, Defaults);
data.collapse();
}, { passive: true });
sidebarOverlay.addEventListener('click', event => {
event.preventDefault();
const target = event.currentTarget;
const data = new PushMenu(target, Defaults);
data.collapse();
});
const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
fullBtn.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
let button = event.currentTarget;
if ((button === null || button === void 0 ? void 0 : button.dataset.lteToggle) !== 'sidebar') {
button = button === null || button === void 0 ? void 0 : button.closest(SELECTOR_SIDEBAR_TOGGLE);
}
if (button) {
event === null || event === void 0 ? void 0 : event.preventDefault();
const data = new PushMenu(button, Defaults);
data.toggle();
}
});
});
});
/**
* --------------------------------------------
* @file AdminLTE treeview.ts
* @description Treeview plugin for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
// const NAME = 'Treeview'
const DATA_KEY$3 = 'lte.treeview';
const EVENT_KEY$3 = `.${DATA_KEY$3}`;
const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$3}`;
const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$3}`;
// const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`
const CLASS_NAME_MENU_OPEN = 'menu-open';
const SELECTOR_NAV_ITEM = '.nav-item';
const SELECTOR_NAV_LINK = '.nav-link';
const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]';
const Default$1 = {
animationSpeed: 300,
accordion: true
};
/**
* Class Definition
* ====================================================
*/
class Treeview {
constructor(element, config) {
this._element = element;
this._config = Object.assign(Object.assign({}, Default$1), config);
}
open() {
var _a, _b;
const event = new Event(EVENT_EXPANDED$2);
if (this._config.accordion) {
const openMenuList = (_a = this._element.parentElement) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
openMenuList === null || openMenuList === void 0 ? void 0 : openMenuList.forEach(openMenu => {
if (openMenu !== this._element.parentElement) {
openMenu.classList.remove(CLASS_NAME_MENU_OPEN);
const childElement = openMenu === null || openMenu === void 0 ? void 0 : openMenu.querySelector(SELECTOR_TREEVIEW_MENU);
if (childElement) {
slideUp(childElement, this._config.animationSpeed);
}
}
});
}
this._element.classList.add(CLASS_NAME_MENU_OPEN);
const childElement = (_b = this._element) === null || _b === void 0 ? void 0 : _b.querySelector(SELECTOR_TREEVIEW_MENU);
if (childElement) {
slideDown(childElement, this._config.animationSpeed);
}
this._element.dispatchEvent(event);
}
close() {
var _a;
const event = new Event(EVENT_COLLAPSED$2);
this._element.classList.remove(CLASS_NAME_MENU_OPEN);
const childElement = (_a = this._element) === null || _a === void 0 ? void 0 : _a.querySelector(SELECTOR_TREEVIEW_MENU);
if (childElement) {
slideUp(childElement, this._config.animationSpeed);
}
this._element.dispatchEvent(event);
}
toggle() {
if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {
this.close();
}
else {
this.open();
}
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
onDOMContentLoaded(() => {
const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE$1);
button.forEach(btn => {
btn.addEventListener('click', event => {
const target = event.target;
const targetItem = target.closest(SELECTOR_NAV_ITEM);
const targetLink = target.closest(SELECTOR_NAV_LINK);
if ((target === null || target === void 0 ? void 0 : target.getAttribute('href')) === '#' || (targetLink === null || targetLink === void 0 ? void 0 : targetLink.getAttribute('href')) === '#') {
event.preventDefault();
}
if (targetItem) {
const data = new Treeview(targetItem, Default$1);
data.toggle();
}
});
});
});
/**
* --------------------------------------------
* @file AdminLTE direct-chat.ts
* @description Direct chat for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* Constants
* ====================================================
*/
const DATA_KEY$2 = 'lte.direct-chat';
const EVENT_KEY$2 = `.${DATA_KEY$2}`;
const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$2}`;
const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$2}`;
const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
const SELECTOR_DIRECT_CHAT = '.direct-chat';
const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
/**
* Class Definition
* ====================================================
*/
class DirectChat {
constructor(element) {
this._element = element;
}
toggle() {
if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
const event = new Event(EVENT_COLLAPSED$1);
this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN);
this._element.dispatchEvent(event);
}
else {
const event = new Event(EVENT_EXPANDED$1);
this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN);
this._element.dispatchEvent(event);
}
}
}
/**
*
* Data Api implementation
* ====================================================
*/
onDOMContentLoaded(() => {
const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
button.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
const target = event.target;
const chatPane = target.closest(SELECTOR_DIRECT_CHAT);
if (chatPane) {
const data = new DirectChat(chatPane);
data.toggle();
}
});
});
});
/**
* --------------------------------------------
* @file AdminLTE card-widget.ts
* @description Card widget for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* Constants
* ====================================================
*/
const DATA_KEY$1 = 'lte.card-widget';
const EVENT_KEY$1 = `.${DATA_KEY$1}`;
const EVENT_COLLAPSED = `collapsed${EVENT_KEY$1}`;
const EVENT_EXPANDED = `expanded${EVENT_KEY$1}`;
const EVENT_REMOVE = `remove${EVENT_KEY$1}`;
const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$1}`;
const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$1}`;
const CLASS_NAME_CARD = 'card';
const CLASS_NAME_COLLAPSED = 'collapsed-card';
const CLASS_NAME_COLLAPSING = 'collapsing-card';
const CLASS_NAME_EXPANDING = 'expanding-card';
const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed';
const CLASS_NAME_MAXIMIZED = 'maximized-card';
const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]';
const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]';
const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]';
const SELECTOR_CARD = `.${CLASS_NAME_CARD}`;
const SELECTOR_CARD_BODY = '.card-body';
const SELECTOR_CARD_FOOTER = '.card-footer';
const Default = {
animationSpeed: 500,
collapseTrigger: SELECTOR_DATA_COLLAPSE,
removeTrigger: SELECTOR_DATA_REMOVE,
maximizeTrigger: SELECTOR_DATA_MAXIMIZE
};
class CardWidget {
constructor(element, config) {
this._element = element;
this._parent = element.closest(SELECTOR_CARD);
if (element.classList.contains(CLASS_NAME_CARD)) {
this._parent = element;
}
this._config = Object.assign(Object.assign({}, Default), config);
}
collapse() {
var _a, _b;
const event = new Event(EVENT_COLLAPSED);
if (this._parent) {
this._parent.classList.add(CLASS_NAME_COLLAPSING);
const elm = (_a = this._parent) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
elm.forEach(el => {
if (el instanceof HTMLElement) {
slideUp(el, this._config.animationSpeed);
}
});
setTimeout(() => {
if (this._parent) {
this._parent.classList.add(CLASS_NAME_COLLAPSED);
this._parent.classList.remove(CLASS_NAME_COLLAPSING);
}
}, this._config.animationSpeed);
}
(_b = this._element) === null || _b === void 0 ? void 0 : _b.dispatchEvent(event);
}
expand() {
var _a, _b;
const event = new Event(EVENT_EXPANDED);
if (this._parent) {
this._parent.classList.add(CLASS_NAME_EXPANDING);
const elm = (_a = this._parent) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
elm.forEach(el => {
if (el instanceof HTMLElement) {
slideDown(el, this._config.animationSpeed);
}
});
setTimeout(() => {
if (this._parent) {
this._parent.classList.remove(CLASS_NAME_COLLAPSED);
this._parent.classList.remove(CLASS_NAME_EXPANDING);
}
}, this._config.animationSpeed);
}
(_b = this._element) === null || _b === void 0 ? void 0 : _b.dispatchEvent(event);
}
remove() {
var _a;
const event = new Event(EVENT_REMOVE);
if (this._parent) {
slideUp(this._parent, this._config.animationSpeed);
}
(_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
}
toggle() {
var _a;
if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_COLLAPSED)) {
this.expand();
return;
}
this.collapse();
}
maximize() {
var _a;
const event = new Event(EVENT_MAXIMIZED$1);
if (this._parent) {
this._parent.style.height = `${this._parent.offsetHeight}px`;
this._parent.style.width = `${this._parent.offsetWidth}px`;
this._parent.style.transition = 'all .15s';
setTimeout(() => {
const htmlTag = document.querySelector('html');
if (htmlTag) {
htmlTag.classList.add(CLASS_NAME_MAXIMIZED);
}
if (this._parent) {
this._parent.classList.add(CLASS_NAME_MAXIMIZED);
if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {
this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED);
}
}
}, 150);
}
(_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
}
minimize() {
var _a;
const event = new Event(EVENT_MINIMIZED$1);
if (this._parent) {
this._parent.style.height = 'auto';
this._parent.style.width = 'auto';
this._parent.style.transition = 'all .15s';
setTimeout(() => {
var _a;
const htmlTag = document.querySelector('html');
if (htmlTag) {
htmlTag.classList.remove(CLASS_NAME_MAXIMIZED);
}
if (this._parent) {
this._parent.classList.remove(CLASS_NAME_MAXIMIZED);
if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {
this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED);
}
}
}, 10);
}
(_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
}
toggleMaximize() {
var _a;
if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_MAXIMIZED)) {
this.minimize();
return;
}
this.maximize();
}
}
/**
*
* Data Api implementation
* ====================================================
*/
onDOMContentLoaded(() => {
const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE);
collapseBtn.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
const target = event.target;
const data = new CardWidget(target, Default);
data.toggle();
});
});
const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE);
removeBtn.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
const target = event.target;
const data = new CardWidget(target, Default);
data.remove();
});
});
const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE);
maxBtn.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
const target = event.target;
const data = new CardWidget(target, Default);
data.toggleMaximize();
});
});
});
/**
* --------------------------------------------
* @file AdminLTE fullscreen.ts
* @description Fullscreen plugin for AdminLTE.
* @license MIT
* --------------------------------------------
*/
/**
* Constants
* ============================================================================
*/
const DATA_KEY = 'lte.fullscreen';
const EVENT_KEY = `.${DATA_KEY}`;
const EVENT_MAXIMIZED = `maximized${EVENT_KEY}`;
const EVENT_MINIMIZED = `minimized${EVENT_KEY}`;
const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
/**
* Class Definition.
* ============================================================================
*/
class FullScreen {
constructor(element, config) {
this._element = element;
this._config = config;
}
inFullScreen() {
const event = new Event(EVENT_MAXIMIZED);
const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
void document.documentElement.requestFullscreen();
if (iconMaximize) {
iconMaximize.style.display = 'none';
}
if (iconMinimize) {
iconMinimize.style.display = 'block';
}
this._element.dispatchEvent(event);
}
outFullscreen() {
const event = new Event(EVENT_MINIMIZED);
const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
void document.exitFullscreen();
if (iconMaximize) {
iconMaximize.style.display = 'block';
}
if (iconMinimize) {
iconMinimize.style.display = 'none';
}
this._element.dispatchEvent(event);
}
toggleFullScreen() {
if (document.fullscreenEnabled) {
if (document.fullscreenElement) {
this.outFullscreen();
}
else {
this.inFullScreen();
}
}
}
}
/**
* Data Api implementation
* ============================================================================
*/
onDOMContentLoaded(() => {
const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
buttons.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
const target = event.target;
const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE);
if (button) {
const data = new FullScreen(button, undefined);
data.toggleFullScreen();
}
});
});
});
exports.CardWidget = CardWidget;
exports.DirectChat = DirectChat;
exports.FullScreen = FullScreen;
exports.Layout = Layout;
exports.PushMenu = PushMenu;
exports.Treeview = Treeview;
}));
//# sourceMappingURL=adminlte.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more