Files
next.orly.dev/app/web/src/RecoveryView.svelte
mleku d4f4f2a186
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
Implement New Views and Refactor App Structure
- Added new components: Header, Sidebar, ExportView, ImportView, EventsView, ComposeView, RecoveryView, SprocketView, and SearchResultsView to enhance the application's functionality and user experience.
- Updated App.svelte to integrate the new views and improve the overall layout.
- Refactored existing components for better organization and maintainability.
- Adjusted CSS styles for improved visual consistency across the application.
- Incremented version number to v0.19.3 to reflect the latest changes and additions.
2025-10-25 23:03:34 +01:00

359 lines
11 KiB
Svelte

<script>
export let recoverySelectedKind = null;
export let recoveryCustomKind = "";
export let isLoadingRecovery = false;
export let recoveryEvents = [];
export let recoveryHasMore = false;
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
const replaceableKinds = [
{ value: 0, label: "Profile (0)" },
{ value: 3, label: "Contacts (3)" },
{ value: 10000, label: "Mute List (10000)" },
{ value: 10001, label: "Pin List (10001)" },
{ value: 10002, label: "Relay List (10002)" },
{ value: 30000, label: "Categorized People (30000)" },
{ value: 30001, label: "Categorized Bookmarks (30001)" },
{ value: 30008, label: "Profile Badges (30008)" },
{ value: 30009, label: "Badge Definition (30009)" },
{ value: 30017, label: "Create or update a stall (30017)" },
{ value: 30018, label: "Create or update a product (30018)" },
{ value: 30023, label: "Long-form Content (30023)" },
{ value: 30024, label: "Draft Long-form Content (30024)" },
{ value: 30078, label: "Application-specific Data (30078)" },
{ value: 30311, label: "Live Event (30311)" },
{ value: 30315, label: "User Statuses (30315)" },
{ value: 30402, label: "Classified Listing (30402)" },
{ value: 30403, label: "Draft Classified Listing (30403)" },
{ value: 31922, label: "Date-Based Calendar Event (31922)" },
{ value: 31923, label: "Time-Based Calendar Event (31923)" },
{ value: 31924, label: "Calendar (31924)" },
{ value: 31925, label: "Calendar Event RSVP (31925)" },
{ value: 31989, label: "Handler recommendation (31989)" },
{ value: 31990, label: "Handler information (31990)" },
{ value: 34550, label: "Community Definition (34550)" },
];
function selectRecoveryKind() {
dispatch("selectRecoveryKind");
}
function handleCustomKindInput() {
dispatch("handleCustomKindInput");
}
function loadRecoveryEvents() {
dispatch("loadRecoveryEvents");
}
function repostEventToAll(event) {
dispatch("repostEventToAll", event);
}
function repostEvent(event) {
dispatch("repostEvent", event);
}
function copyEventToClipboard(event, e) {
dispatch("copyEventToClipboard", { event, e });
}
function isCurrentVersion(event) {
// This logic would need to be passed from parent or implemented here
// For now, just return false for old versions
return false;
}
</script>
<div class="recovery-tab">
<div>
<h3>Event Recovery</h3>
<p>Search and recover old versions of replaceable events</p>
</div>
<div class="recovery-controls-card">
<div class="recovery-controls">
<div class="kind-selector">
<label for="recovery-kind">Select Event Kind:</label>
<select
id="recovery-kind"
bind:value={recoverySelectedKind}
on:change={selectRecoveryKind}
>
<option value={null}>Choose a replaceable kind...</option>
{#each replaceableKinds as kind}
<option value={kind.value}>{kind.label}</option>
{/each}
</select>
</div>
<div class="custom-kind-input">
<label for="custom-kind">Or enter custom kind number:</label>
<input
id="custom-kind"
type="number"
bind:value={recoveryCustomKind}
on:input={handleCustomKindInput}
placeholder="e.g., 10001"
min="0"
/>
</div>
</div>
</div>
{#if (recoverySelectedKind !== null && recoverySelectedKind !== undefined && recoverySelectedKind >= 0) || (recoveryCustomKind !== "" && parseInt(recoveryCustomKind) >= 0)}
<div class="recovery-results">
{#if isLoadingRecovery}
<div class="loading">Loading events...</div>
{:else if recoveryEvents.length === 0}
<div class="no-events">No events found for this kind</div>
{:else}
<div class="events-list">
{#each recoveryEvents as event}
{@const isCurrent = isCurrentVersion(event)}
<div class="event-item" class:old-version={!isCurrent}>
<div class="event-header">
<div class="event-header-left">
<span class="event-kind">
{#if isCurrent}
Current Version{/if}</span
>
<span class="event-timestamp">
{new Date(
event.created_at * 1000,
).toLocaleString()}
</span>
</div>
<div class="event-header-actions">
{#if !isCurrent}
<button
class="repost-all-button"
on:click={() =>
repostEventToAll(event)}
>
🌐 Repost to All
</button>
<button
class="repost-button"
on:click={() => repostEvent(event)}
>
🔄 Repost
</button>
{/if}
<button
class="copy-json-btn"
on:click|stopPropagation={(e) =>
copyEventToClipboard(event, e)}
>
📋 Copy JSON
</button>
</div>
</div>
<div class="event-content">
<pre class="event-json">{JSON.stringify(
event,
null,
2,
)}</pre>
</div>
</div>
{/each}
</div>
{#if recoveryHasMore}
<button
class="load-more"
on:click={loadRecoveryEvents}
disabled={isLoadingRecovery}
>
Load More Events
</button>
{/if}
{/if}
</div>
{/if}
</div>
<style>
.recovery-tab {
width: 100%;
max-width: 1200px;
margin: 0;
padding: 20px;
background: var(--header-bg);
color: var(--text-color);
border-radius: 8px;
}
.recovery-tab h3 {
margin: 0 0 0.5rem 0;
color: var(--text-color);
font-size: 1.5rem;
font-weight: 600;
}
.recovery-tab p {
margin: 0 0 1.5rem 0;
color: var(--text-color);
opacity: 0.8;
line-height: 1.4;
}
.recovery-controls-card {
background-color: var(--card-bg);
border-radius: 0.5em;
padding: 1em;
margin-bottom: 1.5rem;
}
.recovery-controls {
display: flex;
flex-direction: column;
gap: 1em;
}
.kind-selector,
.custom-kind-input {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.kind-selector label,
.custom-kind-input label {
font-weight: 600;
color: var(--text-color);
}
.kind-selector select,
.custom-kind-input input {
padding: 0.5em;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--input-bg);
color: var(--input-text-color);
font-size: 0.9em;
}
.recovery-results {
margin-top: 1.5rem;
}
.loading,
.no-events {
text-align: center;
padding: 2em;
color: var(--text-color);
opacity: 0.7;
}
.events-list {
display: flex;
flex-direction: column;
gap: 1em;
}
.event-item {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1em;
}
.event-item.old-version {
border-color: var(--warning);
background: var(--warning-bg);
}
.event-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
flex-wrap: wrap;
gap: 1em;
}
.event-header-left {
display: flex;
flex-direction: column;
gap: 0.25em;
}
.event-kind {
font-weight: 600;
color: var(--primary);
}
.event-timestamp {
font-size: 0.9em;
color: var(--text-color);
opacity: 0.7;
}
.event-header-actions {
display: flex;
gap: 0.5em;
flex-wrap: wrap;
}
.repost-all-button,
.repost-button,
.copy-json-btn {
background: var(--accent-color);
color: var(--accent-hover-color);
border: none;
padding: 0.5em;
border-radius: 0.5em;
cursor: pointer;
font-size: 0.8em;
transition: background-color 0.2s;
}
.repost-all-button:hover,
.repost-button:hover,
.copy-json-btn:hover {
background: var(--accent-hover-color);
}
.event-content {
margin-top: 1em;
}
.event-json {
background: var(--code-bg);
padding: 1em;
border: 0;
font-size: 0.8em;
line-height: 1.4;
overflow-x: auto;
margin: 0;
color: var(--code-text);
}
.load-more {
width: 100%;
padding: 12px;
background: var(--primary);
color: var(--text-color);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
margin-top: 20px;
transition: background 0.2s ease;
}
.load-more:hover:not(:disabled) {
background: var(--accent-hover-color);
}
.load-more:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>