Browse Source

added helpers texts & template setting. fixed some bugs

master
Aleksandr Bautin 11 months ago
parent
commit
51730dc347
No known key found for this signature in database
GPG Key ID: 9B3364A12DFE9211
  1. 3
      src/api.ts
  2. 209
      src/assets/main.css
  3. 52
      src/components/DomainBar.vue
  4. 44
      src/components/DomainTable.vue
  5. 109
      src/components/SiteSettings.vue
  6. 6
      src/components/TemplatesList.vue
  7. 17
      src/components/Tooltip.vue
  8. 15
      src/components/ZonePricing.vue
  9. 87
      src/components/ZoneTable.vue
  10. 276
      src/result.ts
  11. 2
      src/router/index.ts
  12. 69
      src/views/Explore.vue
  13. 26
      src/views/Find.vue

3
src/api.ts

@ -12,16 +12,19 @@ export class Api {
public readonly tonscan_url: string; public readonly tonscan_url: string;
public agorata_adnl: string = public agorata_adnl: string =
"ed4f2afebb5e49dda9684a474c5771141be1f7d85a2fa39f1823844dd476c52d"; "ed4f2afebb5e49dda9684a474c5771141be1f7d85a2fa39f1823844dd476c52d";
public readonly tonviewer_url: string;
constructor() { constructor() {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
this.api_url = "http://localhost:5170/"; this.api_url = "http://localhost:5170/";
this.ton_api_url = "https://testnet.tonapi.io/v2/"; this.ton_api_url = "https://testnet.tonapi.io/v2/";
this.tonscan_url = "https://testnet.tonscan.org/"; this.tonscan_url = "https://testnet.tonscan.org/";
this.tonviewer_url = "https://testnet.tonviewer.com/";
} else { } else {
this.api_url = "https://api.agorata.io/"; this.api_url = "https://api.agorata.io/";
this.ton_api_url = "https://tonapi.io/v2/"; this.ton_api_url = "https://tonapi.io/v2/";
this.tonscan_url = "https://tonscan.org/"; this.tonscan_url = "https://tonscan.org/";
this.tonviewer_url = "https://testnet.tonviewer.com/";
} }
} }
} }

209
src/assets/main.css

@ -1,197 +1,196 @@
@import './base.css'; @import "./base.css";
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
#app { #app {
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
min-width: 100vw; min-width: 100vw;
min-height: 100vh; min-height: 100vh;
} }
a, a,
.green { .green {
text-decoration: none; text-decoration: none;
color: hsl(201, 100%, 37%); color: hsl(201, 100%, 37%);
transition: 0.4s; transition: 0.4s;
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
body { body {
display: flex; display: flex;
place-items: center; place-items: center;
} }
#app { #app {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
padding: 0; padding: 0;
} }
} }
.b { .b {
border: 2px solid transparent; border: 2px solid transparent;
border-radius: 5px; border-radius: 5px;
padding: 16px 26px; padding: 16px 26px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
display: inline-block; display: inline-block;
font-size: 1.55rem; font-size: 1.55rem;
margin: 4px 2px; margin: 4px 2px;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.b { .b {
font-size: 1.4rem; font-size: 1.4rem;
} }
} }
.b.white { .b.white {
background-color: white; background-color: white;
color: #282e46; color: #282e46;
} }
.b.white:hover { .b.white:hover {
background-color: #282e46; background-color: #282e46;
color: white; color: white;
border: 2px solid white; border: 2px solid white;
} }
.b.blue { .b.blue {
background-color: #0088cc; background-color: #0088cc;
color: white; color: white;
border-radius: 1rem; border-radius: 1rem;
} }
.b > img:first-child { .b > img:first-child {
max-height: 1.4rem; max-height: 1.4rem;
margin-right: 0.7rem; margin-right: 0.7rem;
} }
.b > img:not(:first-child) { .b > img:not(:first-child) {
max-height: 1.4rem; max-height: 1.4rem;
margin-left: 2rem; margin-left: 2rem;
} }
.wide.b { .wide.b {
min-width: 16rem; min-width: 16rem;
margin: 0.5rem; margin: 0.5rem;
} }
/* A #edeef1 box with rounded corners with black content color and content centered vertically and horizontally */ /* A #edeef1 box with rounded corners with black content color and content centered vertically and horizontally */
.rbox { .rbox {
background-color: #edeef1; background-color: #edeef1;
border-radius: 2rem; border-radius: 2rem;
padding: 2.5rem 32px; padding: 2.5rem 32px;
text-align: center; text-align: center;
display: flex; display: flex;
align-items: center; align-items: center;
place-content: center; place-content: center;
justify-content: center; justify-content: center;
font-size: 1.6rem; font-size: 1.6rem;
font-weight: bold; font-weight: bold;
color: #282e46; color: #282e46;
min-width: 16rem; min-width: 16rem;
margin: 0.5rem 3rem; margin: 0.5rem 3rem;
} }
/* small margin on mobile */ /* small margin on mobile */
@media (max-width: 800px) { @media (max-width: 800px) {
.rbox { .rbox {
margin: 0.5rem 0.5rem; margin: 0.5rem 0.5rem;
} }
} }
.rbox > p:not(:first-child) { .rbox > p:not(:first-child) {
margin-top: 1rem; margin-top: 1rem;
} }
.center { .center {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
place-items: center; place-items: center;
flex-direction: column; flex-direction: column;
} }
.b.darkish { .b.darkish {
background-color: #4e5a88; background-color: #4e5a88;
color: white; color: white;
border-radius: 0.5rem; border-radius: 0.5rem;
font-size: 1rem; font-size: 1rem;
} }
.mono { .mono {
font-family: 'Inconsolata', monospace; font-family: "Inconsolata", monospace;
} }
:root { :root {
--popper-theme-background-color: #fff; --popper-theme-background-color: #fff;
--popper-theme-background-color-hover: #fff; --popper-theme-background-color-hover: #fff;
--popper-theme-text-color: black; --popper-theme-text-color: black;
--popper-theme-border-width: 0px; --popper-theme-border-width: 0px;
--popper-theme-border-style: solid; --popper-theme-border-style: solid;
--popper-theme-border-radius: 6px; --popper-theme-border-radius: 6px;
--popper-theme-padding: 4px; --popper-theme-padding: 4px;
--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, 0.25); --popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, 0.25);
} }
.popper { .popper {
font-size: 0.8rem; font-size: 0.8rem;
font-family: 'Inconsolata', monospace; font-family: "Inconsolata", monospace;
} }
.mobile-scale { .mobile-scale {
scale: 100%; scale: 100%;
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.mobile-scale { .mobile-scale {
scale: 80%; scale: 80%;
} }
} }
.get_b { .get_b {
background-color: #e36464; background-color: #e36464;
color: #363e5e; color: #363e5e;
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.2rem 0.8rem; padding: 0.2rem 0.8rem;
margin-left: 0.9rem; cursor: pointer;
cursor: pointer;
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
.get_b { .get_b {
margin-left: 0.1rem; margin-left: 0.1rem;
padding-left: 0.4rem; padding-left: 0.4rem;
padding-right: 0.4rem; padding-right: 0.4rem;
} }
} }
.flex { .flex {
display: flex; display: flex;
} }
.b.back { .b.back {
font-family: 'Inconsolata', monospace; font-family: "Inconsolata", monospace;
font-size: 1.5rem; font-size: 1.5rem;
padding: 0.8rem; padding: 0.8rem;
} }
.b.back > img { .b.back > img {
max-height: 1rem; max-height: 1rem;
margin-right: 0.4rem; margin-right: 0.4rem;
} }
.material-icons.language { .material-icons.language {
position:relative; position: relative;
display:inline-block; display: inline-block;
} }
.material-icons.language:after { .material-icons.language:after {
content: "language"; content: "language";
} }

52
src/components/DomainBar.vue

@ -1,8 +1,18 @@
<template> <template>
<div :class="{'domain-bar': true, 'center': !has_button}"> <div class="title">Find the domain of interest in the zone:</div>
<div :class="{ 'domain-bar': true, center: !has_button }">
<div style="display: flex" class="prompt-cont"> <div style="display: flex" class="prompt-cont">
<contenteditable :contenteditable="editable" tag="div" class="prompt" :no-nl="true" v-model="val" :no-html="true" <contenteditable
ref="domain_field" @returned="search()" spellcheck="false"></contenteditable> :contenteditable="editable"
tag="div"
class="prompt"
:no-nl="true"
v-model="val"
:no-html="true"
ref="domain_field"
@returned="search()"
spellcheck="false"
></contenteditable>
<div class="post-prompt">{{ zone }}</div> <div class="post-prompt">{{ zone }}</div>
</div> </div>
<div class="search" v-if="has_button" @click="search()">Search</div> <div class="search" v-if="has_button" @click="search()">Search</div>
@ -10,33 +20,33 @@
</template> </template>
<script> <script>
import contenteditable from 'vue-contenteditable'; import contenteditable from "vue-contenteditable";
export default { export default {
name: "DomainBar", name: "DomainBar",
components: {contenteditable}, components: { contenteditable },
props: { props: {
zone: { zone: {
type: String, type: String,
default: ".*" default: ".*",
}, },
has_button: { has_button: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
value: { value: {
type: String, type: String,
default: "example" default: "example",
}, },
editable: { editable: {
type: Boolean, type: Boolean,
default: true default: true,
} },
}, },
data() { data() {
return { return {
val: this.value val: this.value,
} };
}, },
mounted() { mounted() {
this.$refs["domain_field"].$el.focus(); this.$refs["domain_field"].$el.focus();
@ -44,18 +54,18 @@ export default {
methods: { methods: {
search() { search() {
this.$emit("search", this.val); this.$emit("search", this.val);
} },
}, },
watch: { watch: {
value: function (val) { value: function (val) {
val = val.replace('\n', ''); val = val.replace("\n", "");
this.val = val; this.val = val;
}, },
val: function (val) { val: function (val) {
this.$emit("input_d", val); this.$emit("input_d", val);
} },
} },
} };
</script> </script>
<style scoped> <style scoped>
@ -87,9 +97,7 @@ export default {
width: 90vw; width: 90vw;
/* allow multiple rows with 0.5 spacing */ /* allow multiple rows with 0.5 spacing */
flex-wrap: wrap; flex-wrap: wrap;
} }
} }
.prompt { .prompt {
@ -131,6 +139,10 @@ export default {
} }
.post-prompt { .post-prompt {
font-family: 'Inconsolata', monospace; font-family: "Inconsolata", monospace;
}
.title {
margin-bottom: 6px;
} }
</style> </style>

44
src/components/DomainTable.vue

@ -85,20 +85,36 @@ onMounted(async () => {
zonesAddresses.value = zones.map(({ address }) => address?.toLowerCase()); zonesAddresses.value = zones.map(({ address }) => address?.toLowerCase());
if (address.value) { if (address.value) {
const { data } = await axios.get<{ nft_items: CollectionItem[] }>( const [
`${config.ton_api_url}accounts/${address.value}/nfts` resultNfts = { data: { nft_items: [] } },
); resultTonNfts = { data: { nft_items: [] } },
] = await Promise.all([
items.value = data.nft_items axios.get<{ nft_items: CollectionItem[] }>(
.map((item) => ({ `${config.ton_api_url}accounts/${address.value}/nfts`
...item, ),
formattedCollectionAddress: item.collection?.address axios.get<{ nft_items: CollectionItem[] }>(
? convertAddress(item.collection?.address).toLowerCase() `${config.ton_api_url}accounts/${address.value}/nfts`,
: "", {
})) params: {
.filter(({ formattedCollectionAddress }) => collection: "EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz",
zonesAddresses.value.includes(formattedCollectionAddress) },
); }
),
]);
items.value = [
...resultTonNfts.data.nft_items,
...resultNfts.data.nft_items
.map((item) => ({
...item,
formattedCollectionAddress: item.collection?.address
? convertAddress(item.collection?.address).toLowerCase()
: "",
}))
.filter(({ formattedCollectionAddress }) =>
zonesAddresses.value.includes(formattedCollectionAddress)
),
];
} }
}); });
</script> </script>

109
src/components/SiteSettings.vue

@ -1,24 +1,53 @@
<template> <template>
<div class="center"> <div class="center">
<div>Host domain by:</div>
<div class="constr-switcher"> <div class="constr-switcher">
<div <div
@click="constructor_site = true" @click="constructor_site = true"
:class="{ inactive: !constructor_site, 'constr-switch': true }" :class="{ inactive: !constructor_site, 'constr-switch': true }"
> >
By template Selected template
</div> </div>
<div <div
@click="constructor_site = false" @click="constructor_site = false"
:class="{ inactive: constructor_site, 'constr-switch': true }" :class="{ inactive: constructor_site, 'constr-switch': true }"
> >
Host your own Custom server
</div> </div>
</div> </div>
<div class="adnl-label" v-if="!constructor_site">
You can use a custom server with an address in ADNL, TON's networking
layer
<Tooltip position="top" :width="300" class="adnl-tooltip">
<template v-slot:content>
The easiest way to set up an ADNL server is to use
<a href="https://github.com/tonwhales/ton-proxy" target="_blank"
>TON Proxy</a
>
</template>
<svg
width="24px"
height="24px"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 how-to-get"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
/>
</svg>
</Tooltip>
</div>
<div id="site_input" class="rec-field" v-if="!constructor_site"> <div id="site_input" class="rec-field" v-if="!constructor_site">
<div style="display: flex; width: 100%"> <div class="adnl-panel">
<p style="width: 9rem">Site:</p> <p class="label">Site:</p>
<contenteditable <contenteditable
class="record-inp site-record-field" class="record-inp site-record-field adnl-input"
tag="div" tag="div"
:no-hl="true" :no-hl="true"
:no-html="true" :no-html="true"
@ -27,14 +56,13 @@
></contenteditable> ></contenteditable>
<div <div
:class="{ :class="{
'record-submit': true,
get_b: true, get_b: true,
inactive: !siteChanged, inactive: !siteChanged,
signing: signingSite, signing: signingSite,
}" }"
@click="$emit('save')" @click="$emit('save')"
> >
<template v-if="!signingSite">Save</template> <template v-if="!signingSite">Save and host</template>
<template v-else> <template v-else>
<Socket <Socket
secondary-color="#a7aab3" secondary-color="#a7aab3"
@ -50,7 +78,12 @@
<div v-else class="constructor-form"> <div v-else class="constructor-form">
<Switcher <Switcher
:items="templates" :items="templates"
@change="(item) => (activeTemplateName = item.name)" @change="
(item) => {
activeTemplateName = item.name;
selectTemplate(item);
}
"
:active-name="activeTemplateName" :active-name="activeTemplateName"
> >
<template v-slot:suffix> <template v-slot:suffix>
@ -63,8 +96,11 @@
</template> </template>
</Switcher> </Switcher>
<TemplatesList <TemplatesList
@save-constructor="$emit('save-constructor')"
:site-changed="siteChanged"
:templates="templates" :templates="templates"
:active-template-name="activeTemplateName" :active-template-name="activeTemplateName"
:signing-site="signingSite"
/> />
</div> </div>
</div> </div>
@ -78,10 +114,11 @@ import { default_links, SiteConstructorParams } from "../result";
import { link_types, link_icons } from "../result"; import { link_types, link_icons } from "../result";
import TemplatesList from "./TemplatesList.vue"; import TemplatesList from "./TemplatesList.vue";
import Switcher from "./Switcher.vue"; import Switcher from "./Switcher.vue";
import Tooltip from "./Tooltip.vue";
export default { export default {
name: "SiteSettings", name: "SiteSettings",
components: { Socket, contenteditable, TemplatesList, Switcher }, components: { Socket, contenteditable, TemplatesList, Switcher, Tooltip },
props: { props: {
site_rec_init: { site_rec_init: {
default: null, default: null,
@ -98,6 +135,9 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
templateId: {
type: String,
},
}, },
data() { data() {
let site_rec = this.site_rec_init; let site_rec = this.site_rec_init;
@ -141,6 +181,9 @@ export default {
}, },
deep: true, deep: true,
}, },
templateId() {
if (this.templateId) this.activeTemplateName = this.templateId;
},
}, },
computed: { computed: {
site_rec_patched() { site_rec_patched() {
@ -150,6 +193,9 @@ export default {
return this.site_rec; return this.site_rec;
} }
}, },
templateId() {
return this.templateId;
},
link_types() { link_types() {
// return the types from link_types that are not in the constructor_params.contacts // return the types from link_types that are not in the constructor_params.contacts
return link_types.filter( return link_types.filter(
@ -181,6 +227,9 @@ export default {
name: `Template #${i + 1}`, name: `Template #${i + 1}`,
})); }));
}, },
selectTemplate(template) {
this.$emit("select-template", template);
},
}, },
mounted() { mounted() {
this.setTemplates(); this.setTemplates();
@ -191,12 +240,9 @@ export default {
<style scoped> <style scoped>
.record-inp { .record-inp {
padding: 0.3rem; padding: 0.3rem;
margin-left: 0.5rem;
border-radius: 0.3rem; border-radius: 0.3rem;
background-color: #4e5a88; background-color: #4e5a88;
color: white; color: white;
min-width: 50vw;
width: 100%;
} }
.rec-field:not(:last-child) { .rec-field:not(:last-child) {
@ -218,10 +264,6 @@ export default {
font-size: 1.4rem; font-size: 1.4rem;
} }
.get_b {
max-width: 8rem;
}
.constructor-form > div > div:not(:last-child) { .constructor-form > div > div:not(:last-child) {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -243,8 +285,8 @@ export default {
border-radius: 0.7rem; border-radius: 0.7rem;
background-color: #cdcee8; background-color: #cdcee8;
color: #282e46; color: #282e46;
width: 13rem;
cursor: default; cursor: default;
width: 250px;
} }
.constr-switch:not(:last-child) { .constr-switch:not(:last-child) {
@ -299,4 +341,37 @@ export default {
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.how-to-get {
cursor: pointer;
}
.adnl-panel {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
& .label {
display: grid;
justify-content: end;
}
}
.adnl-label {
display: grid;
grid-auto-flow: column;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.adnl-tooltip {
display: grid;
align-content: center;
}
.adnl-input {
width: 826px;
}
</style> </style>

6
src/components/TemplatesList.vue

@ -65,7 +65,7 @@
}" }"
@click="$emit('save-constructor')" @click="$emit('save-constructor')"
> >
<template v-if="!signingSite">Save</template> <template v-if="!signingSite">Save and host</template>
<template v-else> <template v-else>
<Socket <Socket
secondary-color="#a7aab3" secondary-color="#a7aab3"
@ -219,10 +219,6 @@ export default {
font-size: 1.4rem; font-size: 1.4rem;
} }
.get_b {
max-width: 8rem;
}
.constructor-form > div > div > div:not(:last-child) { .constructor-form > div > div > div:not(:last-child) {
margin-bottom: 1rem; margin-bottom: 1rem;
} }

17
src/components/Tooltip.vue

@ -5,7 +5,12 @@
class="tooltip-container" class="tooltip-container"
> >
<slot /> <slot />
<div v-if="showTooltip" class="tooltip"> <div
v-if="showTooltip"
:class="{ tooltip: true, top: position === 'top' }"
:style="width ? `width: ${width}px` : ''"
>
<slot name="content" />
{{ text }} {{ text }}
</div> </div>
</div> </div>
@ -21,6 +26,12 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
position: {
type: String,
},
width: {
type: Number,
},
}, },
setup(props) { setup(props) {
const showTooltip = ref(false); const showTooltip = ref(false);
@ -59,5 +70,9 @@ export default {
color: #fff; color: #fff;
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.25rem; border-radius: 0.25rem;
&.top {
transform: translate(-50%, calc(-100% - 24px));
}
} }
</style> </style>

15
src/components/ZonePricing.vue

@ -46,21 +46,22 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{{ "*".repeat(zone.length_1) + "." + zone.zone }}</td> <td>{{ "*".repeat(zone.length_2) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()"> <td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_1 }}</TonButton> <TonButton>{{ zone.price_auction_2 }}</TonButton>
</td> </td>
<td v-if="zone.canBuy()"> <td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_1 }}</TonButton> <TonButton>{{ zone.price_buy_2 }}</TonButton>
<small style="display: block">to</small>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{{ "*".repeat(zone.length_2) + "." + zone.zone }}</td> <td>{{ "*".repeat(zone.length_1) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()"> <td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_2 }}</TonButton> <TonButton>{{ zone.price_auction_1 }}</TonButton>
</td> </td>
<td v-if="zone.canBuy()"> <td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_2 }}</TonButton> <TonButton>{{ zone.price_buy_1 }}</TonButton>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -122,6 +123,6 @@ tr > th {
} }
.get_b { .get_b {
margin-top: 2.1rem; margin-top: 3.1rem;
} }
</style> </style>

87
src/components/ZoneTable.vue

@ -1,46 +1,77 @@
<template> <template>
<div class="center" v-if="zones === []"> <div class="center" v-if="!zones.length">
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem;"/> <RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem" />
</div> </div>
<div style="overflow-x: auto; max-width: 98vw;" v-else> <div style="overflow-x: auto; max-width: 98vw" v-else>
<table class="table_outer"> <table class="table_outer">
<thead class="table_header"> <thead class="table_header">
<tr> <tr>
<th>Domain</th> <th>Zone</th>
<th>Terms</th> <th>
</tr> Prices
<Tooltip style="display: inline" text="Depends on length">
<svg
style="padding-top: 4px"
width="24px"
height="24px"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 how-to-get"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
/>
</svg>
</Tooltip>
</th>
</tr>
</thead> </thead>
<tbody class="table_content"> <tbody class="table_content">
<tr v-for="zone in zones" :key="zone.zone"> <tr v-for="zone in zones" :key="zone.zone">
<td class="zone"> <td class="zone">
<router-link :to="{name: 'GetZ', params: {zone: zone.zone}}" style="color: white"> <router-link
{{ zone.zone }} :to="{
</router-link> name: 'GetZ',
</td> params: { zone: zone.zone, domain_init: domain },
<td style="display: flex; justify-content: flex-end"> }"
<ZonePricing :zone="zone"/> style="color: white"
</td> >
</tr> {{ zone.zone }}
</router-link>
</td>
<td style="display: flex; justify-content: flex-end">
<ZonePricing :zone="zone" />
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<script> <script>
import {Zone} from "../zone"; import { Zone } from "../zone";
import ZonePricing from "./ZonePricing.vue"; import ZonePricing from "./ZonePricing.vue";
import RotateSquare2 from "./RotateSquare2.vue"; import RotateSquare2 from "./RotateSquare2.vue";
import Tooltip from "./Tooltip.vue";
export default { export default {
name: "ZoneTable", name: "ZoneTable",
components: {ZonePricing, RotateSquare2}, components: { ZonePricing, RotateSquare2, Tooltip },
props: { props: {
zones: { zones: {
type: Array[Zone], type: Array[Zone],
default: [] default: [],
} },
domain: {
type: String,
},
}, },
} };
</script> </script>
<style scoped> <style scoped>
@ -74,7 +105,8 @@ thead > tr {
display: flex; display: flex;
} }
tr > :last-child { /* place it on the right */ tr > :last-child {
/* place it on the right */
justify-content: flex-end; justify-content: flex-end;
text-align: center; text-align: center;
width: 100%; width: 100%;
@ -84,7 +116,8 @@ tr > th:first-child {
text-align: center; text-align: center;
} }
tr > :first-child { /* place it on the left */ tr > :first-child {
/* place it on the left */
text-align: left; text-align: left;
} }
@ -98,4 +131,10 @@ tr:not(:first-child) > td {
padding: 1rem; padding: 1rem;
min-height: 10rem; min-height: 10rem;
} }
.ton-coin-icon {
margin-left: 4px;
width: 15px;
height: 15px;
}
</style> </style>

276
src/result.ts

@ -1,68 +1,96 @@
// import {call_api} from "@/api"; // import {call_api} from "@/api";
import {Zone} from "@/zone"; import { Zone } from "@/zone";
import type {Message} from "@/utils"; import type { Message } from "@/utils";
import type {Collection} from "@/collection"; import type { Collection } from "@/collection";
import {call_api} from "@/api"; import { call_api } from "@/api";
// let ex_collection = () => new Collection("example.ton", "Example collection"); // let ex_collection = () => new Collection("example.ton", "Example collection");
export class Result { export class Result {
domain: string; domain: string;
buy_price?: number; buy_price?: number;
auction_price?: number; auction_price?: number;
owner?: string; owner?: string;
collection_required: Collection | null; collection_required: Collection | null;
condition_fulfilled: boolean | null = null; condition_fulfilled: boolean | null = null;
buy_msg: Message | null = null; buy_msg: Message | null = null;
content_msg: Message | null = null; content_msg: Message | null = null;
nft_info?: any; nft_info?: any;
constructor(domain: string, buy_price?: number, auction_price?: number, owner?: string, collection_required: Collection | null = null, condition_fulfilled: boolean | null = null, buy_msg: Message | null = null, content_msg: Message | null = null, nft_info?: any) { constructor(
this.domain = domain; domain: string,
this.buy_price = buy_price; buy_price?: number,
this.auction_price = auction_price; auction_price?: number,
this.owner = owner; owner?: string,
this.collection_required = collection_required; collection_required: Collection | null = null,
this.condition_fulfilled = condition_fulfilled; condition_fulfilled: boolean | null = null,
this.buy_msg = buy_msg; buy_msg: Message | null = null,
this.content_msg = content_msg; content_msg: Message | null = null,
this.nft_info = nft_info; nft_info?: any
} ) {
this.domain = domain;
static fromBackend(data: any): Result { this.buy_price = buy_price;
let domain = data.domain; this.auction_price = auction_price;
let buy_price = data.buy_price; this.owner = owner;
let auction_price = data.auction_price; this.collection_required = collection_required;
let owner = data.owner; this.condition_fulfilled = condition_fulfilled;
let collection_required = data.collection_required; this.buy_msg = buy_msg;
let condition_fulfilled = data.condition_fulfilled; this.content_msg = content_msg;
let buy_msg = data.buy_msg; this.nft_info = nft_info;
let content_msg = data.content_msg; }
let nft_info = data.nft_info;
static fromBackend(data: any): Result {
return new Result(domain, buy_price, auction_price, owner, collection_required, condition_fulfilled, buy_msg, content_msg, nft_info); let domain = data.domain;
} let buy_price = data.buy_price;
let auction_price = data.auction_price;
getRouteParams(): any { let owner = data.owner;
return { let collection_required = data.collection_required;
domain_init: /* domain up to . */ this.domain.split('.')[0], let condition_fulfilled = data.condition_fulfilled;
domain: /* domain up to . */ this.domain.split('.')[0], let buy_msg = data.buy_msg;
zone: /* domain after . */ this.domain.split('.').slice(1).join('.') let content_msg = data.content_msg;
} let nft_info = data.nft_info;
}
return new Result(
canAuction(): boolean { domain,
return this.auction_price !== undefined && this.auction_price !== null && this.owner === undefined; buy_price,
} auction_price,
owner,
canBuy(): boolean { collection_required,
return this.buy_price !== undefined && this.buy_price !== null && this.owner === undefined; condition_fulfilled,
} buy_msg,
content_msg,
zone(): string { nft_info
return this.domain.split('.').slice(1).join('.'); );
} }
getRouteParams(): any {
return {
domain_init: /* domain up to . */ this.domain.split(".")[0],
domain: /* domain up to . */ this.domain.split(".")[0],
zone: /* domain after . */ this.domain.split(".").slice(1).join("."),
};
}
canAuction(): boolean {
return (
this.auction_price !== undefined &&
this.auction_price !== null &&
this.owner === undefined
);
}
canBuy(): boolean {
return (
this.buy_price !== undefined &&
this.buy_price !== null &&
this.owner === undefined
);
}
zone(): string {
return this.domain.split(".").slice(1).join(".");
}
} }
// const sleep = (milliseconds: number) => { // const sleep = (milliseconds: number) => {
@ -70,8 +98,8 @@ export class Result {
// } // }
export async function get_search_results(query: string) { export async function get_search_results(query: string) {
return Result.fromBackend(await call_api('find/' + query)); return Result.fromBackend(await call_api("find/" + query));
/*await sleep(200); /*await sleep(200);
return [ return [
new Result(query + '.ton', 5, 3), new Result(query + '.ton', 5, 3),
new Result(query + '.ton', 1), new Result(query + '.ton', 1),
@ -81,8 +109,8 @@ export async function get_search_results(query: string) {
} }
export async function get_domain_result(domain: string, _address?: string) { export async function get_domain_result(domain: string, _address?: string) {
return Result.fromBackend(await call_api('get/' + domain)); return Result.fromBackend(await call_api("get/" + domain));
/*await sleep(100); /*await sleep(100);
if (domain === 'test.ton') { if (domain === 'test.ton') {
return new Result(domain); return new Result(domain);
} }
@ -105,13 +133,27 @@ export async function get_domain_result(domain: string, _address?: string) {
} }
export async function get_records(address: string) { export async function get_records(address: string) {
return await call_api('records/' + address); return await call_api("records/" + address);
} }
export async function get_zones() { export async function get_zones() {
let zones_back = await call_api('zones'); let zones_back = await call_api("zones");
return zones_back.map((zone: any) => new Zone(zone.zone, zone.price_buy_1, zone.price_buy_2, zone.collection_required, zone.price_auction_1, zone.price_auction_2, zone.min_length, zone.length_1, zone.length_2, zone.address)); return zones_back.map(
/*await sleep(10); (zone: any) =>
new Zone(
zone.zone,
zone.price_buy_1,
zone.price_buy_2,
zone.collection_required,
zone.price_auction_1,
zone.price_auction_2,
zone.min_length,
zone.length_1,
zone.length_2,
zone.address
)
);
/*await sleep(10);
return [ return [
new Zone("example.ton", 3, 5, ex_collection()), new Zone("example.ton", 3, 5, ex_collection()),
new Zone("agorata.ton", 3, 5), new Zone("agorata.ton", 3, 5),
@ -119,54 +161,78 @@ export async function get_zones() {
} }
export class TonLink { export class TonLink {
address: string; address: string;
sum?: number; sum?: number;
message: string; message: string;
constructor(address: string, message: string, sum?: number) { constructor(address: string, message: string, sum?: number) {
this.address = address; this.address = address;
this.sum = sum; this.sum = sum;
this.message = message; this.message = message;
} }
getLink(): string { getLink(): string {
// todo: use tonapi to run dnsresolve on the address (or move the task to the backend) - domains don't work well in links // todo: use tonapi to run dnsresolve on the address (or move the task to the backend) - domains don't work well in links
let link = `ton://transfer/${this.address}?message=${this.message}`; let link = `ton://transfer/${this.address}?message=${this.message}`;
if (this.sum !== undefined) { if (this.sum !== undefined) {
link += `&amount=${this.sum}`; link += `&amount=${this.sum}`;
}
return link;
} }
return link;
}
} }
// Get the link for buying a domain // Get the link for buying a domain
export function get_ton_link(res: Result) { export function get_ton_link(res: Result) {
return new TonLink(res.zone(), 'b/' + res.domain, res.buy_price!); return new TonLink(res.zone(), "b/" + res.domain, res.buy_price!);
} }
export let link_types = ['telegram', /*'website',*/ 'getgems', 'email']; export let link_types = ["telegram", /*'website',*/ "getgems", "email"];
export let link_icons = {'telegram': 'fab fa-telegram', 'website': 'material-icons language', 'getgems': 'fas fa-gem', 'email': 'fas fa-envelope'}; export let link_icons = {
export let default_links = {'telegram': 'https://t.me/', 'website': 'https://', 'getgems': 'https://getgems.org/', 'email': 'example@example.org'}; telegram: "fab fa-telegram",
website: "material-icons language",
getgems: "fas fa-gem",
email: "fas fa-envelope",
};
export let default_links = {
telegram: "https://t.me/",
website: "https://",
getgems: "https://getgems.org/",
email: "example@example.org",
};
export class SiteConstructorParams { export class SiteConstructorParams {
domain: string; domain: string;
title: string; title: string;
description: string; description: string;
contacts: Map<string, string> = new Map<string, string>(); contacts: Map<string, string> = new Map<string, string>();
template_id: string;
constructor(domain: string, title: string = '', description: string = '', contacts: Map<string, string> = new Map<string, string>()) {
this.domain = domain; constructor(
this.title = title; domain: string,
this.description = description; title: string = "",
this.contacts = contacts; description: string = "",
} contacts: Map<string, string> = new Map<string, string>(),
template_id: string = ""
copy(): SiteConstructorParams { ) {
return new SiteConstructorParams(this.domain, this.title); this.domain = domain;
} this.title = title;
this.description = description;
this.contacts = contacts;
this.template_id = template_id;
}
copy(): SiteConstructorParams {
return new SiteConstructorParams(this.domain, this.title);
}
} }
export async function get_constr_params(domain: string) { export async function get_constr_params(domain: string) {
let res = await call_api('get-site-data/' + domain); let res = await call_api("get-site-data/" + domain);
return new SiteConstructorParams(res.domain, res.title, res.description, res.contacts); return new SiteConstructorParams(
res.domain,
res.title,
res.description,
res.contacts,
res.template_id
);
} }

2
src/router/index.ts

@ -38,7 +38,7 @@ const router = createRouter({
component: () => import("../views/TonDns.vue"), component: () => import("../views/TonDns.vue"),
}, },
{ {
path: "/get/:zone", path: "/get/:zone/:domain_init",
name: "GetZ", name: "GetZ",
component: () => import("../views/Get.vue"), component: () => import("../views/Get.vue"),
props: true, props: true,

69
src/views/Explore.vue

@ -42,9 +42,13 @@
</router-link> </router-link>
</div> </div>
<br /> <br />
<div>
You can associate your domain with your wallet<br />to receive
incoming transaction at the domain address
</div>
<div id="wallet_input" class="rec-field"> <div id="wallet_input" class="rec-field">
<div style="display: flex; width: 100%"> <div class="wallet-panel">
<p style="width: 9rem">Wallet:</p> <p class="label">Wallet:</p>
<contenteditable <contenteditable
class="record-inp wallet-record-field" class="record-inp wallet-record-field"
tag="div" tag="div"
@ -53,6 +57,15 @@
spellcheck="false" spellcheck="false"
v-model="wallet_rec" v-model="wallet_rec"
></contenteditable> ></contenteditable>
<div
:class="`use-my-wallet ${
wallet_rec === $store.getters.address ? 'disabled' : ''
}`"
:disabled="wallet_rec === $store.getters.address"
@click="useMyWallet"
>
Use my wallet
</div>
<div <div
:class="{ :class="{
'record-submit': true, 'record-submit': true,
@ -79,8 +92,10 @@
ref="site_settings" ref="site_settings"
@save="saveSite()" @save="saveSite()"
@save-constructor="saveSiteConstr()" @save-constructor="saveSiteConstr()"
@select-template="selectTemplate"
@change="site_rec = $event" @change="site_rec = $event"
@change-constructor="constructor_params = $event" @change-constructor="constructor_params = $event"
:template-id="constructor_params.template_id"
:site-changed="siteChanged" :site-changed="siteChanged"
:signing-site="signingSite" :signing-site="signingSite"
/> />
@ -116,7 +131,6 @@ import contenteditable from "vue-contenteditable";
import { call_api, call_api_post, config } from "../api"; import { call_api, call_api_post, config } from "../api";
import Socket from "../components/Socket.vue"; import Socket from "../components/Socket.vue";
import SiteSettings from "../components/SiteSettings.vue"; import SiteSettings from "../components/SiteSettings.vue";
import { mintCollection } from "@/router/routes";
export default { export default {
name: "Explore", name: "Explore",
@ -134,7 +148,7 @@ export default {
}, },
}, },
data() { data() {
let saved_constructor_params = new SiteConstructorParams( const saved_constructor_params = new SiteConstructorParams(
this.domain, this.domain,
this.domain this.domain
); );
@ -200,7 +214,10 @@ export default {
}, },
isMine() { isMine() {
console.log(this.result); console.log(this.result);
return this.result.owner === this.$store.getters.address; return (
this.result.owner === this.$store.getters.address ||
this.result.nft_info.owner.address === this.$store.getters.address
);
}, },
walletChanged() { walletChanged() {
return this.records && this.wallet_rec !== this.records.wallet; return this.records && this.wallet_rec !== this.records.wallet;
@ -234,7 +251,7 @@ export default {
messages: [ messages: [
{ {
amount: (0.05 * 1000000000).toString(), amount: (0.05 * 1000000000).toString(),
address: this.result.nft_info.address, address: this.wallet_rec ?? this.result.nft_info.address,
payload: message, payload: message,
}, },
], ],
@ -301,6 +318,15 @@ export default {
this.saved_constructor_params; this.saved_constructor_params;
} }
}, },
selectTemplate(template) {
this.constructor_params = {
...this.constructor_params,
template_id: template.name,
};
},
useMyWallet() {
this.wallet_rec = this.$store.getters.address;
},
}, },
watch: { watch: {
records: function (val) { records: function (val) {
@ -363,16 +389,13 @@ export default {
.record-inp { .record-inp {
padding: 0.3rem; padding: 0.3rem;
margin-left: 0.5rem;
border-radius: 0.3rem; border-radius: 0.3rem;
background-color: #4e5a88; background-color: #4e5a88;
color: white; color: white;
min-width: 50vw;
width: 100%;
} }
.rec-field:not(:last-child) { .rec-field:not(:last-child) {
margin-bottom: 1rem; margin-bottom: 4rem;
} }
.get_b.inactive { .get_b.inactive {
@ -418,4 +441,30 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.wallet-panel {
display: grid;
grid-template-columns: auto 1fr 183px auto;
align-items: center;
gap: 10px;
& .label {
display: grid;
justify-content: end;
}
}
.use-my-wallet {
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
cursor: pointer;
background-color: #cdcee8;
color: #282e46;
&.disabled {
cursor: not-allowed;
background-color: #6a6e95;
color: #cecddb;
}
}
</style> </style>

26
src/views/Find.vue

@ -1,9 +1,9 @@
<template> <template>
<DarkLayout> <DarkLayout>
<div class="center"> <div class="center">
<DomainBar :value="query" @search="search()" @input_d="query = $event"/> <DomainBar :value="query" @search="search()" @input_d="query = $event" />
</div> </div>
<ZoneTable :zones="zones"/> <ZoneTable :domain="query" :zones="zones" />
</DarkLayout> </DarkLayout>
</template> </template>
@ -11,28 +11,24 @@
import DomainBar from "../components/DomainBar.vue"; import DomainBar from "../components/DomainBar.vue";
import DarkLayout from "../components/DarkLayout.vue"; import DarkLayout from "../components/DarkLayout.vue";
import ZoneTable from "../components/ZoneTable.vue"; import ZoneTable from "../components/ZoneTable.vue";
import {get_zones} from "../result"; import { get_zones } from "../result";
export default { export default {
name: "Find", name: "Find",
data () { data() {
return { return {
query: 'example', query: "example",
zones: [] zones: [],
} };
}, },
async mounted() { async mounted() {
this.zones = await get_zones(); this.zones = await get_zones();
}, },
methods: { methods: {
search() { search() {
this.$router.push({name: 'FindQ', params: {query: this.query}}); this.$router.push({ name: "FindQ", params: { query: this.query } });
} },
}, },
components: {ZoneTable, DarkLayout, DomainBar} components: { ZoneTable, DarkLayout, DomainBar },
} };
</script> </script>
<style scoped>
</style>
Loading…
Cancel
Save