RecoveryView.svelte raw

   1  <script>
   2      export let recoverySelectedKind = null;
   3      export let recoveryCustomKind = "";
   4      export let isLoadingRecovery = false;
   5      export let recoveryEvents = [];
   6      export let recoveryHasMore = false;
   7  
   8      import { createEventDispatcher } from "svelte";
   9      const dispatch = createEventDispatcher();
  10  
  11      const replaceableKinds = [
  12          { value: 0, label: "Profile (0)" },
  13          { value: 3, label: "Contacts (3)" },
  14          { value: 10000, label: "Mute List (10000)" },
  15          { value: 10001, label: "Pin List (10001)" },
  16          { value: 10002, label: "Relay List (10002)" },
  17          { value: 30000, label: "Categorized People (30000)" },
  18          { value: 30001, label: "Categorized Bookmarks (30001)" },
  19          { value: 30008, label: "Profile Badges (30008)" },
  20          { value: 30009, label: "Badge Definition (30009)" },
  21          { value: 30017, label: "Create or update a stall (30017)" },
  22          { value: 30018, label: "Create or update a product (30018)" },
  23          { value: 30023, label: "Long-form Content (30023)" },
  24          { value: 30024, label: "Draft Long-form Content (30024)" },
  25          { value: 30078, label: "Application-specific Data (30078)" },
  26          { value: 30311, label: "Live Event (30311)" },
  27          { value: 30315, label: "User Statuses (30315)" },
  28          { value: 30402, label: "Classified Listing (30402)" },
  29          { value: 30403, label: "Draft Classified Listing (30403)" },
  30          { value: 31922, label: "Date-Based Calendar Event (31922)" },
  31          { value: 31923, label: "Time-Based Calendar Event (31923)" },
  32          { value: 31924, label: "Calendar (31924)" },
  33          { value: 31925, label: "Calendar Event RSVP (31925)" },
  34          { value: 31989, label: "Handler recommendation (31989)" },
  35          { value: 31990, label: "Handler information (31990)" },
  36          { value: 34550, label: "Community Definition (34550)" },
  37      ];
  38  
  39      function selectRecoveryKind() {
  40          dispatch("selectRecoveryKind");
  41      }
  42  
  43      function handleCustomKindInput() {
  44          dispatch("handleCustomKindInput");
  45      }
  46  
  47      function loadRecoveryEvents() {
  48          dispatch("loadRecoveryEvents");
  49      }
  50  
  51      function repostEventToAll(event) {
  52          dispatch("repostEventToAll", event);
  53      }
  54  
  55      function repostEvent(event) {
  56          dispatch("repostEvent", event);
  57      }
  58  
  59      function copyEventToClipboard(event, e) {
  60          dispatch("copyEventToClipboard", { event, e });
  61      }
  62  
  63      function isCurrentVersion(event) {
  64          // This logic would need to be passed from parent or implemented here
  65          // For now, just return false for old versions
  66          return false;
  67      }
  68  </script>
  69  
  70  <div class="recovery-tab">
  71      <div>
  72          <h3>Event Recovery</h3>
  73          <p>Search and recover old versions of replaceable events</p>
  74      </div>
  75  
  76      <div class="recovery-controls-card">
  77          <div class="recovery-controls">
  78              <div class="kind-selector">
  79                  <label for="recovery-kind">Select Event Kind:</label>
  80                  <select
  81                      id="recovery-kind"
  82                      bind:value={recoverySelectedKind}
  83                      on:change={selectRecoveryKind}
  84                  >
  85                      <option value={null}>Choose a replaceable kind...</option>
  86                      {#each replaceableKinds as kind}
  87                          <option value={kind.value}>{kind.label}</option>
  88                      {/each}
  89                  </select>
  90              </div>
  91  
  92              <div class="custom-kind-input">
  93                  <label for="custom-kind">Or enter custom kind number:</label>
  94                  <input
  95                      id="custom-kind"
  96                      type="number"
  97                      bind:value={recoveryCustomKind}
  98                      on:input={handleCustomKindInput}
  99                      placeholder="e.g., 10001"
 100                      min="0"
 101                  />
 102              </div>
 103          </div>
 104      </div>
 105  
 106      {#if (recoverySelectedKind !== null && recoverySelectedKind !== undefined && recoverySelectedKind >= 0) || (recoveryCustomKind !== "" && parseInt(recoveryCustomKind) >= 0)}
 107          <div class="recovery-results">
 108              {#if isLoadingRecovery}
 109                  <div class="loading">Loading events...</div>
 110              {:else if recoveryEvents.length === 0}
 111                  <div class="no-events">No events found for this kind</div>
 112              {:else}
 113                  <div class="events-list">
 114                      {#each recoveryEvents as event}
 115                          {@const isCurrent = isCurrentVersion(event)}
 116                          <div class="event-item" class:old-version={!isCurrent}>
 117                              <div class="event-header">
 118                                  <div class="event-header-left">
 119                                      <span class="event-kind">
 120                                          {#if isCurrent}
 121                                              Current Version{/if}</span
 122                                      >
 123                                      <span class="event-timestamp">
 124                                          {new Date(
 125                                              event.created_at * 1000,
 126                                          ).toLocaleString()}
 127                                      </span>
 128                                  </div>
 129                                  <div class="event-header-actions">
 130                                      {#if !isCurrent}
 131                                          <button
 132                                              class="repost-all-button"
 133                                              on:click={() =>
 134                                                  repostEventToAll(event)}
 135                                          >
 136                                              🌐 Repost to All
 137                                          </button>
 138                                          <button
 139                                              class="repost-button"
 140                                              on:click={() => repostEvent(event)}
 141                                          >
 142                                              🔄 Repost
 143                                          </button>
 144                                      {/if}
 145                                      <button
 146                                          class="copy-json-btn"
 147                                          on:click|stopPropagation={(e) =>
 148                                              copyEventToClipboard(event, e)}
 149                                      >
 150                                          📋 Copy JSON
 151                                      </button>
 152                                  </div>
 153                              </div>
 154  
 155                              <div class="event-content">
 156                                  <pre class="event-json">{JSON.stringify(
 157                                          event,
 158                                          null,
 159                                          2,
 160                                      )}</pre>
 161                              </div>
 162                          </div>
 163                      {/each}
 164                  </div>
 165  
 166                  {#if recoveryHasMore}
 167                      <button
 168                          class="load-more"
 169                          on:click={loadRecoveryEvents}
 170                          disabled={isLoadingRecovery}
 171                      >
 172                          Load More Events
 173                      </button>
 174                  {/if}
 175              {/if}
 176          </div>
 177      {/if}
 178  </div>
 179  
 180  <style>
 181      .recovery-tab {
 182          width: 100%;
 183          max-width: 1200px;
 184          margin: 0;
 185          padding: 20px;
 186          background: var(--header-bg);
 187          color: var(--text-color);
 188          border-radius: 8px;
 189          box-sizing: border-box;
 190      }
 191  
 192      .recovery-tab h3 {
 193          margin: 0 0 0.5rem 0;
 194          color: var(--text-color);
 195          font-size: 1.5rem;
 196          font-weight: 600;
 197      }
 198  
 199      .recovery-tab p {
 200          margin: 0 0 1.5rem 0;
 201          color: var(--text-color);
 202          opacity: 0.8;
 203          line-height: 1.4;
 204      }
 205  
 206      .recovery-controls-card {
 207          background-color: var(--card-bg);
 208          border-radius: 0.5em;
 209          padding: 1em;
 210          margin-bottom: 1.5rem;
 211      }
 212  
 213      .recovery-controls {
 214          display: flex;
 215          flex-direction: column;
 216          gap: 1em;
 217      }
 218  
 219      .kind-selector,
 220      .custom-kind-input {
 221          display: flex;
 222          flex-direction: column;
 223          gap: 0.5em;
 224      }
 225  
 226      .kind-selector label,
 227      .custom-kind-input label {
 228          font-weight: 600;
 229          color: var(--text-color);
 230      }
 231  
 232      .kind-selector select,
 233      .custom-kind-input input {
 234          padding: 0.5em;
 235          border: 1px solid var(--border-color);
 236          border-radius: 4px;
 237          background: var(--input-bg);
 238          color: var(--input-text-color);
 239          font-size: 0.9em;
 240      }
 241  
 242      .recovery-results {
 243          margin-top: 1.5rem;
 244      }
 245  
 246      .loading,
 247      .no-events {
 248          text-align: center;
 249          padding: 2em;
 250          color: var(--text-color);
 251          opacity: 0.7;
 252      }
 253  
 254      .events-list {
 255          display: flex;
 256          flex-direction: column;
 257          gap: 1em;
 258      }
 259  
 260      .event-item {
 261          background: var(--card-bg);
 262          border: 1px solid var(--border-color);
 263          border-radius: 8px;
 264          padding: 1em;
 265      }
 266  
 267      .event-item.old-version {
 268          border-color: var(--warning);
 269          background: var(--warning-bg);
 270      }
 271  
 272      .event-header {
 273          display: flex;
 274          justify-content: space-between;
 275          align-items: center;
 276          margin-bottom: 1em;
 277          flex-wrap: wrap;
 278          gap: 1em;
 279      }
 280  
 281      .event-header-left {
 282          display: flex;
 283          flex-direction: column;
 284          gap: 0.25em;
 285      }
 286  
 287      .event-kind {
 288          font-weight: 600;
 289          color: var(--primary);
 290      }
 291  
 292      .event-timestamp {
 293          font-size: 0.9em;
 294          color: var(--text-color);
 295          opacity: 0.7;
 296      }
 297  
 298      .event-header-actions {
 299          display: flex;
 300          gap: 0.5em;
 301          flex-wrap: wrap;
 302      }
 303  
 304      .repost-all-button,
 305      .repost-button,
 306      .copy-json-btn {
 307          background: var(--accent-color);
 308          color: var(--accent-hover-color);
 309          border: none;
 310          padding: 0.5em;
 311          border-radius: 0.5em;
 312          cursor: pointer;
 313          font-size: 0.8em;
 314          transition: background-color 0.2s;
 315      }
 316  
 317      .repost-all-button:hover,
 318      .repost-button:hover,
 319      .copy-json-btn:hover {
 320          background: var(--accent-hover-color);
 321      }
 322  
 323      .event-content {
 324          margin-top: 1em;
 325      }
 326  
 327      .event-json {
 328          background: var(--code-bg);
 329          padding: 1em;
 330          border: 0;
 331          font-size: 0.8em;
 332          line-height: 1.4;
 333          overflow-x: auto;
 334          margin: 0;
 335          color: var(--code-text);
 336      }
 337  
 338      .load-more {
 339          width: 100%;
 340          padding: 12px;
 341          background: var(--primary);
 342          color: var(--text-color);
 343          border: none;
 344          border-radius: 4px;
 345          cursor: pointer;
 346          font-size: 1em;
 347          margin-top: 20px;
 348          transition: background 0.2s ease;
 349      }
 350  
 351      .load-more:hover:not(:disabled) {
 352          background: var(--accent-hover-color);
 353      }
 354  
 355      .load-more:disabled {
 356          opacity: 0.6;
 357          cursor: not-allowed;
 358      }
 359  </style>
 360