Enhancement: Refactored copyToClipboard utility function to include notification handling internally, eliminating duplicate notification code across the frontend. The function now accepts optional parameters for customizing success/failure messages while providing consistent behavior across all copy operations.
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Frontend Tests / test (push) Waiting to run

This commit is contained in:
SergeantPanda 2026-01-31 19:01:34 -06:00
parent e20858b7b5
commit aa1f627402
7 changed files with 61 additions and 74 deletions

View file

@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Better auto-generation of request/response schemas
- Improved documentation accuracy with serializer introspection
- Switched to uv for package management: Migrated from pip to uv (Astral's fast Python package installer) for improved dependency resolution speed and reliability. This includes updates to Docker build processes, installation scripts (debian_install.sh), and project configuration (pyproject.toml) to leverage uv's features like virtual environment management and lockfile generation. - Thanks [@tobimichael96](https://github.com/tobimichael96) for getting it started!
- Copy to Clipboard: Refactored `copyToClipboard` utility function to include notification handling internally, eliminating duplicate notification code across the frontend. The function now accepts optional parameters for customizing success/failure messages while providing consistent behavior across all copy operations.
### Fixed

View file

@ -287,13 +287,9 @@ const SeriesModal = ({ series, opened, onClose }) => {
const handleCopyEpisodeLink = async (episode) => {
const streamUrl = getEpisodeStreamUrl(episode);
const success = await copyToClipboard(streamUrl);
notifications.show({
title: success ? 'Link Copied!' : 'Copy Failed',
message: success
? 'Episode link copied to clipboard'
: 'Failed to copy link to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(streamUrl, {
successTitle: 'Link Copied!',
successMessage: 'Episode link copied to clipboard',
});
};

View file

@ -144,16 +144,10 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
// No need to fetch them again here - just use the store values
const copyPublicIP = async () => {
const success = await copyToClipboard(environment.public_ip);
if (success) {
notifications.show({
title: 'Success',
message: 'Public IP copied to clipboard',
color: 'green',
});
} else {
console.error('Failed to copy public IP to clipboard');
}
await copyToClipboard(environment.public_ip, {
successTitle: 'Success',
successMessage: 'Public IP copied to clipboard',
});
};
const onLogout = async () => {

View file

@ -268,13 +268,9 @@ const VODModal = ({ vod, opened, onClose }) => {
const handleCopyLink = async () => {
const streamUrl = getStreamUrl();
if (!streamUrl) return;
const success = await copyToClipboard(streamUrl);
notifications.show({
title: success ? 'Link Copied!' : 'Copy Failed',
message: success
? 'Stream link copied to clipboard'
: 'Failed to copy link to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(streamUrl, {
successTitle: 'Link Copied!',
successMessage: 'Stream link copied to clipboard',
});
};

View file

@ -379,13 +379,9 @@ const ChannelStreams = ({ channel, isExpanded }) => {
style={{ cursor: 'pointer' }}
onClick={async (e) => {
e.stopPropagation();
const success = await copyToClipboard(stream.url);
notifications.show({
title: success ? 'URL Copied' : 'Copy Failed',
message: success
? 'Stream URL copied to clipboard'
: 'Failed to copy URL to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(stream.url, {
successTitle: 'URL Copied',
successMessage: 'Stream URL copied to clipboard',
});
}}
>

View file

@ -727,14 +727,6 @@ const ChannelsTable = ({ onReady }) => {
setRecordingModalOpen(false);
};
const handleCopy = async (textToCopy, ref) => {
const success = await copyToClipboard(textToCopy);
notifications.show({
title: success ? 'Copied!' : 'Copy Failed',
message: success ? undefined : 'Failed to copy to clipboard',
color: success ? 'green' : 'red',
});
};
// Build URLs with parameters
const buildM3UUrl = () => {
const params = new URLSearchParams();
@ -759,35 +751,23 @@ const ChannelsTable = ({ onReady }) => {
};
// Example copy URLs
const copyM3UUrl = async () => {
const success = await copyToClipboard(buildM3UUrl());
notifications.show({
title: success ? 'M3U URL Copied!' : 'Copy Failed',
message: success
? 'The M3U URL has been copied to your clipboard.'
: 'Failed to copy M3U URL to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(buildM3UUrl(), {
successTitle: 'M3U URL Copied!',
successMessage: 'The M3U URL has been copied to your clipboard.',
});
};
const copyEPGUrl = async () => {
const success = await copyToClipboard(buildEPGUrl());
notifications.show({
title: success ? 'EPG URL Copied!' : 'Copy Failed',
message: success
? 'The EPG URL has been copied to your clipboard.'
: 'Failed to copy EPG URL to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(buildEPGUrl(), {
successTitle: 'EPG URL Copied!',
successMessage: 'The EPG URL has been copied to your clipboard.',
});
};
const copyHDHRUrl = async () => {
const success = await copyToClipboard(hdhrUrl);
notifications.show({
title: success ? 'HDHR URL Copied!' : 'Copy Failed',
message: success
? 'The HDHR URL has been copied to your clipboard.'
: 'Failed to copy HDHR URL to clipboard',
color: success ? 'green' : 'red',
await copyToClipboard(hdhrUrl, {
successTitle: 'HDHR URL Copied!',
successMessage: 'The HDHR URL has been copied to your clipboard.',
});
};

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { notifications } from '@mantine/notifications';
export default {
Limiter: (n, list) => {
@ -76,30 +77,53 @@ export function sleep(ms) {
export const getDescendantProp = (obj, path) =>
path.split('.').reduce((acc, part) => acc && acc[part], obj);
export const copyToClipboard = async (value) => {
export const copyToClipboard = async (value, options = {}) => {
const {
successTitle = 'Copied!',
successMessage = 'Copied to clipboard',
failureTitle = 'Copy Failed',
failureMessage = 'Failed to copy to clipboard',
showNotification = true,
} = options;
let success = false;
if (navigator.clipboard) {
// Modern method, using navigator.clipboard
try {
await navigator.clipboard.writeText(value);
return true;
success = true;
} catch (err) {
console.error('Failed to copy: ', err);
}
}
// Fallback method for environments without clipboard support
try {
const textarea = document.createElement('textarea');
textarea.value = value;
document.body.appendChild(textarea);
textarea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textarea);
return successful;
} catch (err) {
console.error('Failed to copy with fallback method: ', err);
return false;
if (!success) {
// Fallback method for environments without clipboard support
try {
const textarea = document.createElement('textarea');
textarea.value = value;
document.body.appendChild(textarea);
textarea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textarea);
success = successful;
} catch (err) {
console.error('Failed to copy with fallback method: ', err);
success = false;
}
}
// Show notification if enabled
if (showNotification) {
notifications.show({
title: success ? successTitle : failureTitle,
message: success ? successMessage : failureMessage,
color: success ? 'green' : 'red',
});
}
return success;
};
export const setCustomProperty = (input, key, value, serialize = false) => {