mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-02 13:33:06 +00:00
Merge pull request #94 from codex-storage/releases/v0.0.14
Releases/v0.0.14
This commit is contained in:
commit
d44a8dc95f
13
.github/workflows/playwright.yml
vendored
13
.github/workflows/playwright.yml
vendored
@ -8,10 +8,11 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
codex_version: v0.1.9
|
codex_version: v0.2.0
|
||||||
circuit_version: v0.1.9
|
circuit_version: v0.2.0
|
||||||
marketplace_address: "0xAB03b6a58C5262f530D54146DA2a552B1C0F7648"
|
marketplace_address: "0xfFaF679D5Cbfdd5Dbc9Be61C616ed115DFb597ed"
|
||||||
eth_provider: "https://rpc.testnet.codex.storage"
|
eth_provider: "https://rpc.testnet.codex.storage"
|
||||||
|
bootstrap_node: "spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P"
|
||||||
VITE_CODEX_API_URL: ${{ secrets.VITE_CODEX_API_URL }}
|
VITE_CODEX_API_URL: ${{ secrets.VITE_CODEX_API_URL }}
|
||||||
VITE_GEO_IP_URL: ${{ secrets.VITE_GEO_IP_URL }}
|
VITE_GEO_IP_URL: ${{ secrets.VITE_GEO_IP_URL }}
|
||||||
jobs:
|
jobs:
|
||||||
@ -36,7 +37,7 @@ jobs:
|
|||||||
key: ${{ env.circuit_version }}-circuits
|
key: ${{ env.circuit_version }}-circuits
|
||||||
|
|
||||||
- name: Download circuits
|
- name: Download circuits
|
||||||
if: steps.circuits-cache-restore.outputs.cache-hit != 'true'
|
# if: steps.circuits-cache-restore.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
mkdir -p datadir/circuits
|
mkdir -p datadir/circuits
|
||||||
chmod 700 datadir
|
chmod 700 datadir
|
||||||
@ -85,13 +86,13 @@ jobs:
|
|||||||
chmod 600 eth.key
|
chmod 600 eth.key
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
./codex-${codex_version}-${platform}-${architecture} --data-dir=datadir --api-cors-origin="*" persistence --eth-provider=${eth_provider} --eth-private-key=./eth.key --marketplace-address=${marketplace_address} prover --circuit-dir=./datadir/circuits &
|
./codex-${codex_version}-${platform}-${architecture} --data-dir=./datadir --bootstrap-node=${bootstrap_node} --nat=any --disc-port=8090 --api-cors-origin="*" persistence --eth-provider=${eth_provider} --eth-private-key=./eth.key --marketplace-address=${marketplace_address} &
|
||||||
|
|
||||||
sleep 15
|
sleep 15
|
||||||
|
|
||||||
- name: Check Codex API
|
- name: Check Codex API
|
||||||
run: |
|
run: |
|
||||||
curl --max-time 5 --fail localhost:8080/api/codex/v1/debug/info -w "\n"
|
curl --max-time 10 --fail localhost:8080/api/codex/v1/debug/info -w "\n"
|
||||||
[[ $? -eq 0 ]] && { echo "Codex node is up"; } || { echo "Please check Codex node"; exit 1; }
|
[[ $? -eq 0 ]] && { echo "Codex node is up"; } || { echo "Please check Codex node"; exit 1; }
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@ -1,121 +1,143 @@
|
|||||||
import test, { expect } from "@playwright/test";
|
import test, { expect } from "@playwright/test";
|
||||||
import { Bytes } from "../src/utils/bytes"
|
import { Bytes } from "../src/utils/bytes";
|
||||||
import { GB } from "../src/utils/constants"
|
import { GB } from "../src/utils/constants";
|
||||||
|
|
||||||
test('create an availability', async ({ page }) => {
|
test("create an availability", async ({ page }) => {
|
||||||
await page.goto('/dashboard/availabilities');
|
await page.goto("/dashboard/availabilities");
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.locator('.availability-edit button').first().click();
|
await page.locator(".availability-edit button").first().click();
|
||||||
await page.getByLabel('Total size').click();
|
await page.getByLabel("Total size").click();
|
||||||
|
|
||||||
const value = (Math.random() * 0.5) + 0.1;
|
const value = Math.random() * 0.5 + 0.1;
|
||||||
|
|
||||||
await page.getByLabel('Total size').fill(value.toFixed(1));
|
await page.getByLabel("Total size").fill(value.toFixed(1));
|
||||||
await page.getByLabel('Duration').click();
|
await page.getByLabel("Duration").click();
|
||||||
await page.getByLabel('Duration').fill('30');
|
await page.getByLabel("Duration").fill("30");
|
||||||
await page.getByLabel('Min price').click();
|
await page.getByLabel("Min price").click();
|
||||||
await page.getByLabel('Min price').fill('5');
|
await page.getByLabel("Min price").fill("5");
|
||||||
await page.getByLabel('Max collateral').click();
|
await page.getByLabel("Total collateral").click();
|
||||||
await page.getByLabel('Max collateral').fill('30');
|
await page.getByLabel("Total collateral").fill("30");
|
||||||
await page.getByLabel('Min price').fill('5');
|
await page.getByLabel("Min price").fill("5");
|
||||||
await page.getByLabel('Nickname').click();
|
await page.getByLabel("Nickname").click();
|
||||||
await page.getByLabel('Nickname').fill('test');
|
await page.getByLabel("Nickname").fill("test");
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
await expect(page.getByText("Confirm your new sale")).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
await expect(page.getByText("Success", { exact: true })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
await expect(
|
||||||
})
|
page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('availability navigation buttons', async ({ page }) => {
|
test("availability navigation buttons", async ({ page }) => {
|
||||||
await page.goto('/dashboard/availabilities');
|
await page.goto("/dashboard/availabilities");
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.locator('.availability-edit button').first().click();
|
await page.locator(".availability-edit button").first().click();
|
||||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
await expect(page.locator(".stepper-number-done")).not.toBeVisible();
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
await expect(
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
page.locator("footer .button--outline").first()
|
||||||
await page.getByLabel('Total size').click();
|
).not.toHaveAttribute("disabled");
|
||||||
await page.getByLabel('Total size').fill('19');
|
await page.getByLabel("Total size").click();
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
await page.getByLabel("Total size").fill("");
|
||||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
await page.getByLabel("Duration").click();
|
||||||
await page.getByLabel('Total size').click();
|
|
||||||
await page.getByLabel('Total size').fill('0.5');
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
|
||||||
await expect(page.locator('.step--done')).toBeVisible()
|
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Back' }).click();
|
|
||||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
|
||||||
await expect(page.locator('.step--done')).toHaveCount(2)
|
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
|
||||||
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
|
||||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
|
||||||
})
|
|
||||||
|
|
||||||
test('create an availability with changing the duration to months', async ({ page }) => {
|
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||||
await page.goto('/dashboard/availabilities');
|
"disabled",
|
||||||
await page.waitForTimeout(500);
|
{ timeout: 3000 }
|
||||||
await page.locator('.availability-edit button').first().click();
|
);
|
||||||
await page.getByLabel('Total size').click();
|
|
||||||
|
|
||||||
await page.getByLabel('Total size').fill("0.1");
|
await page.getByLabel("Total size").click();
|
||||||
await page.getByLabel('Duration').click();
|
await page.getByLabel("Total size").fill("0.5");
|
||||||
await page.getByLabel('Duration').fill("3");
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await page.getByRole('combobox').nth(1).selectOption('months');
|
await expect(
|
||||||
|
page.locator("footer .button--outline").first()
|
||||||
|
).not.toHaveAttribute("disabled");
|
||||||
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
await expect(page.locator(".step--done")).toBeVisible();
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Back" }).click();
|
||||||
|
await expect(page.locator(".step--done")).not.toBeVisible();
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await expect(page.locator(".step--done")).toHaveCount(2);
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await expect(page.locator("footer .button--outline").first()).toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
|
await expect(page.locator(".modal--open")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
await page.getByLabel('Min price').click();
|
test("create an availability with changing the duration to months", async ({
|
||||||
await page.getByLabel('Min price').fill('5');
|
page,
|
||||||
await page.getByLabel('Max collateral').click();
|
}) => {
|
||||||
await page.getByLabel('Max collateral').fill('30');
|
await page.goto("/dashboard/availabilities");
|
||||||
await page.getByLabel('Min price').fill('5');
|
await page.waitForTimeout(500);
|
||||||
await page.getByLabel('Nickname').click();
|
await page.locator(".availability-edit button").first().click();
|
||||||
await page.getByLabel('Nickname').fill('test');
|
await page.getByLabel("Total size").click();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
|
||||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
|
||||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
|
||||||
await expect(page.getByText("3 months").first()).toBeVisible();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
await page.getByLabel("Total size").fill("0.1");
|
||||||
|
await page.getByLabel("Duration").click();
|
||||||
|
await page.getByLabel("Duration").fill("3");
|
||||||
|
await page.getByRole("combobox").nth(1).selectOption("months");
|
||||||
|
|
||||||
test('create an availability after checking max size and invalid input', async ({ page }) => {
|
await page.getByLabel("Min price").click();
|
||||||
await page.goto('/dashboard/availabilities');
|
await page.getByLabel("Min price").fill("5");
|
||||||
await page.waitForTimeout(500);
|
await page.getByLabel("Total collateral").click();
|
||||||
await page.locator('.availability-edit button').first().click();
|
await page.getByLabel("Total collateral").fill("30");
|
||||||
await page.getByLabel('Total size').click();
|
await page.getByLabel("Min price").fill("5");
|
||||||
|
await page.getByLabel("Nickname").click();
|
||||||
|
await page.getByLabel("Nickname").fill("test");
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await expect(page.getByText("Confirm your new sale")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await expect(page.getByText("Success", { exact: true })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
|
await expect(page.getByText("3 months").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create an availability after checking max size and invalid input", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/dashboard/availabilities");
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.locator(".availability-edit button").first().click();
|
||||||
|
await page.getByLabel("Total size").click();
|
||||||
|
|
||||||
await page.getByLabel('Total size').fill("9999");
|
await page.getByLabel("Total size").fill("9999");
|
||||||
await expect(page.getByLabel('Total size')).toHaveAttribute("aria-invalid");
|
await expect(page.getByLabel("Total size")).toHaveAttribute("aria-invalid");
|
||||||
|
|
||||||
await page.getByText("Use max size").click()
|
await page.getByText("Use max size").click();
|
||||||
await expect(page.getByLabel('Total size')).not.toHaveAttribute("aria-invalid");
|
await expect(page.getByLabel("Total size")).not.toHaveAttribute(
|
||||||
|
"aria-invalid"
|
||||||
|
);
|
||||||
|
|
||||||
const value = (Math.random() * 0.5);
|
const value = 0.2;
|
||||||
await page.getByLabel('Total size').fill(value.toFixed(1));
|
await page.getByLabel("Total size").fill(value.toString());
|
||||||
|
|
||||||
await page.getByLabel('Duration').click();
|
await page.getByLabel("Duration").click();
|
||||||
await page.getByLabel('Duration').fill('30');
|
await page.getByLabel("Duration").fill("30");
|
||||||
await page.getByLabel('Min price').click();
|
await page.getByLabel("Min price").click();
|
||||||
await page.getByLabel('Min price').fill('5');
|
await page.getByLabel("Min price").fill("5");
|
||||||
await page.getByLabel('Max collateral').click();
|
await page.getByLabel("Total collateral").click();
|
||||||
await page.getByLabel('Max collateral').fill('30');
|
await page.getByLabel("Total collateral").fill("30");
|
||||||
await page.getByLabel('Min price').fill('5');
|
await page.getByLabel("Min price").fill("5");
|
||||||
await page.getByLabel('Nickname').click();
|
await page.getByLabel("Nickname").click();
|
||||||
await page.getByLabel('Nickname').fill('test');
|
await page.getByLabel("Nickname").fill("test");
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
await expect(page.getByText("Confirm your new sale")).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
await expect(page.getByText("Success", { exact: true })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
await expect(
|
||||||
})
|
page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|||||||
@ -1,29 +1,34 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from "@playwright/test";
|
||||||
import path, { dirname } from 'path';
|
import path, { dirname } from "path";
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
test('download a file', async ({ page, browserName }) => {
|
test("download a file", async ({ page, browserName }) => {
|
||||||
// https://github.com/microsoft/playwright/issues/13037
|
// https://github.com/microsoft/playwright/issues/13037
|
||||||
test.skip(browserName.toLowerCase() !== 'chromium',
|
test.skip(
|
||||||
`Test only for chromium!`);
|
browserName.toLowerCase() !== "chromium",
|
||||||
|
`Test only for chromium!`
|
||||||
|
);
|
||||||
|
|
||||||
await page.goto('/dashboard');
|
await page.goto("/dashboard");
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
.locator("div")
|
||||||
]);
|
.getByTestId("upload")
|
||||||
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await page.locator('.file-cell button').first().click();
|
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
await page.locator(".file-cell button").first().click();
|
||||||
const cid = await handle.jsonValue()
|
const handle = await page.evaluateHandle(() =>
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
);
|
||||||
|
const cid = await handle.jsonValue();
|
||||||
|
|
||||||
await page.locator('.download-input input').fill(cid);
|
await page.locator(".download-input input").fill(cid);
|
||||||
// const page1Promise = page.waitForEvent('popup');
|
// const page1Promise = page.waitForEvent('popup');
|
||||||
const downloadPromise = page.waitForEvent('download');
|
const downloadPromise = page.waitForEvent("download");
|
||||||
await page.locator('.download-input + button').click();
|
await page.locator(".download-input + button").click();
|
||||||
// const page1 = await page1Promise;
|
// const page1 = await page1Promise;
|
||||||
const download = await downloadPromise;
|
const download = await downloadPromise;
|
||||||
expect(await download.failure()).toBeNull()
|
expect(await download.failure()).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,58 +1,85 @@
|
|||||||
import { test, expect, } from '@playwright/test';
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
test.describe('onboarding', () => {
|
test.describe("onboarding", () => {
|
||||||
test('onboarding steps', async ({ page, browserName }) => {
|
test("onboarding steps", async ({ page, browserName }) => {
|
||||||
await page.context().setOffline(false)
|
await page.context().setOffline(false);
|
||||||
await page.goto('/');
|
await page.goto("/");
|
||||||
await expect(page.getByText("Codex is a durable, decentralised data storage protocol, created so the world community can preserve its most important knowledge without risk of censorship.")).toBeVisible()
|
await expect(
|
||||||
await page.locator('.navigation').click();
|
page.getByText(
|
||||||
await expect(page.locator('.navigation')).toHaveAttribute("aria-disabled");
|
"Codex is a durable, decentralised data storage protocol, created so the world community can preserve its most important knowledge without risk of censorship."
|
||||||
await page.getByLabel('Preferred name').fill('Arnaud');
|
)
|
||||||
await expect(page.locator('.navigation')).not.toHaveAttribute("aria-disabled");
|
).toBeVisible();
|
||||||
await page.locator('.navigation').click();
|
await page.locator(".navigation").click();
|
||||||
|
await expect(page.locator(".navigation")).toHaveAttribute("aria-disabled");
|
||||||
|
await page.getByLabel("Preferred name").fill("Arnaud");
|
||||||
|
await expect(page.locator(".navigation")).not.toHaveAttribute(
|
||||||
|
"aria-disabled"
|
||||||
|
);
|
||||||
|
await page.locator(".navigation").click();
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).not.toBeVisible()
|
await expect(
|
||||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).toBeVisible()
|
page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")
|
||||||
|
).not.toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
// Port forwarding
|
// Port forwarding
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
await expect(
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")
|
||||||
|
).not.toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
// Codex node
|
// Codex node
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
await expect(
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")
|
||||||
|
).not.toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
// Marketplace
|
// Marketplace
|
||||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-warning")).toBeVisible({ timeout: 10_000 })
|
//await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-warning")).toBeVisible({ timeout: 10_000 })
|
||||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).not.toBeVisible()
|
await expect(page.locator(".health-checks ul li").nth(3)).toBeVisible();
|
||||||
|
|
||||||
// Can be simulated with File -> Work offline
|
// Can be simulated with File -> Work offline
|
||||||
if (browserName.toLowerCase() !== 'firefox') {
|
if (browserName.toLowerCase() !== "firefox") {
|
||||||
await page.context().setOffline(true)
|
await page.context().setOffline(true);
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).toBeVisible()
|
await expect(
|
||||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).not.toBeVisible()
|
page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
await page.context().setOffline(false)
|
await page.context().setOffline(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.context().setOffline(false)
|
await page.context().setOffline(false);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
test('does not display undefined when delete the url value', async ({ page }) => {
|
test("does not display undefined when delete the url value", async ({
|
||||||
await page.goto('/onboarding-checks');
|
page,
|
||||||
await page.locator('#url').focus()
|
}) => {
|
||||||
|
await page.goto("/onboarding-checks");
|
||||||
|
await page.locator("#url").focus();
|
||||||
|
|
||||||
for (let i = 0; i < "http://localhost:8080".length; i++) {
|
for (let i = 0; i < "http://localhost:8080".length; i++) {
|
||||||
await page.keyboard.press('Backspace');
|
await page.keyboard.press("Backspace");
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(page.locator('#url')).toHaveValue("");
|
await expect(page.locator("#url")).toHaveValue("");
|
||||||
await expect(page.locator('#url')).toHaveAttribute("aria-invalid")
|
await expect(page.locator("#url")).toHaveAttribute("aria-invalid");
|
||||||
await expect(page.locator('.refresh svg')).toHaveAttribute("color", "#494949")
|
await expect(page.locator(".refresh svg")).toHaveAttribute(
|
||||||
});
|
"color",
|
||||||
|
"#494949"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@ -1,113 +1,158 @@
|
|||||||
import test, { expect } from "@playwright/test";
|
import test, { expect } from "@playwright/test";
|
||||||
import path, { dirname } from "path";
|
import path, { dirname } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
import { Times } from "../src/utils/times";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
test('create a storage request', async ({ page }) => {
|
test("create a storage request", async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto("/dashboard");
|
||||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||||
|
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
.locator("div")
|
||||||
]);
|
.getByTestId("upload")
|
||||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
await expect(page.locator("#cid")).not.toBeEmpty();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await expect(page.getByText("Success, the CID has been")).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await expect(
|
||||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
page.getByText("Your request is being processed.")
|
||||||
await expect(page.getByTestId('cell-pending').first()).toBeVisible();
|
).toBeVisible();
|
||||||
})
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
|
await expect(page.getByText("No data.")).not.toBeVisible();
|
||||||
|
await expect(page.getByTestId("cell-pending").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('select a uploaded cid when creating a storage request', async ({ page }) => {
|
test("select a uploaded cid when creating a storage request", async ({
|
||||||
await page.goto('/dashboard');
|
page,
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
}) => {
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
await page.goto("/dashboard");
|
||||||
]);
|
await page
|
||||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
.locator("div")
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
.getByTestId("upload")
|
||||||
await page.getByPlaceholder('CID').click();
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await page.locator('.dropdown ul li').nth(1).click();
|
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||||
await expect(page.getByText('button[disabled]')).not.toBeVisible();
|
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||||
})
|
await page.getByPlaceholder("CID").click();
|
||||||
|
await page.locator(".dropdown ul li").nth(1).click();
|
||||||
|
await expect(page.getByText("button[disabled]")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('storage request navigation buttons', async ({ page }) => {
|
test("storage request navigation buttons", async ({ page }) => {
|
||||||
await page.goto('/dashboard/purchases');
|
await page.goto("/dashboard/purchases");
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
await expect(page.locator(".step--done")).not.toBeVisible();
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
"disabled"
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
);
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
await expect(
|
||||||
]);
|
page.locator("footer .button--outline").first()
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
await page
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
.locator("div")
|
||||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
.getByTestId("upload")
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await expect(page.locator('.step--done')).toBeVisible()
|
await expect(
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
page.locator("footer .button--outline").first()
|
||||||
await page.getByRole('button', { name: 'Back' }).click();
|
).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
"disabled"
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
);
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.locator('.step--done')).toHaveCount(2)
|
await expect(
|
||||||
await expect(page.locator('.step--active')).toBeVisible()
|
page.locator("footer .button--outline").first()
|
||||||
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
"disabled"
|
||||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
);
|
||||||
})
|
await expect(page.locator(".step--done")).toBeVisible();
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Back" }).click();
|
||||||
|
await expect(page.locator(".step--done")).not.toBeVisible();
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await expect(page.locator(".step--done")).toHaveCount(2);
|
||||||
|
await expect(page.locator(".step--active")).toBeVisible();
|
||||||
|
await expect(page.locator("footer .button--outline").first()).toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
|
await expect(page.locator(".modal--open")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('remove the CID when the file is deleted', async ({ page }) => {
|
test("remove the CID when the file is deleted", async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto("/dashboard");
|
||||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
.locator("div")
|
||||||
]);
|
.getByTestId("upload")
|
||||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await page.locator('.button-icon--small').nth(1).click();
|
await expect(page.locator("#cid")).not.toBeEmpty();
|
||||||
await expect(page.locator('#cid')).toBeEmpty()
|
await page.locator(".button-icon--small").nth(1).click();
|
||||||
})
|
await expect(page.locator("#cid")).toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
test('create a storage request by using decimal values', async ({ page }) => {
|
test("create a storage request by using decimal values", async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto("/dashboard");
|
||||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
await page.locator("a").filter({ hasText: "Settings" }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByLabel("Address").fill("http://127.0.0.1:8080");
|
||||||
|
await page.locator(".refresh").click();
|
||||||
|
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||||
|
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||||
|
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
.locator("div")
|
||||||
]);
|
.getByTestId("upload")
|
||||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
await expect(page.locator("#cid")).not.toBeEmpty();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await expect(page.getByText("Success, the CID has been")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
|
||||||
await page.getByLabel("Full period of the contract").fill("10")
|
await page.getByLabel("Full period of the contract").fill("10");
|
||||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
|
||||||
await page.getByLabel("Full period of the contract").fill("1")
|
await page.getByLabel("Full period of the contract").fill("1");
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
|
||||||
await page.getByLabel("Full period of the contract").fill("0")
|
await page.getByLabel("Full period of the contract").fill("0");
|
||||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
|
||||||
const value = (Math.random() * 7);
|
const days = 4;
|
||||||
await page.getByLabel("Full period of the contract").fill(value.toFixed(1))
|
|
||||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByLabel("Full period of the contract").fill(days.toFixed(1));
|
||||||
await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
"disabled"
|
||||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
);
|
||||||
await expect(page.getByText(value.toFixed(1) + " days").first()).toBeVisible();
|
|
||||||
})
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Your request is being processed.")
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Finish" }).click();
|
||||||
|
await expect(page.getByText("No data.")).not.toBeVisible();
|
||||||
|
|
||||||
|
const oneDay = 24 * 60 * 60;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByText(Times.pretty(days * oneDay)).first()
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
// test('create a storage request by using months', async ({ page }) => {
|
// test('create a storage request by using months', async ({ page }) => {
|
||||||
// await page.goto('/dashboard');
|
// await page.goto('/dashboard');
|
||||||
@ -130,4 +175,4 @@ test('create a storage request by using decimal values', async ({ page }) => {
|
|||||||
// await page.getByRole('button', { name: 'Finish' }).click();
|
// await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
// await expect(page.getByText('No data.')).not.toBeVisible();
|
// await expect(page.getByText('No data.')).not.toBeVisible();
|
||||||
// await expect(page.getByText("3 months").first()).toBeVisible();
|
// await expect(page.getByText("3 months").first()).toBeVisible();
|
||||||
// })
|
// })
|
||||||
|
|||||||
2432
package-lock.json
generated
2432
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/codex-storage/codex-marketplace-ui"
|
"url": "https://github.com/codex-storage/codex-marketplace-ui"
|
||||||
},
|
},
|
||||||
"version": "0.0.13",
|
"version": "0.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 127.0.0.1 --port 5173",
|
"dev": "vite --host 127.0.0.1 --port 5173",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
||||||
"@codex-storage/sdk-js": "^0.0.16",
|
"@codex-storage/sdk-js": "^0.0.22",
|
||||||
"@sentry/browser": "^8.32.0",
|
"@sentry/browser": "^8.32.0",
|
||||||
"@sentry/react": "^8.31.0",
|
"@sentry/react": "^8.31.0",
|
||||||
"@tanstack/react-query": "^5.51.15",
|
"@tanstack/react-query": "^5.51.15",
|
||||||
@ -60,9 +60,9 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.4.7",
|
"vite": "^6.1.0",
|
||||||
"vite-plugin-svgr": "^4.3.0",
|
"vite-plugin-svgr": "^4.3.0",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@ -7,33 +7,33 @@ const __dirname = dirname(__filename);
|
|||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
*/
|
*/
|
||||||
import dotenv from 'dotenv';
|
import dotenv from "dotenv";
|
||||||
import path, { dirname } from 'path';
|
import path, { dirname } from "path";
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from "url";
|
||||||
dotenv.config({ path: path.resolve(__dirname, '.env.ci') });
|
dotenv.config({ path: path.resolve(__dirname, ".env.ci") });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
*/
|
*/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: "./e2e",
|
||||||
/* Run tests in files in parallel */
|
/* Run tests in files in parallel */
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 3 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: 'html',
|
reporter: "html",
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: 'http://127.0.0.1:5173',
|
baseURL: "http://127.0.0.1:5173",
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: "on-first-retry",
|
||||||
|
|
||||||
screenshot: "only-on-failure",
|
screenshot: "only-on-failure",
|
||||||
},
|
},
|
||||||
@ -41,18 +41,18 @@ export default defineConfig({
|
|||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: "chromium",
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices["Desktop Chrome"] },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'firefox',
|
name: "firefox",
|
||||||
use: { ...devices['Desktop Firefox'] },
|
use: { ...devices["Desktop Firefox"] },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'webkit',
|
name: "webkit",
|
||||||
use: { ...devices['Desktop Safari'] },
|
use: { ...devices["Desktop Safari"] },
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
/* Test against mobile viewports. */
|
||||||
@ -78,8 +78,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run preview',
|
command: "npm run preview",
|
||||||
url: 'http://127.0.0.1:5173',
|
url: "http://127.0.0.1:5173",
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -57,8 +57,8 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
|||||||
["id", onSortById],
|
["id", onSortById],
|
||||||
["total size", onSortBySize],
|
["total size", onSortBySize],
|
||||||
["duration", onSortByDuration],
|
["duration", onSortByDuration],
|
||||||
["min price", onSortByPrice],
|
["min price per byte", onSortByPrice],
|
||||||
["max collateral", onSortByCollateral],
|
["remaining collateral", onSortByCollateral],
|
||||||
["actions"],
|
["actions"],
|
||||||
] satisfies [string, ((state: TabSortState) => void)?][];
|
] satisfies [string, ((state: TabSortState) => void)?][];
|
||||||
|
|
||||||
@ -88,8 +88,8 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
|||||||
<AvailabilityIdCell value={a} />,
|
<AvailabilityIdCell value={a} />,
|
||||||
<Cell>{Bytes.pretty(a.totalSize)}</Cell>,
|
<Cell>{Bytes.pretty(a.totalSize)}</Cell>,
|
||||||
<Cell>{Times.pretty(a.duration)}</Cell>,
|
<Cell>{Times.pretty(a.duration)}</Cell>,
|
||||||
<Cell>{a.minPrice.toString()}</Cell>,
|
<Cell>{a.minPricePerBytePerSecond.toString()}</Cell>,
|
||||||
<Cell>{a.maxCollateral.toString()}</Cell>,
|
<Cell>{a.totalRemainingCollateral.toString()}</Cell>,
|
||||||
<AvailabilityActionsCell availability={a} />,
|
<AvailabilityActionsCell availability={a} />,
|
||||||
]}></Row>
|
]}></Row>
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
|||||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||||
import { WebStorage } from "../../utils/web-storage";
|
import { WebStorage } from "../../utils/web-storage";
|
||||||
import { AvailabilityState } from "./types";
|
import { AvailabilityState } from "./types";
|
||||||
import { STEPPER_DURATION } from "../../utils/constants";
|
import { GB, STEPPER_DURATION } from "../../utils/constants";
|
||||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||||
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
||||||
import { AvailabilityError } from "./AvailabilityError";
|
import { AvailabilityError } from "./AvailabilityError";
|
||||||
@ -30,8 +30,8 @@ const CONFIRM_STATE = 2;
|
|||||||
const defaultAvailabilityData: AvailabilityState = {
|
const defaultAvailabilityData: AvailabilityState = {
|
||||||
totalSize: 0.5,
|
totalSize: 0.5,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
minPrice: 0,
|
minPricePerBytePerSecond: 0,
|
||||||
maxCollateral: 0,
|
totalCollateral: 0,
|
||||||
totalSizeUnit: "gb",
|
totalSizeUnit: "gb",
|
||||||
durationUnit: "days",
|
durationUnit: "days",
|
||||||
};
|
};
|
||||||
@ -143,6 +143,9 @@ export function AvailabilityEdit({
|
|||||||
|
|
||||||
setAvailability({
|
setAvailability({
|
||||||
...a,
|
...a,
|
||||||
|
totalSize: a.totalSize / GB,
|
||||||
|
totalSizeUnit: "gb",
|
||||||
|
duration: a.duration / Times.value(unit),
|
||||||
durationUnit: unit as "hours" | "days" | "months",
|
durationUnit: unit as "hours" | "days" | "months",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -184,32 +184,35 @@ export function AvailabilityForm({
|
|||||||
<div className="row gap">
|
<div className="row gap">
|
||||||
<div className="group">
|
<div className="group">
|
||||||
<Input
|
<Input
|
||||||
id="minPrice"
|
id="minPricePerBytePerSecond"
|
||||||
name="minPrice"
|
name="minPricePerBytePerSecond"
|
||||||
type="number"
|
type="number"
|
||||||
label="Min price"
|
label="Min price per byte per second"
|
||||||
min={0}
|
min={0}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
value={availability.minPrice.toString()}
|
value={availability.minPricePerBytePerSecond.toString()}
|
||||||
/>
|
/>
|
||||||
<Tooltip message={"Minimum price to be paid (in amount of tokens)"}>
|
<Tooltip
|
||||||
|
message={
|
||||||
|
"inimal price per byte per second paid (in amount of tokens) for the hosted request's slot for the request's duration"
|
||||||
|
}>
|
||||||
<InfoIcon></InfoIcon>
|
<InfoIcon></InfoIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="group">
|
<div className="group">
|
||||||
<Input
|
<Input
|
||||||
id="maxCollateral"
|
id="totalCollateral"
|
||||||
name="maxCollateral"
|
name="totalCollateral"
|
||||||
type="number"
|
type="number"
|
||||||
label="Max collateral"
|
label="Total collateral"
|
||||||
min={0}
|
min={0}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
value={availability.maxCollateral.toString()}
|
value={availability.totalCollateral.toString()}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
message={
|
message={
|
||||||
"Maximum collateral user is willing to pay per filled Slot (in amount of tokens)"
|
"Total collateral (in amount of tokens) that can be used for matching requests"
|
||||||
}>
|
}>
|
||||||
<InfoIcon></InfoIcon>
|
<InfoIcon></InfoIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -22,7 +22,8 @@ export function AvailabilityIdCell({ value }: Props) {
|
|||||||
</small>
|
</small>
|
||||||
<br />
|
<br />
|
||||||
<small className="text--light">
|
<small className="text--light">
|
||||||
Max collateral {value.maxCollateral} | Min price {value.minPrice}
|
Collateral {value.totalCollateral} | Min price{" "}
|
||||||
|
{value.minPricePerBytePerSecond}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -68,11 +68,11 @@ export function Sunburst({ availabilities, space }: Props) {
|
|||||||
"Duration " +
|
"Duration " +
|
||||||
Times.pretty(a.duration) +
|
Times.pretty(a.duration) +
|
||||||
"<br/>" +
|
"<br/>" +
|
||||||
"Max collateral " +
|
"Total remaining collateral " +
|
||||||
a.maxCollateral +
|
a.totalRemainingCollateral +
|
||||||
"<br/>" +
|
"<br/>" +
|
||||||
"Min price " +
|
"Min price per byte per second " +
|
||||||
a.minPrice +
|
a.minPricePerBytePerSecond +
|
||||||
"<br/>" +
|
"<br/>" +
|
||||||
"Size " +
|
"Size " +
|
||||||
Bytes.pretty(a.totalSize)
|
Bytes.pretty(a.totalSize)
|
||||||
|
|||||||
@ -5,191 +5,229 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
|||||||
import { AvailabilityState } from "./types";
|
import { AvailabilityState } from "./types";
|
||||||
|
|
||||||
describe("files", () => {
|
describe("files", () => {
|
||||||
it("sorts by id", async () => {
|
it("sorts by id", async () => {
|
||||||
const a = {
|
const a = {
|
||||||
id: "a",
|
id: "a",
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
minPrice: 0,
|
freeSize: 0,
|
||||||
maxCollateral: 0,
|
minPricePerBytePerSecond: 0,
|
||||||
name: "",
|
totalCollateral: 0,
|
||||||
slots: []
|
totalRemainingCollateral: 0,
|
||||||
}
|
name: "",
|
||||||
const b = {
|
slots: [],
|
||||||
id: "b",
|
};
|
||||||
totalSize: 0,
|
const b = {
|
||||||
duration: 0,
|
id: "b",
|
||||||
minPrice: 0,
|
totalSize: 0,
|
||||||
maxCollateral: 0,
|
freeSize: 0,
|
||||||
name: "",
|
duration: 0,
|
||||||
slots: []
|
minPricePerBytePerSecond: 0,
|
||||||
}
|
totalCollateral: 0,
|
||||||
|
totalRemainingCollateral: 0,
|
||||||
|
name: "",
|
||||||
|
slots: [],
|
||||||
|
};
|
||||||
|
|
||||||
const items = [a, b,]
|
const items = [a, b];
|
||||||
|
|
||||||
const descSorted = items.slice().sort(AvailabilityUtils.sortById("desc"))
|
const descSorted = items.slice().sort(AvailabilityUtils.sortById("desc"));
|
||||||
|
|
||||||
assert.deepEqual(descSorted, [b, a]);
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortById("asc"))
|
const ascSorted = items.slice().sort(AvailabilityUtils.sortById("asc"));
|
||||||
|
|
||||||
assert.deepEqual(ascSorted, [a, b]);
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sorts by size", async () => {
|
it("sorts by size", async () => {
|
||||||
const a = {
|
const a = {
|
||||||
id: "",
|
id: "",
|
||||||
totalSize: 1,
|
totalSize: 1,
|
||||||
duration: 0,
|
freeSize: 0,
|
||||||
minPrice: 0,
|
duration: 0,
|
||||||
maxCollateral: 0,
|
minPricePerBytePerSecond: 0,
|
||||||
name: "",
|
totalCollateral: 0,
|
||||||
slots: []
|
totalRemainingCollateral: 0,
|
||||||
}
|
name: "",
|
||||||
const b = {
|
slots: [],
|
||||||
id: "",
|
};
|
||||||
totalSize: 2,
|
const b = {
|
||||||
duration: 0,
|
id: "",
|
||||||
minPrice: 0,
|
totalSize: 2,
|
||||||
maxCollateral: 0,
|
freeSize: 0,
|
||||||
name: "",
|
duration: 0,
|
||||||
slots: []
|
minPricePerBytePerSecond: 0,
|
||||||
}
|
totalCollateral: 0,
|
||||||
|
totalRemainingCollateral: 0,
|
||||||
|
name: "",
|
||||||
|
slots: [],
|
||||||
|
};
|
||||||
|
|
||||||
const items = [a, b,]
|
const items = [a, b];
|
||||||
|
|
||||||
const descSorted = items.slice().sort(AvailabilityUtils.sortBySize("desc"))
|
const descSorted = items.slice().sort(AvailabilityUtils.sortBySize("desc"));
|
||||||
|
|
||||||
assert.deepEqual(descSorted, [b, a]);
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortBySize("asc"))
|
const ascSorted = items.slice().sort(AvailabilityUtils.sortBySize("asc"));
|
||||||
|
|
||||||
assert.deepEqual(ascSorted, [a, b]);
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sorts by duration", async () => {
|
it("sorts by duration", async () => {
|
||||||
const a = {
|
const a = {
|
||||||
id: "",
|
id: "",
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
duration: 1,
|
freeSize: 0,
|
||||||
minPrice: 0,
|
duration: 1,
|
||||||
maxCollateral: 0,
|
minPricePerBytePerSecond: 0,
|
||||||
name: "",
|
totalCollateral: 0,
|
||||||
slots: []
|
totalRemainingCollateral: 0,
|
||||||
}
|
name: "",
|
||||||
const b = {
|
slots: [],
|
||||||
id: "",
|
};
|
||||||
totalSize: 0,
|
const b = {
|
||||||
duration: 2,
|
id: "",
|
||||||
minPrice: 0,
|
totalSize: 0,
|
||||||
maxCollateral: 0,
|
freeSize: 0,
|
||||||
name: "",
|
duration: 2,
|
||||||
slots: []
|
minPricePerBytePerSecond: 0,
|
||||||
}
|
totalCollateral: 0,
|
||||||
|
totalRemainingCollateral: 0,
|
||||||
|
name: "",
|
||||||
|
slots: [],
|
||||||
|
};
|
||||||
|
|
||||||
const items = [a, b,]
|
const items = [a, b];
|
||||||
|
|
||||||
const descSorted = items.slice().sort(AvailabilityUtils.sortByDuration("desc"))
|
const descSorted = items
|
||||||
|
.slice()
|
||||||
|
.sort(AvailabilityUtils.sortByDuration("desc"));
|
||||||
|
|
||||||
assert.deepEqual(descSorted, [b, a]);
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByDuration("asc"))
|
const ascSorted = items
|
||||||
|
.slice()
|
||||||
|
.sort(AvailabilityUtils.sortByDuration("asc"));
|
||||||
|
|
||||||
assert.deepEqual(ascSorted, [a, b]);
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sorts by price", async () => {
|
it("sorts by price", async () => {
|
||||||
const a = {
|
const a = {
|
||||||
id: "",
|
id: "",
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
duration: 0,
|
freeSize: 0,
|
||||||
minPrice: 1,
|
duration: 0,
|
||||||
maxCollateral: 0,
|
minPricePerBytePerSecond: 1,
|
||||||
name: "",
|
totalCollateral: 0,
|
||||||
slots: []
|
totalRemainingCollateral: 0,
|
||||||
}
|
name: "",
|
||||||
const b = {
|
slots: [],
|
||||||
id: "",
|
};
|
||||||
totalSize: 0,
|
const b = {
|
||||||
duration: 0,
|
id: "",
|
||||||
minPrice: 2,
|
freeSize: 0,
|
||||||
maxCollateral: 0,
|
totalSize: 0,
|
||||||
name: "",
|
duration: 0,
|
||||||
slots: []
|
minPricePerBytePerSecond: 2,
|
||||||
}
|
totalCollateral: 0,
|
||||||
|
totalRemainingCollateral: 0,
|
||||||
|
name: "",
|
||||||
|
slots: [],
|
||||||
|
};
|
||||||
|
|
||||||
const items = [a, b,]
|
const items = [a, b];
|
||||||
|
|
||||||
const descSorted = items.slice().sort(AvailabilityUtils.sortByPrice("desc"))
|
const descSorted = items
|
||||||
|
.slice()
|
||||||
|
.sort(AvailabilityUtils.sortByPrice("desc"));
|
||||||
|
|
||||||
assert.deepEqual(descSorted, [b, a]);
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByPrice("asc"))
|
const ascSorted = items.slice().sort(AvailabilityUtils.sortByPrice("asc"));
|
||||||
|
|
||||||
assert.deepEqual(ascSorted, [a, b]);
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sorts by collateral", async () => {
|
it("sorts by collateral", async () => {
|
||||||
const a = {
|
const a = {
|
||||||
id: "",
|
id: "",
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
duration: 0,
|
freeSize: 0,
|
||||||
minPrice: 0,
|
duration: 0,
|
||||||
maxCollateral: 1,
|
minPricePerBytePerSecond: 0,
|
||||||
name: "",
|
totalCollateral: 0,
|
||||||
slots: []
|
totalRemainingCollateral: 1,
|
||||||
}
|
name: "",
|
||||||
const b = {
|
slots: [],
|
||||||
id: "",
|
};
|
||||||
totalSize: 0,
|
const b = {
|
||||||
duration: 0,
|
id: "",
|
||||||
minPrice: 0,
|
totalSize: 0,
|
||||||
maxCollateral: 2,
|
freeSize: 0,
|
||||||
name: "",
|
duration: 0,
|
||||||
slots: []
|
minPricePerBytePerSecond: 0,
|
||||||
}
|
totalCollateral: 0,
|
||||||
|
totalRemainingCollateral: 2,
|
||||||
|
name: "",
|
||||||
|
slots: [],
|
||||||
|
};
|
||||||
|
|
||||||
const items = [a, b,]
|
const items = [a, b];
|
||||||
|
|
||||||
const descSorted = items.slice().sort(AvailabilityUtils.sortByCollateral("desc"))
|
const descSorted = items
|
||||||
|
.slice()
|
||||||
|
.sort(AvailabilityUtils.sortByCollateral("desc"));
|
||||||
|
|
||||||
assert.deepEqual(descSorted, [b, a]);
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByCollateral("asc"))
|
const ascSorted = items
|
||||||
|
.slice()
|
||||||
|
.sort(AvailabilityUtils.sortByCollateral("asc"));
|
||||||
|
|
||||||
assert.deepEqual(ascSorted, [a, b]);
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns the number of bytes per unit", async () => {
|
it("returns the number of bytes per unit", async () => {
|
||||||
assert.deepEqual(AvailabilityUtils.toUnit(GB, "gb"), 1);
|
assert.deepEqual(AvailabilityUtils.toUnit(GB, "gb"), 1);
|
||||||
assert.deepEqual(AvailabilityUtils.toUnit(TB, "tb"), 1);
|
assert.deepEqual(AvailabilityUtils.toUnit(TB, "tb"), 1);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
it("returns the max value possible for an availability", async () => {
|
||||||
|
const space: CodexNodeSpace = {
|
||||||
|
quotaMaxBytes: 8 * GB,
|
||||||
|
quotaReservedBytes: 2 * GB,
|
||||||
|
quotaUsedBytes: GB,
|
||||||
|
totalBlocks: 0,
|
||||||
|
};
|
||||||
|
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB - 1);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns the max value possible for an availability", async () => {
|
it("checks the availability max value", async () => {
|
||||||
const space: CodexNodeSpace = {
|
const availability = {
|
||||||
quotaMaxBytes: 8 * GB,
|
totalSizeUnit: "gb",
|
||||||
quotaReservedBytes: 2 * GB,
|
totalSize: 1,
|
||||||
quotaUsedBytes: GB,
|
} as AvailabilityState;
|
||||||
totalBlocks: 0
|
|
||||||
}
|
|
||||||
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB - 1);
|
|
||||||
})
|
|
||||||
|
|
||||||
it("checks the availability max value", async () => {
|
assert.deepEqual(AvailabilityUtils.isValid(availability, GB * 2), true);
|
||||||
const availability = {
|
assert.deepEqual(
|
||||||
totalSizeUnit: "gb",
|
AvailabilityUtils.isValid({ ...availability, totalSize: -1 }, GB),
|
||||||
totalSize: 1
|
false
|
||||||
} as AvailabilityState
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
AvailabilityUtils.isValid({ ...availability, totalSize: GB }, 2 * GB),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepEqual(AvailabilityUtils.isValid(availability, GB * 2), true);
|
it("toggles item in array", async () => {
|
||||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: -1 }, GB), false);
|
const array: string[] = [];
|
||||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: GB }, 2 * GB), false);
|
assert.deepEqual(AvailabilityUtils.toggle(array, "1"), ["1"]);
|
||||||
})
|
assert.deepEqual(
|
||||||
|
AvailabilityUtils.toggle(AvailabilityUtils.toggle(array, "1"), "1"),
|
||||||
it("toggles item in array", async () => {
|
[]
|
||||||
const array: string[] = []
|
);
|
||||||
assert.deepEqual(AvailabilityUtils.toggle(array, "1"), ["1"]);
|
});
|
||||||
assert.deepEqual(AvailabilityUtils.toggle(AvailabilityUtils.toggle(array, "1"), "1"), []);
|
});
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,93 +1,91 @@
|
|||||||
import { TabSortState } from "@codex-storage/marketplace-ui-components"
|
import { TabSortState } from "@codex-storage/marketplace-ui-components";
|
||||||
import { AvailabilityState, AvailabilityWithSlots } from "./types"
|
import { AvailabilityState, AvailabilityWithSlots } from "./types";
|
||||||
import { GB, TB } from "../../utils/constants";
|
import { GB, TB } from "../../utils/constants";
|
||||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||||
|
|
||||||
export const AvailabilityUtils = {
|
export const AvailabilityUtils = {
|
||||||
sortById: (state: TabSortState) =>
|
sortById:
|
||||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => {
|
(state: TabSortState) =>
|
||||||
|
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => {
|
||||||
return state === "desc"
|
return state === "desc"
|
||||||
? b.id
|
? b.id.toLocaleLowerCase().localeCompare(a.id.toLocaleLowerCase())
|
||||||
.toLocaleLowerCase()
|
: a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
||||||
.localeCompare(a.id.toLocaleLowerCase())
|
|
||||||
: a.id
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.localeCompare(b.id.toLocaleLowerCase())
|
|
||||||
},
|
|
||||||
sortBySize: (state: TabSortState) =>
|
|
||||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => state === "desc"
|
|
||||||
? b.totalSize - a.totalSize
|
|
||||||
: a.totalSize - b.totalSize
|
|
||||||
,
|
|
||||||
sortByDuration: (state: TabSortState) =>
|
|
||||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => state === "desc"
|
|
||||||
? b.duration - a.duration
|
|
||||||
: a.duration - b.duration
|
|
||||||
,
|
|
||||||
sortByPrice: (state: TabSortState) =>
|
|
||||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => state === "desc"
|
|
||||||
? b.minPrice - a.minPrice
|
|
||||||
: a.minPrice - b.minPrice
|
|
||||||
,
|
|
||||||
sortByCollateral: (state: TabSortState) =>
|
|
||||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => state === "desc"
|
|
||||||
? b.maxCollateral - a.maxCollateral
|
|
||||||
: a.maxCollateral - b.maxCollateral
|
|
||||||
,
|
|
||||||
toUnit(bytes: number, unit: "gb" | "tb") {
|
|
||||||
return bytes / this.unitValue(unit || "gb")
|
|
||||||
},
|
},
|
||||||
maxValue(space: CodexNodeSpace) {
|
sortBySize:
|
||||||
// Remove 1 byte to allow to create an availability with the max space possible
|
(state: TabSortState) =>
|
||||||
return space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes - 1
|
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) =>
|
||||||
},
|
state === "desc" ? b.totalSize - a.totalSize : a.totalSize - b.totalSize,
|
||||||
unitValue(unit: "gb" | "tb") {
|
sortByDuration:
|
||||||
return unit === "tb" ? TB : GB
|
(state: TabSortState) =>
|
||||||
},
|
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) =>
|
||||||
isValid: (
|
state === "desc" ? b.duration - a.duration : a.duration - b.duration,
|
||||||
availability: AvailabilityState,
|
sortByPrice:
|
||||||
max: number
|
(state: TabSortState) =>
|
||||||
) => availability.totalSize > 0 && availability.totalSize * AvailabilityUtils.unitValue(availability.totalSizeUnit) <= max
|
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) =>
|
||||||
,
|
state === "desc"
|
||||||
toggle: <T>(arr: Array<T>, value: T) =>
|
? b.minPricePerBytePerSecond - a.minPricePerBytePerSecond
|
||||||
arr.includes(value) ? arr.filter(i => i !== value) : [...arr, value],
|
: a.minPricePerBytePerSecond - b.minPricePerBytePerSecond,
|
||||||
|
sortByCollateral:
|
||||||
|
(state: TabSortState) =>
|
||||||
|
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) =>
|
||||||
|
state === "desc"
|
||||||
|
? b.totalRemainingCollateral - a.totalRemainingCollateral
|
||||||
|
: a.totalRemainingCollateral - b.totalRemainingCollateral,
|
||||||
|
toUnit(bytes: number, unit: "gb" | "tb") {
|
||||||
|
return bytes / this.unitValue(unit || "gb");
|
||||||
|
},
|
||||||
|
maxValue(space: CodexNodeSpace) {
|
||||||
|
// Remove 1 byte to allow to create an availability with the max space possible
|
||||||
|
return (
|
||||||
|
space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes - 1
|
||||||
|
);
|
||||||
|
},
|
||||||
|
unitValue(unit: "gb" | "tb") {
|
||||||
|
return unit === "tb" ? TB : GB;
|
||||||
|
},
|
||||||
|
isValid: (availability: AvailabilityState, max: number) =>
|
||||||
|
availability.totalSize > 0 &&
|
||||||
|
availability.totalSize *
|
||||||
|
AvailabilityUtils.unitValue(availability.totalSizeUnit) <=
|
||||||
|
max,
|
||||||
|
toggle: <T>(arr: Array<T>, value: T) =>
|
||||||
|
arr.includes(value) ? arr.filter((i) => i !== value) : [...arr, value],
|
||||||
|
|
||||||
availabilityColors: [
|
availabilityColors: [
|
||||||
"#34A0FFFF",
|
"#34A0FFFF",
|
||||||
"#34A0FFEE",
|
"#34A0FFEE",
|
||||||
"#34A0FFDD",
|
"#34A0FFDD",
|
||||||
"#34A0FFCC",
|
"#34A0FFCC",
|
||||||
"#34A0FFBB",
|
"#34A0FFBB",
|
||||||
"#34A0FFAA",
|
"#34A0FFAA",
|
||||||
"#34A0FF99",
|
"#34A0FF99",
|
||||||
"#34A0FF88",
|
"#34A0FF88",
|
||||||
"#34A0FF77",
|
"#34A0FF77",
|
||||||
"#34A0FF66",
|
"#34A0FF66",
|
||||||
"#34A0FF55",
|
"#34A0FF55",
|
||||||
"#34A0FF44",
|
"#34A0FF44",
|
||||||
"#34A0FF33",
|
"#34A0FF33",
|
||||||
"#34A0FF22",
|
"#34A0FF22",
|
||||||
"#34A0FF11",
|
"#34A0FF11",
|
||||||
"#34A0FF00",
|
"#34A0FF00",
|
||||||
],
|
],
|
||||||
|
|
||||||
slotColors: [
|
slotColors: [
|
||||||
"#D2493CFF",
|
"#D2493CFF",
|
||||||
"#D2493CEE",
|
"#D2493CEE",
|
||||||
"#D2493CDD",
|
"#D2493CDD",
|
||||||
"#D2493CCC",
|
"#D2493CCC",
|
||||||
"#D2493CBB",
|
"#D2493CBB",
|
||||||
"#D2493CAA",
|
"#D2493CAA",
|
||||||
"#D2493C99",
|
"#D2493C99",
|
||||||
"#D2493C88",
|
"#D2493C88",
|
||||||
"#D2493C77",
|
"#D2493C77",
|
||||||
"#D2493C66",
|
"#D2493C66",
|
||||||
"#D2493C55",
|
"#D2493C55",
|
||||||
"#D2493C44",
|
"#D2493C44",
|
||||||
"#D2493C33",
|
"#D2493C33",
|
||||||
"#D2493C22",
|
"#D2493C22",
|
||||||
"#D2493C11",
|
"#D2493C11",
|
||||||
"#D2493C00",
|
"#D2493C00",
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|||||||
@ -14,8 +14,8 @@ export type AvailabilityState = {
|
|||||||
totalSize: number;
|
totalSize: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
durationUnit: "hours" | "days" | "months";
|
durationUnit: "hours" | "days" | "months";
|
||||||
minPrice: number;
|
minPricePerBytePerSecond: number;
|
||||||
maxCollateral: number;
|
totalCollateral: number;
|
||||||
totalSizeUnit: "gb" | "tb";
|
totalSizeUnit: "gb" | "tb";
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { CodexAvailabilityCreateResponse } from "@codex-storage/sdk-js";
|
|||||||
import { Times } from "../../utils/times";
|
import { Times } from "../../utils/times";
|
||||||
import { AvailabilityUtils } from "./availability.utils";
|
import { AvailabilityUtils } from "./availability.utils";
|
||||||
|
|
||||||
|
|
||||||
export function useAvailabilityMutation(
|
export function useAvailabilityMutation(
|
||||||
dispatch: Dispatch<StepperAction>,
|
dispatch: Dispatch<StepperAction>,
|
||||||
state: StepperState
|
state: StepperState
|
||||||
@ -33,11 +32,18 @@ export function useAvailabilityMutation(
|
|||||||
const fn: (
|
const fn: (
|
||||||
input: Omit<AvailabilityState, "totalSizeUnit" | "durationUnit">
|
input: Omit<AvailabilityState, "totalSizeUnit" | "durationUnit">
|
||||||
) => Promise<"" | CodexAvailabilityCreateResponse> = input.id
|
) => Promise<"" | CodexAvailabilityCreateResponse> = input.id
|
||||||
? (input) =>
|
? (input) => {
|
||||||
CodexSdk.marketplace()
|
return CodexSdk.marketplace()
|
||||||
.updateAvailability({ ...input, id: input.id || "" })
|
.updateAvailability({
|
||||||
.then((s) => Promises.rejectOnError(s))
|
totalSize: input.totalSize,
|
||||||
: (input) =>
|
duration: input.duration,
|
||||||
|
minPricePerBytePerSecond: input.minPricePerBytePerSecond,
|
||||||
|
totalCollateral: input.totalCollateral,
|
||||||
|
id: input.id || "",
|
||||||
|
})
|
||||||
|
.then((s) => Promises.rejectOnError(s));
|
||||||
|
}
|
||||||
|
: (input) =>
|
||||||
CodexSdk.marketplace()
|
CodexSdk.marketplace()
|
||||||
.createAvailability(input)
|
.createAvailability(input)
|
||||||
.then((s) => Promises.rejectOnError(s));
|
.then((s) => Promises.rejectOnError(s));
|
||||||
@ -45,7 +51,9 @@ export function useAvailabilityMutation(
|
|||||||
return fn({
|
return fn({
|
||||||
...input,
|
...input,
|
||||||
duration: Times.value(durationUnit) * duration,
|
duration: Times.value(durationUnit) * duration,
|
||||||
totalSize: Math.trunc(totalSize * AvailabilityUtils.unitValue(totalSizeUnit)),
|
totalSize: Math.trunc(
|
||||||
|
totalSize * AvailabilityUtils.unitValue(totalSizeUnit)
|
||||||
|
),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: (res, body) => {
|
onSuccess: (res, body) => {
|
||||||
@ -56,7 +64,7 @@ export function useAvailabilityMutation(
|
|||||||
WebStorage.delete("availability-step");
|
WebStorage.delete("availability-step");
|
||||||
|
|
||||||
if (typeof res === "object" && body.name) {
|
if (typeof res === "object" && body.name) {
|
||||||
WebStorage.availabilities.add(res.id, body.name)
|
WebStorage.availabilities.add(res.id, body.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import "./FileCell.css";
|
import "./FileCell.css";
|
||||||
import { WebStorage } from "../../utils/web-storage";
|
import { WebStorage } from "../../utils/web-storage";
|
||||||
import { CodexDataContent } from "@codex-storage/sdk-js";
|
import { CodexDataContent } from "@codex-storage/sdk-js";
|
||||||
|
import { FilesUtils } from "../Files/files.utils";
|
||||||
|
|
||||||
type FileMetadata = {
|
type FileMetadata = {
|
||||||
mimetype: string | null;
|
mimetype: string | null;
|
||||||
@ -37,11 +38,10 @@ export function FileCell({ requestId, purchaseCid, data, onMetadata }: Props) {
|
|||||||
|
|
||||||
const content = data.find((m) => m.cid === cid);
|
const content = data.find((m) => m.cid === cid);
|
||||||
if (content) {
|
if (content) {
|
||||||
const {
|
const { filename = "-", mimetype = "application/octet-stream" } =
|
||||||
filename = "-",
|
content.manifest;
|
||||||
mimetype = "application/octet-stream",
|
const uploadedAt = FilesUtils.getUploadedAt(content.cid);
|
||||||
uploadedAt = 0,
|
|
||||||
} = content.manifest;
|
|
||||||
setMetadata({
|
setMetadata({
|
||||||
filename,
|
filename,
|
||||||
mimetype,
|
mimetype,
|
||||||
|
|||||||
@ -117,7 +117,7 @@ export function FileDetails({ onClose, details }: Props) {
|
|||||||
<p>Date:</p>
|
<p>Date:</p>
|
||||||
<p>
|
<p>
|
||||||
{FilesUtils.formatDate(
|
{FilesUtils.formatDate(
|
||||||
details.manifest.uploadedAt
|
FilesUtils.getUploadedAt(details.cid)
|
||||||
).toString()}
|
).toString()}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -166,7 +166,7 @@ export function Files({ limit }: Props) {
|
|||||||
<FileCell content={c}></FileCell>,
|
<FileCell content={c}></FileCell>,
|
||||||
<Cell>{Bytes.pretty(c.manifest.datasetSize)}</Cell>,
|
<Cell>{Bytes.pretty(c.manifest.datasetSize)}</Cell>,
|
||||||
<Cell>
|
<Cell>
|
||||||
{FilesUtils.formatDate(c.manifest.uploadedAt).toString()}
|
{FilesUtils.formatDate(FilesUtils.getUploadedAt(c.cid)).toString()}
|
||||||
</Cell>,
|
</Cell>,
|
||||||
<FileActions
|
<FileActions
|
||||||
content={c}
|
content={c}
|
||||||
|
|||||||
@ -20,75 +20,95 @@ export const FilesUtils = {
|
|||||||
return !!type && type.startsWith("video");
|
return !!type && type.startsWith("video");
|
||||||
},
|
},
|
||||||
isArchive(mimetype: string | null) {
|
isArchive(mimetype: string | null) {
|
||||||
return !!mimetype && archiveMimetypes.includes(mimetype)
|
return !!mimetype && archiveMimetypes.includes(mimetype);
|
||||||
},
|
},
|
||||||
type(mimetype: string | null) {
|
type(mimetype: string | null) {
|
||||||
if (FilesUtils.isArchive(mimetype)) {
|
if (FilesUtils.isArchive(mimetype)) {
|
||||||
return "archive"
|
return "archive";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FilesUtils.isVideo(mimetype)) {
|
if (FilesUtils.isVideo(mimetype)) {
|
||||||
return "video"
|
return "video";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FilesUtils.isImage(mimetype)) {
|
if (FilesUtils.isImage(mimetype)) {
|
||||||
return "image"
|
return "image";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "document"
|
return "document";
|
||||||
},
|
},
|
||||||
sortByName: (state: TabSortState) =>
|
sortByName:
|
||||||
(a: CodexDataContent, b: CodexDataContent) => {
|
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) => {
|
||||||
const { manifest: { filename: afilename } } = a
|
const {
|
||||||
const { manifest: { filename: bfilename } } = b
|
manifest: { filename: afilename },
|
||||||
|
} = a;
|
||||||
|
const {
|
||||||
|
manifest: { filename: bfilename },
|
||||||
|
} = b;
|
||||||
|
|
||||||
return state === "desc"
|
return state === "desc"
|
||||||
? (bfilename || "")
|
? (bfilename || "")
|
||||||
.toLocaleLowerCase()
|
.toLocaleLowerCase()
|
||||||
.localeCompare((afilename || "").toLocaleLowerCase())
|
.localeCompare((afilename || "").toLocaleLowerCase())
|
||||||
: (afilename || "")
|
: (afilename || "")
|
||||||
.toLocaleLowerCase()
|
.toLocaleLowerCase()
|
||||||
.localeCompare((bfilename || "").toLocaleLowerCase())
|
.localeCompare((bfilename || "").toLocaleLowerCase());
|
||||||
},
|
},
|
||||||
sortBySize: (state: TabSortState) =>
|
sortBySize:
|
||||||
(a: CodexDataContent, b: CodexDataContent) => state === "desc"
|
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) =>
|
||||||
? b.manifest.datasetSize - a.manifest.datasetSize
|
state === "desc"
|
||||||
: a.manifest.datasetSize - b.manifest.datasetSize
|
? b.manifest.datasetSize - a.manifest.datasetSize
|
||||||
,
|
: a.manifest.datasetSize - b.manifest.datasetSize,
|
||||||
sortByDate: (state: TabSortState) =>
|
sortByDate:
|
||||||
(a: CodexDataContent, b: CodexDataContent) => state === "desc"
|
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) => {
|
||||||
? new Date(b.manifest.uploadedAt).getTime() -
|
const aUploadedAt = FilesUtils.getUploadedAt(a.cid);
|
||||||
new Date(a.manifest.uploadedAt).getTime()
|
const bUploadedAt = FilesUtils.getUploadedAt(b.cid);
|
||||||
: new Date(a.manifest.uploadedAt).getTime() -
|
|
||||||
new Date(b.manifest.uploadedAt).getTime()
|
return state === "desc"
|
||||||
,
|
? new Date(bUploadedAt).getTime() - new Date(aUploadedAt).getTime()
|
||||||
removeCidFromFolder(folders: [string, string[]][], folder: string, cid: string): [string, string[]][] {
|
: new Date(aUploadedAt).getTime() - new Date(bUploadedAt).getTime();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeCidFromFolder(
|
||||||
|
folders: [string, string[]][],
|
||||||
|
folder: string,
|
||||||
|
cid: string
|
||||||
|
): [string, string[]][] {
|
||||||
return folders.map(([name, files]) =>
|
return folders.map(([name, files]) =>
|
||||||
name === folder
|
name === folder ? [name, files.filter((id) => id !== cid)] : [name, files]
|
||||||
? [name, files.filter((id) => id !== cid)]
|
);
|
||||||
: [name, files]
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
addCidToFolder(folders: [string, string[]][], folder: string, cid: string): [string, string[]][] {
|
addCidToFolder(
|
||||||
|
folders: [string, string[]][],
|
||||||
|
folder: string,
|
||||||
|
cid: string
|
||||||
|
): [string, string[]][] {
|
||||||
return folders.map(([name, files]) =>
|
return folders.map(([name, files]) =>
|
||||||
name === folder ? [name, [...files, cid]] : [name, files]
|
name === folder ? [name, [...files, cid]] : [name, files]
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
exists(folders: [string, string[]][], name: string) {
|
exists(folders: [string, string[]][], name: string) {
|
||||||
return !!folders.find(([folder]) => folder === name)
|
return !!folders.find(([folder]) => folder === name);
|
||||||
},
|
},
|
||||||
toggleFilters: (filters: string[], filter: string) => filters.includes(filter)
|
toggleFilters: (filters: string[], filter: string) =>
|
||||||
? filters.filter((f) => f !== filter)
|
filters.includes(filter)
|
||||||
: [...filters, filter],
|
? filters.filter((f) => f !== filter)
|
||||||
listInFolder(files: CodexDataContent[], folders: [string, string[]][], index: number) {
|
: [...filters, filter],
|
||||||
|
listInFolder(
|
||||||
|
files: CodexDataContent[],
|
||||||
|
folders: [string, string[]][],
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
return index === 0
|
return index === 0
|
||||||
? files
|
? files
|
||||||
: files.filter((file) => folders[index - 1][1].includes(file.cid));
|
: files.filter((file) => folders[index - 1][1].includes(file.cid));
|
||||||
},
|
},
|
||||||
applyFilters(files: CodexDataContent[], filters: string[]) {
|
applyFilters(files: CodexDataContent[], filters: string[]) {
|
||||||
return files.filter(
|
return files.filter(
|
||||||
(file) => filters.length === 0 || filters.includes(this.type(file.manifest.mimetype))
|
(file) =>
|
||||||
)
|
filters.length === 0 ||
|
||||||
|
filters.includes(this.type(file.manifest.mimetype))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
formatDate(date: number) {
|
formatDate(date: number) {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
@ -99,7 +119,14 @@ export const FilesUtils = {
|
|||||||
dateStyle: "medium",
|
dateStyle: "medium",
|
||||||
timeStyle: "short",
|
timeStyle: "short",
|
||||||
}).format(new Date(date * 1000));
|
}).format(new Date(date * 1000));
|
||||||
}
|
},
|
||||||
|
getUploadedAt(key: string) {
|
||||||
|
return parseInt(localStorage.getItem(key + "-uploadedAt") || "0", 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploadedAt(key: string, value: number) {
|
||||||
|
localStorage.setItem(key + "-uploadedAt", value.toString());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CodexFileMetadata = {
|
export type CodexFileMetadata = {
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { classnames } from "../../utils/classnames";
|
|||||||
import "./HealthChecks.css";
|
import "./HealthChecks.css";
|
||||||
import { CodexSdk } from "../../sdk/codex";
|
import { CodexSdk } from "../../sdk/codex";
|
||||||
import { HealthCheckUtils } from "./health-check.utils";
|
import { HealthCheckUtils } from "./health-check.utils";
|
||||||
import { PortForwardingUtil } from "../../hooks/port-forwarding.util";
|
|
||||||
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
||||||
import ErrorCircleIcon from "../../assets/icons/error-circle.svg?react";
|
import ErrorCircleIcon from "../../assets/icons/error-circle.svg?react";
|
||||||
import DeviceIcon from "../../assets/icons/device.svg?react";
|
import DeviceIcon from "../../assets/icons/device.svg?react";
|
||||||
@ -21,7 +20,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const throwOnError = false;
|
const throwOnError = false;
|
||||||
const defaultPort = 8070;
|
|
||||||
|
|
||||||
export function HealthChecks({ online, onStepValid }: Props) {
|
export function HealthChecks({ online, onStepValid }: Props) {
|
||||||
const codex = useDebug(throwOnError);
|
const codex = useDebug(throwOnError);
|
||||||
@ -87,15 +85,6 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
.then(() => codex.refetch());
|
.then(() => codex.refetch());
|
||||||
};
|
};
|
||||||
|
|
||||||
let forwardingPortValue = defaultPort;
|
|
||||||
|
|
||||||
if (codex.isSuccess && codex.data) {
|
|
||||||
const port = PortForwardingUtil.getTcpPort(codex.data);
|
|
||||||
if (!port.error) {
|
|
||||||
forwardingPortValue = port.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="health-checks">
|
<div className="health-checks">
|
||||||
<div
|
<div
|
||||||
@ -140,10 +129,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<li>
|
<li>Ensure that port forwarding is enabled for your settings.</li>
|
||||||
Port forwarding should be {forwardingPortValue} for TCP and 8090 by
|
|
||||||
default for UDP.
|
|
||||||
</li>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@ -11,6 +11,17 @@ export function ManifestFetch() {
|
|||||||
|
|
||||||
const { refetch } = useQuery({
|
const { refetch } = useQuery({
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
|
CodexSdk.data()
|
||||||
|
.networkDownload(cid)
|
||||||
|
.then((s) => {
|
||||||
|
if (s.error === false) {
|
||||||
|
setCid("");
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||||
|
console.info("Done");
|
||||||
|
}
|
||||||
|
return Promises.rejectOnError(s);
|
||||||
|
});
|
||||||
|
|
||||||
return CodexSdk.data()
|
return CodexSdk.data()
|
||||||
.fetchManifest(cid)
|
.fetchManifest(cid)
|
||||||
.then((s) => {
|
.then((s) => {
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export function PurchasesTable() {
|
|||||||
["request id"],
|
["request id"],
|
||||||
["duration", onSortByDuration],
|
["duration", onSortByDuration],
|
||||||
["slots"],
|
["slots"],
|
||||||
["reward", onSortByReward],
|
["price per byte", onSortByReward],
|
||||||
["proof probability"],
|
["proof probability"],
|
||||||
["state", onSortByState],
|
["state", onSortByState],
|
||||||
] satisfies [string, ((state: TabSortState) => void)?][];
|
] satisfies [string, ((state: TabSortState) => void)?][];
|
||||||
@ -90,9 +90,8 @@ export function PurchasesTable() {
|
|||||||
const rows = sorted.map((p, index) => {
|
const rows = sorted.map((p, index) => {
|
||||||
const r = p.request;
|
const r = p.request;
|
||||||
const ask = p.request.ask;
|
const ask = p.request.ask;
|
||||||
const duration = parseInt(p.request.ask.duration, 10);
|
const duration = parseInt(ask.duration, 10);
|
||||||
const pf = parseInt(p.request.ask.proofProbability, 10);
|
const pf = parseInt(ask.proofProbability, 10);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
cells={[
|
cells={[
|
||||||
@ -105,7 +104,7 @@ export function PurchasesTable() {
|
|||||||
<TruncateCell value={r.id} />,
|
<TruncateCell value={r.id} />,
|
||||||
<Cell>{Times.pretty(duration)}</Cell>,
|
<Cell>{Times.pretty(duration)}</Cell>,
|
||||||
<Cell>{ask.slots.toString()}</Cell>,
|
<Cell>{ask.slots.toString()}</Cell>,
|
||||||
<Cell>{ask.reward + " CDX"}</Cell>,
|
<Cell>{p.request.ask.pricePerBytePerSecond + " CDX"}</Cell>,
|
||||||
<Cell>{pf.toString()}</Cell>,
|
<Cell>{pf.toString()}</Cell>,
|
||||||
<CustomStateCellRender state={p.state} message={p.error} />,
|
<CustomStateCellRender state={p.state} message={p.error} />,
|
||||||
]}></Row>
|
]}></Row>
|
||||||
|
|||||||
@ -1,42 +1,42 @@
|
|||||||
import { TabSortState } from "@codex-storage/marketplace-ui-components"
|
import { TabSortState } from "@codex-storage/marketplace-ui-components";
|
||||||
import { CodexPurchase } from "@codex-storage/sdk-js"
|
import { CodexPurchase, CodexStorageRequest } from "@codex-storage/sdk-js";
|
||||||
|
|
||||||
export const PurchaseUtils = {
|
export const PurchaseUtils = {
|
||||||
sortById: (state: TabSortState) =>
|
sortById: (state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) => {
|
||||||
(a: CodexPurchase, b: CodexPurchase) => {
|
return state === "desc"
|
||||||
|
? b.requestId
|
||||||
return state === "desc"
|
.toLocaleLowerCase()
|
||||||
? b.requestId
|
.localeCompare(a.requestId.toLocaleLowerCase())
|
||||||
.toLocaleLowerCase()
|
: a.requestId
|
||||||
.localeCompare(a.requestId.toLocaleLowerCase())
|
.toLocaleLowerCase()
|
||||||
: a.requestId
|
.localeCompare(b.requestId.toLocaleLowerCase());
|
||||||
.toLocaleLowerCase()
|
},
|
||||||
.localeCompare(b.requestId.toLocaleLowerCase())
|
sortByState: (state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) =>
|
||||||
},
|
state === "desc"
|
||||||
sortByState: (state: TabSortState) =>
|
? b.state.toLocaleLowerCase().localeCompare(a.state.toLocaleLowerCase())
|
||||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
: a.state.toLocaleLowerCase().localeCompare(b.state.toLocaleLowerCase()),
|
||||||
? b.state
|
sortByDuration:
|
||||||
.toLocaleLowerCase()
|
(state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) =>
|
||||||
.localeCompare(a.state.toLocaleLowerCase())
|
state === "desc"
|
||||||
: a.state
|
? Number(b.request.ask.duration) - Number(a.request.ask.duration)
|
||||||
.toLocaleLowerCase()
|
: Number(a.request.ask.duration) - Number(b.request.ask.duration),
|
||||||
.localeCompare(b.state.toLocaleLowerCase())
|
sortByReward:
|
||||||
,
|
(state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) => {
|
||||||
sortByDuration: (state: TabSortState) =>
|
const aPrice = parseInt(a.request.ask.pricePerBytePerSecond, 10);
|
||||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
const bPrice = parseInt(b.request.ask.pricePerBytePerSecond, 10);
|
||||||
? Number(b.request.ask.duration) - Number(a.request.ask.duration)
|
return state === "desc" ? bPrice - aPrice : aPrice - bPrice;
|
||||||
: Number(a.request.ask.duration) - Number(b.request.ask.duration)
|
},
|
||||||
,
|
sortByUploadedAt:
|
||||||
sortByReward: (state: TabSortState) =>
|
(state: TabSortState, table: Record<string, number>) =>
|
||||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
(a: CodexPurchase, b: CodexPurchase) => {
|
||||||
? Number(b.request.ask.reward) - Number(a.request.ask.reward)
|
return state === "desc"
|
||||||
: Number(a.request.ask.reward) - Number(b.request.ask.reward)
|
? (table[b.requestId] || 0) - (table[a.requestId] || 0)
|
||||||
,
|
: (table[a.requestId] || 0) - (table[b.requestId] || 0);
|
||||||
sortByUploadedAt: (state: TabSortState, table: Record<string, number>) =>
|
},
|
||||||
(a: CodexPurchase, b: CodexPurchase) => {
|
calculatePrice(request: CodexStorageRequest) {
|
||||||
return state === "desc"
|
return (
|
||||||
? (table[b.requestId] || 0) - (table[a.requestId] || 0)
|
parseInt(request.ask.slotSize, 10) *
|
||||||
: (table[a.requestId] || 0) - (table[b.requestId] || 0)
|
parseInt(request.ask.pricePerBytePerSecond, 10)
|
||||||
}
|
);
|
||||||
,
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@ -83,12 +83,26 @@ export function StorageRequestCreate() {
|
|||||||
WebStorage.set("storage-request-step", step);
|
WebStorage.set("storage-request-step", step);
|
||||||
|
|
||||||
if (step == CONFIRM_STATE) {
|
if (step == CONFIRM_STATE) {
|
||||||
const { availability, availabilityUnit, expiration, ...rest } =
|
const {
|
||||||
storageRequest;
|
availability,
|
||||||
|
availabilityUnit,
|
||||||
|
expiration,
|
||||||
|
reward,
|
||||||
|
collateral,
|
||||||
|
proofProbability,
|
||||||
|
cid,
|
||||||
|
nodes,
|
||||||
|
tolerance,
|
||||||
|
} = storageRequest;
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
...rest,
|
|
||||||
duration: Math.trunc(availability * Times.value(availabilityUnit)),
|
duration: Math.trunc(availability * Times.value(availabilityUnit)),
|
||||||
|
pricePerBytePerSecond: reward,
|
||||||
|
proofProbability,
|
||||||
|
collateralPerByte: collateral,
|
||||||
expiry: expiration * 60,
|
expiry: expiration * 60,
|
||||||
|
cid,
|
||||||
|
nodes,
|
||||||
|
tolerance,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { StorageRequestComponentProps } from "./types";
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import ChooseCidIcon from "../../assets/icons/choose-cid.svg?react";
|
import ChooseCidIcon from "../../assets/icons/choose-cid.svg?react";
|
||||||
import UploadIcon from "../../assets/icons/upload.svg?react";
|
import UploadIcon from "../../assets/icons/upload.svg?react";
|
||||||
|
import { FilesUtils } from "../Files/files.utils";
|
||||||
|
|
||||||
export function StorageRequestFileChooser({
|
export function StorageRequestFileChooser({
|
||||||
storageRequest,
|
storageRequest,
|
||||||
@ -39,6 +40,8 @@ export function StorageRequestFileChooser({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSuccess = (data: string) => {
|
const onSuccess = (data: string) => {
|
||||||
|
FilesUtils.setUploadedAt(data, Date.now() / 1000);
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||||
|
|
||||||
onStorageRequestChange({ cid: data });
|
onStorageRequestChange({ cid: data });
|
||||||
|
|||||||
@ -282,7 +282,7 @@ export function StorageRequestReview({
|
|||||||
onChange={onAvailabilityChange}
|
onChange={onAvailabilityChange}
|
||||||
onValidation={isInvalidAvailability}></Commitment>
|
onValidation={isInvalidAvailability}></Commitment>
|
||||||
<CardNumbers
|
<CardNumbers
|
||||||
helper="Represents how much collateral is asked from hosts if they don't fulfill the contract."
|
helper="Represents how much collateral is asked from hosts per byte if they don't fulfill the contract."
|
||||||
id="collateral"
|
id="collateral"
|
||||||
unit={"Collateral"}
|
unit={"Collateral"}
|
||||||
value={storageRequest.collateral.toString()}
|
value={storageRequest.collateral.toString()}
|
||||||
@ -290,13 +290,13 @@ export function StorageRequestReview({
|
|||||||
onValidation={isInvalidNumber}
|
onValidation={isInvalidNumber}
|
||||||
title="Penality tokens"></CardNumbers>
|
title="Penality tokens"></CardNumbers>
|
||||||
<CardNumbers
|
<CardNumbers
|
||||||
helper="The maximum amount of tokens paid per second per slot to hosts the client is willing to pay."
|
helper="The maximum amount of tokens paid per second per byte to hosts the client is willing to pay."
|
||||||
id="reward"
|
id="reward"
|
||||||
unit={"Reward"}
|
unit={"Reward"}
|
||||||
value={storageRequest.reward.toString()}
|
value={storageRequest.reward.toString()}
|
||||||
onChange={onRewardChange}
|
onChange={onRewardChange}
|
||||||
onValidation={isInvalidNumber}
|
onValidation={isInvalidNumber}
|
||||||
title="Reward tokens for hosts"></CardNumbers>
|
title="Price per byte"></CardNumbers>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|||||||
@ -2,11 +2,13 @@ import { Upload } from "@codex-storage/marketplace-ui-components";
|
|||||||
import { CodexSdk } from "../../sdk/codex";
|
import { CodexSdk } from "../../sdk/codex";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import UploadIcon from "../../assets/icons/upload.svg?react";
|
import UploadIcon from "../../assets/icons/upload.svg?react";
|
||||||
|
import { FilesUtils } from "../Files/files.utils";
|
||||||
|
|
||||||
export function UploadCard() {
|
export function UploadCard() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const onSuccess = () => {
|
const onSuccess = (cid: string) => {
|
||||||
|
FilesUtils.setUploadedAt(cid, Date.now() / 1000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { CodexSdk } from "../sdk/codex";
|
import { CodexSdk } from "../sdk/codex";
|
||||||
import { CodexDataResponse } from "@codex-storage/sdk-js";
|
import { CodexDataResponse } from "@codex-storage/sdk-js";
|
||||||
import { Promises } from "../utils/promises";
|
import { Promises } from "../utils/promises";
|
||||||
|
import { FilesUtils } from "../components/Files/files.utils";
|
||||||
|
|
||||||
export function useData() {
|
export function useData() {
|
||||||
const { data = { content: [] } satisfies CodexDataResponse } =
|
const { data = { content: [] } satisfies CodexDataResponse } =
|
||||||
@ -29,5 +30,8 @@ export function useData() {
|
|||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.content;
|
return data.content.map((c) => ({
|
||||||
|
...c,
|
||||||
|
uploadedAt: FilesUtils.getUploadedAt(c.cid),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/main.tsx
43
src/main.tsx
@ -21,6 +21,7 @@ import { SettingsRoute } from "./routes/dashboard/settings.tsx";
|
|||||||
import { HelpRoute } from "./routes/dashboard/help.tsx";
|
import { HelpRoute } from "./routes/dashboard/help.tsx";
|
||||||
import { DisclaimerRoute } from "./routes/dashboard/disclaimer.tsx";
|
import { DisclaimerRoute } from "./routes/dashboard/disclaimer.tsx";
|
||||||
import { RouteErrorBoundary } from "./components/RouteErrorBoundary/RouteErrorBoundary.tsx";
|
import { RouteErrorBoundary } from "./components/RouteErrorBoundary/RouteErrorBoundary.tsx";
|
||||||
|
import { HealthCheckUtils } from "./components/HealthChecks/health-check.utils.ts";
|
||||||
|
|
||||||
if (import.meta.env.PROD && !import.meta.env.CI) {
|
if (import.meta.env.PROD && !import.meta.env.CI) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
@ -117,14 +118,36 @@ const queryClient = new QueryClient();
|
|||||||
const rootElement = document.getElementById("root")!;
|
const rootElement = document.getElementById("root")!;
|
||||||
|
|
||||||
if (rootElement) {
|
if (rootElement) {
|
||||||
CodexSdk.load().then(() => {
|
CodexSdk.load()
|
||||||
render(
|
.then(() => {
|
||||||
<StrictMode>
|
const queryString = window.location.search;
|
||||||
<QueryClientProvider client={queryClient}>
|
if (queryString) {
|
||||||
<RouterProvider router={router} />
|
const urlParams = new URLSearchParams(queryString);
|
||||||
</QueryClientProvider>
|
const param = urlParams.get("api-port");
|
||||||
</StrictMode>,
|
if (param) {
|
||||||
rootElement
|
const port = parseInt(param, 10);
|
||||||
);
|
if (!isNaN(port)) {
|
||||||
});
|
const address = HealthCheckUtils.removePort(CodexSdk.url());
|
||||||
|
|
||||||
|
const url = address + ":" + port;
|
||||||
|
|
||||||
|
if (HealthCheckUtils.isUrlInvalid(url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CodexSdk.updateURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
render(
|
||||||
|
<StrictMode>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</StrictMode>,
|
||||||
|
rootElement
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/proxy.ts
28
src/proxy.ts
@ -7,10 +7,7 @@ import {
|
|||||||
import { CodexSdk as Sdk } from "./sdk/codex";
|
import { CodexSdk as Sdk } from "./sdk/codex";
|
||||||
import { WebStorage } from "./utils/web-storage";
|
import { WebStorage } from "./utils/web-storage";
|
||||||
|
|
||||||
class CodexDataMock extends CodexData {
|
class CodexDataMock extends CodexData {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CodexMarketplaceMock extends CodexMarketplace {
|
class CodexMarketplaceMock extends CodexMarketplace {
|
||||||
// override async purchases(): Promise<SafeValue<CodexPurchase[]>> {
|
// override async purchases(): Promise<SafeValue<CodexPurchase[]>> {
|
||||||
@ -31,26 +28,29 @@ class CodexMarketplaceMock extends CodexMarketplace {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintains a temporary link between the CID and the file metadata.
|
* Maintains a temporary link between the CID and the file metadata.
|
||||||
* When the metadata is available in the manifest, the CID link
|
* When the metadata is available in the manifest, the CID link
|
||||||
* should still be maintained, but the metadata should be retrieved
|
* should still be maintained, but the metadata should be retrieved
|
||||||
* using a REST API call.
|
* using a REST API call.
|
||||||
*/
|
*/
|
||||||
override async createStorageRequest(input: CodexCreateStorageRequestInput): Promise<SafeValue<string>> {
|
override async createStorageRequest(
|
||||||
const res = await super.createStorageRequest(input)
|
input: CodexCreateStorageRequestInput
|
||||||
|
): Promise<SafeValue<string>> {
|
||||||
|
console.info(input);
|
||||||
|
const res = await super.createStorageRequest(input);
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
return res
|
console.error(res.data);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
await WebStorage.purchases.set("0x" + res.data, input.cid)
|
await WebStorage.purchases.set("0x" + res.data, input.cid);
|
||||||
|
|
||||||
// await PurchaseDatesStorage.set(res.data, new Date().toJSON())
|
// await PurchaseDatesStorage.set(res.data, new Date().toJSON())
|
||||||
|
|
||||||
return res
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// override createStorageRequest(
|
// override createStorageRequest(
|
||||||
// input: CodexCreateStorageRequestInput
|
// input: CodexCreateStorageRequestInput
|
||||||
// ): Promise<SafeValue<string>> {
|
// ): Promise<SafeValue<string>> {
|
||||||
@ -139,5 +139,3 @@ export const CodexSdk = {
|
|||||||
marketplace: () => new CodexMarketplaceMock(CodexSdk.url()),
|
marketplace: () => new CodexMarketplaceMock(CodexSdk.url()),
|
||||||
data: () => new CodexDataMock(CodexSdk.url()),
|
data: () => new CodexDataMock(CodexSdk.url()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ export type TimesUnit =
|
|||||||
| "seconds";
|
| "seconds";
|
||||||
|
|
||||||
const plural = (value: number, unit: TimesUnit) => {
|
const plural = (value: number, unit: TimesUnit) => {
|
||||||
const val = Number.isInteger(value) ? value : value.toFixed(1)
|
const val = Number.isInteger(value) ? value : value.toFixed(1);
|
||||||
return value > 1 ? val + ` ${unit}` : val + ` ${unit.slice(0, -1)}`;
|
return value > 1 ? val + ` ${unit}` : val + ` ${unit.slice(0, -1)}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Times = {
|
export const Times = {
|
||||||
toSeconds(value: number, unit: TimesUnit) {
|
toSeconds(value: number, unit: TimesUnit) {
|
||||||
@ -73,24 +73,23 @@ export const Times = {
|
|||||||
|
|
||||||
seconds /= 30;
|
seconds /= 30;
|
||||||
if (value >= seconds) {
|
if (value >= seconds) {
|
||||||
return "days"
|
return "days";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "hours"
|
return "hours";
|
||||||
},
|
},
|
||||||
|
|
||||||
value(unit: "hours" | "days" | "months") {
|
value(unit: "hours" | "days" | "months") {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "months": {
|
case "months": {
|
||||||
return 30 * 24 * 60 * 60
|
return 30 * 24 * 60 * 60;
|
||||||
}
|
}
|
||||||
case "days": {
|
case "days": {
|
||||||
return 24 * 60 * 60
|
return 24 * 60 * 60;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return 60 * 60
|
return 60 * 60;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { createStore, del, entries, get, set } from "idb-keyval";
|
import { createStore, del, entries, get, set } from "idb-keyval";
|
||||||
|
|
||||||
|
|
||||||
export const WebStorage = {
|
export const WebStorage = {
|
||||||
set(key: string, value: unknown) {
|
set(key: string, value: unknown) {
|
||||||
return set(key, value);
|
return set(key, value);
|
||||||
@ -16,27 +15,27 @@ export const WebStorage = {
|
|||||||
|
|
||||||
onBoarding: {
|
onBoarding: {
|
||||||
getStep() {
|
getStep() {
|
||||||
return parseInt(localStorage.getItem("onboarding-step") || "0", 10)
|
return parseInt(localStorage.getItem("onboarding-step") || "0", 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
setStep(step: number) {
|
setStep(step: number) {
|
||||||
localStorage.setItem("onboarding-step", step.toString())
|
localStorage.setItem("onboarding-step", step.toString());
|
||||||
},
|
},
|
||||||
|
|
||||||
setDisplayName(displayName: string) {
|
setDisplayName(displayName: string) {
|
||||||
localStorage.setItem("display-name", displayName)
|
localStorage.setItem("display-name", displayName);
|
||||||
},
|
},
|
||||||
|
|
||||||
getDisplayName() {
|
getDisplayName() {
|
||||||
return localStorage.getItem("display-name") || ""
|
return localStorage.getItem("display-name") || "";
|
||||||
},
|
},
|
||||||
|
|
||||||
setEmoji(emoji: string) {
|
setEmoji(emoji: string) {
|
||||||
localStorage.setItem("emoji", emoji)
|
localStorage.setItem("emoji", emoji);
|
||||||
},
|
},
|
||||||
|
|
||||||
getEmoji() {
|
getEmoji() {
|
||||||
return localStorage.getItem("emoji") || "🤖"
|
return localStorage.getItem("emoji") || "🤖";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -48,33 +47,35 @@ export const WebStorage = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async list(): Promise<[string, string[]][]> {
|
async list(): Promise<[string, string[]][]> {
|
||||||
const items = await entries<string, string[]>(this.store) || []
|
const items = (await entries<string, string[]>(this.store)) || [];
|
||||||
|
|
||||||
if (items.length == 0) {
|
if (items.length == 0) {
|
||||||
return [["Favorites", []]]
|
return [["Favorites", []]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items[0][0] !== "Favorites") {
|
if (items[0][0] !== "Favorites") {
|
||||||
return [["Favorites", []], ...items]
|
return [["Favorites", []], ...items];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
return items
|
|
||||||
},
|
},
|
||||||
delete(key: string) {
|
delete(key: string) {
|
||||||
return del(key, this.store);
|
return del(key, this.store);
|
||||||
},
|
},
|
||||||
async addFile(folder: string, cid: string) {
|
async addFile(folder: string, cid: string) {
|
||||||
const files = await get<string[]>(folder, this.store) || []
|
const files = (await get<string[]>(folder, this.store)) || [];
|
||||||
|
|
||||||
return set(folder, [...files, cid], this.store)
|
return set(folder, [...files, cid], this.store);
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteFile(folder: string, cid: string) {
|
async deleteFile(folder: string, cid: string) {
|
||||||
const files = await get<string[]>(folder, this.store) || []
|
const files = (await get<string[]>(folder, this.store)) || [];
|
||||||
|
|
||||||
return set(folder, files.filter(item => item !== cid), this.store)
|
|
||||||
|
|
||||||
|
return set(
|
||||||
|
folder,
|
||||||
|
files.filter((item) => item !== cid),
|
||||||
|
this.store
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -97,7 +98,6 @@ export const WebStorage = {
|
|||||||
purchases: {
|
purchases: {
|
||||||
store: createStore("purchases", "purchases"),
|
store: createStore("purchases", "purchases"),
|
||||||
|
|
||||||
|
|
||||||
async get(key: string) {
|
async get(key: string) {
|
||||||
return get<string>(key, this.store);
|
return get<string>(key, this.store);
|
||||||
},
|
},
|
||||||
@ -120,6 +120,6 @@ export const WebStorage = {
|
|||||||
async set(key: string, date: string) {
|
async set(key: string, date: string) {
|
||||||
return set(key, date, this.store);
|
return set(key, date, this.store);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user