Update src/

This commit is contained in:
freearhey 2025-04-30 04:06:54 +03:00
parent e411cec545
commit 2a893a827b
33 changed files with 416 additions and 186 deletions

View file

@ -13,14 +13,14 @@
{$selected.count()} selected
</div>
<div class="flex space-x-1 sm:space-x-2 items-center">
<ResetButton />
<SelectAllButton />
<DownloadButton />
<ResetButton variant="dark" />
<SelectAllButton variant="dark" />
<DownloadButton variant="dark" />
<CloseButton
onClick={() => {
downloadMode.set(false)
}}
variant="light"
variant="dark"
/>
</div>
</div>

View file

@ -9,11 +9,11 @@
class="w-full text-left rounded-md text-sm h-10 flex items-center text-gray-500 dark:text-gray-400 font-normal hover:bg-gray-100 dark:hover:bg-primary-750 space-x-3 px-2 border border-transparent cursor-pointer"
{...$$restProps}
>
<div class="w-5 h-5 flex items-center justify-center">
<div class="w-5 flex shrink-0 items-center justify-center">
<slot name="left" />
</div>
<div class="w-full">{label}</div>
<div>
<div class="w-4 flex shrink-0">
<slot name="right" />
</div>
</button>

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { ChannelRemoveButton, ChannelEditButton, CopyLinkButton, Menu } from '~/components'
import { toast } from '@zerodevx/svelte-toast'
import type { Channel } from '~/models'
export let channel: Channel
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
function onLinkCopy() {
toast.push('Link copied to clipboard')
closeMenu()
}
</script>
<Menu bind:isOpened={isMenuOpened}>
<CopyLinkButton link={channel.getPageUrl()} onCopy={onLinkCopy} />
<ChannelEditButton {channel} onClick={closeMenu} />
<ChannelRemoveButton {channel} onClick={closeMenu} />
</Menu>

View file

@ -1,20 +1,16 @@
<script lang="ts">
import type { Context } from 'svelte-simple-modal'
import { toast } from '@zerodevx/svelte-toast'
import { getContext } from 'svelte'
import { Channel } from '~/models'
import {
ChannelRemoveButton,
ShareChannelButton,
ChannelEditButton,
CopyLinkButton,
BlockedBadge,
CloseButton,
ClosedBadge,
ChannelMenu,
HTMLPreview,
Popup,
Card,
Menu
Card
} from '~/components'
export let channel: Channel
@ -29,16 +25,6 @@
close()
}
}
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
function onLinkCopy() {
toast.push('Link copied to clipboard')
closeMenu()
}
</script>
<Popup onClose={close}>
@ -58,11 +44,7 @@
{#if isTouchDevice}
<ShareChannelButton {channel} />
{/if}
<Menu bind:isOpened={isMenuOpened}>
<CopyLinkButton link={channel.getPageUrl()} onCopy={onLinkCopy} />
<ChannelEditButton {channel} onClick={closeMenu} />
<ChannelRemoveButton {channel} onClick={closeMenu} />
</Menu>
<ChannelMenu {channel} />
<CloseButton onClick={close} />
</div>
<div slot="body" class="pt-4 pb-3 px-4 sm:py-9 sm:px-11">

View file

@ -6,6 +6,8 @@
import { selected } from '~/store'
import * as Icon from '~/icons'
export let variant = 'default'
const playlistCreator = new PlaylistCreator()
function onClick() {
@ -52,7 +54,7 @@
disabled={!$selected.count()}
aria-label="Download Playlist"
title="Download Playlist"
variant="light"
{variant}
>
<Icon.Download size={16} />
</IconButton>

View file

@ -11,7 +11,7 @@
const params = qs.stringify({
labels: 'feeds:add',
template: '4_feeds_add.yml',
title: 'Add: ',
title: `Add: ${channel.name} Feed`,
channel_id: channel.id
})
@ -23,7 +23,7 @@
}
</script>
<Button onClick={_onClick} label="Add feed">
<Icon.Add slot="left" class="text-gray-400" size={20} />
<Button onClick={_onClick} label="Add Feed">
<Icon.Add slot="left" class="text-gray-400" size={19} />
<Icon.ExternalLink slot="right" class="text-gray-400 dark:text-gray-500" size={17} />
</Button>

View file

@ -0,0 +1,28 @@
<script lang="ts">
import IconButton from '~/components/IconButton.svelte'
import type { Channel } from '~/models'
import * as Icon from '~/icons'
import qs from 'qs'
export let channel: Channel
export let onClick = () => {}
const endpoint = 'https://github.com/iptv-org/database/issues/new'
const params = qs.stringify({
labels: 'feeds:add',
template: '4_feeds_add.yml',
title: `Add: ${channel.name} Feed`,
channel_id: channel.id
})
const url = `${endpoint}?${params}`
function _onClick() {
window.open(url, '_blank')
onClick()
}
</script>
<IconButton onClick={_onClick} title="Add Feed">
<Icon.AddCircle class="text-gray-400" size={20} />
</IconButton>

View file

@ -1,20 +1,16 @@
<script lang="ts">
import type { Context } from 'svelte-simple-modal'
import { toast } from '@zerodevx/svelte-toast'
import { getContext } from 'svelte'
import { page } from '$app/state'
import * as Icon from '~/icons'
import { Feed } from '~/models'
import {
FeedRemoveButton,
CopyLinkButton,
FeedEditButton,
ExpandButton,
StreamsPopup,
HTMLPreview,
GuidesPopup,
CodeBlock,
Menu
FeedMenu
} from '~/components'
export let feed: Feed
@ -28,7 +24,7 @@
function showGuides() {
modal.open(
GuidesPopup,
{ guides: feed.getGuides(), title: 'Guides' },
{ feed },
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
)
}
@ -36,7 +32,7 @@
function showStreams() {
modal.open(
StreamsPopup,
{ streams: feed.getStreams(), title: 'Streams' },
{ feed },
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
)
}
@ -45,16 +41,6 @@
modal.close()
onClose()
}
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
function onLinkCopy() {
toast.push('Link copied to clipboard')
closeMenu()
}
</script>
<div class="w-full rounded-md border border-gray-200 dark:border-gray-700" id={feed.id}>
@ -86,23 +72,19 @@
<button
onclick={showGuides}
class="text-sm text-gray-400 inline-flex space-x-1 flex items-center hover:text-blue-500 dark:hover:text-blue-400 cursor-pointer"
title="Streams"
title="Guides"
>
<Icon.Guide size={20} />
<div>{feed.getGuides().count()}</div>
</button>
{/if}
</div>
<Menu bind:isOpened={isMenuOpened}>
<CopyLinkButton link={feed.getPageUrl()} onCopy={onLinkCopy} />
<FeedEditButton {feed} onClick={closeMenu} />
<FeedRemoveButton {feed} onClick={closeMenu} />
</Menu>
<FeedMenu {feed} />
</div>
</div>
</div>
{#if isExpanded}
<div class="w-full flex px-6 py-6">
<div class="w-full flex px-6 pt-5 pb-2">
<HTMLPreview fieldset={feed.getFieldset()} onClick={_onClose} />
</div>
{/if}

View file

@ -0,0 +1,30 @@
<script lang="ts">
import { toast } from '@zerodevx/svelte-toast'
import type { Feed } from '~/models'
import {
FeedRemoveButton,
StreamAddButton,
CopyLinkButton,
FeedEditButton,
Menu
} from '~/components'
export let feed: Feed
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
function onLinkCopy() {
toast.push('Link copied to clipboard')
closeMenu()
}
</script>
<Menu bind:isOpened={isMenuOpened}>
<CopyLinkButton link={feed.getPageUrl()} onCopy={onLinkCopy} />
<StreamAddButton {feed} onClick={closeMenu} />
<FeedEditButton {feed} onClick={closeMenu} />
<FeedRemoveButton {feed} onClick={closeMenu} />
</Menu>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Popup, Card, Menu, FeedAddButton, CloseButton } from '~/components'
import { Popup, Card, FeedAddIconButton, CloseButton } from '~/components'
import { Collection } from '@freearhey/core/browser'
import type { Context } from 'svelte-simple-modal'
import type { Channel, Feed } from '~/models'
@ -17,11 +17,6 @@
)
const { close } = getContext<Context>('simple-modal')
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
</script>
<Popup onClose={close}>
@ -37,9 +32,7 @@
</span>{channel.getDisplayName()}
</div>
<div slot="headerRight" class="inline-flex">
<Menu bind:isOpened={isMenuOpened}>
<FeedAddButton {channel} onClick={closeMenu} />
</Menu>
<FeedAddIconButton {channel} />
<CloseButton onClick={close} />
</div>

View file

@ -1,12 +1,12 @@
<script lang="ts">
import { CloseButton, GuideItem, Popup, Card } from '~/components'
import { Collection } from '@freearhey/core/browser'
import type { Context } from 'svelte-simple-modal'
import type { Feed } from '~/models'
import { getContext } from 'svelte'
import * as Icon from '~/icons'
export let feed: Feed
export let title = 'Guides'
export let guides: Collection = new Collection()
const { close } = getContext<Context>('simple-modal')
</script>
@ -29,7 +29,7 @@
</div>
<div slot="body" class="p-2 sm:p-5 w-full">
<div class="dark:border-gray-700 rounded-md border border-gray-200">
{#each guides.all() as guide}
{#each feed.getGuides().all() as guide}
<GuideItem {guide} />
{/each}
</div>

View file

@ -8,74 +8,76 @@
<table class="table-fixed w-full">
<tbody>
{#each fieldset as field}
<tr>
<td class="align-top w-[140px] sm:w-[200px]">
<div class="flex pr-5 pb-3 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400">
{field.name}
</div>
</td>
<td class="align-top w-full overflow-hidden">
<div class="pb-3 text-sm text-gray-900 dark:text-gray-100">
{#if field.type === 'image'}
<img
src={field.value.src}
alt={field.value.alt}
title={field.value.title}
referrerpolicy="no-referrer"
class="border rounded-sm overflow-hidden border-gray-200 bg-[#e6e6e6]"
/>
{:else if field.type === 'link'}
<div class="truncate">
<a
href="/?q={field.value.query}"
onclick={onClick}
class="underline hover:text-blue-400"
title={field.value.label}
>
{field.value.label}
</a>
</div>
{:else if field.type === 'link[]'}
<div class="overflow-hidden text-ellipsis">
{#each field.value as value, i}
{#if i > 0}<span>,&nbsp; </span>
{/if}
{#if field}
<tr>
<td class="align-top w-[135px] sm:w-[200px]">
<div class="flex pr-5 pb-3 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400">
{field.name}
</div>
</td>
<td class="align-top w-full overflow-hidden">
<div class="pb-3 text-sm text-gray-900 dark:text-gray-100">
{#if field.type === 'image'}
<img
src={field.value.src}
alt={field.value.alt}
title={field.value.title}
referrerpolicy="no-referrer"
class="border rounded-sm overflow-hidden border-gray-200 bg-[#e6e6e6]"
/>
{:else if field.type === 'link'}
<div class="truncate">
<a
href="/?q={value.query}"
href="/?q={field.value.query}"
onclick={onClick}
class="underline hover:text-blue-400"
title={value.label}
title={field.value.label}
>
{value.label}
{field.value.label}
</a>
{/each}
</div>
{:else if field.type === 'external_link'}
<div class="truncate">
<a
href={field.value.href}
class="underline hover:text-blue-400"
target="_blank"
rel="noopener noreferrer"
title={field.value.title}>{field.value.label}</a
>
</div>
{:else if field.name === 'id'}
<span class="break-all" title={field.value.toString()}>{field.value}</span>
{:else if field.type === 'string[]'}
<div class="overflow-hidden text-ellipsis">
{#each field.value as value, i}
{#if i > 0}<span>,&nbsp; </span>
{/if}
<span title={value.toString()}>{value}</span>
{/each}
</div>
{:else if field.type === 'string'}
<span title={field.title}>{field.value}</span>
{/if}
</div>
</td>
</tr>
</div>
{:else if field.type === 'link[]'}
<div class="overflow-hidden text-ellipsis">
{#each field.value as value, i}
{#if i > 0}<span>,&nbsp; </span>
{/if}
<a
href="/?q={value.query}"
onclick={onClick}
class="underline hover:text-blue-400"
title={value.label}
>
{value.label}
</a>
{/each}
</div>
{:else if field.type === 'external_link'}
<div class="truncate">
<a
href={field.value.href}
class="underline hover:text-blue-400"
target="_blank"
rel="noopener noreferrer"
title={field.value.title}>{field.value.label}</a
>
</div>
{:else if field.name === 'id'}
<span class="break-all" title={field.value.toString()}>{field.value}</span>
{:else if field.type === 'string[]'}
<div class="overflow-hidden text-ellipsis">
{#each field.value as value, i}
{#if i > 0}<span>,&nbsp; </span>
{/if}
<span title={value.toString()}>{value}</span>
{/each}
</div>
{:else if field.type === 'string'}
<span class="break-words" title={field.title}>{field.value}</span>
{/if}
</div>
</td>
</tr>
{/if}
{/each}
</tbody>
</table>

View file

@ -3,9 +3,11 @@
export let variant = 'default'
export let size = 40
let className = 'rounded-lg text-sm flex items-center justify-center cursor-pointer shrink-0'
if (variant === 'light') className += ' hover:bg-primary-810 text-gray-300'
else className += ' hover:bg-gray-100 dark:hover:bg-primary-750 text-gray-400'
let className =
'rounded-lg text-sm flex items-center justify-center cursor-pointer shrink-0 text-gray-400'
if (variant === 'dark') className += ' hover:bg-primary-750'
else if (variant === 'light') className += ' hover:bg-gray-100'
else className += ' hover:bg-gray-100 dark:hover:bg-primary-750'
</script>
<button

View file

@ -15,13 +15,13 @@
</script>
<div class="relative" use:clickOutside on:outside={closeMenu}>
<IconButton onClick={toggleMenu} aria-label="Menu">
<IconButton onClick={toggleMenu} aria-label="Menu" title="Menu">
<Icon.Menu size={16} />
</IconButton>
{#if isOpened}
<div
class="rounded-md bg-white dark:bg-primary-810 absolute top-11 right-0 w-48 z-10 p-1 border border-gray-200 dark:border-primary-750"
class="rounded-md bg-white dark:bg-primary-810 absolute top-10 right-0 w-48 z-10 p-1 border border-gray-200 dark:border-primary-750"
>
<slot />
</div>

View file

@ -4,6 +4,8 @@
import { selected } from '~/store'
import * as Icon from '~/icons'
export let variant = 'default'
let isAnySelected = true
selected.subscribe((_selected: Collection) => {
@ -16,7 +18,7 @@
</script>
{#if isAnySelected}
<IconButton onClick={reset} aria-label="Reset" title="Reset" variant="light">
<IconButton onClick={reset} aria-label="Reset" title="Reset" {variant}>
<Icon.Reset size={24} />
</IconButton>
{/if}

View file

@ -5,6 +5,8 @@
import { Channel } from '~/models'
import * as Icon from '~/icons'
export let variant = 'default'
const channelsWithStreams: Collection = $channels.filter((channel: Channel) =>
channel.hasStreams()
)
@ -91,11 +93,11 @@
<Icon.Spinner size={21} />
</div>
{:else if isAllSelected}
<IconButton onClick={deselectAll} aria-label="Deselect All" title="Deselect All" variant="light">
<IconButton onClick={deselectAll} aria-label="Deselect All" title="Deselect All" {variant}>
<Icon.DeselectAll size={24} />
</IconButton>
{:else}
<IconButton onClick={selectAll} aria-label="Select All" title="Select All" variant="light">
<IconButton onClick={selectAll} aria-label="Select All" title="Select All" {variant}>
<Icon.SelectAll size={24} />
</IconButton>
{/if}

View file

@ -0,0 +1,29 @@
<script lang="ts">
import Button from '~/components/Button.svelte'
import type { Feed } from '~/models'
import * as Icon from '~/icons'
import qs from 'qs'
export let feed: Feed
export let onClick = () => {}
const endpoint = 'https://github.com/iptv-org/iptv/issues/new'
const params = qs.stringify({
labels: 'streams:add',
template: '1_streams_add.yml',
title: `Add: ${feed.getDisplayName()}`,
stream_id: feed.getStreamId()
})
const url = `${endpoint}?${params}`
function _onClick() {
window.open(url, '_blank')
onClick()
}
</script>
<Button onClick={_onClick} label="Add Stream">
<Icon.Stream slot="left" class="text-gray-400" size={19} />
<Icon.ExternalLink slot="right" class="text-gray-400 dark:text-gray-500" size={17} />
</Button>

View file

@ -0,0 +1,28 @@
<script lang="ts">
import IconButton from '~/components/IconButton.svelte'
import type { Feed } from '~/models'
import * as Icon from '~/icons'
import qs from 'qs'
export let feed: Feed
export let onClick = () => {}
const endpoint = 'https://github.com/iptv-org/iptv/issues/new'
const params = qs.stringify({
labels: 'streams:add',
template: '1_streams_add.yml',
title: `Add: ${feed.getDisplayName()}`,
stream_id: feed.getStreamId()
})
const url = `${endpoint}?${params}`
function _onClick() {
window.open(url, '_blank')
onClick()
}
</script>
<IconButton onClick={_onClick} title="Add Stream">
<Icon.AddCircle class="text-gray-400" size={20} />
</IconButton>

View file

@ -0,0 +1,29 @@
<script lang="ts">
import Button from '~/components/Button.svelte'
import type { Stream } from '~/models'
import * as Icon from '~/icons'
import qs from 'qs'
export let stream: Stream
export let onClick = () => {}
const endpoint = 'https://github.com/iptv-org/iptv/issues/new'
const params = qs.stringify({
labels: 'streams:edit',
template: '2_streams_edit.yml',
title: `Edit: ${stream.getDisplayName()}`,
stream_url: stream.url
})
const editUrl = `${endpoint}?${params}`
function _onClick() {
window.open(editUrl, '_blank')
onClick()
}
</script>
<Button onClick={_onClick} label="Edit">
<Icon.Edit slot="left" class="text-gray-400" size={16} />
<Icon.ExternalLink slot="right" class="text-gray-400 dark:text-gray-500" size={17} />
</Button>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { CopyToClipboard, ExpandButton, JsonDataViewer } from '~/components'
import { StreamMenu, ExpandButton, HTMLPreview } from '~/components'
import { Stream } from '~/models'
import * as Icon from '~/icons'
@ -8,11 +8,9 @@
let isExpanded = false
</script>
<div
class="w-full bg-gray-100 dark:bg-primary-750 dark:border-gray-600 rounded-md border border-gray-200"
>
<div class="w-full rounded-md border border-gray-200 dark:border-gray-700">
<div
class="w-full inline-flex justify-between pl-2 pr-3 py-2 border-gray-200 dark:border-gray-600"
class="w-full inline-flex justify-between px-2 py-1.5 border-gray-200 dark:border-gray-700"
class:border-b={isExpanded}
>
<div class="flex space-x-2 items-center w-full">
@ -33,14 +31,14 @@
<Icon.ExternalLink size={17} />
</div>
</div>
<div class="flex w-8 justify-end shrink-0">
<CopyToClipboard text={stream.url} />
<div class="flex w-9 justify-end shrink-0">
<StreamMenu {stream} />
</div>
</div>
</div>
{#if isExpanded}
<div class="w-full flex px-2 py-4">
<JsonDataViewer fieldset={stream.getFieldset()} />
<div class="w-full flex px-6 pt-5 pb-2">
<HTMLPreview fieldset={stream.getFieldset()} />
</div>
{/if}
</div>

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { CopyLinkButton, Menu, StreamEditButton, StreamReportButton } from '~/components'
import { toast } from '@zerodevx/svelte-toast'
import type { Stream } from '~/models'
export let stream: Stream
let isMenuOpened = false
function closeMenu() {
isMenuOpened = false
}
function onLinkCopy() {
toast.push('Link copied to clipboard')
closeMenu()
}
</script>
<Menu bind:isOpened={isMenuOpened}>
<CopyLinkButton link={stream.url} onCopy={onLinkCopy} />
<StreamEditButton {stream} onClick={closeMenu} />
<StreamReportButton {stream} onClick={closeMenu} />
</Menu>

View file

@ -0,0 +1,29 @@
<script lang="ts">
import Button from '~/components/Button.svelte'
import type { Stream } from '~/models'
import * as Icon from '~/icons'
import qs from 'qs'
export let stream: Stream
export let onClick = () => {}
const endpoint = 'https://github.com/iptv-org/iptv/issues/new'
const params = qs.stringify({
labels: 'streams:remove',
template: '3_streams_report.yml',
title: `Report: ${stream.getDisplayName()}`,
stream_url: stream.url
})
const editUrl = `${endpoint}?${params}`
function _onClick() {
window.open(editUrl, '_blank')
onClick()
}
</script>
<Button onClick={_onClick} label="Report">
<Icon.Alert slot="left" class="text-gray-400" size={17} />
<Icon.ExternalLink slot="right" class="text-gray-400 dark:text-gray-500" size={17} />
</Button>

View file

@ -1,11 +1,11 @@
<script lang="ts">
import { CloseButton, StreamItem, Popup, Card } from '~/components'
import { Collection } from '@freearhey/core/browser'
import { CloseButton, StreamItem, Popup, Card, StreamAddIconButton } from '~/components'
import type { Context } from 'svelte-simple-modal'
import type { Feed } from '~/models'
import { getContext } from 'svelte'
import * as Icon from '~/icons'
export let streams: Collection = new Collection()
export let feed: Feed
export let title = 'Streams'
const { close } = getContext<Context>('simple-modal')
@ -23,11 +23,12 @@
<Icon.Stream size={21} />
</span>{title}
</div>
<div slot="headerRight">
<div slot="headerRight" class="inline-flex">
<StreamAddIconButton {feed} />
<CloseButton onClick={() => close()} />
</div>
<div slot="body" class="flex flex-col gap-2 p-2 sm:p-5">
{#each streams.all() as stream, index (stream.getUUID())}
{#each feed.getStreams().all() as stream (stream.getUUID())}
<StreamItem {stream} />
{/each}
</div>

View file

@ -6,6 +6,7 @@ export { default as Card } from './Card.svelte'
export { default as ChannelEditButton } from './ChannelEditButton.svelte'
export { default as ChannelGrid } from './ChannelGrid.svelte'
export { default as ChannelItem } from './ChannelItem.svelte'
export { default as ChannelMenu } from './ChannelMenu.svelte'
export { default as ChannelPopup } from './ChannelPopup.svelte'
export { default as ChannelRemoveButton } from './ChannelRemoveButton.svelte'
export { default as Checkbox } from './Checkbox.svelte'
@ -20,8 +21,10 @@ export { default as CreatePlaylistButton } from './CreatePlaylistButton.svelte'
export { default as DownloadButton } from './DownloadButton.svelte'
export { default as ExpandButton } from './ExpandButton.svelte'
export { default as FeedAddButton } from './FeedAddButton.svelte'
export { default as FeedAddIconButton } from './FeedAddIconButton.svelte'
export { default as FeedEditButton } from './FeedEditButton.svelte'
export { default as FeedItem } from './FeedItem.svelte'
export { default as FeedMenu } from './FeedMenu.svelte'
export { default as FeedPopup } from './FeedPopup.svelte'
export { default as FeedRemoveButton } from './FeedRemoveButton.svelte'
export { default as GitHubButton } from './GitHubButton.svelte'
@ -40,6 +43,11 @@ export { default as SearchField } from './SearchField.svelte'
export { default as SearchSyntaxPopup } from './SearchSyntaxPopup.svelte'
export { default as SelectAllButton } from './SelectAllButton.svelte'
export { default as ShareChannelButton } from './ShareChannelButton.svelte'
export { default as StreamAddButton } from './StreamAddButton.svelte'
export { default as StreamAddIconButton } from './StreamAddIconButton.svelte'
export { default as StreamEditButton } from './StreamEditButton.svelte'
export { default as StreamReportButton } from './StreamReportButton.svelte'
export { default as StreamItem } from './StreamItem.svelte'
export { default as StreamMenu } from './StreamMenu.svelte'
export { default as StreamsPopup } from './StreamsPopup.svelte'
export { default as ToggleModeButton } from './ToggleModeButton.svelte'

View file

@ -0,0 +1,19 @@
<svg
{...$$restProps}
width={$$props.size}
height={$$props.size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V11H16C16.5523 11 17 11.4477 17 12C17 12.5523 16.5523 13 16 13H13V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V13H8C7.44771 13 7 12.5523 7 12C7 11.4477 7.44772 11 8 11H11V8Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12ZM3.00683 12C3.00683 16.9668 7.03321 20.9932 12 20.9932C16.9668 20.9932 20.9932 16.9668 20.9932 12C20.9932 7.03321 16.9668 3.00683 12 3.00683C7.03321 3.00683 3.00683 7.03321 3.00683 12Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 834 B

16
src/icons/Alert.svelte Normal file
View file

@ -0,0 +1,16 @@
<svg
{...$$restProps}
width={$$props.size}
height={$$props.size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>

After

Width:  |  Height:  |  Size: 434 B

View file

@ -1,4 +1,6 @@
export { default as Add } from './Add.svelte'
export { default as AddCircle } from './AddCircle.svelte'
export { default as Alert } from './Alert.svelte'
export { default as CheckboxChecked } from './CheckboxChecked.svelte'
export { default as CheckboxDisabled } from './CheckboxDisabled.svelte'
export { default as CheckboxIndeterminate } from './CheckboxIndeterminate.svelte'

View file

@ -272,6 +272,10 @@ export class Channel {
return broadcastArea.uniqBy((broadcastArea: BroadcastArea) => broadcastArea.code)
}
getFeedNames(): Collection {
return this.getFeeds().map((feed: Feed) => feed.name)
}
getSearchable(): ChannelSearchable {
return {
id: this.id,
@ -308,7 +312,8 @@ export class Channel {
_broadcastLocationNames: this.getBroadcastLocationNames().all(),
_countryName: this.getCountryName(),
_guideSiteNames: this.getGuideSiteNames().all(),
_streamUrls: this.getStreamUrls().all()
_streamUrls: this.getStreamUrls().all(),
_feedNames: this.getFeedNames().all()
}
}

View file

@ -86,6 +86,10 @@ export class Feed {
return this
}
getStreamId(): string {
return `${this.channelId}@${this.id}`
}
getUUID(): string {
return this.channelId + this.id
}

View file

@ -1,10 +1,10 @@
import type { JsonDataViewerField } from '~/types/jsonDataViewerField'
import type { StreamData, StreamSerializedData } from '~/types/stream'
import type { HTMLPreviewField } from '~/types/htmlPreviewField'
import type { Dictionary } from '@freearhey/core/browser'
import { Link } from 'iptv-playlist-generator'
import type { Category } from './category'
import type { Channel } from './channel'
import type { Feed } from './feed'
import type { Category } from './category'
export class Stream {
channelId?: string
@ -54,31 +54,6 @@ export class Stream {
return `${this.channelId}@${this.feedId}`
}
toJSON() {
return {
channel: this.channelId,
feed: this.feedId,
url: this.url,
referrer: this.referrer,
user_agent: this.userAgent,
quality: this.quality
}
}
getFieldset(): JsonDataViewerField[] {
let fieldset = []
const data = this.toJSON()
for (let key in data) {
fieldset.push({
name: key,
value: data[key]
})
}
return fieldset
}
getQuality(): string {
if (!this.quality) return ''
@ -89,7 +64,7 @@ export class Stream {
return parseInt(this.getQuality().replace(/p|i/, ''))
}
getTitle(): string {
getDisplayName(): string {
if (!this.channel) return ''
if (!this.feed) return this.channel.name
@ -101,7 +76,7 @@ export class Stream {
const link = new Link(this.url)
link.title = this.getTitle()
link.title = this.getDisplayName()
link.attrs = {
'tvg-id': this.getId(),
'tvg-logo': this.channel.logoUrl,
@ -125,6 +100,21 @@ export class Stream {
return link
}
getFieldset(): HTMLPreviewField[] {
return [
{ name: 'url', type: 'string', value: this.url, title: this.url },
this.referrer
? { name: 'referrer', type: 'string', value: this.referrer, title: this.referrer }
: null,
this.userAgent
? { name: 'user_agent', type: 'string', value: this.userAgent, title: this.userAgent }
: null,
this.quality
? { name: 'quality', type: 'string', value: this.quality, title: this.quality }
: null
]
}
serialize(): StreamSerializedData {
return {
channelId: this.channelId,

View file

@ -7,8 +7,8 @@
ChannelRemoveButton,
ShareChannelButton,
ChannelEditButton,
FeedAddIconButton,
CopyLinkButton,
FeedAddButton,
BlockedBadge,
HTMLPreview,
ClosedBadge,
@ -118,9 +118,7 @@
</div>
</div>
<div slot="headerRight">
<Menu bind:isOpened={isFeedMenuOpened}>
<FeedAddButton {channel} onClick={closeFeedMenu} />
</Menu>
<FeedAddIconButton {channel} />
</div>
<div slot="body">
<div class="flex flex-col gap-2 p-2 sm:p-5">

View file

@ -40,6 +40,7 @@ export type ChannelSearchable = {
_countryName: string
_guideSiteNames: string[]
_streamUrls: string[]
_feedNames: string[]
}
export type ChannelSerializedData = {
@ -60,6 +61,7 @@ export type ChannelSerializedData = {
launchedDate?: string
closedDateString?: string
closedDate?: string
replacedByStreamId?: string
replacedByChannelId?: string
websiteUrl?: string
logoUrl: string

View file

@ -5,4 +5,5 @@ export type HTMLPreviewField = {
name: string
type: string
value: HTMLPreviewImage | HTMLPreviewLink | HTMLPreviewLink[] | HTMLPreviewExternalLink | string
title?: string
}