@meddleware/walrus-ui 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/walrus-ui",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "homepage": "https://docs.meddleware.co.uk/blockchain/sui/walrus-storage/",
5
5
  "description": "Standalone Vue 3 SPA for uploading blobs to Walrus decentralised storage and managing owned blobs — wallet-connected, NFT-gated relay support.",
6
6
  "author": "Meddleware <dev@meddleware.co.uk>",
@@ -35,21 +35,25 @@
35
35
  "type-check": "vue-tsc --noEmit",
36
36
  "test": "vitest run",
37
37
  "test:watch": "vitest",
38
- "test:e2e": "playwright test"
38
+ "test:e2e": "playwright test",
39
+ "lint:css": "stylelint \"src/**/*.{css,vue}\"",
40
+ "lint:js": "eslint .",
41
+ "lint": "npm run lint:css && npm run lint:js && npm run lint:html",
42
+ "lint:html": "html-validate \"src/**/*.vue\""
39
43
  },
40
44
  "publishConfig": {
41
45
  "access": "public"
42
46
  },
43
47
  "dependencies": {
44
- "@meddleware/design-tokens": "^0.1.5",
45
- "@meddleware/nft-gate-client": "^0.0.9",
46
- "@meddleware/ui": "^0.1.21",
47
- "@meddleware/wallet-adapter": "^0.0.7",
48
- "@meddleware/walrus-client": "^0.0.15",
49
- "@meddleware/walrus-relay": "^0.1.15",
50
- "@mysten/sui": "^2.31.0",
48
+ "@meddleware/design-tokens": "^0.1.7",
49
+ "@meddleware/nft-gate-client": "^0.0.11",
50
+ "@meddleware/ui": "^0.1.28",
51
+ "@meddleware/wallet-adapter": "^0.0.9",
52
+ "@meddleware/walrus-client": "^0.0.17",
53
+ "@meddleware/walrus-relay": "^0.1.18",
54
+ "@mysten/sui": "^2.33.0",
51
55
  "@mysten/wallet-standard": "^0.21.0",
52
- "@mysten/walrus": "~1.2.28",
56
+ "@mysten/walrus": "~1.2.31",
53
57
  "@mysten/walrus-wasm": "~0.3.1",
54
58
  "vue": "^3.5.43"
55
59
  },
@@ -57,12 +61,30 @@
57
61
  "@playwright/test": "~1.63.0",
58
62
  "@types/node": "~24.12.2",
59
63
  "@vitejs/plugin-vue": "^6.0.8",
64
+ "@vue/eslint-config-typescript": "^14.9.0",
65
+ "autoprefixer": "^10.4.20",
66
+ "eslint": "^10.11.0",
67
+ "eslint-plugin-vue": "^10.11.1",
68
+ "eslint-plugin-vuejs-accessibility": "^2.6.0",
69
+ "html-validate": "^11.16.1",
70
+ "html-validate-vue": "^9.3.1",
71
+ "jiti": "^2.7.0",
72
+ "postcss": "^8.4.49",
73
+ "postcss-html": "^2.0.0",
74
+ "stylelint": "^17.15.0",
75
+ "stylelint-config-standard": "^40.0.0",
60
76
  "typescript": "~6.0.0",
61
77
  "vite": "^8.3.0",
62
78
  "vitest": "~5.0.1",
79
+ "vue-eslint-parser": "^10.4.1",
63
80
  "vue-tsc": "~3.3.0"
64
81
  },
65
82
  "engines": {
66
83
  "node": "^22.18.0 || >=24.12.0"
67
- }
84
+ },
85
+ "browserslist": [
86
+ "> 0.5%",
87
+ "last 2 versions",
88
+ "not dead"
89
+ ]
68
90
  }
package/src/App.vue CHANGED
@@ -23,7 +23,9 @@ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/
23
23
  </template>
24
24
  </AppHeader>
25
25
 
26
- <WalrusView />
26
+ <div class="app__content">
27
+ <WalrusView />
28
+ </div>
27
29
 
28
30
  <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL" />
29
31
  </div>
@@ -36,6 +38,17 @@ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/
36
38
  flex-direction: column;
37
39
  }
38
40
 
41
+ /* Centre the tool at the shared tool-content width when running standalone. The dashboard
42
+ supplies its own width container, so this lives in the shell, not WalrusView. */
43
+ .app__content {
44
+ flex: 1;
45
+ width: 100%;
46
+ max-width: var(--mw-tool-content-max);
47
+ margin: 0 auto;
48
+ box-sizing: border-box;
49
+ padding: 1.5rem 1.25rem 4rem;
50
+ }
51
+
39
52
  .brand-title {
40
53
  font: inherit;
41
54
  margin: 0;
@@ -93,7 +93,7 @@ function refreshPending(): void {
93
93
  }
94
94
 
95
95
  // ── Extend ──────────────────────────────────────────────────────────────────
96
- let extendReq: Record<string, number> = {}
96
+ const extendReq: Record<string, number> = {}
97
97
 
98
98
  // User picked an amount → clamp and (re)price it. We estimate only on interaction, not for every row
99
99
  // on load, to avoid spinning up a Walrus client per blob.
@@ -10,7 +10,7 @@ import {
10
10
  useAccessGate,
11
11
  } from '@meddleware/walrus-relay'
12
12
  import type { UploadResult, UploadProgress, ExistingCopy } from '@meddleware/walrus-relay'
13
- import { AppTabNav, CopyableAddress, ExplorerLink, UiNotice, suiExplorerUrl, safeHref, type AppTab } from '@meddleware/ui'
13
+ import { AppTabNav, CopyableAddress, ExplorerLink, UiNotice, UiTabPanel, UiToolIntro, suiExplorerUrl, safeHref, type AppTab } from '@meddleware/ui'
14
14
  // Lightweight URL import — just the wasm asset URL (does not pull the walrus client).
15
15
  import walrusWasmUrl from '@mysten/walrus-wasm/web/walrus_wasm_bg.wasm?url'
16
16
  import { WalletGuard } from '@meddleware/wallet-adapter'
@@ -230,110 +230,101 @@ function onSettled(): void {
230
230
  </script>
231
231
 
232
232
  <template>
233
- <div class="page">
234
- <p class="sub">Upload and manage blobs on Walrus decentralised storage ({{ NETWORK }}).</p>
233
+ <UiToolIntro>Upload and manage blobs on Walrus decentralised storage ({{ NETWORK }}).</UiToolIntro>
235
234
 
236
- <AppTabNav :tabs="TABS" v-model="activeTab" aria-label="Feature tabs" style="margin: 1rem 0 0.5rem" />
235
+ <AppTabNav v-model="activeTab" :tabs="TABS" id-prefix="walrus" aria-label="Feature tabs" class="walrus-tabs" />
237
236
 
238
237
  <WalletGuard message="Connect a Sui wallet to upload and manage your blobs.">
239
- <template v-if="activeTab === 'upload'">
240
- <!-- Gated + no access: replace the form with the purchase CTA so the user is guided to buy
241
- first, rather than facing a disabled form. -->
242
- <div v-if="gateState.gateConfigured && gateState.hasAccess.value === false" class="gate-card">
243
- <AccessGateCta
244
- :gate-configured="gateState.gateConfigured"
245
- :has-access="gateState.hasAccess.value"
246
- :busy="purchasing"
247
- :price-mist="gate?.priceMist ?? null"
248
- @purchase="onPurchase"
249
- />
250
- </div>
251
-
252
- <!-- Ownership check still in flight. -->
253
- <p
254
- v-else-if="gateState.gateConfigured && gateState.hasAccess.value === null"
255
- class="checking"
256
- >
257
- Checking access…
258
- </p>
259
-
260
- <!-- Access held (or ungated relay): show the upload form. -->
261
- <template v-else>
262
- <!-- Gated relays spend a credit before the file is stored — make the "attempt, not a
263
- guarantee" nature explicit, while reassuring that attempts resume. -->
264
- <UiNotice v-if="gateState.gateConfigured" type="info" class="credit-notice">
265
- Uploading spends <strong>one credit</strong> from your access NFT (an on-chain step)
266
- before the file is stored — it pays for an upload <em>attempt</em>, not a guaranteed
267
- upload. Your attempt resumes automatically, even after a page reload if you re-select
268
- the same file, so a credit is normally not lost. A credit is spent without a completed
269
- upload only if you abandon the upload entirely, cancel a required wallet approval, or
270
- wait long enough that the reserved storage lapses.
271
- </UiNotice>
272
-
273
- <WalrusUpload
274
- :hosts="relayHosts(NETWORK)"
275
- :connected="!!account"
276
- :access="{ gateConfigured: gateState.gateConfigured, hasAccess: gateState.hasAccess }"
277
- :perform-upload="performUpload"
278
- :estimate-storage-cost="estimateUploadStorageCost"
279
- @uploaded="onUploaded"
280
- @settled="onSettled"
281
- @manage-existing="onManageExisting"
282
- />
283
-
284
- <section v-if="result" class="result">
285
- <h2>Uploaded ✓</h2>
286
- <p>
287
- <strong>Blob ID:</strong>
288
- <CopyableAddress :address="result.blobId" label="Copy blob ID">
289
- <ExplorerLink
290
- :href="walruscanBlobUrl(NETWORK, result.blobId)"
291
- :value="result.blobId"
292
- :chars="[8, 6]"
293
- />
294
- </CopyableAddress>
295
- </p>
296
- <p>
297
- <strong>URL:</strong>
298
- <a :href="safeHref(result.url)" target="_blank" rel="noopener noreferrer">{{ result.url }}</a>
299
- </p>
300
- <p v-if="result.digest">
301
- <strong>Certify tx:</strong>
302
- <CopyableAddress :address="result.digest" label="Copy transaction digest">
303
- <ExplorerLink
304
- :href="suiExplorerUrl('txblock', result.digest, NETWORK)"
305
- :value="result.digest"
306
- :chars="[8, 6]"
307
- />
308
- </CopyableAddress>
309
- </p>
310
- </section>
238
+ <UiTabPanel id-prefix="walrus" :tab="activeTab">
239
+ <template v-if="activeTab === 'upload'">
240
+ <!-- Gated + no access: replace the form with the purchase CTA so the user is guided to buy
241
+ first, rather than facing a disabled form. -->
242
+ <div v-if="gateState.gateConfigured && gateState.hasAccess.value === false" class="gate-card">
243
+ <AccessGateCta
244
+ :gate-configured="gateState.gateConfigured"
245
+ :has-access="gateState.hasAccess.value"
246
+ :busy="purchasing"
247
+ :price-mist="gate?.priceMist ?? null"
248
+ @purchase="onPurchase"
249
+ />
250
+ </div>
251
+
252
+ <!-- Ownership check still in flight. -->
253
+ <p
254
+ v-else-if="gateState.gateConfigured && gateState.hasAccess.value === null"
255
+ class="checking"
256
+ >
257
+ Checking access…
258
+ </p>
259
+
260
+ <!-- Access held (or ungated relay): show the upload form. -->
261
+ <template v-else>
262
+ <!-- Gated relays spend a credit before the file is stored — make the "attempt, not a
263
+ guarantee" nature explicit, while reassuring that attempts resume. -->
264
+ <UiNotice v-if="gateState.gateConfigured" type="info" class="credit-notice">
265
+ Uploading spends <strong>one credit</strong> from your access NFT (an on-chain step)
266
+ before the file is stored — it pays for an upload <em>attempt</em>, not a guaranteed
267
+ upload. Your attempt resumes automatically, even after a page reload if you re-select
268
+ the same file, so a credit is normally not lost. A credit is spent without a completed
269
+ upload only if you abandon the upload entirely, cancel a required wallet approval, or
270
+ wait long enough that the reserved storage lapses.
271
+ </UiNotice>
272
+
273
+ <WalrusUpload
274
+ :hosts="relayHosts(NETWORK)"
275
+ :connected="!!account"
276
+ :access="{ gateConfigured: gateState.gateConfigured, hasAccess: gateState.hasAccess }"
277
+ :perform-upload="performUpload"
278
+ :estimate-storage-cost="estimateUploadStorageCost"
279
+ @uploaded="onUploaded"
280
+ @settled="onSettled"
281
+ @manage-existing="onManageExisting"
282
+ />
283
+
284
+ <section v-if="result" class="result">
285
+ <h2>Uploaded ✓</h2>
286
+ <p>
287
+ <strong>Blob ID:</strong>
288
+ <CopyableAddress :address="result.blobId" label="Copy blob ID">
289
+ <ExplorerLink
290
+ :href="walruscanBlobUrl(NETWORK, result.blobId)"
291
+ :value="result.blobId"
292
+ :chars="[8, 6]"
293
+ />
294
+ </CopyableAddress>
295
+ </p>
296
+ <p>
297
+ <strong>URL:</strong>
298
+ <a :href="safeHref(result.url)" target="_blank" rel="noopener noreferrer">{{ result.url }}</a>
299
+ </p>
300
+ <p v-if="result.digest">
301
+ <strong>Certify tx:</strong>
302
+ <CopyableAddress :address="result.digest" label="Copy transaction digest">
303
+ <ExplorerLink
304
+ :href="suiExplorerUrl('txblock', result.digest, NETWORK)"
305
+ :value="result.digest"
306
+ :chars="[8, 6]"
307
+ />
308
+ </CopyableAddress>
309
+ </p>
310
+ </section>
311
+ </template>
311
312
  </template>
312
- </template>
313
313
 
314
- <MyBlobs
315
- v-if="activeTab === 'blobs'"
316
- :address="account?.address ?? null"
317
- :build-executor="() => buildExecutor()"
318
- :highlight-blob-id="highlightBlobId"
319
- />
314
+ <MyBlobs
315
+ v-if="activeTab === 'blobs'"
316
+ :address="account?.address ?? null"
317
+ :build-executor="() => buildExecutor()"
318
+ :highlight-blob-id="highlightBlobId"
319
+ />
320
+ </UiTabPanel>
320
321
  </WalletGuard>
321
- </div>
322
322
  </template>
323
323
 
324
324
  <style scoped>
325
- .page {
326
- max-width: 640px;
327
- margin: 0 auto;
328
- padding: 2rem 1.25rem 4rem;
329
- flex: 1;
325
+ .walrus-tabs {
326
+ margin: 1rem 0 0.5rem;
330
327
  }
331
-
332
- .sub {
333
- color: var(--muted);
334
- margin: 0.25rem 0 0;
335
- }
336
-
337
328
  .result {
338
329
  margin-top: 1.5rem;
339
330
  padding: 1rem;