Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 110 additions & 98 deletions extension/popup.html
Original file line number Diff line number Diff line change
@@ -1,114 +1,126 @@
<html>
<head>
<script src="scripts/date_fns.min.js"></script>
<script src="scripts/pop-up.js"></script>
<link rel="stylesheet" type="text/css" href="styles/bootstrap.min.css" />
<link
rel="stylesheet"
type="text/css"
href="styles/bootstrap-theme.min.css"
/>
<link rel="stylesheet" type="text/css" href="styles/font-awesome.min.css" />
<link rel="stylesheet" type="text/css" href="styles/main.css" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans"
rel="stylesheet"
type="text/css"
/>
</head>
<head>
<script src="scripts/date_fns.min.js"></script>
<script src="scripts/pop-up.js"></script>
<link rel="stylesheet" type="text/css" href="styles/bootstrap.min.css" />
<link
rel="stylesheet"
type="text/css"
href="styles/bootstrap-theme.min.css"
/>
<link rel="stylesheet" type="text/css" href="styles/font-awesome.min.css" />
<link rel="stylesheet" type="text/css" href="styles/main.css" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans"
rel="stylesheet"
type="text/css"
/>
</head>

<body>
<div id="main">
<div class="header">
<div class="container">
<div class="row purple banner">
<div class="col-xs-12 text-center display-flex">
<img src="images/logo_128.png" />
<h1 class="heading">Streamers</h1>
</div>
<body>
<div id="main">
<div class="header">
<div class="container">
<div class="row purple banner">
<div class="col-xs-7 display-flex header-left">
<img src="images/logo_128.png" class="logo" />
<h1 class="heading">Streamers</h1>
</div>
<div class="col-xs-5 header-actions text-right">
<div class="btn-group btn-group-sm header-btns">
<button id="exportBtn" class="btn btn-default" title="Export streamers">
<i class="fa fa-download"></i> Export
</button>
<button id="importBtn" class="btn btn-default" title="Import streamers">
<i class="fa fa-upload"></i> Import
</button>
<input type="file" id="importFile" accept=".json,.txt" class="hidden" />
</div>
</div>
</div>
<div class="content section">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h3 id="loading" class="text-center">
<i class="fa fa-5x fa-spin fa-refresh"></i>
</h3>
<h3 id="emptyState" class="hidden">
Add a streamer below to start tracking their status.
</h3>
<ul id="streamers" class="list-unstyled streamers"></ul>
</div>
</div>
</div>
</div>
<div class="content section">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h3 id="loading" class="text-center">
<i class="fa fa-5x fa-spin fa-refresh"></i>
</h3>
<h3 id="emptyState" class="hidden">
Add a streamer below to start tracking their status.
</h3>
<ul id="streamers" class="list-unstyled streamers"></ul>
</div>
</div>
<div class="footer">
<div class="container">
<div class="row">
<div class="col-xs-6">
<form id="addForm">
<div class="input-group">
<input
type="text"
name="username"
id="streamerUsername"
class="form-control"
placeholder="Streamer's username"
/>
<span class="input-group-btn">
</div>
</div>
<div class="footer">
<div class="container">
<div class="row">
<div class="col-xs-6">
<form id="addForm">
<div class="input-group">
<input
type="text"
name="username"
id="streamerUsername"
class="form-control"
placeholder="Streamer's username"
/>
<span class="input-group-btn">
<input type="submit" class="btn btn-default" value="Add" />
</span>
</div>
</form>
</div>
</form>
</div>
</div>
<div class="row spacing">
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hideOffline"
/>
<label class="form-check-label" for="hideOffline">
Show offline
</label>
</div>
<div class="row spacing">
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hideOffline"
/>
<label class="form-check-label" for="hideOffline">
Show offline
</label>
</div>
</div>
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hidePreviews"
/>
<label class="form-check-label" for="hidePreviews">
Show previews
</label>
</div>
</div>
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hideStreamersOnlineCount"
/>
<label class="form-check-label" for="hideStreamersOnlineCount">
Show # online
</label>
</div>
</div>
<div class="col-xs-3 text-right">
<a href="#" class="text-danger remove-all">
<i class="fa fa-times"></i> Remove all
</a>
</div>
</div>
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hidePreviews"
/>
<label class="form-check-label" for="hidePreviews">
Show previews
</label>
</div>
</div>
<div class="col-xs-3">
<div class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
id="hideStreamersOnlineCount"
/>
<label class="form-check-label" for="hideStreamersOnlineCount">
Show # online
</label>
</div>
</div>
<div class="col-xs-3 text-right">
<a href="#" class="text-danger remove-all">
<i class="fa fa-times"></i> Remove all
</a>
</div>
</div>

</div>
</body>
</div>
</div>
</body>
</html>
129 changes: 129 additions & 0 deletions extension/scripts/pop-up.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,133 @@ document.addEventListener('DOMContentLoaded', () => {
}
);
});

/**
* Downloads data as a JSON file
*/
const downloadJson = (data, filename = 'twitch_streamers.json') => {
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();

// Cleanup
document.body.removeChild(link);
URL.revokeObjectURL(url);
};

/**
* Parses import content into an array of streamer usernames
*/
const parseImportContent = (text) => {
// Try JSON first
try {
const parsed = JSON.parse(text);
if (Array.isArray(parsed)) {
return parsed
.map(String)
.map((s) => s.trim())
.filter(Boolean);
}
} catch {
// Not JSON, fall through
}

// Fallback: split by newlines or commas
return text
.split(/[\r\n,]+/)
.map((s) => s.trim())
.filter(Boolean);
};

/**
* Loads current streamers from storage
*/
const loadStreamers = () => {
return new Promise((resolve) => {
chrome.storage.sync.get('twitchStreams', (storage) => {
const list = Array.isArray(storage.twitchStreams)
? storage.twitchStreams
: [];
resolve(list);
});
});
};

/**
* Saves streamers to storage and refreshes status
*/
const saveAndRefresh = (streamers) => {
chrome.storage.sync.set({ twitchStreams: streamers }, () => {
fetchStreamerStatus({ twitchStreams: streamers });
});
};

/**
* Merges new streamers with existing ones (deduplicated)
*/
const mergeStreamers = (existing, incoming) => {
return Array.from(new Set([...existing, ...incoming]));
};

// === Export Functionality ===
const exportBtn = document.getElementById('exportBtn');
if (exportBtn) {
exportBtn.addEventListener('click', async () => {
try {
const streamers = await loadStreamers();
downloadJson(streamers);
} catch (err) {
alert('Failed to export streamers.');
console.error('Export error:', err);
}
});
}

// === Import Functionality ===
const importBtn = document.getElementById('importBtn');
const importFile = document.getElementById('importFile');

if (importBtn && importFile) {
importBtn.addEventListener('click', () => importFile.click());

importFile.addEventListener('change', async (evt) => {
const file = evt.target.files?.[0];
if (!file) return;

const reader = new FileReader();

reader.onload = async () => {
try {
const text = reader.result;
if (typeof text !== 'string') throw new Error('Invalid file content');

const newStreamers = parseImportContent(text);
if (!newStreamers.length) throw new Error('No valid usernames found');

const existing = await loadStreamers();
const merged = mergeStreamers(existing, newStreamers);

saveAndRefresh(merged);
evt.target.value = ''; // Allow re-import of same file
} catch (err) {
alert(
'Import failed. Please provide a JSON array or a list of usernames separated by commas or newlines.'
);
console.error('Import error:', err);
}
};

reader.onerror = () => {
alert('Failed to read the file.');
};

reader.readAsText(file);
});
}
});
Loading