diff options
| author | A Farzat <a@farzat.xyz> | 2025-10-09 09:51:14 +0300 |
|---|---|---|
| committer | A Farzat <a@farzat.xyz> | 2025-10-09 09:51:14 +0300 |
| commit | 9fb20553d9f9e21c012f730a728c33d368e09bf2 (patch) | |
| tree | 9299ef0ee912eaad281c1edb783b5c975c6fe0a2 | |
| parent | 6e117b5438413d12cf84860ea5c8e3bfd1727f40 (diff) | |
| download | csca5028-9fb20553d9f9e21c012f730a728c33d368e09bf2.tar.gz csca5028-9fb20553d9f9e21c012f730a728c33d368e09bf2.zip | |
Add methods to modify/delete subscriptions
| -rw-r--r-- | front-end/src/App.css | 127 | ||||
| -rw-r--r-- | front-end/src/App.jsx | 123 | ||||
| -rw-r--r-- | wsgi/__init__.py | 20 |
3 files changed, 268 insertions, 2 deletions
diff --git a/front-end/src/App.css b/front-end/src/App.css index 20d839e..2808f4e 100644 --- a/front-end/src/App.css +++ b/front-end/src/App.css @@ -221,6 +221,133 @@ body { margin-top: 0.3rem; } +.subscription-actions { + display: flex; + gap: 0.5rem; + margin-top: 0.5rem; + justify-content: center; +} + +.edit-button, .delete-button { + padding: 0.4rem 0.8rem; + border: none; + border-radius: 10px; + font-size: 0.8rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.edit-button { + background: rgba(255, 193, 7, 0.2); + color: #ffc107; + border: 1px solid rgba(255, 193, 7, 0.3); +} + +.edit-button:hover:not(:disabled) { + background: rgba(255, 193, 7, 0.3); + transform: translateY(-1px); +} + +.delete-button { + background: rgba(220, 53, 69, 0.2); + color: #dc3545; + border: 1px solid rgba(220, 53, 69, 0.3); +} + +.delete-button:hover:not(:disabled) { + background: rgba(220, 53, 69, 0.3); + transform: translateY(-1px); +} + +.edit-button:disabled, .delete-button:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.edit-subscription-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(5px); +} + +.edit-subscription-content { + background: rgba(255, 255, 255, 0.15); + padding: 2rem; + border-radius: 15px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; +} + +.edit-subscription-content h3 { + color: white; + margin-bottom: 1.5rem; + text-align: center; + font-size: 1.3rem; +} + +.cancel-button { + padding: 0.8rem 1.5rem; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 20px; + background: rgba(255, 255, 255, 0.1); + color: white; + cursor: pointer; + transition: all 0.3s ease; + font-size: 1rem; + margin-left: 0.5rem; +} + +.cancel-button:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +.cancel-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Responsive updates for subscription actions */ +@media (max-width: 768px) { + .subscription-actions { + flex-direction: column; + align-items: center; + } + + .edit-button, .delete-button { + width: 100%; + max-width: 200px; + } + + .edit-subscription-content { + padding: 1.5rem; + margin: 1rem; + } + + .form-actions { + flex-direction: column; + gap: 0.5rem; + } + + .cancel-button { + margin-left: 0; + margin-top: 0.5rem; + } +} + .subscription-error { color: #ff6b6b; background: rgba(255, 107, 107, 0.1); diff --git a/front-end/src/App.jsx b/front-end/src/App.jsx index da6057e..1bdcdaf 100644 --- a/front-end/src/App.jsx +++ b/front-end/src/App.jsx @@ -19,6 +19,10 @@ function App() { const [addingSubscription, setAddingSubscription] = useState(false); const [subscriptionError, setSubscriptionError] = useState(null); const [subscriptionSuccess, setSubscriptionSuccess] = useState(null); + const [editingSubscription, setEditingSubscription] = useState(null); + const [newTimeBetweenFetches, setNewTimeBetweenFetches] = useState(300); + const [updatingSubscription, setUpdatingSubscription] = useState(false); + const [deletingSubscription, setDeletingSubscription] = useState(false); const fetchChannels = async () => { try { @@ -72,6 +76,57 @@ function App() { } }; + const updateTimeBetweenFetches = async (subscriptionId, newTime) => { + try { + setUpdatingSubscription(true); + const formData = new FormData(); + formData.append('_id', subscriptionId); + formData.append('time_between_fetches', newTime.toString()); + + await axios.post(`${API_BASE_URL}/set-time-between-fetches/`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + // Refresh the channels list to show updated data + fetchChannels(); + setEditingSubscription(null); + setSubscriptionSuccess('Fetch interval updated successfully!'); + } catch (err) { + console.error('Error updating fetch interval:', err); + setSubscriptionError('Failed to update fetch interval. Please try again.'); + } finally { + setUpdatingSubscription(false); + } + }; + + const deleteSubscription = async (subscriptionId) => { + if (!window.confirm('Are you sure you want to delete this subscription? This action cannot be undone.')) { + return; + } + + try { + setDeletingSubscription(true); + await axios.delete(`${API_BASE_URL}/delete-sub/${subscriptionId}`); + + // If the deleted subscription was selected, clear the selection + if (selectedChannelId === subscriptionId.replace('yt:channel:', '').replace('yt:playlist:', '')) { + setSelectedChannelId(''); + setVideos([]); + } + + // Refresh the channels list + fetchChannels(); + setSubscriptionSuccess('Subscription deleted successfully!'); + } catch (err) { + console.error('Error deleting subscription:', err); + setSubscriptionError('Failed to delete subscription. Please try again.'); + } finally { + setDeletingSubscription(false); + } + }; + const fetchVideos = async (channelId) => { try { setLoading(true); @@ -228,6 +283,16 @@ function App() { setSubscriptionSuccess(null); }; + const startEditingSubscription = (subscription) => { + setEditingSubscription(subscription); + setNewTimeBetweenFetches(subscription.time_between_fetches); + }; + + const cancelEditing = () => { + setEditingSubscription(null); + setNewTimeBetweenFetches(300); + }; + return ( <div className="App"> <header className="App-header"> @@ -286,9 +351,67 @@ function App() { )} </p> )} + <div className="subscription-actions"> + <button + onClick={() => startEditingSubscription(channels.find(ch => ch.id === selectedChannelId))} + className="edit-button" + > + ⚙️ Edit + </button> + <button + onClick={() => deleteSubscription(channels.find(ch => ch.id === selectedChannelId)?._id)} + className="delete-button" + disabled={deletingSubscription} + > + {deletingSubscription ? 'Deleting...' : '🗑️ Delete'} + </button> + </div> </div> )} </div> + + {editingSubscription && ( + <div className="edit-subscription-modal"> + <div className="edit-subscription-content"> + <h3>Edit Subscription</h3> + <div className="form-group"> + <label htmlFor="edit-time-between-fetches"> + Fetch Interval (seconds): + </label> + <input + id="edit-time-between-fetches" + type="number" + value={newTimeBetweenFetches} + onChange={(e) => setNewTimeBetweenFetches(parseInt(e.target.value) || 0)} + min="60" + max="86400" + className="subscription-input" + disabled={updatingSubscription} + /> + <small className="interval-help"> + How often to check for new videos (current: {editingSubscription.time_between_fetches}s) + </small> + </div> + <div className="form-actions"> + <button + onClick={() => updateTimeBetweenFetches(editingSubscription._id, newTimeBetweenFetches)} + className="submit-subscription" + disabled={updatingSubscription} + > + {updatingSubscription ? 'Updating...' : 'Update'} + </button> + <button + onClick={cancelEditing} + className="cancel-button" + disabled={updatingSubscription} + > + Cancel + </button> + </div> + </div> + </div> + )} + <div className="subscription-section"> <button onClick={() => { diff --git a/wsgi/__init__.py b/wsgi/__init__.py index 9a504b0..f8f59db 100644 --- a/wsgi/__init__.py +++ b/wsgi/__init__.py @@ -42,13 +42,29 @@ def add_sub() -> Dict[str, Any]: @app.post("/set-time-between-fetches/") def set_time_between_fetches() -> Dict[str, Any]: - return {} + time_between_fetches = int(request.form["time_between_fetches"]) + result = subscriptions.update_one( + {"_id": request.form["_id"]}, + {"$set": {"time_between_fetches": time_between_fetches}} + ) + if not result.modified_count: + raise Exception("Subscription %s not found" % request.form["_id"]) + return { + "_id": request.form["_id"], + "time_between_fetches": time_between_fetches, + } + +@app.delete("/delete-sub/<id>") +def delete_sub(id: str) -> Dict[str, Any]: + result = subscriptions.delete_one({"_id": id}) + if not result.deleted_count: + raise Exception("Subscription %s not found" % id) + return { "_id": id, } @app.post("/set-viewed/") def set_viewed() -> Dict[str, Any]: viewed_time_str = request.form.get("viewed_time") if viewed_time_str: - print(viewed_time_str) viewed_time = datetime.fromisoformat(viewed_time_str) else: viewed_time = datetime.now(tz=UTC) |
