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:
|
||||
|
||||
env:
|
||||
codex_version: v0.1.9
|
||||
circuit_version: v0.1.9
|
||||
marketplace_address: "0xAB03b6a58C5262f530D54146DA2a552B1C0F7648"
|
||||
codex_version: v0.2.0
|
||||
circuit_version: v0.2.0
|
||||
marketplace_address: "0xfFaF679D5Cbfdd5Dbc9Be61C616ed115DFb597ed"
|
||||
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_GEO_IP_URL: ${{ secrets.VITE_GEO_IP_URL }}
|
||||
jobs:
|
||||
@ -36,7 +37,7 @@ jobs:
|
||||
key: ${{ env.circuit_version }}-circuits
|
||||
|
||||
- name: Download circuits
|
||||
if: steps.circuits-cache-restore.outputs.cache-hit != 'true'
|
||||
# if: steps.circuits-cache-restore.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p datadir/circuits
|
||||
chmod 700 datadir
|
||||
@ -85,13 +86,13 @@ jobs:
|
||||
chmod 600 eth.key
|
||||
|
||||
# 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
|
||||
|
||||
- name: Check Codex API
|
||||
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; }
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -1,121 +1,143 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { Bytes } from "../src/utils/bytes"
|
||||
import { GB } from "../src/utils/constants"
|
||||
import { Bytes } from "../src/utils/bytes";
|
||||
import { GB } from "../src/utils/constants";
|
||||
|
||||
test('create an availability', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
test("create an availability", 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.locator(".availability-edit button").first().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('Duration').click();
|
||||
await page.getByLabel('Duration').fill('30');
|
||||
await page.getByLabel('Min price').click();
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Max collateral').click();
|
||||
await page.getByLabel('Max collateral').fill('30');
|
||||
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(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
||||
})
|
||||
await page.getByLabel("Total size").fill(value.toFixed(1));
|
||||
await page.getByLabel("Duration").click();
|
||||
await page.getByLabel("Duration").fill("30");
|
||||
await page.getByLabel("Min price").click();
|
||||
await page.getByLabel("Min price").fill("5");
|
||||
await page.getByLabel("Total collateral").click();
|
||||
await page.getByLabel("Total collateral").fill("30");
|
||||
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(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('availability navigation buttons', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
test("availability navigation buttons", async ({ page }) => {
|
||||
await page.goto("/dashboard/availabilities");
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await page.getByLabel('Total size').click();
|
||||
await page.getByLabel('Total size').fill('19');
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
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();
|
||||
})
|
||||
await page.locator(".availability-edit button").first().click();
|
||||
await expect(page.locator(".stepper-number-done")).not.toBeVisible();
|
||||
await expect(page.locator(".step--active")).toBeVisible();
|
||||
await expect(
|
||||
page.locator("footer .button--outline").first()
|
||||
).not.toHaveAttribute("disabled");
|
||||
await page.getByLabel("Total size").click();
|
||||
await page.getByLabel("Total size").fill("");
|
||||
await page.getByLabel("Duration").click();
|
||||
|
||||
test('create an availability with changing the duration to months', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||
"disabled",
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
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 page.goto("/dashboard/availabilities");
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await page.getByLabel('Total size').click();
|
||||
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('Duration').click();
|
||||
await page.getByLabel('Duration').fill("3");
|
||||
await page.getByRole('combobox').nth(1).selectOption('months');
|
||||
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");
|
||||
|
||||
await page.getByLabel('Min price').click();
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Max collateral').click();
|
||||
await page.getByLabel('Max collateral').fill('30');
|
||||
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 page.getByLabel("Min price").click();
|
||||
await page.getByLabel("Min price").fill("5");
|
||||
await page.getByLabel("Total collateral").click();
|
||||
await page.getByLabel("Total collateral").fill("30");
|
||||
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');
|
||||
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.locator(".availability-edit button").first().click();
|
||||
await page.getByLabel("Total size").click();
|
||||
|
||||
await page.getByLabel("Total size").fill("9999");
|
||||
await expect(page.getByLabel("Total size")).toHaveAttribute("aria-invalid");
|
||||
|
||||
await page.getByLabel('Total size').fill("9999");
|
||||
await expect(page.getByLabel('Total size')).toHaveAttribute("aria-invalid");
|
||||
await page.getByText("Use max size").click();
|
||||
await expect(page.getByLabel("Total size")).not.toHaveAttribute(
|
||||
"aria-invalid"
|
||||
);
|
||||
|
||||
await page.getByText("Use max size").click()
|
||||
await expect(page.getByLabel('Total size')).not.toHaveAttribute("aria-invalid");
|
||||
const value = 0.2;
|
||||
await page.getByLabel("Total size").fill(value.toString());
|
||||
|
||||
const value = (Math.random() * 0.5);
|
||||
await page.getByLabel('Total size').fill(value.toFixed(1));
|
||||
|
||||
await page.getByLabel('Duration').click();
|
||||
await page.getByLabel('Duration').fill('30');
|
||||
await page.getByLabel('Min price').click();
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Max collateral').click();
|
||||
await page.getByLabel('Max collateral').fill('30');
|
||||
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(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
||||
})
|
||||
await page.getByLabel("Duration").click();
|
||||
await page.getByLabel("Duration").fill("30");
|
||||
await page.getByLabel("Min price").click();
|
||||
await page.getByLabel("Min price").fill("5");
|
||||
await page.getByLabel("Total collateral").click();
|
||||
await page.getByLabel("Total collateral").fill("30");
|
||||
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(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@ -1,29 +1,34 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { test, expect } from "@playwright/test";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
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
|
||||
test.skip(browserName.toLowerCase() !== 'chromium',
|
||||
`Test only for chromium!`);
|
||||
test.skip(
|
||||
browserName.toLowerCase() !== "chromium",
|
||||
`Test only for chromium!`
|
||||
);
|
||||
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await page.goto("/dashboard");
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await page.locator('.file-cell button').first().click();
|
||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||
const cid = await handle.jsonValue()
|
||||
await page.locator(".file-cell button").first().click();
|
||||
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 downloadPromise = page.waitForEvent('download');
|
||||
await page.locator('.download-input + button').click();
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
await page.locator(".download-input + button").click();
|
||||
// const page1 = await page1Promise;
|
||||
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('onboarding steps', async ({ page, browserName }) => {
|
||||
await page.context().setOffline(false)
|
||||
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 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();
|
||||
test.describe("onboarding", () => {
|
||||
test("onboarding steps", async ({ page, browserName }) => {
|
||||
await page.context().setOffline(false);
|
||||
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 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
|
||||
await expect(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()
|
||||
await expect(
|
||||
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
|
||||
await expect(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()
|
||||
await expect(
|
||||
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
|
||||
await expect(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()
|
||||
await expect(
|
||||
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
|
||||
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).getByTestId("icon-warning")).toBeVisible({ timeout: 10_000 })
|
||||
await expect(page.locator(".health-checks ul li").nth(3)).toBeVisible();
|
||||
|
||||
// Can be simulated with File -> Work offline
|
||||
if (browserName.toLowerCase() !== 'firefox') {
|
||||
await page.context().setOffline(true)
|
||||
if (browserName.toLowerCase() !== "firefox") {
|
||||
await page.context().setOffline(true);
|
||||
|
||||
// Network
|
||||
await expect(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 expect(
|
||||
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 }) => {
|
||||
await page.context().setOffline(false)
|
||||
await page.context().setOffline(false);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
test('does not display undefined when delete the url value', async ({ page }) => {
|
||||
await page.goto('/onboarding-checks');
|
||||
await page.locator('#url').focus()
|
||||
test("does not display undefined when delete the url value", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/onboarding-checks");
|
||||
await page.locator("#url").focus();
|
||||
|
||||
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')).toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator('.refresh svg')).toHaveAttribute("color", "#494949")
|
||||
await expect(page.locator("#url")).toHaveValue("");
|
||||
await expect(page.locator("#url")).toHaveAttribute("aria-invalid");
|
||||
await expect(page.locator(".refresh svg")).toHaveAttribute(
|
||||
"color",
|
||||
"#494949"
|
||||
);
|
||||
});
|
||||
@ -1,113 +1,158 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { Times } from "../src/utils/times";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
test('create a storage request', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
test("create a storage request", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
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: 'Finish' }).click();
|
||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
||||
await expect(page.getByTestId('cell-pending').first()).toBeVisible();
|
||||
})
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await expect(page.locator("#cid")).not.toBeEmpty();
|
||||
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: "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 }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
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("select a uploaded cid when creating a storage request", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/dashboard");
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||
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 }) => {
|
||||
await page.goto('/dashboard/purchases');
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
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("storage request navigation buttons", async ({ page }) => {
|
||||
await page.goto("/dashboard/purchases");
|
||||
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||
await expect(page.locator(".step--done")).not.toBeVisible();
|
||||
await expect(page.locator(".step--active")).toBeVisible();
|
||||
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
await expect(
|
||||
page.locator("footer .button--outline").first()
|
||||
).not.toHaveAttribute("disabled");
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await expect(
|
||||
page.locator("footer .button--outline").first()
|
||||
).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
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('remove the CID when the file is deleted', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await page.locator('.button-icon--small').nth(1).click();
|
||||
await expect(page.locator('#cid')).toBeEmpty()
|
||||
})
|
||||
test("remove the CID when the file is deleted", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
await page.locator("a").filter({ hasText: "Purchases" }).click();
|
||||
await page.getByRole("button", { name: "Storage Request" }).click();
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await expect(page.locator("#cid")).not.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 }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
test("create a storage request by using decimal values", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
await page.locator("a").filter({ hasText: "Settings" }).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([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await page
|
||||
.locator("div")
|
||||
.getByTestId("upload")
|
||||
.setInputFiles([path.join(__dirname, "assets", "chatgpt.jpg")]);
|
||||
await expect(page.locator("#cid")).not.toBeEmpty();
|
||||
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 expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
await page.getByLabel("Full period of the contract").fill("10");
|
||||
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("1")
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await page.getByLabel("Full period of the contract").fill("1");
|
||||
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("0")
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
await page.getByLabel("Full period of the contract").fill("0");
|
||||
await expect(page.locator("footer .button--primary")).toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
|
||||
const value = (Math.random() * 7);
|
||||
await page.getByLabel("Full period of the contract").fill(value.toFixed(1))
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
const days = 4;
|
||||
|
||||
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();
|
||||
await expect(page.getByText(value.toFixed(1) + " days").first()).toBeVisible();
|
||||
})
|
||||
await page.getByLabel("Full period of the contract").fill(days.toFixed(1));
|
||||
await expect(page.locator("footer .button--primary")).not.toHaveAttribute(
|
||||
"disabled"
|
||||
);
|
||||
|
||||
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 }) => {
|
||||
// await page.goto('/dashboard');
|
||||
|
||||
2422
package-lock.json
generated
2422
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/codex-storage/codex-marketplace-ui"
|
||||
},
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1 --port 5173",
|
||||
@ -27,7 +27,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@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/react": "^8.31.0",
|
||||
"@tanstack/react-query": "^5.51.15",
|
||||
@ -60,9 +60,9 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.7",
|
||||
"vite": "^6.1.0",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vitest": "^2.1.4"
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"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 __dirname = dirname(__filename);
|
||||
@ -7,33 +7,33 @@ const __dirname = dirname(__filename);
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
import dotenv from 'dotenv';
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
dotenv.config({ path: path.resolve(__dirname, '.env.ci') });
|
||||
import dotenv from "dotenv";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
dotenv.config({ path: path.resolve(__dirname, ".env.ci") });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
testDir: "./e2e",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
retries: process.env.CI ? 3 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* 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. */
|
||||
use: {
|
||||
/* 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 */
|
||||
trace: 'on-first-retry',
|
||||
trace: "on-first-retry",
|
||||
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
@ -41,18 +41,18 @@ export default defineConfig({
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
@ -78,8 +78,8 @@ export default defineConfig({
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run preview',
|
||||
url: 'http://127.0.0.1:5173',
|
||||
command: "npm run preview",
|
||||
url: "http://127.0.0.1:5173",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
|
||||
@ -57,8 +57,8 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
||||
["id", onSortById],
|
||||
["total size", onSortBySize],
|
||||
["duration", onSortByDuration],
|
||||
["min price", onSortByPrice],
|
||||
["max collateral", onSortByCollateral],
|
||||
["min price per byte", onSortByPrice],
|
||||
["remaining collateral", onSortByCollateral],
|
||||
["actions"],
|
||||
] satisfies [string, ((state: TabSortState) => void)?][];
|
||||
|
||||
@ -88,8 +88,8 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
||||
<AvailabilityIdCell value={a} />,
|
||||
<Cell>{Bytes.pretty(a.totalSize)}</Cell>,
|
||||
<Cell>{Times.pretty(a.duration)}</Cell>,
|
||||
<Cell>{a.minPrice.toString()}</Cell>,
|
||||
<Cell>{a.maxCollateral.toString()}</Cell>,
|
||||
<Cell>{a.minPricePerBytePerSecond.toString()}</Cell>,
|
||||
<Cell>{a.totalRemainingCollateral.toString()}</Cell>,
|
||||
<AvailabilityActionsCell availability={a} />,
|
||||
]}></Row>
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { AvailabilityState } from "./types";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { GB, STEPPER_DURATION } from "../../utils/constants";
|
||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
||||
import { AvailabilityError } from "./AvailabilityError";
|
||||
@ -30,8 +30,8 @@ const CONFIRM_STATE = 2;
|
||||
const defaultAvailabilityData: AvailabilityState = {
|
||||
totalSize: 0.5,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
};
|
||||
@ -143,6 +143,9 @@ export function AvailabilityEdit({
|
||||
|
||||
setAvailability({
|
||||
...a,
|
||||
totalSize: a.totalSize / GB,
|
||||
totalSizeUnit: "gb",
|
||||
duration: a.duration / Times.value(unit),
|
||||
durationUnit: unit as "hours" | "days" | "months",
|
||||
});
|
||||
|
||||
|
||||
@ -184,32 +184,35 @@ export function AvailabilityForm({
|
||||
<div className="row gap">
|
||||
<div className="group">
|
||||
<Input
|
||||
id="minPrice"
|
||||
name="minPrice"
|
||||
id="minPricePerBytePerSecond"
|
||||
name="minPricePerBytePerSecond"
|
||||
type="number"
|
||||
label="Min price"
|
||||
label="Min price per byte per second"
|
||||
min={0}
|
||||
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>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="group">
|
||||
<Input
|
||||
id="maxCollateral"
|
||||
name="maxCollateral"
|
||||
id="totalCollateral"
|
||||
name="totalCollateral"
|
||||
type="number"
|
||||
label="Max collateral"
|
||||
label="Total collateral"
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
value={availability.maxCollateral.toString()}
|
||||
value={availability.totalCollateral.toString()}
|
||||
/>
|
||||
<Tooltip
|
||||
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>
|
||||
</Tooltip>
|
||||
|
||||
@ -22,7 +22,8 @@ export function AvailabilityIdCell({ value }: Props) {
|
||||
</small>
|
||||
<br />
|
||||
<small className="text--light">
|
||||
Max collateral {value.maxCollateral} | Min price {value.minPrice}
|
||||
Collateral {value.totalCollateral} | Min price{" "}
|
||||
{value.minPricePerBytePerSecond}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -68,11 +68,11 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
"Duration " +
|
||||
Times.pretty(a.duration) +
|
||||
"<br/>" +
|
||||
"Max collateral " +
|
||||
a.maxCollateral +
|
||||
"Total remaining collateral " +
|
||||
a.totalRemainingCollateral +
|
||||
"<br/>" +
|
||||
"Min price " +
|
||||
a.minPrice +
|
||||
"Min price per byte per second " +
|
||||
a.minPricePerBytePerSecond +
|
||||
"<br/>" +
|
||||
"Size " +
|
||||
Bytes.pretty(a.totalSize)
|
||||
|
||||
@ -10,28 +10,32 @@ describe("files", () => {
|
||||
id: "a",
|
||||
totalSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
freeSize: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
slots: [],
|
||||
};
|
||||
const b = {
|
||||
id: "b",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
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]);
|
||||
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortById("asc"))
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortById("asc"));
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
@ -40,29 +44,33 @@ describe("files", () => {
|
||||
const a = {
|
||||
id: "",
|
||||
totalSize: 1,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
slots: [],
|
||||
};
|
||||
const b = {
|
||||
id: "",
|
||||
totalSize: 2,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
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]);
|
||||
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortBySize("asc"))
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortBySize("asc"));
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
@ -71,29 +79,37 @@ describe("files", () => {
|
||||
const a = {
|
||||
id: "",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
slots: [],
|
||||
};
|
||||
const b = {
|
||||
id: "",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 2,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
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]);
|
||||
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByDuration("asc"))
|
||||
const ascSorted = items
|
||||
.slice()
|
||||
.sort(AvailabilityUtils.sortByDuration("asc"));
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
@ -102,29 +118,35 @@ describe("files", () => {
|
||||
const a = {
|
||||
id: "",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 1,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 1,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
slots: [],
|
||||
};
|
||||
const b = {
|
||||
id: "",
|
||||
freeSize: 0,
|
||||
totalSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 2,
|
||||
maxCollateral: 0,
|
||||
minPricePerBytePerSecond: 2,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 0,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
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]);
|
||||
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByPrice("asc"))
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByPrice("asc"));
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
@ -133,29 +155,37 @@ describe("files", () => {
|
||||
const a = {
|
||||
id: "",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 1,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 1,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
slots: [],
|
||||
};
|
||||
const b = {
|
||||
id: "",
|
||||
totalSize: 0,
|
||||
freeSize: 0,
|
||||
duration: 0,
|
||||
minPrice: 0,
|
||||
maxCollateral: 2,
|
||||
minPricePerBytePerSecond: 0,
|
||||
totalCollateral: 0,
|
||||
totalRemainingCollateral: 2,
|
||||
name: "",
|
||||
slots: []
|
||||
}
|
||||
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]);
|
||||
|
||||
const ascSorted = items.slice().sort(AvailabilityUtils.sortByCollateral("asc"))
|
||||
const ascSorted = items
|
||||
.slice()
|
||||
.sort(AvailabilityUtils.sortByCollateral("asc"));
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
@ -163,33 +193,41 @@ describe("files", () => {
|
||||
it("returns the number of bytes per unit", async () => {
|
||||
assert.deepEqual(AvailabilityUtils.toUnit(GB, "gb"), 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
|
||||
}
|
||||
totalBlocks: 0,
|
||||
};
|
||||
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB - 1);
|
||||
})
|
||||
});
|
||||
|
||||
it("checks the availability max value", async () => {
|
||||
const availability = {
|
||||
totalSizeUnit: "gb",
|
||||
totalSize: 1
|
||||
} as AvailabilityState
|
||||
totalSize: 1,
|
||||
} as AvailabilityState;
|
||||
|
||||
assert.deepEqual(AvailabilityUtils.isValid(availability, GB * 2), true);
|
||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: -1 }, GB), false);
|
||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: GB }, 2 * GB), false);
|
||||
})
|
||||
assert.deepEqual(
|
||||
AvailabilityUtils.isValid({ ...availability, totalSize: -1 }, GB),
|
||||
false
|
||||
);
|
||||
assert.deepEqual(
|
||||
AvailabilityUtils.isValid({ ...availability, totalSize: GB }, 2 * GB),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("toggles item in array", async () => {
|
||||
const array: string[] = []
|
||||
const array: string[] = [];
|
||||
assert.deepEqual(AvailabilityUtils.toggle(array, "1"), ["1"]);
|
||||
assert.deepEqual(AvailabilityUtils.toggle(AvailabilityUtils.toggle(array, "1"), "1"), []);
|
||||
})
|
||||
})
|
||||
assert.deepEqual(
|
||||
AvailabilityUtils.toggle(AvailabilityUtils.toggle(array, "1"), "1"),
|
||||
[]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,57 +1,55 @@
|
||||
import { TabSortState } from "@codex-storage/marketplace-ui-components"
|
||||
import { AvailabilityState, AvailabilityWithSlots } from "./types"
|
||||
import { TabSortState } from "@codex-storage/marketplace-ui-components";
|
||||
import { AvailabilityState, AvailabilityWithSlots } from "./types";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
|
||||
export const AvailabilityUtils = {
|
||||
sortById: (state: TabSortState) =>
|
||||
sortById:
|
||||
(state: TabSortState) =>
|
||||
(a: AvailabilityWithSlots, b: AvailabilityWithSlots) => {
|
||||
|
||||
return state === "desc"
|
||||
? b.id
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(a.id.toLocaleLowerCase())
|
||||
: a.id
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(b.id.toLocaleLowerCase())
|
||||
? 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
|
||||
,
|
||||
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.minPricePerBytePerSecond - a.minPricePerBytePerSecond
|
||||
: 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")
|
||||
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
|
||||
return (
|
||||
space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes - 1
|
||||
);
|
||||
},
|
||||
unitValue(unit: "gb" | "tb") {
|
||||
return unit === "tb" ? TB : GB
|
||||
return unit === "tb" ? TB : GB;
|
||||
},
|
||||
isValid: (
|
||||
availability: AvailabilityState,
|
||||
max: number
|
||||
) => availability.totalSize > 0 && availability.totalSize * AvailabilityUtils.unitValue(availability.totalSizeUnit) <= max
|
||||
,
|
||||
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],
|
||||
arr.includes(value) ? arr.filter((i) => i !== value) : [...arr, value],
|
||||
|
||||
availabilityColors: [
|
||||
"#34A0FFFF",
|
||||
@ -90,4 +88,4 @@ export const AvailabilityUtils = {
|
||||
"#D2493C11",
|
||||
"#D2493C00",
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
@ -14,8 +14,8 @@ export type AvailabilityState = {
|
||||
totalSize: number;
|
||||
duration: number;
|
||||
durationUnit: "hours" | "days" | "months";
|
||||
minPrice: number;
|
||||
maxCollateral: number;
|
||||
minPricePerBytePerSecond: number;
|
||||
totalCollateral: number;
|
||||
totalSizeUnit: "gb" | "tb";
|
||||
name?: string;
|
||||
};
|
||||
|
||||
@ -12,7 +12,6 @@ import { CodexAvailabilityCreateResponse } from "@codex-storage/sdk-js";
|
||||
import { Times } from "../../utils/times";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
|
||||
|
||||
export function useAvailabilityMutation(
|
||||
dispatch: Dispatch<StepperAction>,
|
||||
state: StepperState
|
||||
@ -33,10 +32,17 @@ export function useAvailabilityMutation(
|
||||
const fn: (
|
||||
input: Omit<AvailabilityState, "totalSizeUnit" | "durationUnit">
|
||||
) => Promise<"" | CodexAvailabilityCreateResponse> = input.id
|
||||
? (input) =>
|
||||
CodexSdk.marketplace()
|
||||
.updateAvailability({ ...input, id: input.id || "" })
|
||||
.then((s) => Promises.rejectOnError(s))
|
||||
? (input) => {
|
||||
return CodexSdk.marketplace()
|
||||
.updateAvailability({
|
||||
totalSize: input.totalSize,
|
||||
duration: input.duration,
|
||||
minPricePerBytePerSecond: input.minPricePerBytePerSecond,
|
||||
totalCollateral: input.totalCollateral,
|
||||
id: input.id || "",
|
||||
})
|
||||
.then((s) => Promises.rejectOnError(s));
|
||||
}
|
||||
: (input) =>
|
||||
CodexSdk.marketplace()
|
||||
.createAvailability(input)
|
||||
@ -45,7 +51,9 @@ export function useAvailabilityMutation(
|
||||
return fn({
|
||||
...input,
|
||||
duration: Times.value(durationUnit) * duration,
|
||||
totalSize: Math.trunc(totalSize * AvailabilityUtils.unitValue(totalSizeUnit)),
|
||||
totalSize: Math.trunc(
|
||||
totalSize * AvailabilityUtils.unitValue(totalSizeUnit)
|
||||
),
|
||||
});
|
||||
},
|
||||
onSuccess: (res, body) => {
|
||||
@ -56,7 +64,7 @@ export function useAvailabilityMutation(
|
||||
WebStorage.delete("availability-step");
|
||||
|
||||
if (typeof res === "object" && body.name) {
|
||||
WebStorage.availabilities.add(res.id, body.name)
|
||||
WebStorage.availabilities.add(res.id, body.name);
|
||||
}
|
||||
|
||||
setError(null);
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
import "./FileCell.css";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { CodexDataContent } from "@codex-storage/sdk-js";
|
||||
import { FilesUtils } from "../Files/files.utils";
|
||||
|
||||
type FileMetadata = {
|
||||
mimetype: string | null;
|
||||
@ -37,11 +38,10 @@ export function FileCell({ requestId, purchaseCid, data, onMetadata }: Props) {
|
||||
|
||||
const content = data.find((m) => m.cid === cid);
|
||||
if (content) {
|
||||
const {
|
||||
filename = "-",
|
||||
mimetype = "application/octet-stream",
|
||||
uploadedAt = 0,
|
||||
} = content.manifest;
|
||||
const { filename = "-", mimetype = "application/octet-stream" } =
|
||||
content.manifest;
|
||||
const uploadedAt = FilesUtils.getUploadedAt(content.cid);
|
||||
|
||||
setMetadata({
|
||||
filename,
|
||||
mimetype,
|
||||
|
||||
@ -117,7 +117,7 @@ export function FileDetails({ onClose, details }: Props) {
|
||||
<p>Date:</p>
|
||||
<p>
|
||||
{FilesUtils.formatDate(
|
||||
details.manifest.uploadedAt
|
||||
FilesUtils.getUploadedAt(details.cid)
|
||||
).toString()}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
@ -166,7 +166,7 @@ export function Files({ limit }: Props) {
|
||||
<FileCell content={c}></FileCell>,
|
||||
<Cell>{Bytes.pretty(c.manifest.datasetSize)}</Cell>,
|
||||
<Cell>
|
||||
{FilesUtils.formatDate(c.manifest.uploadedAt).toString()}
|
||||
{FilesUtils.formatDate(FilesUtils.getUploadedAt(c.cid)).toString()}
|
||||
</Cell>,
|
||||
<FileActions
|
||||
content={c}
|
||||
|
||||
@ -20,27 +20,31 @@ export const FilesUtils = {
|
||||
return !!type && type.startsWith("video");
|
||||
},
|
||||
isArchive(mimetype: string | null) {
|
||||
return !!mimetype && archiveMimetypes.includes(mimetype)
|
||||
return !!mimetype && archiveMimetypes.includes(mimetype);
|
||||
},
|
||||
type(mimetype: string | null) {
|
||||
if (FilesUtils.isArchive(mimetype)) {
|
||||
return "archive"
|
||||
return "archive";
|
||||
}
|
||||
|
||||
if (FilesUtils.isVideo(mimetype)) {
|
||||
return "video"
|
||||
return "video";
|
||||
}
|
||||
|
||||
if (FilesUtils.isImage(mimetype)) {
|
||||
return "image"
|
||||
return "image";
|
||||
}
|
||||
|
||||
return "document"
|
||||
return "document";
|
||||
},
|
||||
sortByName: (state: TabSortState) =>
|
||||
(a: CodexDataContent, b: CodexDataContent) => {
|
||||
const { manifest: { filename: afilename } } = a
|
||||
const { manifest: { filename: bfilename } } = b
|
||||
sortByName:
|
||||
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) => {
|
||||
const {
|
||||
manifest: { filename: afilename },
|
||||
} = a;
|
||||
const {
|
||||
manifest: { filename: bfilename },
|
||||
} = b;
|
||||
|
||||
return state === "desc"
|
||||
? (bfilename || "")
|
||||
@ -48,47 +52,63 @@ export const FilesUtils = {
|
||||
.localeCompare((afilename || "").toLocaleLowerCase())
|
||||
: (afilename || "")
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare((bfilename || "").toLocaleLowerCase())
|
||||
.localeCompare((bfilename || "").toLocaleLowerCase());
|
||||
},
|
||||
sortBySize: (state: TabSortState) =>
|
||||
(a: CodexDataContent, b: CodexDataContent) => state === "desc"
|
||||
sortBySize:
|
||||
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) =>
|
||||
state === "desc"
|
||||
? b.manifest.datasetSize - a.manifest.datasetSize
|
||||
: a.manifest.datasetSize - b.manifest.datasetSize
|
||||
,
|
||||
sortByDate: (state: TabSortState) =>
|
||||
(a: CodexDataContent, b: CodexDataContent) => state === "desc"
|
||||
? new Date(b.manifest.uploadedAt).getTime() -
|
||||
new Date(a.manifest.uploadedAt).getTime()
|
||||
: new Date(a.manifest.uploadedAt).getTime() -
|
||||
new Date(b.manifest.uploadedAt).getTime()
|
||||
,
|
||||
removeCidFromFolder(folders: [string, string[]][], folder: string, cid: string): [string, string[]][] {
|
||||
return folders.map(([name, files]) =>
|
||||
name === folder
|
||||
? [name, files.filter((id) => id !== cid)]
|
||||
: [name, files]
|
||||
)
|
||||
: a.manifest.datasetSize - b.manifest.datasetSize,
|
||||
sortByDate:
|
||||
(state: TabSortState) => (a: CodexDataContent, b: CodexDataContent) => {
|
||||
const aUploadedAt = FilesUtils.getUploadedAt(a.cid);
|
||||
const bUploadedAt = FilesUtils.getUploadedAt(b.cid);
|
||||
|
||||
return state === "desc"
|
||||
? new Date(bUploadedAt).getTime() - new Date(aUploadedAt).getTime()
|
||||
: new Date(aUploadedAt).getTime() - new Date(bUploadedAt).getTime();
|
||||
},
|
||||
addCidToFolder(folders: [string, string[]][], folder: string, cid: string): [string, string[]][] {
|
||||
|
||||
removeCidFromFolder(
|
||||
folders: [string, string[]][],
|
||||
folder: string,
|
||||
cid: string
|
||||
): [string, string[]][] {
|
||||
return folders.map(([name, files]) =>
|
||||
name === folder ? [name, files.filter((id) => id !== cid)] : [name, files]
|
||||
);
|
||||
},
|
||||
addCidToFolder(
|
||||
folders: [string, string[]][],
|
||||
folder: string,
|
||||
cid: string
|
||||
): [string, string[]][] {
|
||||
return folders.map(([name, files]) =>
|
||||
name === folder ? [name, [...files, cid]] : [name, files]
|
||||
)
|
||||
);
|
||||
},
|
||||
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.includes(filter)
|
||||
? filters.filter((f) => f !== filter)
|
||||
: [...filters, filter],
|
||||
listInFolder(files: CodexDataContent[], folders: [string, string[]][], index: number) {
|
||||
listInFolder(
|
||||
files: CodexDataContent[],
|
||||
folders: [string, string[]][],
|
||||
index: number
|
||||
) {
|
||||
return index === 0
|
||||
? files
|
||||
: files.filter((file) => folders[index - 1][1].includes(file.cid));
|
||||
},
|
||||
applyFilters(files: CodexDataContent[], filters: string[]) {
|
||||
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) {
|
||||
if (!date) {
|
||||
@ -99,7 +119,14 @@ export const FilesUtils = {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).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 = {
|
||||
|
||||
@ -8,7 +8,6 @@ import { classnames } from "../../utils/classnames";
|
||||
import "./HealthChecks.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { HealthCheckUtils } from "./health-check.utils";
|
||||
import { PortForwardingUtil } from "../../hooks/port-forwarding.util";
|
||||
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
||||
import ErrorCircleIcon from "../../assets/icons/error-circle.svg?react";
|
||||
import DeviceIcon from "../../assets/icons/device.svg?react";
|
||||
@ -21,7 +20,6 @@ type Props = {
|
||||
};
|
||||
|
||||
const throwOnError = false;
|
||||
const defaultPort = 8070;
|
||||
|
||||
export function HealthChecks({ online, onStepValid }: Props) {
|
||||
const codex = useDebug(throwOnError);
|
||||
@ -87,15 +85,6 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
.then(() => codex.refetch());
|
||||
};
|
||||
|
||||
let forwardingPortValue = defaultPort;
|
||||
|
||||
if (codex.isSuccess && codex.data) {
|
||||
const port = PortForwardingUtil.getTcpPort(codex.data);
|
||||
if (!port.error) {
|
||||
forwardingPortValue = port.data;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="health-checks">
|
||||
<div
|
||||
@ -140,10 +129,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<li>
|
||||
Port forwarding should be {forwardingPortValue} for TCP and 8090 by
|
||||
default for UDP.
|
||||
</li>
|
||||
<li>Ensure that port forwarding is enabled for your settings.</li>
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
|
||||
@ -11,6 +11,17 @@ export function ManifestFetch() {
|
||||
|
||||
const { refetch } = useQuery({
|
||||
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()
|
||||
.fetchManifest(cid)
|
||||
.then((s) => {
|
||||
|
||||
@ -80,7 +80,7 @@ export function PurchasesTable() {
|
||||
["request id"],
|
||||
["duration", onSortByDuration],
|
||||
["slots"],
|
||||
["reward", onSortByReward],
|
||||
["price per byte", onSortByReward],
|
||||
["proof probability"],
|
||||
["state", onSortByState],
|
||||
] satisfies [string, ((state: TabSortState) => void)?][];
|
||||
@ -90,9 +90,8 @@ export function PurchasesTable() {
|
||||
const rows = sorted.map((p, index) => {
|
||||
const r = p.request;
|
||||
const ask = p.request.ask;
|
||||
const duration = parseInt(p.request.ask.duration, 10);
|
||||
const pf = parseInt(p.request.ask.proofProbability, 10);
|
||||
|
||||
const duration = parseInt(ask.duration, 10);
|
||||
const pf = parseInt(ask.proofProbability, 10);
|
||||
return (
|
||||
<Row
|
||||
cells={[
|
||||
@ -105,7 +104,7 @@ export function PurchasesTable() {
|
||||
<TruncateCell value={r.id} />,
|
||||
<Cell>{Times.pretty(duration)}</Cell>,
|
||||
<Cell>{ask.slots.toString()}</Cell>,
|
||||
<Cell>{ask.reward + " CDX"}</Cell>,
|
||||
<Cell>{p.request.ask.pricePerBytePerSecond + " CDX"}</Cell>,
|
||||
<Cell>{pf.toString()}</Cell>,
|
||||
<CustomStateCellRender state={p.state} message={p.error} />,
|
||||
]}></Row>
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
import { TabSortState } from "@codex-storage/marketplace-ui-components"
|
||||
import { CodexPurchase } from "@codex-storage/sdk-js"
|
||||
import { TabSortState } from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexPurchase, CodexStorageRequest } from "@codex-storage/sdk-js";
|
||||
|
||||
export const PurchaseUtils = {
|
||||
sortById: (state: TabSortState) =>
|
||||
(a: CodexPurchase, b: CodexPurchase) => {
|
||||
|
||||
sortById: (state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) => {
|
||||
return state === "desc"
|
||||
? b.requestId
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(a.requestId.toLocaleLowerCase())
|
||||
: a.requestId
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(b.requestId.toLocaleLowerCase())
|
||||
.localeCompare(b.requestId.toLocaleLowerCase());
|
||||
},
|
||||
sortByState: (state: TabSortState) =>
|
||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
||||
? b.state
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(a.state.toLocaleLowerCase())
|
||||
: a.state
|
||||
.toLocaleLowerCase()
|
||||
.localeCompare(b.state.toLocaleLowerCase())
|
||||
,
|
||||
sortByDuration: (state: TabSortState) =>
|
||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
||||
sortByState: (state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) =>
|
||||
state === "desc"
|
||||
? b.state.toLocaleLowerCase().localeCompare(a.state.toLocaleLowerCase())
|
||||
: a.state.toLocaleLowerCase().localeCompare(b.state.toLocaleLowerCase()),
|
||||
sortByDuration:
|
||||
(state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) =>
|
||||
state === "desc"
|
||||
? Number(b.request.ask.duration) - Number(a.request.ask.duration)
|
||||
: Number(a.request.ask.duration) - Number(b.request.ask.duration)
|
||||
,
|
||||
sortByReward: (state: TabSortState) =>
|
||||
(a: CodexPurchase, b: CodexPurchase) => state === "desc"
|
||||
? Number(b.request.ask.reward) - Number(a.request.ask.reward)
|
||||
: Number(a.request.ask.reward) - Number(b.request.ask.reward)
|
||||
,
|
||||
sortByUploadedAt: (state: TabSortState, table: Record<string, number>) =>
|
||||
: Number(a.request.ask.duration) - Number(b.request.ask.duration),
|
||||
sortByReward:
|
||||
(state: TabSortState) => (a: CodexPurchase, b: CodexPurchase) => {
|
||||
const aPrice = parseInt(a.request.ask.pricePerBytePerSecond, 10);
|
||||
const bPrice = parseInt(b.request.ask.pricePerBytePerSecond, 10);
|
||||
return state === "desc" ? bPrice - aPrice : aPrice - bPrice;
|
||||
},
|
||||
sortByUploadedAt:
|
||||
(state: TabSortState, table: Record<string, number>) =>
|
||||
(a: CodexPurchase, b: CodexPurchase) => {
|
||||
return state === "desc"
|
||||
? (table[b.requestId] || 0) - (table[a.requestId] || 0)
|
||||
: (table[a.requestId] || 0) - (table[b.requestId] || 0)
|
||||
}
|
||||
,
|
||||
}
|
||||
: (table[a.requestId] || 0) - (table[b.requestId] || 0);
|
||||
},
|
||||
calculatePrice(request: CodexStorageRequest) {
|
||||
return (
|
||||
parseInt(request.ask.slotSize, 10) *
|
||||
parseInt(request.ask.pricePerBytePerSecond, 10)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -83,12 +83,26 @@ export function StorageRequestCreate() {
|
||||
WebStorage.set("storage-request-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
const { availability, availabilityUnit, expiration, ...rest } =
|
||||
storageRequest;
|
||||
const {
|
||||
availability,
|
||||
availabilityUnit,
|
||||
expiration,
|
||||
reward,
|
||||
collateral,
|
||||
proofProbability,
|
||||
cid,
|
||||
nodes,
|
||||
tolerance,
|
||||
} = storageRequest;
|
||||
mutateAsync({
|
||||
...rest,
|
||||
duration: Math.trunc(availability * Times.value(availabilityUnit)),
|
||||
pricePerBytePerSecond: reward,
|
||||
proofProbability,
|
||||
collateralPerByte: collateral,
|
||||
expiry: expiration * 60,
|
||||
cid,
|
||||
nodes,
|
||||
tolerance,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
|
||||
@ -12,6 +12,7 @@ import { StorageRequestComponentProps } from "./types";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import ChooseCidIcon from "../../assets/icons/choose-cid.svg?react";
|
||||
import UploadIcon from "../../assets/icons/upload.svg?react";
|
||||
import { FilesUtils } from "../Files/files.utils";
|
||||
|
||||
export function StorageRequestFileChooser({
|
||||
storageRequest,
|
||||
@ -39,6 +40,8 @@ export function StorageRequestFileChooser({
|
||||
};
|
||||
|
||||
const onSuccess = (data: string) => {
|
||||
FilesUtils.setUploadedAt(data, Date.now() / 1000);
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||
|
||||
onStorageRequestChange({ cid: data });
|
||||
|
||||
@ -282,7 +282,7 @@ export function StorageRequestReview({
|
||||
onChange={onAvailabilityChange}
|
||||
onValidation={isInvalidAvailability}></Commitment>
|
||||
<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"
|
||||
unit={"Collateral"}
|
||||
value={storageRequest.collateral.toString()}
|
||||
@ -290,13 +290,13 @@ export function StorageRequestReview({
|
||||
onValidation={isInvalidNumber}
|
||||
title="Penality tokens"></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"
|
||||
unit={"Reward"}
|
||||
value={storageRequest.reward.toString()}
|
||||
onChange={onRewardChange}
|
||||
onValidation={isInvalidNumber}
|
||||
title="Reward tokens for hosts"></CardNumbers>
|
||||
title="Price per byte"></CardNumbers>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
|
||||
@ -2,11 +2,13 @@ import { Upload } from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import UploadIcon from "../../assets/icons/upload.svg?react";
|
||||
import { FilesUtils } from "../Files/files.utils";
|
||||
|
||||
export function UploadCard() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const onSuccess = () => {
|
||||
const onSuccess = (cid: string) => {
|
||||
FilesUtils.setUploadedAt(cid, Date.now() / 1000);
|
||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { CodexSdk } from "../sdk/codex";
|
||||
import { CodexDataResponse } from "@codex-storage/sdk-js";
|
||||
import { Promises } from "../utils/promises";
|
||||
import { FilesUtils } from "../components/Files/files.utils";
|
||||
|
||||
export function useData() {
|
||||
const { data = { content: [] } satisfies CodexDataResponse } =
|
||||
@ -29,5 +30,8 @@ export function useData() {
|
||||
throwOnError: true,
|
||||
});
|
||||
|
||||
return data.content;
|
||||
return data.content.map((c) => ({
|
||||
...c,
|
||||
uploadedAt: FilesUtils.getUploadedAt(c.cid),
|
||||
}));
|
||||
}
|
||||
|
||||
25
src/main.tsx
25
src/main.tsx
@ -21,6 +21,7 @@ import { SettingsRoute } from "./routes/dashboard/settings.tsx";
|
||||
import { HelpRoute } from "./routes/dashboard/help.tsx";
|
||||
import { DisclaimerRoute } from "./routes/dashboard/disclaimer.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) {
|
||||
Sentry.init({
|
||||
@ -117,7 +118,29 @@ const queryClient = new QueryClient();
|
||||
const rootElement = document.getElementById("root")!;
|
||||
|
||||
if (rootElement) {
|
||||
CodexSdk.load().then(() => {
|
||||
CodexSdk.load()
|
||||
.then(() => {
|
||||
const queryString = window.location.search;
|
||||
if (queryString) {
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const param = urlParams.get("api-port");
|
||||
if (param) {
|
||||
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}>
|
||||
|
||||
22
src/proxy.ts
22
src/proxy.ts
@ -7,10 +7,7 @@ import {
|
||||
import { CodexSdk as Sdk } from "./sdk/codex";
|
||||
import { WebStorage } from "./utils/web-storage";
|
||||
|
||||
class CodexDataMock extends CodexData {
|
||||
|
||||
}
|
||||
|
||||
class CodexDataMock extends CodexData {}
|
||||
|
||||
class CodexMarketplaceMock extends CodexMarketplace {
|
||||
// override async purchases(): Promise<SafeValue<CodexPurchase[]>> {
|
||||
@ -36,21 +33,24 @@ class CodexMarketplaceMock extends CodexMarketplace {
|
||||
* should still be maintained, but the metadata should be retrieved
|
||||
* using a REST API call.
|
||||
*/
|
||||
override async createStorageRequest(input: CodexCreateStorageRequestInput): Promise<SafeValue<string>> {
|
||||
const res = await super.createStorageRequest(input)
|
||||
override async createStorageRequest(
|
||||
input: CodexCreateStorageRequestInput
|
||||
): Promise<SafeValue<string>> {
|
||||
console.info(input);
|
||||
const res = await super.createStorageRequest(input);
|
||||
|
||||
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())
|
||||
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// override createStorageRequest(
|
||||
// input: CodexCreateStorageRequestInput
|
||||
// ): Promise<SafeValue<string>> {
|
||||
@ -139,5 +139,3 @@ export const CodexSdk = {
|
||||
marketplace: () => new CodexMarketplaceMock(CodexSdk.url()),
|
||||
data: () => new CodexDataMock(CodexSdk.url()),
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@ export type TimesUnit =
|
||||
| "seconds";
|
||||
|
||||
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)}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const Times = {
|
||||
toSeconds(value: number, unit: TimesUnit) {
|
||||
@ -73,24 +73,23 @@ export const Times = {
|
||||
|
||||
seconds /= 30;
|
||||
if (value >= seconds) {
|
||||
return "days"
|
||||
return "days";
|
||||
}
|
||||
|
||||
return "hours"
|
||||
return "hours";
|
||||
},
|
||||
|
||||
value(unit: "hours" | "days" | "months") {
|
||||
switch (unit) {
|
||||
case "months": {
|
||||
return 30 * 24 * 60 * 60
|
||||
return 30 * 24 * 60 * 60;
|
||||
}
|
||||
case "days": {
|
||||
return 24 * 60 * 60
|
||||
return 24 * 60 * 60;
|
||||
}
|
||||
default: {
|
||||
return 60 * 60
|
||||
return 60 * 60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { createStore, del, entries, get, set } from "idb-keyval";
|
||||
|
||||
|
||||
export const WebStorage = {
|
||||
set(key: string, value: unknown) {
|
||||
return set(key, value);
|
||||
@ -16,27 +15,27 @@ export const WebStorage = {
|
||||
|
||||
onBoarding: {
|
||||
getStep() {
|
||||
return parseInt(localStorage.getItem("onboarding-step") || "0", 10)
|
||||
return parseInt(localStorage.getItem("onboarding-step") || "0", 10);
|
||||
},
|
||||
|
||||
setStep(step: number) {
|
||||
localStorage.setItem("onboarding-step", step.toString())
|
||||
localStorage.setItem("onboarding-step", step.toString());
|
||||
},
|
||||
|
||||
setDisplayName(displayName: string) {
|
||||
localStorage.setItem("display-name", displayName)
|
||||
localStorage.setItem("display-name", displayName);
|
||||
},
|
||||
|
||||
getDisplayName() {
|
||||
return localStorage.getItem("display-name") || ""
|
||||
return localStorage.getItem("display-name") || "";
|
||||
},
|
||||
|
||||
setEmoji(emoji: string) {
|
||||
localStorage.setItem("emoji", emoji)
|
||||
localStorage.setItem("emoji", emoji);
|
||||
},
|
||||
|
||||
getEmoji() {
|
||||
return localStorage.getItem("emoji") || "🤖"
|
||||
return localStorage.getItem("emoji") || "🤖";
|
||||
},
|
||||
},
|
||||
|
||||
@ -48,33 +47,35 @@ export const WebStorage = {
|
||||
},
|
||||
|
||||
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) {
|
||||
return [["Favorites", []]]
|
||||
return [["Favorites", []]];
|
||||
}
|
||||
|
||||
if (items[0][0] !== "Favorites") {
|
||||
return [["Favorites", []], ...items]
|
||||
return [["Favorites", []], ...items];
|
||||
}
|
||||
|
||||
|
||||
return items
|
||||
return items;
|
||||
},
|
||||
delete(key: string) {
|
||||
return del(key, this.store);
|
||||
},
|
||||
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) {
|
||||
const files = await get<string[]>(folder, this.store) || []
|
||||
|
||||
return set(folder, files.filter(item => item !== cid), this.store)
|
||||
const files = (await get<string[]>(folder, this.store)) || [];
|
||||
|
||||
return set(
|
||||
folder,
|
||||
files.filter((item) => item !== cid),
|
||||
this.store
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@ -97,7 +98,6 @@ export const WebStorage = {
|
||||
purchases: {
|
||||
store: createStore("purchases", "purchases"),
|
||||
|
||||
|
||||
async get(key: string) {
|
||||
return get<string>(key, this.store);
|
||||
},
|
||||
@ -120,6 +120,6 @@ export const WebStorage = {
|
||||
async set(key: string, date: string) {
|
||||
return set(key, date, this.store);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user