Skip to content

Commit d9a3c94

Browse files
antoinedcAntoine de Chevigné
and
Antoine de Chevigné
authored
Custom links (#399)
* allow custom address * merge develop * test * fix tests * alternative link test --------- Co-authored-by: Antoine de Chevigné <antoine@tryethernal.com>
1 parent 1e5c647 commit d9a3c94

18 files changed

+439
-94
lines changed

docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ services:
8383
- "8080:8080"
8484
volumes:
8585
- ./public:/app/public
86+
- ./index.html:/app/index.html
8687
- ./src:/app/src
8788
- type: bind
8889
source: package.json

index.html

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<script>
1414
window.Browser = { T: () => {} };
15+
var exports = {};
1516
</script>
1617
<script type="module" src="/src/main.js"></script>
1718
</head>

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
"vuexfire": "^3.2.5",
7575
"web3": "^1.3.1",
7676
"web3-providers-ws": "^1.3.1",
77-
"webfontloader": "^1.6.28"
77+
"webfontloader": "^1.6.28",
78+
"workerboxjs": "^6.4.2"
7879
},
7980
"devDependencies": {
8081
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",

run/api/explorers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ router.get('/search', async (req, res, next) => {
812812
routerAddress: explorer.v2Dex.routerAddress
813813
};
814814

815-
res.status(200).json({ explorer: fields });
815+
res.status(200).json({ explorer: fields });
816816
} catch(error) {
817817
unmanagedError(error, req, next);
818818
}

run/models/explorer.js

+26-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,19 @@ module.exports = (sequelize, DataTypes) => {
7272
{
7373
model: sequelize.models.Workspace,
7474
attributes: ['id', 'name', 'storageEnabled', 'defaultAccount', 'gasPrice', 'gasLimit', 'erc721LoadingEnabled', 'statusPageEnabled', 'public'],
75-
as: 'workspace'
75+
as: 'workspace',
76+
include: [
77+
{
78+
model: sequelize.models.CustomField,
79+
as: 'packages',
80+
attributes: ['function']
81+
},
82+
{
83+
model: sequelize.models.CustomField,
84+
as: 'functions',
85+
attributes: ['function']
86+
}
87+
]
7688
},
7789
{
7890
model: sequelize.models.ExplorerFaucet,
@@ -119,7 +131,19 @@ module.exports = (sequelize, DataTypes) => {
119131
{
120132
model: sequelize.models.Workspace,
121133
attributes: ['id', 'name', 'storageEnabled', 'defaultAccount', 'gasPrice', 'gasLimit', 'erc721LoadingEnabled', 'statusPageEnabled', 'public'],
122-
as: 'workspace'
134+
as: 'workspace',
135+
include: [
136+
{
137+
model: sequelize.models.CustomField,
138+
as: 'packages',
139+
attributes: ['function']
140+
},
141+
{
142+
model: sequelize.models.CustomField,
143+
as: 'functions',
144+
attributes: ['function']
145+
}
146+
]
123147
},
124148
{
125149
model: sequelize.models.ExplorerFaucet,

run/models/user.js

+34-22
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,40 @@ module.exports = (sequelize, DataTypes) => {
5858
{
5959
model: sequelize.models.Workspace,
6060
as: 'currentWorkspace',
61-
include: {
62-
model: sequelize.models.Explorer,
63-
as: 'explorer',
64-
attributes: currentExplorerAttributes,
65-
include: [
66-
{
67-
model: sequelize.models.ExplorerDomain,
68-
as: 'domains',
69-
attributes: ['domain']
70-
},
71-
{
72-
model: sequelize.models.ExplorerFaucet,
73-
as: 'faucet',
74-
attributes: ['id', 'address', 'interval', 'amount', 'active']
75-
},
76-
{
77-
model: sequelize.models.ExplorerV2Dex,
78-
as: 'v2Dex',
79-
attributes: ['id', 'routerAddress', 'active']
80-
}
81-
]
82-
}
61+
include: [
62+
{
63+
model: sequelize.models.Explorer,
64+
as: 'explorer',
65+
attributes: currentExplorerAttributes,
66+
include: [
67+
{
68+
model: sequelize.models.ExplorerDomain,
69+
as: 'domains',
70+
attributes: ['domain']
71+
},
72+
{
73+
model: sequelize.models.ExplorerFaucet,
74+
as: 'faucet',
75+
attributes: ['id', 'address', 'interval', 'amount', 'active']
76+
},
77+
{
78+
model: sequelize.models.ExplorerV2Dex,
79+
as: 'v2Dex',
80+
attributes: ['id', 'routerAddress', 'active']
81+
}
82+
]
83+
},
84+
{
85+
model: sequelize.models.CustomField,
86+
as: 'packages',
87+
attributes: ['function']
88+
},
89+
{
90+
model: sequelize.models.CustomField,
91+
as: 'functions',
92+
attributes: ['function']
93+
}
94+
]
8395
},
8496
]
8597
});

run/models/workspace.js

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ module.exports = (sequelize, DataTypes) => {
3333
Workspace.hasMany(models.TokenBalanceChange, { foreignKey: 'workspaceId', as: 'tokenBalanceChanges' });
3434
Workspace.hasMany(models.TokenTransfer, { foreignKey: 'workspaceId', as: 'tokenTransfers' });
3535
Workspace.hasMany(models.CustomField, { foreignKey: 'workspaceId', as: 'customFields' });
36+
Workspace.hasMany(models.CustomField, { foreignKey: 'workspaceId', as: 'packages', scope: { location: 'package' } });
37+
Workspace.hasMany(models.CustomField, { foreignKey: 'workspaceId', as: 'functions', scope: { location: 'global' } });
3638
}
3739

3840
static findPublicWorkspaceById(id) {

src/App.vue

+6-2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import { useCurrentWorkspaceStore } from './stores/currentWorkspace';
9393
import { useEnvStore } from './stores/env';
9494
import { useExplorerStore } from './stores/explorer';
9595
import { useUserStore } from './stores/user';
96+
import { useCustomisationStore } from './stores/customisation';
9697
import RpcConnector from './components/RpcConnector';
9798
import OnboardingModal from './components/OnboardingModal';
9899
import BrowserSyncExplainerModal from './components/BrowserSyncExplainerModal';
@@ -300,7 +301,9 @@ export default {
300301
id: explorer.workspace.id,
301302
defaultAccount: explorer.workspace.defaultAccount,
302303
gasPrice: explorer.workspace.gasPrice,
303-
gasLimit: explorer.workspace.gasLimit
304+
gasLimit: explorer.workspace.gasLimit,
305+
functions: explorer.workspace.functions,
306+
packages: explorer.workspace.packages
304307
});
305308
},
306309
initWorkspace(workspace) {
@@ -317,7 +320,8 @@ export default {
317320
useCurrentWorkspaceStore,
318321
useEnvStore,
319322
useExplorerStore,
320-
useUserStore
323+
useUserStore,
324+
useCustomisationStore
321325
),
322326
hasNetworkInfo() {
323327
return !!(this.explorerStore.name && this.explorerStore.domain && this.explorerStore.token && this.explorerStore.rpcServer);

src/components/HashLink.vue

+39-13
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,21 @@
1515
&nbsp;(<router-link :to="`/address/${hash}/${tokenId}`">#{{ tokenId }}</router-link>)
1616
</span>
1717
<span v-if="hash && !copied && !notCopiable">
18-
&nbsp;<v-icon class="text-medium-emphasis" @click="copyHash()" size="x-small">mdi-content-copy</v-icon><input type="hidden" :id="`copyElement-${hash}`" :value="hash">
18+
&nbsp;<v-icon class="text-medium-emphasis" @click="copyHash()" size="x-small">mdi-content-copy</v-icon><input type="hidden" :id="`copyElement-${hash}`" :value="alternateLink && showAlternateLink ? alternateLink : hash">
1919
</span>
2020
<span v-if="copied">
2121
&nbsp; <v-icon class="text-medium-emphasis" size="x-small">mdi-check</v-icon>
2222
</span>
23+
<span v-if="alternateLink">
24+
&nbsp; <v-icon class="text-medium-emphasis" size="x-small" @click="showAlternateLink = !showAlternateLink">mdi-swap-horizontal</v-icon>
25+
</span>
2326
</span>
2427
</template>
2528
<script>
2629
import { inject } from 'vue';
2730
import { mapStores } from 'pinia';
2831
import { useExplorerStore } from '../stores/explorer';
32+
import { useCustomisationStore } from '../stores/customisation';
2933
import { sanitize } from '../lib/utils';
3034
3135
export default {
@@ -36,11 +40,17 @@ export default {
3640
token: null,
3741
contractName: null,
3842
verified: false,
43+
alternateLink: null,
44+
showAlternateLink: false
3945
}),
4046
setup() {
4147
const isEmbedded = inject('isEmbedded', false);
4248
return { embedded: !!isEmbedded };
4349
},
50+
mounted() {
51+
if (this.type == 'address')
52+
this.setupAlternateLink();
53+
},
4454
watch: {
4555
hash: {
4656
immediate: true,
@@ -82,21 +92,17 @@ export default {
8292
}
8393
},
8494
computed: {
85-
...mapStores(useExplorerStore),
95+
...mapStores(useExplorerStore, useCustomisationStore),
8696
formattedHash() {
87-
if (!this.hash) return;
88-
if (this.fullHash) {
89-
return this.hash;
90-
}
91-
else if (this.xsHash) {
92-
return `${this.hash.slice(0, 5)}...${this.hash.slice(-4)}`;
93-
}
94-
else {
95-
return `${this.hash.slice(0, 8)}...${this.hash.slice(-4)}`;
96-
}
97+
if (this.alternateLink && this.showAlternateLink)
98+
return this.formatHash(this.alternateLink);
99+
else
100+
return this.formatHash(this.hash);
97101
},
98102
name() {
99-
if (this.customLabel)
103+
if (this.alternateLink && this.showAlternateLink)
104+
return this.formatHash(this.alternateLink);
105+
else if (this.customLabel)
100106
return this.customLabel;
101107
if (this.withName != false && this.explorerStore.faucet && this.hash == this.explorerStore.faucet.address)
102108
return `${this.explorerStore.token || 'ETH'} faucet`;
@@ -111,6 +117,26 @@ export default {
111117
externalLink() { return `//${this.explorerStore.domain}/${[this.type, this.hash].join('/')}`; }
112118
},
113119
methods: {
120+
setupAlternateLink() {
121+
this.customisationStore.alternateLink(this.hash)
122+
.then(result => {
123+
this.alternateLink = result;
124+
if (!result) {
125+
let unsubscribe = this.customisationStore.$subscribe((mutation, state) => {
126+
if (state.worker) {
127+
unsubscribe();
128+
this.setupAlternateLink();
129+
}
130+
});
131+
}
132+
});
133+
},
134+
formatHash(hash) {
135+
if (!hash) return;
136+
if (this.fullHash) return hash;
137+
else if (this.xsHash) return `${hash.slice(0, 5)}...${hash.slice(-4)}`;
138+
else return `${hash.slice(0, 8)}...${hash.slice(-4)}`;
139+
},
114140
copyHash() {
115141
const webhookField = document.querySelector(`#copyElement-${this.hash}`);
116142
webhookField.setAttribute('type', 'text');

src/stores/currentWorkspace.js

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { defineStore } from 'pinia';
44
import { useExplorerStore } from './explorer';
55
import { useUserStore } from './user';
66
import { useEnvStore } from './env';
7+
import { useCustomisationStore } from './customisation';
78

89
const usePrivateCurrentWorkspaceStore = defineStore('privateCurrentWorkspace', {
910
state: () => ({
@@ -78,6 +79,32 @@ export const useCurrentWorkspaceStore = defineStore('currentWorkspace', {
7879
if (workspace.explorer)
7980
useExplorerStore().updateExplorer(workspace.explorer);
8081

82+
let functions = {};
83+
let packages = {};
84+
85+
try {
86+
if (workspace.packages)
87+
workspace.packages.forEach(p => {
88+
packages = { ...packages, ...JSON.parse(p.function) };
89+
});
90+
91+
if (workspace.functions)
92+
workspace.functions.forEach(f => {
93+
functions = { ...functions, ...JSON.parse(f.function) };
94+
});
95+
} catch (error) {
96+
console.error(error);
97+
functions = {};
98+
packages = {};
99+
}
100+
101+
const customisations = { functions, packages};
102+
103+
if (Object.keys(customisations.functions).length || Object.keys(customisations.packages).length) {
104+
const customisationStore = useCustomisationStore();
105+
customisationStore.updateCustomisations(customisations);
106+
}
107+
81108
const userStore = useUserStore();
82109
if (this.userId && this.userId === userStore.id)
83110
userStore.isAdmin = true;

src/stores/customisation.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import createWorkerBox from 'workerboxjs';
2+
import { defineStore } from 'pinia';
3+
4+
export const useCustomisationStore = defineStore('customisation', {
5+
state: () => ({
6+
packages: {},
7+
functions: {},
8+
worker: null
9+
}),
10+
actions: {
11+
async updateCustomisations(customisations) {
12+
for (const [alias, name] of Object.entries(customisations.packages)) {
13+
this.packages[alias] = await import(`https://cdn.skypack.dev/${name}?min`);
14+
}
15+
for (const [alias, fn] of Object.entries(customisations.functions)) {
16+
this.functions[alias] = fn;
17+
}
18+
this.worker = await createWorkerBox();
19+
},
20+
alternateLink(address) {
21+
if (!this.functions.alternateLink || !this.worker) return new Promise(resolve => resolve(null));
22+
23+
return this.worker.run(this.functions.alternateLink,
24+
{
25+
address,
26+
swisstronikUtils: this.packages.swisstronikUtils
27+
}
28+
);
29+
}
30+
}
31+
});
32+

src/stores/explorer.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ export const useExplorerStore = defineStore('explorer', {
1919

2020
actions: {
2121
updateExplorer(explorer) {
22-
if (explorer)
23-
this.$patch(explorer);
24-
else
22+
if (!explorer)
2523
this.$reset();
24+
25+
this.$patch(explorer);
2626
}
2727
}
2828
});
29+

tests/setup.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import flushPromises from 'flush-promises';
66
import * as components from 'vuetify/components'
77
import * as directives from 'vuetify/directives'
88
import { VStepperVertical, VStepperVerticalItem } from 'vuetify/labs/VStepperVertical'
9+
910
import $server from './unit/mocks/server';
1011
import FromWei from '@/filters/FromWei';
1112
import dt from '@/filters/dt';

tests/unit/components/DexTokenSelectionModal.spec.js

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)