├── .browserslistrc
├── .eslintrc.js
├── .firebaserc
├── .github
└── workflows
│ ├── codeql-analysis.yml
│ ├── firebase-hosting-merge.yml
│ └── firebase-hosting-pull-request.yml
├── .gitignore
├── .prettierignore
├── README.md
├── babel.config.js
├── firebase.json
├── firestore.indexes.json
├── firestore.rules
├── functions
├── .eslintrc.js
├── .gitignore
├── package.json
├── src
│ ├── common
│ │ └── exportifneeded.ts
│ ├── functions
│ │ └── test.ts
│ ├── index.ts
│ └── wrappers
│ │ └── tests
│ │ └── test.ts
├── tsconfig.dev.json
└── tsconfig.json
├── generated
├── action50.ts
├── addresses_draw_goerli.ts
├── addresses_draw_localhost.ts
├── addresses_draw_rinkeby.ts
├── addresses_flag_goerli.ts
├── addresses_flag_localhost.ts
├── addresses_flag_mainnet.ts
├── addresses_flag_rinkeby.ts
├── addresses_goerli.ts
├── addresses_kamon_goerli.ts
├── addresses_kamon_localhost.ts
├── addresses_kamon_mainnet.ts
├── addresses_kamon_rinkeby.ts
├── addresses_localhost.ts
├── addresses_mainnet.ts
├── addresses_matic.ts
├── addresses_rinkeby.ts
├── alert.ts
├── av.ts
├── communication.ts
├── emoji_assets.ts
├── hardware.ts
└── kamon_assets.ts
├── keep
└── addresses_mainnet.ts
├── package.json
├── postcss.config.js
├── public
├── AssetStoreLogo.ico
├── AssetStoreLogo.png
├── banner.svg
├── favicon.ico
├── index.html
└── logo.png
├── src
├── App.vue
├── abis
│ ├── AssetComposer.json
│ ├── AssetProviderRegistry.json
│ ├── AssetStore.json
│ ├── AssetStoreProvider.json
│ ├── DrawYourOwn.json
│ ├── EmojiFlagToken.json
│ ├── IAssetProvider.json
│ ├── ICategorizedAssetProvider.json
│ ├── KamonToken.json
│ └── MaterialToken.json
├── assets
│ ├── AssetStoreLogo.png
│ ├── NounsFes.jpeg
│ ├── NounsFes.svg
│ ├── banner.jpeg
│ ├── banner.png
│ ├── banner.svg
│ ├── firebase.svg
│ ├── logo.png
│ ├── outright.png
│ ├── red160px.png
│ ├── squiggle0.svg
│ ├── squiggle1.svg
│ ├── squiggle2.svg
│ ├── squiggle3.svg
│ └── squiggle4.svg
├── components
│ ├── AssetsPanel.vue
│ ├── Blank.vue
│ ├── Connect.vue
│ ├── Header.vue
│ ├── KamonMessage.vue
│ ├── KeyMessage.vue
│ ├── Languages.vue
│ ├── Layout.vue
│ ├── MintPanel.vue
│ ├── MintView.vue
│ ├── NFTList.vue
│ ├── NetworkError.vue
│ ├── NetworkGate.vue
│ ├── NotFound.vue
│ ├── References.vue
│ └── StoreView.vue
├── config
│ └── project.ts
├── i18n
│ ├── en.ts
│ ├── index.ts
│ ├── ja.ts
│ ├── languages.ts
│ └── utils.ts
├── index.css
├── main.ts
├── models
│ ├── asset.ts
│ ├── point.ts
│ └── token.ts
├── resources
│ ├── crypto.ts
│ ├── emoji.ts
│ ├── kamon.ts
│ ├── materials.ts
│ ├── multi.ts
│ ├── openemoji.ts
│ └── silhouettes.ts
├── router
│ └── index.ts
├── shims-vue.d.ts
├── store
│ └── index.ts
├── utils
│ ├── Drag.ts
│ ├── MetaMask.ts
│ ├── canvasUtil.ts
│ ├── const.ts
│ ├── createAsset.ts
│ ├── currency.ts
│ ├── fetchTokens.ts
│ ├── mintUtils.ts
│ ├── networks.ts
│ ├── pathUtils.ts
│ ├── resources.ts
│ └── undo.ts
└── views
│ ├── Assets.vue
│ ├── Emoji.vue
│ ├── Kamons.vue
│ └── Materials.vue
├── storage.rules
├── tailwind.config.js
├── tsconfig.json
├── vue.config.js
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | 'prettier',
10 | '@vue/typescript/recommended'
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 2020
14 | },
15 | rules: {
16 | 'vue/multi-word-component-names': 'off',
17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "assetstore-wtf"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '36 15 * * 0'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-merge.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on merge
5 | 'on':
6 | push:
7 | branches:
8 | - master
9 | jobs:
10 | build_and_deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/cache@v2
15 | with:
16 | path: '**/node_modules'
17 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
18 | - name: Yarn
19 | run: yarn install
20 | - run: yarn run build
21 | - uses: FirebaseExtended/action-hosting-deploy@v0
22 | with:
23 | repoToken: '${{ secrets.GITHUB_TOKEN }}'
24 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_FIR_VUE_STARTUP_KIT }}'
25 | channelId: live
26 | projectId: '${{ secrets.FIREBASE_PROJECT }}'
27 | env:
28 | FIREBASE_CLI_PREVIEWS: hostingchannels
29 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-pull-request.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on PR
5 | 'on': pull_request
6 | jobs:
7 | build_and_preview:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/cache@v2
12 | with:
13 | path: '**/node_modules'
14 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
15 | - name: Yarn
16 | run: yarn install
17 | - run: yarn run build
18 | - uses: FirebaseExtended/action-hosting-deploy@v0
19 | with:
20 | repoToken: '${{ secrets.GITHUB_TOKEN }}'
21 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_FIR_VUE_STARTUP_KIT }}'
22 | projectId: '${{ secrets.FIREBASE_PROJECT }}'
23 | env:
24 | FIREBASE_CLI_PREVIEWS: hostingchannels
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /src/generated
5 | /.firebase
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | *~
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | shims-vue.d.ts
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # firebase-vue3-startup-kit
2 |
3 | ## Purpose
4 |
5 | This is a Start-Up kit for a Firebase web project, which uses Vue3 and firebase 9.
6 |
7 | ## Requirements
8 |
9 | - Node.js version 14 or later.
10 |
11 | ## Instruction
12 |
13 | 1. Git clone this repository
14 | 2. Run "yarn global add firebase-tools" to install firebase tools.
15 | 3. Run "yarn install" once to get necessary node modules.
16 | 4. Run "yarn install" once in the functions directory as well.
17 | 5. Open the firebase console (from https://firebase.google.com) and add a project
18 | 6. From the dashboard of this project, add an app and choose "web" (>).
19 | 7. From the setting of this app, choose "Config" (in Firebase SDK snippet)
20 | 8. Copy the config file, and paste into src/config/project.ts file.
21 | 9. Replace the word "fir-vue-startup-kit" in .firebaserc file with your Firebase project name.
22 | 10. Open the firebase console, and create a Cloud Firestore (make it "secure" for now).
23 | 11. Enable Firebase Hosting on the firebase console.
24 |
25 | ## Funcitons
26 | Because Firebase Functions is very slow in the case of cold start by default setting, this startup-kit is a bit of a custom Firebase functions.
27 |
28 | ### Functions side
29 | - Functions is invoked using a wrapper function (`exportIfNeeded` function in `functions/src/common/exportifneeded.ts`). It loads only the functions it needs.
30 | - Functions will start with enough memory. `test` function in `functions/src/wrappers/tests/test.ts` run with 1GB memory.
31 | - Functions run in a nearby region. In my case it is Japan, so it is set in the Japanese region. Please change it to suit your location.
32 |
33 | For this reason, Functions are used in a slightly unusual way.
34 | Functions called by the client are written in `src/index.ts` like `exportIfNeeded ("test", "tests/test", exports);`
35 |
36 | In this case, the client calls test as a function. And when the client call the test Function, the default function in `functions/src/wrappers/tests/test.ts` is called. See this file for more information.
37 |
38 | ### Vue.js side.
39 | - The functions settings are in `src/utils/firebase.ts`. By default, it set to call asia-northeast1 (Tokyo) region.
40 | - All functions put together in `src/utils/functions.ts`. You should add new functions in this file.
41 |
42 | ### Region
43 |
44 | The region of Functions is set in asia-northeast1(Tokyo). If you change the region, be sure to change both Vue.js in Functions.
45 |
46 | ## i18n
47 | - This startup-kit supports i18n using url path.
48 | - You can use one Vue file in both `/en/index` and `/jp/index` .
49 | - Language files are in `src/i18n/` directory.
50 | - `en.ts` and `ja.ts` are Language files.
51 | - The language file used for the language switching pull-down (select) is `language.ts`. The same file is read from `en.ts` and `ja.ts`. Write in each language.
52 | - If you want to add new language, add the language to `index.ts`, add the `{language}.ts`, and add language to `language.ts`.
53 | - See also `src/router/index.ts` for how to switch languages with url path.
54 | - You can use the language switching pull-down in `src/components/Languages.vue`. This file needs to read `route.param.lang`, so don't use it in `App.vue` and `Layout.vue`. Other than that, it can be used anywhere.
55 | - i18n uses `vue-i18n@next`, so please refer to that for details on how to use it.
56 |
57 |
58 | ## Available Scripts
59 |
60 | In the project directory, you can run:
61 |
62 | ### `yarn run serve`
63 |
64 | Runs the app in the development mode.
65 | Open [http://localhost:8080](http://localhost:8080) to view it in the browser.
66 |
67 | The page will reload if you make edits.
68 | You will also see any lint errors in the console.
69 |
70 | ### `yarn run build`
71 |
72 | Builds the app for production to the `dist` folder.
73 | It correctly bundles Vue in production mode and optimizes the build for the best performance.
74 |
75 | The build is minified and the filenames include the hashes.
76 | Your app is ready to be deployed!
77 |
78 | ### `firebase deploy`
79 |
80 | Deploys the app to the Firebase cloud. You need to run "yarn run build" before the deployment.
81 |
82 | ### `yarn run format`
83 |
84 | Run Prettier, rewrite code as code formatting.
85 |
86 | ## Firebase Hosting GitHub Action
87 |
88 | ### Get that service account's key and add it to your repository as a secret
89 |
90 | 1. Create and download the new service account's JSON key
91 | 2. Add that JSON key as a secret in your GitHub repository. JSON key is FIREBASE_SERVICE_ACCOUNT_FIR_VUE_STARTUP_KIT.
92 | 3. Add firebase project id as a secret in your GitHub repository. JSON key is FIREBASE_PROJECT.
93 |
94 |
95 | More detail: see, https://github.com/marketplace/actions/deploy-to-firebase-hosting
96 |
97 | This is the sample site I deployed.
98 |
99 | https://firebase-vue3-startup-kit.firebase.love/
100 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "firestore": {
3 | "rules": "firestore.rules",
4 | "indexes": "firestore.indexes.json"
5 | },
6 | "functions": {
7 | "predeploy": [
8 | "npm --prefix \"$RESOURCE_DIR\" run lint",
9 | "npm --prefix \"$RESOURCE_DIR\" run build"
10 | ]
11 | },
12 | "hosting": {
13 | "public": "dist",
14 | "ignore": [
15 | "firebase.json",
16 | "**/.*",
17 | "**/node_modules/**"
18 | ],
19 | "headers": [{
20 | "source": "**",
21 | "headers": [
22 | { "key" : "Access-Control-Allow-Origin", "value" : "*" },
23 | { "key" : "X-Frame-Options", "value" : "deny" },
24 | { "key" : "X-Content-Type-Options", "value" : "nosniff" },
25 | { "key" : "X-XSS-Protection", "value" : "1; mode=block" },
26 | { "key" : "X-Permitted-Cross-Domain-Policies", "value" : "none" },
27 | { "key" : "Referrer-Policy", "value": "no-referrer" }
28 | ]
29 | }],
30 | "rewrites": [
31 | {
32 | "source": "**",
33 | "destination": "/index.html"
34 | }
35 | ]
36 | },
37 | "storage": {
38 | "rules": "storage.rules"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": [],
3 | "fieldOverrides": []
4 | }
5 |
--------------------------------------------------------------------------------
/firestore.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 |
3 | service cloud.firestore {
4 | match /databases/{database}/documents {
5 | match /message/{messageId} {
6 | allow read, create;
7 | allow delete, update: if false;
8 | }
9 | match /test/{testId} {
10 | allow read;
11 | allow create, delete, update: if false;
12 | }
13 | match /{document=**} {
14 | allow read, write: if false;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/functions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es6: true,
5 | node: true,
6 | },
7 | extends: [
8 | "eslint:recommended",
9 | "plugin:import/errors",
10 | "plugin:import/warnings",
11 | "plugin:import/typescript"
12 | ],
13 | parser: "@typescript-eslint/parser",
14 | parserOptions: {
15 | project: ["tsconfig.json", "tsconfig.dev.json"],
16 | sourceType: "module",
17 | },
18 | ignorePatterns: [
19 | "/lib/**/*", // Ignore built files.
20 | ],
21 | plugins: [
22 | "@typescript-eslint",
23 | "import",
24 | ],
25 | rules: {
26 | quotes: ["error", "double"],
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled JavaScript files
2 | lib/**/*.js
3 | lib/**/*.js.map
4 |
5 | # TypeScript v1 declaration files
6 | typings/
7 |
8 | # Node.js dependency directory
9 | node_modules/
10 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "lint": "eslint --ext .js,.ts .",
5 | "format": "prettier --write 'src/**/*.{js,ts}'",
6 | "build": "tsc",
7 | "serve": "npm run build && firebase emulators:start --only functions",
8 | "shell": "npm run build && firebase functions:shell",
9 | "start": "npm run shell",
10 | "deploy": "firebase deploy --only functions",
11 | "logs": "firebase functions:log"
12 | },
13 | "engines": {
14 | "node": "16"
15 | },
16 | "main": "lib/index.js",
17 | "dependencies": {
18 | "firebase-admin": "^10.1.0",
19 | "firebase-functions": "^3.20.1",
20 | "prettier": "^2.6.2"
21 | },
22 | "devDependencies": {
23 | "@typescript-eslint/eslint-plugin": "^5.21.0",
24 | "@typescript-eslint/parser": "^5.21.0",
25 | "eslint": "^8.14.0",
26 | "eslint-plugin-import": "^2.26.0",
27 | "firebase-functions-test": "^0.3.3",
28 | "typescript": "^4.6.3"
29 | },
30 | "private": true
31 | }
32 |
--------------------------------------------------------------------------------
/functions/src/common/exportifneeded.ts:
--------------------------------------------------------------------------------
1 | function isFunctionCalled(functionName: string): boolean {
2 | return (
3 | !process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === functionName
4 | );
5 | }
6 |
7 | export default function exportIfNeeded(
8 | functionName: string,
9 | fileName: string,
10 | exports: any
11 | ): void {
12 | if (isFunctionCalled(functionName)) {
13 | // eslint-disable-next-line @typescript-eslint/no-var-requires
14 | exports[functionName] = require(`../wrappers/${fileName}`).default;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/functions/src/functions/test.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions";
2 |
3 | export const test = async (
4 | data: {},
5 | context: functions.https.CallableContext
6 | ) => {
7 | const uid = context?.auth?.uid;
8 | console.log(uid, data);
9 | return {};
10 | };
11 |
--------------------------------------------------------------------------------
/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | // import * as functions from "firebase-functions";
2 |
3 | import exportIfNeeded from "./common/exportifneeded";
4 |
5 | // // Start writing Firebase Functions
6 | // // https://firebase.google.com/docs/functions/typescript
7 | //
8 | // export const helloWorld = functions.https.onRequest((request, response) => {
9 | // functions.logger.info("Hello logs!", {structuredData: true});
10 | // response.send("Hello from Firebase!");
11 | // });
12 |
13 | exportIfNeeded("test", "tests/test", exports);
14 |
--------------------------------------------------------------------------------
/functions/src/wrappers/tests/test.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions";
2 |
3 | import { test } from "../../functions/test";
4 |
5 | export default functions
6 | .region("asia-northeast1")
7 | .runWith({
8 | memory: "1GB",
9 | })
10 | .https.onCall(async (data, context) => {
11 | return await test(data, context);
12 | });
13 |
--------------------------------------------------------------------------------
/functions/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | ".eslintrc.js"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "skipLibCheck": true,
8 | "sourceMap": true,
9 | "strict": true,
10 | "target": "es2017"
11 | },
12 | "compileOnSave": true,
13 | "include": [
14 | "src"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/generated/addresses_draw_goerli.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | customTokenAddress:"0xa70ebebD812E48E25D907A5bD9C78FfDA9DA9F59",
3 | composerAddress:"0xfb125E8463666389Af224B40d2cCE38BFE09bBd6",
4 | registryAddress:"0xB7aD37DA6A1138D68CcA3D69a62A0e4A260eBbEc"
5 | }
6 |
--------------------------------------------------------------------------------
/generated/addresses_draw_localhost.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | customTokenAddress:"0x30426D33a78afdb8788597D5BFaBdADc3Be95698",
3 | composerAddress:"0x53CA7a8cba6d06D8B6623331AB564d03a036CC09",
4 | registryAddress:"0xB2c9e8ae6DC7A856102cE9807Db4A175087C7A90"
5 | }
6 |
--------------------------------------------------------------------------------
/generated/addresses_draw_rinkeby.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | customTokenAddress:"0x2F11f02eAB7b981958fe3eA0AF69336323e6070d",
3 | composerAddress:"0x884715B710a81F4542dD6632BB3769D1308eB451",
4 | registryAddress:"0x55eEa354443b40d504b433ABA2fdd4eef8F53A89"
5 | }
6 |
--------------------------------------------------------------------------------
/generated/addresses_flag_goerli.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | emojiFlagAddress:"0x4B71cC6c98182b803a15f2bdCf4c3cBF4C3035A3"
3 | }
4 |
--------------------------------------------------------------------------------
/generated/addresses_flag_localhost.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | emojiFlagAddress:"0x5E5713a0d915701F464DEbb66015adD62B2e6AE9"
3 | }
4 |
--------------------------------------------------------------------------------
/generated/addresses_flag_mainnet.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | emojiFlagAddress:"0xafe4126da89c03c4fB63A82A863083A7b675a936"
3 | }
4 |
--------------------------------------------------------------------------------
/generated/addresses_flag_rinkeby.ts:
--------------------------------------------------------------------------------
1 | export const token_addresses = {
2 | emojiFlagAddress:"0xA491F2133fbb0D5Caa2645532FB740ad8B3F2D51"
3 | }
4 |
--------------------------------------------------------------------------------
/generated/addresses_goerli.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x449EBbdd48e598EeD989461515ace9A5887131D8",
3 | tokenAddress:"0x2A5edcf675AE15861F4af2aBCac20aB1296800fc"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_kamon_goerli.ts:
--------------------------------------------------------------------------------
1 | export const kamon_addresses = {
2 | decoderAddress:"0xAa37fA6cEb855500E269513cA9e6E5F13B4D0D95",
3 | kamonAddress:"0x0259F141Af3FE5Fd7632D927291060e1B841e8BD"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_kamon_localhost.ts:
--------------------------------------------------------------------------------
1 | export const kamon_addresses = {
2 | decoderAddress:"0xAa37fA6cEb855500E269513cA9e6E5F13B4D0D95",
3 | kamonAddress:"0x94A44de106112A761B69dFBEB1ac6cfdD1B80304"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_kamon_mainnet.ts:
--------------------------------------------------------------------------------
1 | export const kamon_addresses = {
2 | decoderAddress:"0xAa37fA6cEb855500E269513cA9e6E5F13B4D0D95",
3 | kamonAddress:"0x8a7376bb3BaEa9d697E078E3168189D49b9cCEeB"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_kamon_rinkeby.ts:
--------------------------------------------------------------------------------
1 | export const kamon_addresses = {
2 | decoderAddress:"0xAa37fA6cEb855500E269513cA9e6E5F13B4D0D95",
3 | kamonAddress:"0x31AF158f7350377B12849C69BfE34865c55710c7"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_localhost.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x74Df809b1dfC099E8cdBc98f6a8D1F5c2C3f66f8",
3 | tokenAddress:"0x3f9A1B67F3a3548e0ea5c9eaf43A402d12b6a273"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_mainnet.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x847A044aF5225f994C60f43e8cF74d20F756187C",
3 | tokenAddress:"0xd724c63ADaC76Bf7632609dFc808b606c5811b35"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_matic.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x31E87E18282Aa2F8196c9C188D34fe575e5Fe1ab",
3 | tokenAddress:"0x523f600738E5FC42214E05FA3B589B504C3ea49A"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/addresses_rinkeby.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x6c11C6A8F1CE9EBe279f29d1466d77D9406B31d1",
3 | tokenAddress:"0x05C3Cb9d28Ff7675389300131849609d91519DF7"
4 | }
5 |
--------------------------------------------------------------------------------
/generated/alert.ts:
--------------------------------------------------------------------------------
1 | export const assets = [{"name":"add alert","width":24,"height":24,"bodies":["M10.01 21.01c0 1.1.89 1.99 1.99 1.99s1.99-.89 1.99-1.99h-3.98zm8.87-4.19V11c0-3.25-2.25-5.97-5.29-6.69v-.72C13.59 2.71 12.88 2 12 2s-1.59.71-1.59 1.59v.72C7.37 5.03 5.12 7.75 5.12 11v5.82L3 18.94V20h18v-1.06l-2.12-2.12zM16 13.01h-3v3h-2v-3H8V11h3V8h2v3h3v2.01z"]},{"name":"auto delete","width":24,"height":24,"bodies":["M 4.5,2 H 1 V 4 H 15 V 2 h -3.5 l -1,-1 h -5 z","M 16,9 C 15.3,9 14.63,9.1 14,9.29 V 5 H 2 v 12 c 0,1.1 0.9,2 2,2 h 5.68 c 1.12,2.36 3.53,4 6.32,4 3.87,0 7,-3.13 7,-7 0,-3.87 -3.13,-7 -7,-7 z m 0,12 c -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z","M 19.4,17.9 16.5,16.2 V 12 H 15 v 5 l 3.6,2.1 z"]},{"name":"error","width":24,"height":24,"bodies":["M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"]},{"name":"error outline","width":24,"height":24,"bodies":["M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"]},{"name":"notification important","width":24,"height":24,"bodies":["M18 16v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-5 0h-2v-2h2v2zm0-4h-2V8h2v4zm-1 10c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2z"]},{"name":"warning","width":24,"height":24,"bodies":["M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"]},{"name":"warning amber","width":24,"height":24,"bodies":["M 12,5.99 19.53,19 H 4.47 L 12,5.99 M 12,2 1,21 h 22 z","m 11,18 h 2 v -2 h -2 z","m 11,15 h 2 v -5 h -2 z"]}] ;
2 |
--------------------------------------------------------------------------------
/generated/hardware.ts:
--------------------------------------------------------------------------------
1 | export const assets = [{"name":"Reddit Logo","width":167,"height":167,"bodies":["m 139.7,83.8 c 0,-6.8 -5.5,-12.2 -12.2,-12.2 -3.3,0 -6.3,1.3 -8.5,3.4 -8.4,-6 -19.9,-9.9 -32.7,-10.4 l 5.6,-26.2 18.2,3.9 c 0.2,4.6 4,8.3 8.7,8.3 4.8,0 8.7,-3.9 8.7,-8.7 0,-4.8 -3.9,-8.7 -8.7,-8.7 -3.4,0 -6.4,2 -7.8,4.9 L 90.7,33.8 c -0.6,-0.1 -1.2,0 -1.6,0.3 -0.5,0.3 -0.8,0.8 -0.9,1.4 L 82,64.7 C 69,65.1 57.3,69 48.8,75.1 46.6,73 43.6,71.7 40.3,71.7 c -6.8,0 -12.2,5.5 -12.2,12.2 0,5 3,9.2 7.2,11.1 -0.2,1.2 -0.3,2.4 -0.3,3.7 0,18.8 21.9,34.1 48.9,34.1 27,0 48.9,-15.2 48.9,-34.1 0,-1.2 -0.1,-2.5 -0.3,-3.7 4.2,-1.9 7.2,-6.2 7.2,-11.2 z m -83.8,8.7 c 0,-4.8 3.9,-8.7 8.7,-8.7 4.8,0 8.7,3.9 8.7,8.7 0,4.8 -3.9,8.7 -8.7,8.7 -4.8,0.1 -8.7,-3.9 -8.7,-8.7 z m 48.7,23.1 c -6,6 -17.4,6.4 -20.7,6.4 -3.4,0 -14.8,-0.5 -20.7,-6.4 -0.9,-0.9 -0.9,-2.3 0,-3.2 0.9,-0.9 2.3,-0.9 3.2,0 3.8,3.8 11.8,5.1 17.5,5.1 5.7,0 13.8,-1.3 17.5,-5.1 0.9,-0.9 2.3,-0.9 3.2,0 0.8,0.9 0.8,2.3 0,3.2 z M 103,101.3 c -4.8,0 -8.7,-3.9 -8.7,-8.7 0,-4.8 3.9,-8.7 8.7,-8.7 4.8,0 8.7,3.9 8.7,8.7 0,4.7 -3.9,8.7 -8.7,8.7 z"]}] ;
2 |
--------------------------------------------------------------------------------
/keep/addresses_mainnet.ts:
--------------------------------------------------------------------------------
1 | export const addresses = {
2 | storeAddress:"0x847A044aF5225f994C60f43e8cF74d20F756187C",
3 | tokenAddress:"0xd724c63ADaC76Bf7632609dFc808b606c5811b35"
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebase-vue3-startup-kit",
3 | "version": "1.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "format": "prettier --write 'src/**/*.{js,ts,vue}'"
10 | },
11 | "dependencies": {
12 | "@vueuse/head": "^0.7.7",
13 | "core-js": "^3.24.0",
14 | "ethers": "^5.6.9",
15 | "vue": "^3.2.37",
16 | "vue-i18n": "9",
17 | "vue-router": "^4.0.14",
18 | "vue3-colorpicker": "^2.0.4",
19 | "vuex": "^4.0.2"
20 | },
21 | "devDependencies": {
22 | "@material-design-icons/svg": "^0.11.4",
23 | "@types/uuid": "^8.3.4",
24 | "@typescript-eslint/eslint-plugin": "^5.19.0",
25 | "@typescript-eslint/parser": "^5.19.0",
26 | "@vue/cli-plugin-eslint": "^5.0.8",
27 | "@vue/cli-plugin-router": "^5.0.8",
28 | "@vue/cli-plugin-typescript": "^5.0.8",
29 | "@vue/cli-plugin-vuex": "^5.0.8",
30 | "@vue/cli-service": "^5.0.8",
31 | "@vue/compiler-sfc": "^3.2.33",
32 | "@vue/eslint-config-typescript": "^10.0.0",
33 | "autoprefixer": "^10.4.4",
34 | "eslint": "^8.13.0",
35 | "eslint-config-prettier": "^8.5.0",
36 | "eslint-plugin-vue": "^8.6.0",
37 | "postcss": "^8.4.12",
38 | "prettier": "^2.6.2",
39 | "prettier-plugin-tailwindcss": "^0.1.13",
40 | "tailwindcss": "^3.0.23",
41 | "typescript": "^4.6.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/AssetStoreLogo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/public/AssetStoreLogo.ico
--------------------------------------------------------------------------------
/public/AssetStoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/public/AssetStoreLogo.png
--------------------------------------------------------------------------------
/public/banner.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | On-chain Asset Store
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/public/logo.png
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
21 |
69 |
--------------------------------------------------------------------------------
/src/abis/IAssetProvider.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "IAssetProvider",
4 | "sourceName": "contracts/interfaces/IAssetComposer.sol",
5 | "abi": [
6 | {
7 | "inputs": [
8 | {
9 | "internalType": "uint256",
10 | "name": "_assetId",
11 | "type": "uint256"
12 | }
13 | ],
14 | "name": "generateSVGPart",
15 | "outputs": [
16 | {
17 | "internalType": "string",
18 | "name": "",
19 | "type": "string"
20 | },
21 | {
22 | "internalType": "string",
23 | "name": "",
24 | "type": "string"
25 | }
26 | ],
27 | "stateMutability": "view",
28 | "type": "function"
29 | },
30 | {
31 | "inputs": [],
32 | "name": "getProviderInfo",
33 | "outputs": [
34 | {
35 | "components": [
36 | {
37 | "internalType": "string",
38 | "name": "key",
39 | "type": "string"
40 | },
41 | {
42 | "internalType": "string",
43 | "name": "name",
44 | "type": "string"
45 | },
46 | {
47 | "internalType": "contract IAssetProvider",
48 | "name": "provider",
49 | "type": "address"
50 | }
51 | ],
52 | "internalType": "struct IAssetProvider.ProviderInfo",
53 | "name": "",
54 | "type": "tuple"
55 | }
56 | ],
57 | "stateMutability": "view",
58 | "type": "function"
59 | },
60 | {
61 | "inputs": [],
62 | "name": "totalSupply",
63 | "outputs": [
64 | {
65 | "internalType": "uint256",
66 | "name": "",
67 | "type": "uint256"
68 | }
69 | ],
70 | "stateMutability": "view",
71 | "type": "function"
72 | }
73 | ],
74 | "bytecode": "0x",
75 | "deployedBytecode": "0x",
76 | "linkReferences": {},
77 | "deployedLinkReferences": {}
78 | }
79 |
--------------------------------------------------------------------------------
/src/abis/ICategorizedAssetProvider.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "ICategorizedAssetProvider",
4 | "sourceName": "contracts/interfaces/IAssetComposer.sol",
5 | "abi": [
6 | {
7 | "inputs": [
8 | {
9 | "internalType": "uint256",
10 | "name": "_assetId",
11 | "type": "uint256"
12 | }
13 | ],
14 | "name": "generateSVGPart",
15 | "outputs": [
16 | {
17 | "internalType": "string",
18 | "name": "",
19 | "type": "string"
20 | },
21 | {
22 | "internalType": "string",
23 | "name": "",
24 | "type": "string"
25 | }
26 | ],
27 | "stateMutability": "view",
28 | "type": "function"
29 | },
30 | {
31 | "inputs": [
32 | {
33 | "internalType": "string",
34 | "name": "_group",
35 | "type": "string"
36 | },
37 | {
38 | "internalType": "string",
39 | "name": "_category",
40 | "type": "string"
41 | }
42 | ],
43 | "name": "getAssetCountInCategory",
44 | "outputs": [
45 | {
46 | "internalType": "uint32",
47 | "name": "",
48 | "type": "uint32"
49 | }
50 | ],
51 | "stateMutability": "view",
52 | "type": "function"
53 | },
54 | {
55 | "inputs": [
56 | {
57 | "internalType": "string",
58 | "name": "_group",
59 | "type": "string"
60 | },
61 | {
62 | "internalType": "string",
63 | "name": "_category",
64 | "type": "string"
65 | },
66 | {
67 | "internalType": "uint32",
68 | "name": "_assetIndex",
69 | "type": "uint32"
70 | }
71 | ],
72 | "name": "getAssetIdInCategory",
73 | "outputs": [
74 | {
75 | "internalType": "uint256",
76 | "name": "",
77 | "type": "uint256"
78 | }
79 | ],
80 | "stateMutability": "view",
81 | "type": "function"
82 | },
83 | {
84 | "inputs": [
85 | {
86 | "internalType": "string",
87 | "name": "_group",
88 | "type": "string"
89 | }
90 | ],
91 | "name": "getCategoryCount",
92 | "outputs": [
93 | {
94 | "internalType": "uint32",
95 | "name": "",
96 | "type": "uint32"
97 | }
98 | ],
99 | "stateMutability": "view",
100 | "type": "function"
101 | },
102 | {
103 | "inputs": [
104 | {
105 | "internalType": "string",
106 | "name": "_group",
107 | "type": "string"
108 | },
109 | {
110 | "internalType": "uint32",
111 | "name": "_categoryIndex",
112 | "type": "uint32"
113 | }
114 | ],
115 | "name": "getCategoryNameAtIndex",
116 | "outputs": [
117 | {
118 | "internalType": "string",
119 | "name": "",
120 | "type": "string"
121 | }
122 | ],
123 | "stateMutability": "view",
124 | "type": "function"
125 | },
126 | {
127 | "inputs": [],
128 | "name": "getGroupCount",
129 | "outputs": [
130 | {
131 | "internalType": "uint32",
132 | "name": "",
133 | "type": "uint32"
134 | }
135 | ],
136 | "stateMutability": "view",
137 | "type": "function"
138 | },
139 | {
140 | "inputs": [
141 | {
142 | "internalType": "uint32",
143 | "name": "_groupIndex",
144 | "type": "uint32"
145 | }
146 | ],
147 | "name": "getGroupNameAtIndex",
148 | "outputs": [
149 | {
150 | "internalType": "string",
151 | "name": "",
152 | "type": "string"
153 | }
154 | ],
155 | "stateMutability": "view",
156 | "type": "function"
157 | },
158 | {
159 | "inputs": [],
160 | "name": "getOwner",
161 | "outputs": [
162 | {
163 | "internalType": "address",
164 | "name": "",
165 | "type": "address"
166 | }
167 | ],
168 | "stateMutability": "view",
169 | "type": "function"
170 | },
171 | {
172 | "inputs": [],
173 | "name": "getProviderInfo",
174 | "outputs": [
175 | {
176 | "components": [
177 | {
178 | "internalType": "string",
179 | "name": "key",
180 | "type": "string"
181 | },
182 | {
183 | "internalType": "string",
184 | "name": "name",
185 | "type": "string"
186 | },
187 | {
188 | "internalType": "contract IAssetProvider",
189 | "name": "provider",
190 | "type": "address"
191 | }
192 | ],
193 | "internalType": "struct IAssetProvider.ProviderInfo",
194 | "name": "",
195 | "type": "tuple"
196 | }
197 | ],
198 | "stateMutability": "view",
199 | "type": "function"
200 | },
201 | {
202 | "inputs": [],
203 | "name": "totalSupply",
204 | "outputs": [
205 | {
206 | "internalType": "uint256",
207 | "name": "",
208 | "type": "uint256"
209 | }
210 | ],
211 | "stateMutability": "view",
212 | "type": "function"
213 | }
214 | ],
215 | "bytecode": "0x",
216 | "deployedBytecode": "0x",
217 | "linkReferences": {},
218 | "deployedLinkReferences": {}
219 | }
220 |
--------------------------------------------------------------------------------
/src/assets/AssetStoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/AssetStoreLogo.png
--------------------------------------------------------------------------------
/src/assets/NounsFes.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/NounsFes.jpeg
--------------------------------------------------------------------------------
/src/assets/banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/banner.jpeg
--------------------------------------------------------------------------------
/src/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/banner.png
--------------------------------------------------------------------------------
/src/assets/banner.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/firebase.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/outright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/outright.png
--------------------------------------------------------------------------------
/src/assets/red160px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cryptocoders-wtf/assetstore/6acf986e062a21434370e54a6dcf6a0665edd4b3/src/assets/red160px.png
--------------------------------------------------------------------------------
/src/assets/squiggle0.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/squiggle1.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/squiggle2.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/squiggle3.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/squiggle4.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/components/AssetsPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ $t("message.loading") }}
5 |
6 |
7 |
8 | {{ $tc("assetPanel.thanks", { totalCount: loadedAssets.length }) }}
9 |
10 |
11 |
12 |
13 | {{ $t("assetPanel.mint") }}
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
33 |
--------------------------------------------------------------------------------
/src/components/Blank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/src/components/Connect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
24 |
25 |
26 |
38 |
45 |
46 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
94 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
40 |
--------------------------------------------------------------------------------
/src/components/KamonMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | * 家紋のベクトルデータは発行大王堂様よりご提供いただいています。
9 |
10 |
11 | *All Kamon vector data were provided by
12 | Hakko Daiodo.
13 |
14 |
15 |
16 |
17 |
33 |
--------------------------------------------------------------------------------
/src/components/KeyMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t("keyMessage.message1") }}
5 | {{ $t("keyMessage.message1a") }}
6 | On-chain Asset Store and Composer
12 | {{ $t("keyMessage.message1b") }}
13 |
14 |
15 | {{ $t("keyMessage.message2") }}
16 |
17 |
18 |
19 |
20 |
34 |
--------------------------------------------------------------------------------
/src/components/Languages.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
51 |
--------------------------------------------------------------------------------
/src/components/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
33 |
--------------------------------------------------------------------------------
/src/components/MintPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | {{ selection.asset.name }},
9 | {{ selection.asset.category }}
11 |
12 |
13 |
14 |
![]()
18 |
{{ $t("mintPanel.preparing") }}
19 |
20 |
21 |
![]()
27 |
28 |
29 |
{{ $t("message.processing") }}
30 |
31 |
32 |
33 | {{ $t("mintPanel.thanks") }}
34 |
35 |
36 |
40 |
41 | {{ $t("mintPanel.sorry") }}
42 |
43 |
44 |
45 |
{{ $t("mintPanel.error1") }}
46 |
{{ messageRef }}
47 |
{{ $t("mintPanel.error2") }}
48 |
49 |
50 |
51 |
52 |
53 |
59 |
66 |
67 |
73 |
74 |
75 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | {{
89 | $tc("mintPanel.mintMessage1", {
90 | low: priceRange.low,
91 | high: priceRange.high,
92 | })
93 | }}
94 |
95 |
96 | {{
97 | $tc("mintPanel.mintMessage2", {
98 | tokensPerAsset: tokensPerAsset - 1,
99 | bonousTokensPerAsset: tokensPerAsset - 2,
100 | })
101 | }}
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
291 |
--------------------------------------------------------------------------------
/src/components/MintView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
5 |
onSelect(asset)"
7 | :availableAssets="availableAssets"
8 | :loadedAssets="loadedAssets"
9 | >
10 |
11 |
12 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
187 |
--------------------------------------------------------------------------------
/src/components/NFTList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t("message.recentNFTs", { count: tokens.length }) }}
7 |
8 |
9 | {{ $t("message.nftListTitle", { count: tokens.length }) }}
10 |
11 |
12 |
13 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
--------------------------------------------------------------------------------
/src/components/NetworkError.vue:
--------------------------------------------------------------------------------
1 |
2 | Network Error
3 |
4 |
--------------------------------------------------------------------------------
/src/components/NetworkGate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ $t("mint.switchNetwork") }}
5 |
11 |
12 |
13 |
14 | {{ $t("mint.connectMetamask") }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
53 |
--------------------------------------------------------------------------------
/src/components/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t("message.notFoundMessage") }}
6 |
7 |
8 |
9 |
10 |
14 |
--------------------------------------------------------------------------------
/src/components/References.vue:
--------------------------------------------------------------------------------
1 |
2 |
56 |
57 |
58 |
77 |
--------------------------------------------------------------------------------
/src/components/StoreView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
On-chain Asset Store
4 |
5 |
6 | {{ $t("storyView.message") }}
7 |
8 |
9 | {{ $t("storyView.totalAssetCount") }} : {{ assetCount }}
10 |
11 |
28 |
29 |
43 |
44 |
{{ $t("storyView.loadingCategories") }}
45 |
46 |
47 |
48 |
{{ $t("storyView.selectAssets") }}
49 |
50 |
{
53 | assetSelected(asset);
54 | }
55 | "
56 | :src="asset.image"
57 | class="inline-block w-12 cursor-pointer rounded-xl"
58 | />
59 |
63 |
64 |
65 | {{ $t("storyView.sampleCodeMessage") }}
66 |
72 |
73 |
76 |
77 | {{ $t("storyView.fetchedImage") }}
78 |
84 |
85 |
88 |
89 |
![]()
93 |
94 |
95 |
...
96 |
97 |
98 |
99 |
100 |
{{ $t("storyView.loadingAssets") }}
101 |
102 |
103 |
104 |
105 |
106 |
408 |
--------------------------------------------------------------------------------
/src/config/project.ts:
--------------------------------------------------------------------------------
1 | export const firebaseConfig = {
2 | apiKey: "AIzaSyCJP07F3ZmWLeiYiMLKrSeRcXQa__bkEAw",
3 | authDomain: "assetstore-wtf.firebaseapp.com",
4 | projectId: "assetstore-wtf",
5 | storageBucket: "assetstore-wtf.appspot.com",
6 | messagingSenderId: "991565220882",
7 | appId: "1:991565220882:web:14ec3ebeaf58948c15dccb",
8 | measurementId: "G-QP3Z4HZM1E",
9 | };
10 |
--------------------------------------------------------------------------------
/src/i18n/en.ts:
--------------------------------------------------------------------------------
1 | import languages from "./languages";
2 |
3 | const lang = {
4 | menu: {
5 | connect: "Connect",
6 | connected: "Connected",
7 | nometamask: "No Metamask",
8 | },
9 | header: {
10 | home: "Home",
11 | material: "Material",
12 | kamon: "Kamon",
13 | emoji: "Emoji",
14 | flags: "Flags",
15 | },
16 | keyMessage: {
17 | message1:
18 | 'This is a part of "On-chain Asset Store" project, which is an effort to make variety of vector assets available on blockchains and make them composable.',
19 | message1a: "Please see",
20 | message1b: "for the details.",
21 | message2:
22 | 'We are using a "crowd-minting" method, where each minter pays a small amount of gas fees to upload a vector image to the blockchain, and receives multiple NFTs as rewards.',
23 | },
24 | mint: {
25 | switchNetwork: "Please switch the network.",
26 | switchNetworkButton: "Switch Network.",
27 | connectMetamask: "Please connect with Metamask.",
28 | },
29 | mintPanel: {
30 | writeName: "Name to be permanently stored to the Asset Store.",
31 | maxLength: "Maximum 32 bytes.",
32 | cc0Message:
33 | "Images you mint here will be available for other people to reuse and remix as cc0 (public domain).",
34 | drawMessage:
35 | "This is a free mint, if you draw everything from scratch, but costs { mintPrice} ETH if you use remix or overlays. 97.5% of this mint fee will be distributed to creators. It means you can earn money as a creator (Draw2Earn).",
36 | mintMessage1:
37 | "This is a free mint, but you need to pay the gas fee, which is typically { low }〜{ high } ETH (depending on the complexity of the image, assuming the Gas price is ~15 Gwei).",
38 | mintMessage2:
39 | "If you participate in this crowd-minting effort, you will receive not only the primary NFT (which is the proof that you are one of minters), but also { bonousTokensPerAs } additional bonus NFTs.",
40 | placeHolder: "Name (such as Twitter Id, optional)",
41 | thanks:
42 | "Thank you for participating in this crowd-minting effort. When the blockchain is updated, this message will dissapear automatically.",
43 | sorry:
44 | "Another user has just minted this NFT. Please select another image.",
45 | mint: "Mint",
46 | preparing: "Preparing to mint...",
47 | error1: "We have received the following error message.",
48 | error2: "Please try again from the selection of an image.",
49 | },
50 | message: {
51 | notFoundMessage: "Page not Found",
52 | hello: "hello world",
53 | noAccount: "Please connect Metamask.",
54 | invalidNetwork: "Please switch network to { networkName }.",
55 | processing: "Processing...",
56 | copy: "copy",
57 | nftListTitle: "Recently crowd-minted NFTs",
58 | recentNFTs: "Recently minted NFTs",
59 | resources: "Resources",
60 | loading: "Loading...",
61 | },
62 | storyView: {
63 | message:
64 | 'You are able to see the list of vector assets already uploaded to the blockchain by minters. Please select "group" and "category", and click one of images below. It will explain how to access the vector data.',
65 | totalAssetCount: "Total Asset Count",
66 | loadingGroups: "Loading groups...",
67 | loadingCategories: "Loading categories...",
68 | loadingAssets: "Loading assets...",
69 | selectAssets: "Please select one of assets below.",
70 | sampleCodeMessage: "A sample code to fetch the SVG image of this asset.",
71 | fetchedImage: 'The fetched "svg" data.',
72 | },
73 | assetPanel: {
74 | mint: "Please select one of images below and the follow the instruction displayed further below.",
75 | thanks:
76 | "Thanks to all the minters, the current release of { totalCount } NFTs were sold out. We are going to add more NTFs soon. Please stay tuned!",
77 | },
78 | dfraw: {
79 | remix: "Remix",
80 | overlay: "Overlay",
81 | },
82 | assetPicker: {
83 | chooseProvider: "Choose a provider",
84 | chooseGroup: "Choose a group",
85 | chooseCategory: "Choose a category",
86 | },
87 | draw: {
88 | message1:
89 | "This is a service, which allows you to create your own drawing and mint a fully on-chain NFT with it.",
90 | message2:
91 | "It is a free mint if you draw everything from scratch (the gas fee is required).",
92 | message3:
93 | "You may also create a drawing based on somebody else's drawing (remix),",
94 | message4:
95 | "or place various on-chain assets (such as Nouns and Kamon) on top of your drawing (overlay).",
96 | message5:
97 | "We charge a small fee for remix and overlay, and distribute it to original creators.",
98 | message6: "It means you can earn money from your drawing (Draw2Earn)!",
99 | message7: "Please click the 'Plus' icon below to start drawing.",
100 | message8:
101 | "After drawing it, close the canvas ('X' button at the top-right cornder) and mint it.",
102 | message9: "",
103 | },
104 | languages,
105 | };
106 |
107 | export default lang;
108 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import en from "./en";
2 | import ja from "./ja";
3 |
4 | const messages = {
5 | en,
6 | ja,
7 | };
8 |
9 | const config = {
10 | locale: "en",
11 | messages,
12 | };
13 |
14 | export const languages = Object.keys(messages);
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/src/i18n/ja.ts:
--------------------------------------------------------------------------------
1 | import languages from "./languages";
2 |
3 | const lang = {
4 | menu: {
5 | connect: "接続",
6 | connected: "接続済み",
7 | nometamask: "メタマスクがありません",
8 | },
9 | header: {
10 | home: "ホーム",
11 | material: "マテリアル",
12 | kamon: "家紋",
13 | emoji: "絵文字",
14 | flags: "国旗",
15 | },
16 | keyMessage: {
17 | message1:
18 | "これは、フル・オンチェーンNFTの表現力をより高めるために、ブロック・チェーン上にさまざまなベクトル画像をアップロードし、人類の共有アセットとして活用しようという「On-chain Asset Store」プロジェクトの一環です。",
19 | message1a: "このプロジェクトの詳細は、",
20 | message1b: "を参照してください。",
21 | message2:
22 | "大量のベクトル画像をチェーン上にアップロードするには多くの「ガス代」が必要ですが、それをNFTをミントする方に少しつづ負担していただく「クラウドミント」への参加をみなさんにお願いしています。",
23 | },
24 | mint: {
25 | switchNetwork: "ネットワークを切り替えて下さい。",
26 | switchNetworkButton: "ネットワーク切り替え",
27 | connectMetamask: "Metamaskと接続してください。",
28 | },
29 | mintPanel: {
30 | writeName: "Asset Storeに刻み込む名前。",
31 | maxLength: "最大32バイト。",
32 | cc0Message:
33 | "ここで投稿した画像は、cc0(パブリックドメイン)として他の方に自由に再利用・加工していただくことが可能になりますので、そこに同意した上でミントして下さい。",
34 | drawMessage:
35 | "全部自分で描いた場合はフリーミントですが(画像の複雑度に応じたガス代は必要です)、リミックスやオーバーレイを活用した場合は{mintPrice}ETHが必要です。このミント代の97.5%はクリエーターに渡されますが、逆に、自分が描いた絵が他の人に利用された場合、それがあなたの収入になります(Draw2Earn)。",
36 | mintMessage1:
37 | "フリーミントですが、ガス代が{ low }〜{ high } ETH程度かかります(画像の複雑さや混雑状況によって大きく変動)。",
38 | mintMessage2:
39 | "クラウドミンティングにご協力していただいた方には、「プライマリーNFT」と呼ばれるあなたがクラウドミンティングに協力した証のNFT1つと、転売用の「ボーナスNFT」を{ bonousTokensPerAsset}つ、合計{ tokensPerAsset }つのNFTを発行します。",
40 | placeHolder: "お名前(オプション、Twitter名推奨)",
41 | thanks:
42 | "クラウドミンティングにご協力ありがとうございます。ブロックチェーンへの反映には少し時間がかかります。順調に反映されれば、このメッセージは自動的に消滅します。",
43 | sorry:
44 | "残念ながら、他のユーザーによりちょうどミントされたところです。別の画像を選択してください。",
45 | mint: "ミント",
46 | preparing: "ミントの準備中...",
47 | preparing2: "Preparing to mint...",
48 | error1: "以下のエラーメッセージを受け取りました。",
49 | error2: "再度、画像の選択からやり直してください。",
50 | },
51 | message: {
52 | notFoundMessage: "ページが見つかりません",
53 | hello: "こんにちは、世界",
54 | noAccount: "メタマスに接続してください",
55 | invalidNetwork: "ネットワークを切り替えてください { networkName }.",
56 | processing: "処理中...",
57 | copy: "コピー",
58 | nftListTitle: "最近クラウドミントされたNFT",
59 | recentNFTs: "最近ミントされたNFT",
60 | resources: "参考リンク",
61 | loading: "読み込み中です...",
62 | },
63 | storyView: {
64 | message:
65 | "以下は、これまでクラウドミントによりブロックチェーン上にセーブされたベクトルアセットで、他のスマートコントラクトからアクセスが可能になっています。グループ、カテゴリーを選び、表示されたイメージとクリックすると、アクセスの方法が表示されます。",
66 | totalAssetCount: "アセット総数",
67 | loadingGroups: "グループ読込中...",
68 | loadingCategories: "カテゴリー読込中...",
69 | loadingAssets: "アセット読込中...",
70 | selectAssets: "下からアセットを1つ選択してください",
71 | sampleCodeMessage: "このアセットのSVG画像を取得するサンプルコードです",
72 | fetchedImage: '取得した"SVG"のデータ',
73 | },
74 | assetPanel: {
75 | mint: "下に表示されている画像の一つをクリックし、下に表示されるミントボタンを押して下さい。",
76 | thanks:
77 | "今回の発行分({totalCount}個)に関しては、クラウドミントが完了いたししました。ご協力、ありがとうございます。さらにNFTを追加する予定なので、少々お待ちください。",
78 | },
79 | dfraw: {
80 | remix: "リミックス",
81 | overlay: "オーバーレイ",
82 | },
83 | assetPicker: {
84 | chooseProvider: "プロバイダーを選んで下さい",
85 | chooseGroup: "グループを選んで下さい",
86 | chooseCategory: "カテゴリーを選んで下さい",
87 | },
88 | draw: {
89 | message1:
90 | "これは、自由に絵を描いて、それをフルオンチェーンNFTとしてミントできるサービスです。",
91 | message2:
92 | "全てを自分で描く限りは、フリーミントです(絵の複雑さに応じたガス代が必要です)。",
93 | message3: "他の人の作品をベースに絵を描いたり(リミックス)、",
94 | message4:
95 | "ブロックチェーン上のアセットを貼り付けることも可能です(オーバーレイ)。",
96 | message5:
97 | "リミックスやオーバーレイをした作品をミントする場合は有料で、そのお金はクリエーターたちに配布されます。",
98 | message6:
99 | "つまり、あなた自身がクリエーターとしてお金を稼ぐことも可能なのです(Draw2Earn)。",
100 | message7: "下のプラスアイコンをクリックして描き初めてください。",
101 | message8:
102 | "描き終わったら、右上のXボタンでキャンバスを閉じ、NFTをミントしてみて下さい。",
103 | message9: "",
104 | },
105 | languages,
106 | };
107 |
108 | export default lang;
109 |
--------------------------------------------------------------------------------
/src/i18n/languages.ts:
--------------------------------------------------------------------------------
1 | const lang = {
2 | ja: "日本語",
3 | en: "English",
4 | };
5 |
6 | export default lang;
7 |
--------------------------------------------------------------------------------
/src/i18n/utils.ts:
--------------------------------------------------------------------------------
1 | import { App, computed, watch } from "vue";
2 | import { useI18n } from "vue-i18n";
3 | import { useRoute } from "vue-router";
4 |
5 | export const i18nUtils = (app: App) => {
6 | app.config.globalProperties.localizedUrl = (path: string) => {
7 | const lang = app.config.globalProperties.$route.params.lang;
8 | if (lang) {
9 | return `/${lang}` + path;
10 | }
11 | return path;
12 | };
13 | };
14 |
15 | export const useI18nParam = () => {
16 | const route = useRoute();
17 | const i18n = useI18n();
18 |
19 | const lang = computed(() => {
20 | return (route.params.lang as string) || "en";
21 | });
22 | watch(lang, () => {
23 | i18n.locale.value = lang.value;
24 | });
25 | i18n.locale.value = lang.value;
26 | };
27 |
28 | export const useLocalizedPath = () => {
29 | const route = useRoute();
30 | const getLocalizedPath = (path: string) => {
31 | const lang = (route.params.lang as string) || "en";
32 | return `/${lang}` + path;
33 | };
34 | return {
35 | getLocalizedPath,
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* ./src/index.css */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import { createI18n } from "vue-i18n";
3 | import App from "./App.vue";
4 | import "./index.css";
5 | import router from "./router";
6 | import store from "./store";
7 |
8 | import i18nConf from "./i18n/index";
9 | import { i18nUtils } from "./i18n/utils";
10 |
11 | const i18n = createI18n(i18nConf);
12 |
13 | const app = createApp(App);
14 | app.use(store);
15 | app.use(router);
16 | app.use(i18n);
17 | app.use(i18nUtils);
18 |
19 | app.mount("#app");
20 |
--------------------------------------------------------------------------------
/src/models/asset.ts:
--------------------------------------------------------------------------------
1 | export interface AssetData {
2 | name: string;
3 | assetId: number;
4 | image: string;
5 | index: number;
6 | svg: string;
7 | }
8 |
9 | // for import data
10 | export interface OriginalAssetPart {
11 | body: string | Uint8Array;
12 | color?: string;
13 | }
14 |
15 | export interface AssetBase {
16 | width: number;
17 | height: number;
18 | minter: string;
19 | name: string;
20 | group?: string;
21 | category?: string;
22 | svgPart?: string;
23 | image?: string;
24 | svg?: string;
25 | metadata?: Uint8Array;
26 | parts?: OriginalAssetPart[];
27 | }
28 |
29 | export interface OriginalAssetData {
30 | name: string;
31 | group?: string;
32 | category?: string;
33 | width?: number;
34 | height?: number;
35 | bodies?: string[];
36 | parts?: OriginalAssetPart[];
37 | body?: string;
38 | svgPart?: string;
39 | registered?: boolean;
40 | minter?: string;
41 | soulbound?: string;
42 | }
43 | export interface OriginalAssetDataSet {
44 | group?: string;
45 | category?: string;
46 | width?: number;
47 | height: number;
48 | assets: OriginalAssetData[];
49 | }
50 |
51 | export interface MintSelectionAsset {
52 | isLoading?: boolean;
53 | asset: OriginalAssetData;
54 | images?: string[];
55 | soulbound?: string;
56 | }
57 |
--------------------------------------------------------------------------------
/src/models/point.ts:
--------------------------------------------------------------------------------
1 | export interface Point {
2 | x: number;
3 | y: number;
4 | c: boolean; // true:line, false:bezier
5 | r: number; // ratio (0 to 1)
6 | }
7 |
8 | export enum LayerType {
9 | NONE,
10 | REMIX,
11 | LAYER,
12 | OVERLAY,
13 | }
14 |
15 | export interface Layer {
16 | points: Point[];
17 | color: string;
18 | path: string;
19 | svgImage: string;
20 | }
21 |
22 | export interface Transform {
23 | tx: number;
24 | ty: number;
25 | scale: number;
26 | rotate: number;
27 | }
28 |
29 | export const identityTransform: Transform = {
30 | tx: 0,
31 | ty: 0,
32 | scale: 1,
33 | rotate: 0,
34 | };
35 |
36 | export const transformString = (xf: Transform) => {
37 | if (
38 | xf.tx == identityTransform.tx &&
39 | xf.ty == identityTransform.ty &&
40 | xf.scale == identityTransform.scale &&
41 | xf.rotate == identityTransform.rotate
42 | ) {
43 | return "";
44 | }
45 | const d = Math.round(512 * (xf.scale - 1));
46 | return (
47 | `translate(${xf.tx - d} ${xf.ty - d}) ` +
48 | `scale(${xf.scale}) rotate(${xf.rotate} 512 512)`
49 | );
50 | };
51 |
52 | export const transformStyle = (xf: Transform, ratio: number) => {
53 | return (
54 | `translate(${xf.tx * ratio}px,${xf.ty * ratio}px)` +
55 | ` scale(${xf.scale}) rotate(${xf.rotate}deg) `
56 | );
57 | };
58 |
59 | export interface Overlay {
60 | provider: string;
61 | assetId: number;
62 | fill: string;
63 | transform: Transform;
64 | image: string; // cached svg image (base64)
65 | svgPart: string; // cached svg part
66 | svgTag: string; // cached svg tag
67 | }
68 |
69 | export interface Remix {
70 | tokenId: number; // remix tokenId
71 | color?: string;
72 | transform: Transform;
73 | image?: string; // cached svg image
74 | svgPart?: string; // cached svgPart
75 | svgTag?: string; // cached svg tag
76 | }
77 |
78 | export interface Drawing {
79 | remix: Remix;
80 | layers: Layer[];
81 | overlays: Overlay[];
82 | stroke: number; // optional stroke width
83 | }
84 |
85 | // asset,
86 | // props.remixId, // remixId
87 | // "", // color
88 | // "", // transform
89 | // [] // overlays
90 | /*
91 | [{
92 | assetId: 54,
93 | provider: "asset",
94 | fill: "blue",
95 | transform: "scale(0.4, 0.4)"
96 | }]
97 | */
98 |
99 | export const pathFromPoints = (points: Point[]) => {
100 | const length = points.length;
101 | return points.reduce((path, cursor, index) => {
102 | const prev = points[(index + length - 1) % length];
103 | const next = points[(index + 1) % length];
104 | const sx = (cursor.x + prev.x) / 2;
105 | const sy = (cursor.y + prev.y) / 2;
106 | const head = index == 0 ? `M${sx},${sy},` : "";
107 | const ex = (cursor.x + next.x) / 2;
108 | const ey = (cursor.y + next.y) / 2;
109 | const last = `${ex},${ey}`;
110 | if (cursor.c) {
111 | return path + head + `L${cursor.x},${cursor.y},` + last;
112 | }
113 | const c1x = sx + cursor.r * (cursor.x - sx);
114 | const c1y = sy + cursor.r * (cursor.y - sy);
115 | const c2x = ex + cursor.r * (cursor.x - ex);
116 | const c2y = ey + cursor.r * (cursor.y - ey);
117 | return path + head + `C${c1x},${c1y},${c2x},${c2y},` + last;
118 | }, "");
119 | };
120 |
121 | const svgHead =
122 | '`;
126 | const svg =
127 | svgHead + '' + svgTail;
128 | const image =
129 | "data:image/svg+xml;base64," + Buffer.from(svg).toString("base64");
130 | return image;
131 | };
132 |
133 | export const svgImageFromDrawing = (drawing: Drawing) => {
134 | const paths = drawing.layers.map((layer) => {
135 | return ``;
136 | });
137 | const svg = svgHead + "\n" + paths.join("\n") + "\n\n";
138 | return "data:image/svg+xml;base64," + Buffer.from(svg).toString("base64");
139 | };
140 |
141 | export const togglePointType = (points: Point[], index: number) => {
142 | return points.map((point, _index) => {
143 | if (_index == index) {
144 | return { x: point.x, y: point.y, c: !point.c, r: point.r };
145 | }
146 | return point;
147 | });
148 | };
149 |
150 | export const splitPoint = (points: Point[], index: number) => {
151 | const prev = points[index];
152 | const next = points[(index + 1) % points.length];
153 | const newItem = {
154 | x: (prev.x + next.x) / 2,
155 | y: (prev.y + next.y) / 2,
156 | c: false,
157 | r: prev.r,
158 | };
159 | const array = points.map((point) => point);
160 | array.splice(index + 1, 0, newItem);
161 | return array;
162 | };
163 |
--------------------------------------------------------------------------------
/src/models/token.ts:
--------------------------------------------------------------------------------
1 | export interface Token {
2 | tokenId: number;
3 | image: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/resources/crypto.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | const crypto = {
4 | group: "Crypto",
5 | category: "Logos",
6 | width: 4091.27,
7 | height: 4091.73,
8 | assets: [
9 | /*
10 | {
11 | name: "Bitcoin Circle",
12 | parts: [
13 | {
14 | body: "M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z",
15 | },
16 | ],
17 | },
18 | */
19 | {
20 | name: "Bitcoin Logo",
21 | parts: [
22 | {
23 | body: "M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z",
24 | },
25 | ],
26 | },
27 | ],
28 | };
29 |
30 | export const loadedAssets = loadAssets(crypto);
31 |
--------------------------------------------------------------------------------
/src/resources/emoji.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | import { assets } from "../../generated/emoji_assets";
4 |
5 | export const actions = {
6 | group: "OpenMoji (CC BY-SA 4.0)",
7 | category: "Flags",
8 | width: 72,
9 | height: 72,
10 | assets: assets,
11 | };
12 |
13 | export const loadedAssets = loadAssets(actions);
14 |
--------------------------------------------------------------------------------
/src/resources/kamon.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | import { assets } from "../../generated/kamon_assets";
4 |
5 | export const actions = {
6 | group: "Hakko Daiodo (CC-BY equivalent)",
7 | category: "Suhama",
8 | width: 24,
9 | height: 24,
10 | assets: assets,
11 | };
12 |
13 | export const loadedAssets = loadAssets(actions);
14 |
--------------------------------------------------------------------------------
/src/resources/materials.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | import { assets } from "../../generated/hardware";
4 |
5 | export const actions = {
6 | group: "Material Icons (Apache 2.0)",
7 | category: "Logos",
8 | width: 24,
9 | height: 24,
10 | assets: assets,
11 | };
12 |
13 | export const loadedAssets = loadAssets(actions);
14 |
--------------------------------------------------------------------------------
/src/resources/multi.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | const misc = {
4 | group: "Misc.",
5 | category: "Standard",
6 | width: 24,
7 | height: 24,
8 | assets: [
9 | {
10 | name: "Done",
11 | parts: [
12 | {
13 | color: "",
14 | body: "M2 2h15v15h-15z",
15 | },
16 | {
17 | color: "yellow",
18 | body: "M5 5h15v15h-15z",
19 | },
20 | {
21 | color: "blue",
22 | body: "M7 7h15v15h-15z",
23 | },
24 | {
25 | color: "#ff000080",
26 | body: "M9 9h15v15h-15z",
27 | },
28 | ],
29 | },
30 | ],
31 | };
32 |
33 | export const multiAssets = loadAssets(misc);
34 |
--------------------------------------------------------------------------------
/src/resources/openemoji.ts:
--------------------------------------------------------------------------------
1 | import { loadAssets } from "../utils/createAsset";
2 |
3 | export const emojis = {
4 | group: "Open Emoji (CC BY-SA 2.0)",
5 | category: "Extra",
6 | width: 72,
7 | height: 72,
8 | assets: [
9 | {
10 | name: "Female Nurse",
11 | parts: [
12 | {
13 | color: "#fff",
14 | body: "M16.6716,60.8806S14.5412,47.1,26.5412,47.1a15.3055,15.3055,0,0,0,9.1305,2.673l.2749.1a16.9557,16.9557,0,0,0,9.5016-3.052c11.5646-.0346,9.1074,13.96,9.3384,14.06",
15 | },
16 | {
17 | color: "#d0cfce",
18 | body: "M45.0221,46.2625a18.2742,18.2742,0,0,1-8.87,3.514c13.1.4,13.1,7.1,13.8,11.1h5.1S57.0221,46.2625,45.0221,46.2625Z",
19 | },
20 | {
21 | color: "#fff",
22 | body: "M27.1022,21.99V14.02s6.72.26,9.19-2.64c2.47,2.9,9.19,2.64,9.19,2.64v7.94l-9.07-.88Z",
23 | },
24 | {
25 | color: "#fcea2b",
26 | body: "M45.8668,22.844l-9.9942-1.593L25.0811,28.5725a17.718,17.718,0,0,0-.1965,2.641c0,7.8276,5.0765,14.1732,11.3386,14.1732s11.3386-6.3456,11.3386-14.1732A21.8907,21.8907,0,0,0,45.8668,22.844Z",
27 | },
28 | {
29 | color: "#a57939",
30 | body: "M31.77,43.1l-3.17.02h-.33a13.2769,13.2769,0,0,1-2.95-.41c-.33-.08-.66-.17-.98-.28-4.15-1.5-6.52-6.3.21-16.12.52-.76,1.09-1.56,1.73-2.38h1a.8524.8524,0,0,1-.03.23.6789.6789,0,0,1-.07.2c-.02.05-.05.1-.08.16l-.02.02c-.32.6-1.2467,1.1207-1.9167,2.0307-.26.35-.4525,1.1449-.7325,1.5349.5-.19,1.3092-.5456,1.9192-.7656a14.483,14.483,0,0,0-.39,2.28c-.05.53-.08,1.06-.08,1.59C25.88,36.44,28.29,40.97,31.77,43.1Z",
31 | },
32 | {
33 | color: "#a57939",
34 | body: "M44.16,43.02h-.22l-3.18.02c3.43-2.14,5.8-6.64,5.8-11.83a15.9119,15.9119,0,0,0-1.53-6.89,1.0707,1.0707,0,0,1-.1-.39h1.22c.64.83,1.23,1.63,1.75,2.4C56.59,38.98,50.04,43.02,44.16,43.02Z",
35 | },
36 | {
37 | color: "#a57939",
38 | body: "M35.297,21.46c-2.9757-1.1514-8.2325,1.36-8.2325,1.36a12.5967,12.5967,0,0,0-2.1279,6.2442c5.63-1.3521,12.8372-7.1512,12.8372-7.1512C37.42,21.7047,35.6847,21.61,35.297,21.46Z",
39 | },
40 | {
41 | color: "#000",
42 | body: "M54.8545,60.9756a.9889.9889,0,0,1-.1524-.0108,1,1,0,0,1-.8378-1.1386c.0087-.0576.8271-5.7412-2.1289-9.1778-1.4825-1.7226-3.711-2.62-6.627-2.67l-.0156.0069c-3.29,2.22-5.9795,3.5908-9.1407,3.5908-.0107,0-.122,0-.1328-.001-3.1338-.1035-5.9423-1.5059-9.1591-3.5947a13.8012,13.8012,0,0,0-3.8125.6972c-2.9668.9043-4.335,4.7686-4.3477,4.8077a15.343,15.343,0,0,0-.56,6.3408,1,1,0,0,1-1.9765.3027,17.1466,17.1466,0,0,1,.6435-7.292c.0713-.2041,1.7276-4.875,5.6572-6.0723a15.4926,15.4926,0,0,1,4.6875-.788.9965.9965,0,0,1,.5489.164c3.13,2.0537,5.624,3.3448,8.3847,3.4365,2.7549-.0009,5.1739-1.2744,8.2061-3.3291a1.05,1.05,0,0,1,.123-.0712,1.1069,1.1069,0,0,1,.6377-.2c3.6563,0,6.4844,1.1357,8.4073,3.3769,3.5537,4.1426,2.622,10.5059,2.581,10.7754A.9991.9991,0,0,1,54.8545,60.9756ZM44.6523,46.0762h0Z",
43 | },
44 | {
45 | color: "#000",
46 | body: "M30.6523,56.877a1.0029,1.0029,0,0,1-.9238-.6163l-3.7-8.9a1,1,0,1,1,1.8477-.7676L30.99,54.084l4.041-4.2012A1,1,0,0,1,36.4727,51.27l-5.1,5.3008A.9979.9979,0,0,1,30.6523,56.877Z",
47 | },
48 | {
49 | color: "#000",
50 | body: "M38.3525,54.1768a.9969.9969,0,0,1-.707-.293l-2.6006-2.6006a1,1,0,1,1,1.4141-1.4141L39.06,52.47a1,1,0,0,1-.7071,1.7071Z",
51 | },
52 | {
53 | color: "#000",
54 | body: "M40.9521,56.877a.9982.9982,0,0,1-.7207-.3067l-2.6-2.7a1,1,0,0,1,1.4414-1.3867l1.5459,1.6055,3.087-7.3418a1.0228,1.0228,0,0,1,.9462-.7207.9581.9581,0,0,1,1,.95v.1a.9993.9993,0,0,1-.0781.3877l-3.7,8.8007a1.0027,1.0027,0,0,1-.7383.5958A1.0731,1.0731,0,0,1,40.9521,56.877Z",
55 | },
56 | {
57 | color: "#000",
58 | body: "M50.8593,59.3765h-5.8a1,1,0,0,1,0-2h5.8a.9448.9448,0,0,1,1,1A1.0021,1.0021,0,0,1,50.8593,59.3765Z",
59 | },
60 | {
61 | color: "#000",
62 | body: "M48.1345,42.6223a1,1,0,0,1-.3261-1.9453,4.4658,4.4658,0,0,0,3.1045-2.8877c.5957-1.9932.1142-5.9087-5.5577-13.2491a1,1,0,0,1,.18-1.4028.8264.8264,0,0,1,1.2505.1717c4.9707,6.4316,7.104,11.5016,6.0435,15.0524a6.4352,6.4352,0,0,1-4.3682,4.2061A1.0033,1.0033,0,0,1,48.1345,42.6223Z",
63 | },
64 | {
65 | color: "#000",
66 | body: "M28.1808,23.065a.841.841,0,0,1-.0335.2407.6824.6824,0,0,1-.0779.2093c-.0223.0523-.3552-.2571-.3886-.1943-.0111.0105.4065-.08.3953-.07a52.6931,52.6931,0,0,0-3.5134,5.0364c-3.0615,4.7411-3.3064,7.5772-2.8278,9.1994A4.6947,4.6947,0,0,0,24.8966,40.48a1.0281,1.0281,0,0,1,.6346,1.3605,1.1225,1.1225,0,0,1-1.0354.6594,1.1425,1.1425,0,0,1-.4119-.0733,6.7591,6.7591,0,0,1-4.4864-4.3851c-1.091-3.7049,1.1244-8.9693,6.59-15.615a1.1316,1.1316,0,0,1,.8238-.4082h.0111a1.2774,1.2774,0,0,1,.4008.0523.8962.8962,0,0,1,.1782.0733.47.47,0,0,1,.1558.0942.9147.9147,0,0,1,.1781.1779.5569.5569,0,0,1,.1113.1674.5225.5225,0,0,1,.0891.1989A.833.833,0,0,1,28.1808,23.065Z",
67 | },
68 | {
69 | color: "#000",
70 | body: "M48.56,31.21a16.572,16.572,0,0,1-4.62,11.81,10.5423,10.5423,0,0,1-15.34.1,16.5692,16.5692,0,0,1-4.72-11.91c0-.26.01-.52.02-.78a15.1192,15.1192,0,0,1,.13-1.51,16.0793,16.0793,0,0,1,.52-2.61,16.2982,16.2982,0,0,1,1.06-2.86,1,1,0,0,1,.62-.52h.01a.1268.1268,0,0,1,.06-.01.8025.8025,0,0,1,.3.06.2843.2843,0,0,1,.12.04c.01.01.03.02.04.03a.42.42,0,0,1,.14.09.8519.8519,0,0,1,.16.17.5372.5372,0,0,1,.1.16.5167.5167,0,0,1,.08.19.8423.8423,0,0,1,.04.27.8524.8524,0,0,1-.03.23.6789.6789,0,0,1-.07.2c-.02.05-.05.1-.08.16l-.02.02c-.32.6-2.3129,1.0664-2.3091,2.1964.0017.5016.4862,1.0156-.0909,1.2336.5-.19,1.06-.41,1.67-.63a14.483,14.483,0,0,0-.39,2.28c-.05.53-.08,1.06-.08,1.59,0,5.23,2.41,9.76,5.89,11.89a8.368,8.368,0,0,0,8.99-.06c3.43-2.14,5.8-6.64,5.8-11.83a15.9119,15.9119,0,0,0-1.53-6.89,7.9907,7.9907,0,0,1-.3725-1.0342,1.0183,1.0183,0,0,1,.6163-.9069c.5-.24.8391-.7107,1.1162-.0582.3952.93,1.24,3.0093,1.51,4.0093A18.0156,18.0156,0,0,1,48.56,31.21Z",
71 | },
72 | {
73 | color: "#000",
74 | body: "M42.2233,29.8527a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2Z",
75 | },
76 | {
77 | color: "#000",
78 | body: "M34.2233,29.8527a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2Z",
79 | },
80 | {
81 | color: "#000",
82 | body: "M36.2232,39.8551a7.6528,7.6528,0,0,1-3.4473-.8579,1,1,0,0,1,.8946-1.7891,5.3772,5.3772,0,0,0,5.1054,0,1,1,0,0,1,.8946,1.7891A7.6528,7.6528,0,0,1,36.2232,39.8551Z",
83 | },
84 | {
85 | color: "#000",
86 | body: "M45.4834,23.6934a1.01,1.01,0,0,1-.3-.0464,29.9057,29.9057,0,0,0-17.794.0039,1,1,0,1,1-.5722-1.916,31.8625,31.8625,0,0,1,18.9658.0039,1,1,0,0,1-.3,1.9546Z",
87 | },
88 | {
89 | color: "#000",
90 | body: "M35.4936,10.7755c.02-.01.03-.03.04-.04a.9078.9078,0,0,1,.47-.31.2663.2663,0,0,1,.12-.03.4954.4954,0,0,1,.17-.02.4678.4678,0,0,1,.17.02.2663.2663,0,0,1,.12.03.8966.8966,0,0,1,.36.19.7518.7518,0,0,1,.15.16c1.65,1.86,5.85,2.25,7.85,2.26.19,0,.36-.01.5-.01a.9781.9781,0,0,1,.73.27.9916.9916,0,0,1,.31.72v8.47a1,1,0,0,1-2,0v-7.47c-.22,0-.48-.01-.76-.03-2.04-.12-5.36-.57-7.43-2.24a4.7017,4.7017,0,0,1-.76-.72A.9943.9943,0,0,1,35.4936,10.7755Z",
91 | },
92 | {
93 | color: "#000",
94 | body: "M26.1036,22.4855v-8.47a1.0224,1.0224,0,0,1,.3-.72,1.0616,1.0616,0,0,1,.74-.28c.14.01.31.01.49.01,2.01,0,6.22-.38,7.86-2.25.02-.01.03-.03.04-.04a.9078.9078,0,0,1,.47-.31.2663.2663,0,0,1,.12-.03.4954.4954,0,0,1,.17-.02.4678.4678,0,0,1,.17.02.2663.2663,0,0,1,.12.03.8966.8966,0,0,1,.36.19.7518.7518,0,0,1,.15.16.9943.9943,0,0,1-.04,1.25,4.7017,4.7017,0,0,1-.76.72c-2.07,1.67-5.39,2.12-7.43,2.24-.28.02-.54.03-.76.03v7.47a1,1,0,0,1-2,0Z",
95 | },
96 | {
97 | color: "#000",
98 | body: "M36.7693,14.9273v1.4419h1.4419v1.0151H36.7693v1.4419H35.7542V17.3843H34.3123V16.3692h1.4419V14.9273h1.0151m0-1H35.7542a1,1,0,0,0-1,1v.4419h-.4419a1,1,0,0,0-1,1v1.0151a1,1,0,0,0,1,1h.4419v.4419a1,1,0,0,0,1,1h1.0151a1,1,0,0,0,1-1v-.4419h.4419a1,1,0,0,0,1-1V16.3692a1,1,0,0,0-1-1h-.4419v-.4419a1,1,0,0,0-1-1Z",
99 | },
100 | {
101 | color: "#000",
102 | body: "M25.2158,30.3091a.9989.9989,0,0,1-.9746-1.2222c.001-.0493.001-.1128.002-.189a11.5742,11.5742,0,0,1,1.9961-6.642,1.0021,1.0021,0,0,1,.5039-.3824c.2607-.0883,6.4521-2.1626,11.6093-1.51a1,1,0,0,1,.542,1.7378c-.3095.2763-7.6367,6.7856-13.4453,8.18A.9741.9741,0,0,1,25.2158,30.3091Zm0-1h0Zm2.4893-5.6431a9.4243,9.4243,0,0,0-1.419,4.2735,37.8381,37.8381,0,0,0,9.3028-5.691A32.7557,32.7557,0,0,0,27.7051,23.666Z",
103 | },
104 | ],
105 | },
106 | ],
107 | };
108 |
109 | export const emojiAssets = loadAssets(emojis);
110 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
2 | import Layout from "../components/Layout.vue";
3 | import Blank from "../components/Blank.vue";
4 | import NotFound from "../components/NotFound.vue";
5 |
6 | import Assets from "../views/Assets.vue";
7 | import Materials from "../views/Materials.vue";
8 | import Kamons from "../views/Kamons.vue";
9 | import Emoji from "../views/Emoji.vue";
10 |
11 | const routeChildren: Array = [
12 | {
13 | path: "",
14 | component: Assets,
15 | },
16 | {
17 | path: "material",
18 | component: Materials,
19 | },
20 | {
21 | path: "kamon",
22 | component: Kamons,
23 | },
24 | {
25 | path: "emoji",
26 | component: Emoji,
27 | },
28 | {
29 | path: "group/:group?/:path(category)?/:category?",
30 | component: Assets,
31 | },
32 | ];
33 |
34 | const routes: Array = [
35 | {
36 | path: "/",
37 | component: Layout,
38 | children: [
39 | {
40 | path: "/:lang(en|ja)",
41 | component: Blank,
42 | children: routeChildren,
43 | },
44 | {
45 | path: "",
46 | component: Blank,
47 | children: routeChildren,
48 | },
49 | ],
50 | },
51 | {
52 | path: "/:page(.*)",
53 | name: "NotFoundPage",
54 | component: Layout,
55 | children: [
56 | {
57 | path: "",
58 | component: NotFound,
59 | },
60 | ],
61 | },
62 | ];
63 |
64 | const router = createRouter({
65 | history: createWebHistory(process.env.BASE_URL),
66 | routes,
67 | });
68 |
69 | export default router;
70 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | import type { DefineComponent } from 'vue'
4 | const component: DefineComponent<{}, {}, any>
5 | export default component
6 | }
7 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex";
2 | import { startMonitoringMetamask } from "../utils/MetaMask";
3 |
4 | interface State {
5 | ethereum: any | null;
6 | chainId: string | null;
7 | account: undefined | null | string;
8 | windowWidth: number;
9 | }
10 |
11 | export default createStore({
12 | state: {
13 | ethereum: null,
14 | chainId: null,
15 | account: undefined,
16 | windowWidth: 500,
17 | },
18 | mutations: {
19 | setEthereum(state: State, ethereum: any | null) {
20 | state.ethereum = ethereum;
21 | if (state.ethereum) {
22 | startMonitoringMetamask();
23 | }
24 | },
25 | setChainId(state: State, chainId: string | null) {
26 | state.chainId = chainId;
27 | },
28 | setAccount(state: State, account: string | null) {
29 | state.account = account && account.toLowerCase();
30 | },
31 | setWindowWidth(state: State, windowWidth: number) {
32 | state.windowWidth = windowWidth;
33 | },
34 | },
35 | getters: {
36 | hasMetaMask: (state: State) => {
37 | return state.ethereum && state.ethereum.isMetaMask;
38 | },
39 | displayAccount: (state: State) => {
40 | const account = state.account;
41 | if (!account) {
42 | return "";
43 | }
44 | return account.substring(0, 6) + "..." + account.substring(38);
45 | },
46 | },
47 | actions: {},
48 | modules: {},
49 | });
50 |
--------------------------------------------------------------------------------
/src/utils/Drag.ts:
--------------------------------------------------------------------------------
1 | import { ref, Ref, ComputedRef, computed } from "vue";
2 |
3 | import {
4 | useCanvasParams,
5 | useCanvasAsset,
6 | Pos,
7 | UIPos,
8 | Tools,
9 | RotationInfo,
10 | } from "@/utils/canvasUtil";
11 |
12 | import {
13 | Point,
14 | LayerType,
15 | identityTransform,
16 | Transform,
17 | Drawing,
18 | } from "@/models/point";
19 |
20 | // const { curw, curh } = canvasParams;
21 |
22 | export const useDrag = (
23 | pointIndex: Ref,
24 | moveToolPos: ComputedRef,
25 | cursors: Ref,
26 | recordState: () => void,
27 | currentLayerType: Ref,
28 | currentDrawing: Ref,
29 | overlayIndex: Ref,
30 | canvasOffset: Ref
31 | ) => {
32 | const { getOffsetX, getOffsetY, canvastoAsset, canvasParams } =
33 | useCanvasParams();
34 | const { getAssetPos } = useCanvasAsset(canvasOffset);
35 | const remixTransform = computed(() => {
36 | return currentDrawing.value.remix.transform;
37 | });
38 |
39 | let pivotPos: Pos = { x: 0, y: 0 };
40 | let offsetX = 0;
41 | let offsetY = 0;
42 | let startPoint: Pos = { x: 0, y: 0 };
43 | let initialCursors: Point[] = [];
44 | let initialTransform = identityTransform;
45 | const grid = ref(0);
46 |
47 | const currentTool = ref(0);
48 |
49 | const toggleGrid = () => {
50 | switch (grid.value) {
51 | case 0:
52 | grid.value = 8;
53 | break;
54 | case 128:
55 | grid.value = 0;
56 | break;
57 | default:
58 | grid.value = grid.value * 2;
59 | break;
60 | }
61 | };
62 |
63 | // rotate and scale
64 | const dragToolHandleStart = (evt: DragEvent | TouchEvent, tool: Tools) => {
65 | currentTool.value = tool;
66 | offsetX = canvasParams.value.curw / 2;
67 | offsetY = canvasParams.value.curh / 2;
68 | startPoint = getAssetPos(evt);
69 | pivotPos = canvastoAsset(moveToolPos.value);
70 | initialCursors = cursors.value;
71 | initialTransform =
72 | currentLayerType.value == LayerType.OVERLAY
73 | ? currentDrawing.value.overlays[overlayIndex.value].transform
74 | : remixTransform.value;
75 | recordState();
76 | };
77 | // move
78 | const dragLayerImgStart = (evt: MouseEvent | TouchEvent) => {
79 | currentTool.value = Tools.MOVE;
80 | offsetX = canvasParams.value.curw / 2;
81 | offsetY = canvasParams.value.curh / 2;
82 | startPoint = getAssetPos(evt);
83 | initialCursors = cursors.value;
84 | initialTransform =
85 | currentLayerType.value == LayerType.OVERLAY
86 | ? currentDrawing.value.overlays[overlayIndex.value].transform
87 | : remixTransform.value;
88 | recordState();
89 | };
90 | // cursors
91 | const dragStart = (evt: DragEvent | TouchEvent, index: number) => {
92 | currentTool.value = Tools.CURSOR;
93 | offsetX = getOffsetX(evt);
94 | offsetY = getOffsetY(evt);
95 | pointIndex.value = index;
96 | recordState();
97 | };
98 | const dragOver = (evt: DragEvent | TouchEvent) => {
99 | const g = grid.value;
100 | const assetPos = getAssetPos(evt);
101 | const gridder = (pos: Pos): Pos => {
102 | const f = (n: number) => (g == 0 ? n : Math.round(n / g) * g);
103 | return {
104 | x: f(pos.x),
105 | y: f(pos.y),
106 | };
107 | };
108 | const limiter = (pos: Pos): Pos => {
109 | const f = (can: number, n: number, offset: number, cur: number) =>
110 | Math.max(0, Math.min(can - g - 1, n - offset + cur / 2));
111 | return {
112 | x: f(canvasParams.value.assw, pos.x, offsetX, canvasParams.value.curw),
113 | y: f(canvasParams.value.assh, pos.y, offsetY, canvasParams.value.curh),
114 | };
115 | };
116 | const magnification =
117 | currentTool.value === Tools.ZOOM &&
118 | Math.abs(pivotPos.y - startPoint.y) !== 0
119 | ? Math.abs(pivotPos.y - assetPos.y) /
120 | Math.abs(pivotPos.y - startPoint.y)
121 | : 0;
122 | const rad =
123 | currentTool.value === Tools.ROTATE
124 | ? pivotPos.x - startPoint.x > 0
125 | ? Math.atan2(pivotPos.y - assetPos.y, pivotPos.x - assetPos.x)
126 | : (Math.atan2(pivotPos.y - assetPos.y, pivotPos.x - assetPos.x) +
127 | Math.PI) %
128 | (2 * Math.PI)
129 | : 0;
130 | const RotationInfo: RotationInfo =
131 | currentTool.value === Tools.ROTATE
132 | ? {
133 | radian: rad,
134 | sin: Math.sin(rad),
135 | cos: Math.cos(rad),
136 | }
137 | : { radian: 0, sin: 0, cos: 0 };
138 |
139 | if (currentLayerType.value === LayerType.LAYER) {
140 | cursors.value = cursors.value.map((cursor, index) => {
141 | switch (currentTool.value) {
142 | case Tools.ZOOM:
143 | return {
144 | ...gridder(
145 | limiter({
146 | x:
147 | initialCursors[index].x * magnification +
148 | (pivotPos.x - pivotPos.x * magnification),
149 | y:
150 | initialCursors[index].y * magnification +
151 | (pivotPos.y - pivotPos.y * magnification),
152 | })
153 | ),
154 | c: cursor.c,
155 | r: cursor.r,
156 | };
157 | case Tools.ROTATE:
158 | return {
159 | ...gridder(
160 | limiter({
161 | x:
162 | (initialCursors[index].x - pivotPos.x) * RotationInfo.cos -
163 | (initialCursors[index].y - pivotPos.y) * RotationInfo.sin +
164 | pivotPos.x,
165 | y:
166 | (initialCursors[index].x - pivotPos.x) * RotationInfo.sin +
167 | (initialCursors[index].y - pivotPos.y) * RotationInfo.cos +
168 | pivotPos.y,
169 | })
170 | ),
171 | c: cursor.c,
172 | r: cursor.r,
173 | };
174 | case Tools.MOVE:
175 | return {
176 | ...gridder(
177 | limiter({
178 | x: initialCursors[index].x - (startPoint.x - assetPos.x),
179 | y: initialCursors[index].y - (startPoint.y - assetPos.y),
180 | })
181 | ),
182 | c: cursor.c,
183 | r: cursor.r,
184 | };
185 | case Tools.CURSOR:
186 | default:
187 | if (index == pointIndex.value) {
188 | return {
189 | ...gridder(
190 | limiter({
191 | x: assetPos.x,
192 | y: assetPos.y,
193 | })
194 | ),
195 | c: cursor.c,
196 | r: cursor.r,
197 | };
198 | }
199 | }
200 | return cursor;
201 | });
202 | }
203 | const updateTransform = (transform: Transform) => {
204 | // Note: We need to create a new instance in order to make it work with undo/redo.
205 | const tx = Object.assign({}, transform);
206 | switch (currentTool.value) {
207 | case Tools.MOVE: {
208 | const { x, y } = {
209 | ...gridder(limiter({ x: assetPos.x, y: assetPos.y })),
210 | };
211 | tx.tx = Math.round(x - canvasParams.value.assw / 2);
212 | tx.ty = Math.round(y - canvasParams.value.assh / 2);
213 | break;
214 | }
215 | case Tools.ZOOM: {
216 | tx.scale =
217 | Math.round(100 * initialTransform.scale * magnification) / 100;
218 | break;
219 | }
220 | case Tools.ROTATE: {
221 | tx.rotate = Math.round(
222 | initialTransform.rotate + (RotationInfo.radian / Math.PI) * 180
223 | );
224 | break;
225 | }
226 | }
227 | return tx;
228 | };
229 | if (currentLayerType.value === LayerType.REMIX) {
230 | const tx = updateTransform(remixTransform.value);
231 | const newDrawing: Drawing = Object.assign({}, currentDrawing.value);
232 | if (newDrawing.remix) {
233 | const newRemix = Object.assign({}, newDrawing.remix);
234 | newRemix.transform = tx;
235 | newDrawing.remix = newRemix;
236 | }
237 | currentDrawing.value = newDrawing;
238 | }
239 | if (currentLayerType.value === LayerType.OVERLAY) {
240 | const overlay = currentDrawing.value.overlays[overlayIndex.value];
241 | const tx = updateTransform(overlay.transform);
242 | const newDrawing: Drawing = Object.assign({}, currentDrawing.value);
243 | newDrawing.overlays = newDrawing.overlays.map((overlay, index) => {
244 | if (index == overlayIndex.value) {
245 | const newOverlay = Object.assign({}, overlay);
246 | newOverlay.transform = tx;
247 | return newOverlay;
248 | }
249 | return overlay;
250 | });
251 | currentDrawing.value = newDrawing;
252 | }
253 |
254 | evt.preventDefault();
255 | };
256 |
257 | return {
258 | dragLayerImgStart,
259 | dragToolHandleStart,
260 | dragStart,
261 | dragOver,
262 | toggleGrid,
263 | grid,
264 | };
265 | };
266 |
--------------------------------------------------------------------------------
/src/utils/MetaMask.ts:
--------------------------------------------------------------------------------
1 | import store from "../store";
2 |
3 | export const requestAccount = async () => {
4 | const ethereum = store.state.ethereum;
5 | if (!ethereum) {
6 | return null;
7 | }
8 | console.log(ethereum.request);
9 | const accounts = await ethereum.request({ method: "eth_requestAccounts" });
10 | return accounts.length > 0 ? accounts[0] : null;
11 | };
12 |
13 | export const getAccount = async (): Promise => {
14 | const ethereum = store.state.ethereum;
15 | if (!ethereum) {
16 | return null;
17 | }
18 | const accounts = await ethereum.request({ method: "eth_accounts" });
19 | return accounts.length > 0 ? accounts[0] : null;
20 | };
21 |
22 | // https://github.com/NoahZinsmeister/web3-react/blob/main/packages/types/src/index.ts
23 | // per EIP-1193
24 | export interface ProviderConnectInfo {
25 | readonly chainId: string;
26 | }
27 |
28 | export interface ProviderRpcError extends Error {
29 | message: string;
30 | code: number;
31 | data?: unknown;
32 | }
33 |
34 | export const ChainIds = {
35 | Mainnet: "0x1",
36 | RinkebyTestNet: "0x4",
37 | Goerli: "0x5",
38 | Polygon: "0x89",
39 | Localhost: "0x7a69", // not sure
40 | };
41 |
42 | export const initializeEthereum = () => {
43 | const setEthereum = () => {
44 | const ethereum = (window as any).ethereum;
45 | if (store.state.ethereum != ethereum) {
46 | store.commit("setEthereum", ethereum);
47 | }
48 | };
49 | const ethereum = (window as any).ethereum;
50 | if (ethereum) {
51 | setEthereum();
52 | } else {
53 | window.addEventListener(
54 | "ethereum#initialized",
55 | () => {
56 | setEthereum();
57 | },
58 | { once: true }
59 | );
60 | setTimeout(setEthereum, 30000); // 30 seconds in which nothing happens on android
61 | }
62 | };
63 |
64 | export const startMonitoringMetamask = () => {
65 | getAccount().then((value) => {
66 | console.log("Eth gotAccount", store.getters.displayAccount);
67 | store.commit("setAccount", value);
68 | const ethereum = store.state.ethereum;
69 | ethereum
70 | .request({
71 | method: "eth_chainId",
72 | })
73 | .then((chainId: string) => {
74 | store.commit("setChainId", chainId);
75 | });
76 | });
77 | if (store.getters.hasMetaMask) {
78 | const ethereum = store.state.ethereum;
79 | ethereum.on("accountsChanged", (accounts: string[]) => {
80 | //console.log("accountsChanged", accounts.length);
81 | if (accounts.length == 0) {
82 | store.commit("setAccount", null);
83 | } else {
84 | console.log("Eth acountsChanged", store.getters.displayAccount);
85 | store.commit("setAccount", accounts[0]);
86 | }
87 | });
88 | ethereum.on("connect", (info: ProviderConnectInfo): void => {
89 | console.log("Eth connect", info, store.getters.displayAccount);
90 | store.commit("setChainId", info.chainId);
91 | });
92 | ethereum.on("disconnect", (info: ProviderRpcError): void => {
93 | console.log("Eth disconnect", info);
94 | });
95 | ethereum.on("chainChanged", (chainId: string) => {
96 | store.commit("setChainId", chainId);
97 | });
98 | }
99 | };
100 |
101 | export const switchNetwork = async (chainId: string) => {
102 | const ethereum = store.state.ethereum;
103 | try {
104 | await ethereum.request({
105 | method: "wallet_switchEthereumChain",
106 | params: [{ chainId }],
107 | });
108 | } catch (e) {
109 | console.log(e);
110 | }
111 | };
112 |
113 | export const displayAddress = (address: string) => {
114 | return address.substring(0, 6) + "..." + address.substring(38);
115 | };
116 |
--------------------------------------------------------------------------------
/src/utils/canvasUtil.ts:
--------------------------------------------------------------------------------
1 | import { ref, Ref, computed } from "vue";
2 | import { useStore } from "vuex";
3 |
4 | import { Point, LayerType, Drawing } from "@/models/point";
5 |
6 | export enum Tools {
7 | CURSOR,
8 | MOVE,
9 | ZOOM,
10 | ROTATE,
11 | }
12 |
13 | export interface Pos {
14 | x: number;
15 | y: number;
16 | }
17 |
18 | export interface UIPos extends Pos {
19 | top: number;
20 | left: number;
21 | }
22 |
23 | export interface RotationInfo {
24 | radian: number;
25 | cos: number;
26 | sin: number;
27 | }
28 |
29 | const menuSize = {
30 | sidew: 150, //fix
31 | headh: 50,
32 | };
33 |
34 | // const canvasSize = { w: 1024, h: 1024 };
35 | export const assetSize = { w: 1024, h: 1024 };
36 |
37 | export const useCanvasParams = () => {
38 | const store = useStore();
39 | // HACK: These numbers must match margin specified for Draw and Canvas
40 | const drawMargin = 32; // m-4 (Draw)
41 | const canvasMargin = 4; // m-1 (Canvas)
42 |
43 | const canvasSize = computed<{ w: number; h: number }>(() => {
44 | const canvasWidth =
45 | store.state.windowWidth -
46 | menuSize.sidew -
47 | drawMargin * 2 -
48 | canvasMargin * 3;
49 | return { w: canvasWidth, h: canvasWidth };
50 | });
51 |
52 | const canvasParams = computed(() => {
53 | return {
54 | canw: canvasSize.value.w,
55 | canh: canvasSize.value.h,
56 | curw: (canvasSize.value.w * 30) / assetSize.w,
57 | curh: (canvasSize.value.h * 30) / assetSize.h,
58 | toold: (canvasSize.value.w * 100) / assetSize.w,
59 | assw: assetSize.w,
60 | assh: assetSize.h,
61 | caratio: canvasSize.value.w / assetSize.w,
62 | ...menuSize,
63 | };
64 | });
65 |
66 | const canvasXtoAssetX = (x: number) => {
67 | return (x / canvasSize.value.w) * assetSize.w;
68 | };
69 | const canvasYtoAssetY = (y: number) => {
70 | return (y / canvasSize.value.h) * assetSize.h;
71 | };
72 | const assetXtoCanvasX = (x: number) => {
73 | return (x / assetSize.w) * canvasSize.value.w;
74 | };
75 | const assetYtoCanvasY = (y: number) => {
76 | return (y / assetSize.h) * canvasSize.value.h;
77 | };
78 |
79 | const canvastoAsset = ({ x, y }: Pos): Pos => {
80 | return { x: canvasXtoAssetX(x), y: canvasYtoAssetY(y) };
81 | };
82 |
83 | const getOffsetX = (evt: DragEvent | MouseEvent | TouchEvent): number =>
84 | evt instanceof DragEvent || evt instanceof MouseEvent
85 | ? evt.offsetX / canvasParams.value.caratio
86 | : 0;
87 |
88 | const getOffsetY = (evt: DragEvent | MouseEvent | TouchEvent): number =>
89 | evt instanceof DragEvent || evt instanceof MouseEvent
90 | ? evt.offsetY / canvasParams.value.caratio
91 | : 0;
92 |
93 | return {
94 | canvasParams,
95 | assetXtoCanvasX,
96 | assetYtoCanvasY,
97 | getOffsetX,
98 | getOffsetY,
99 | canvastoAsset,
100 | };
101 | };
102 |
103 | export const useCanvasAsset = (canvasOffset: Ref) => {
104 | const { canvastoAsset } = useCanvasParams();
105 | const getAssetPos = (evt: DragEvent | MouseEvent | TouchEvent): Pos => {
106 | const x =
107 | evt instanceof DragEvent || evt instanceof MouseEvent
108 | ? evt.pageX - canvasOffset.value.x
109 | : evt.targetTouches[0].pageX;
110 | const y =
111 | evt instanceof DragEvent || evt instanceof MouseEvent
112 | ? evt.pageY - canvasOffset.value.y
113 | : evt.targetTouches[0].pageY;
114 | return canvastoAsset({ x, y });
115 | };
116 | return {
117 | getAssetPos,
118 | };
119 | };
120 |
121 | export const roundRect: Point[] = [
122 | { x: assetSize.w / 4, y: assetSize.h / 4, c: false, r: 0.553 },
123 | { x: assetSize.w - assetSize.w / 4, y: assetSize.h / 4, c: false, r: 0.553 },
124 | {
125 | x: assetSize.w - assetSize.w / 4,
126 | y: assetSize.h - assetSize.h / 4,
127 | c: false,
128 | r: 0.553,
129 | },
130 | { x: assetSize.w / 4, y: assetSize.h - assetSize.h / 4, c: false, r: 0.553 },
131 | ];
132 |
133 | export const useToolHandleMode = (
134 | currentLayerType: Ref,
135 | currentDrawing: Ref,
136 | overlayIndex: Ref
137 | ) => {
138 | const { assetXtoCanvasX, assetYtoCanvasY, canvasParams } = useCanvasParams();
139 | const cursors = ref([]);
140 | const toolHandleMode = ref(true);
141 | const toolHandles = computed(() => {
142 | return [
143 | {
144 | type: Tools.ROTATE,
145 | x: moveToolPos.value.left + canvasParams.value.toold,
146 | y: moveToolPos.value.top,
147 | },
148 | {
149 | type: Tools.ROTATE,
150 | x: moveToolPos.value.left - canvasParams.value.toold,
151 | y: moveToolPos.value.top,
152 | },
153 | {
154 | type: Tools.ZOOM,
155 | x: moveToolPos.value.left,
156 | y: moveToolPos.value.top + canvasParams.value.toold,
157 | },
158 | {
159 | type: Tools.ZOOM,
160 | x: moveToolPos.value.left,
161 | y: moveToolPos.value.top - canvasParams.value.toold,
162 | },
163 | ];
164 | });
165 | const onClickToolHandle = () => {
166 | if (currentLayerType.value == LayerType.LAYER) {
167 | toolHandleMode.value = !toolHandleMode.value;
168 | }
169 | };
170 | const moveToolPos = computed(() => {
171 | switch (currentLayerType.value) {
172 | case LayerType.LAYER:
173 | return cursors.value.reduce(
174 | ({ x, y }: Pos, cursor): UIPos => {
175 | const resultX = Math.round(
176 | x + assetXtoCanvasX(cursor.x) / cursors.value.length
177 | );
178 | const resultY = Math.round(
179 | y + assetYtoCanvasY(cursor.y) / cursors.value.length
180 | );
181 | return {
182 | x: resultX,
183 | y: resultY,
184 | left: resultX - canvasParams.value.curh / 2,
185 | top: resultY - canvasParams.value.curw / 2,
186 | };
187 | },
188 | { x: 0, y: 0, top: 0, left: 0 }
189 | );
190 |
191 | case LayerType.REMIX: {
192 | const resultX =
193 | canvasParams.value.canh / 2 +
194 | assetXtoCanvasX(currentDrawing.value.remix.transform.tx || 0);
195 | const resultY =
196 | canvasParams.value.canw / 2 +
197 | assetYtoCanvasY(currentDrawing.value.remix.transform.ty || 0);
198 | return {
199 | x: resultX,
200 | y: resultY,
201 | left: resultX - canvasParams.value.curh / 2,
202 | top: resultY - canvasParams.value.curw / 2,
203 | };
204 | }
205 | case LayerType.OVERLAY: {
206 | const overlay = currentDrawing.value.overlays[overlayIndex.value];
207 | const resultX =
208 | canvasParams.value.canh / 2 +
209 | assetXtoCanvasX(overlay.transform.tx || 0);
210 | const resultY =
211 | canvasParams.value.canw / 2 +
212 | assetYtoCanvasY(overlay.transform.ty || 0);
213 | return {
214 | x: resultX,
215 | y: resultY,
216 | left: resultX - canvasParams.value.curh / 2,
217 | top: resultY - canvasParams.value.curw / 2,
218 | };
219 | }
220 | }
221 | return { x: 0, y: 0, top: 0, left: 0 };
222 | });
223 |
224 | return {
225 | toolHandleMode,
226 | toolHandles,
227 | onClickToolHandle,
228 | moveToolPos,
229 | cursors,
230 | };
231 | };
232 |
--------------------------------------------------------------------------------
/src/utils/const.ts:
--------------------------------------------------------------------------------
1 | export const getAddresses = (
2 | network: string,
3 | storeAddress: string,
4 | contentAddress: string
5 | ) => {
6 | const EtherscanBase = (() => {
7 | if (network == "rinkeby") {
8 | return "https://rinkeby.etherscan.io/address";
9 | } else if (network == "goerli") {
10 | return "https://goerli.etherscan.io/address";
11 | } else if (network == "matic") {
12 | return "https://polygonscan.com/address";
13 | }
14 | return "https://etherscan.io/address";
15 | })();
16 | const OpenSeaBase = (() => {
17 | if (network == "rinkeby") {
18 | return "https://testnets.opensea.io/assets/rinkeby";
19 | } else if (network == "goerli") {
20 | return "https://testnets.opensea.io/assets/goerli";
21 | }
22 | return "https://opensea.io/assets/ethereum";
23 | })();
24 | const EtherscanStore = `${EtherscanBase}/${storeAddress}`;
25 | const EtherscanToken = `${EtherscanBase}/${contentAddress}`;
26 | const OpenSeaPath = `${OpenSeaBase}/${contentAddress}`;
27 |
28 | return {
29 | EtherscanBase,
30 | OpenSeaBase,
31 | EtherscanStore,
32 | EtherscanToken,
33 | OpenSeaPath,
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/src/utils/createAsset.ts:
--------------------------------------------------------------------------------
1 | import { assetBase, compressPath, normalizePath } from "./pathUtils";
2 | import {
3 | OriginalAssetData,
4 | OriginalAssetDataSet,
5 | OriginalAssetPart,
6 | } from "@/models/asset";
7 |
8 | export const createAsset = (
9 | _asset: OriginalAssetData,
10 | group: string,
11 | category: string,
12 | _width: number
13 | ) => {
14 | const asset = Object.assign({}, assetBase);
15 | asset.group = group;
16 | asset.category = category;
17 | asset.name = _asset.name;
18 | asset.metadata = new Uint8Array();
19 | const width = _asset.width || _width;
20 | const svgPath = (() => {
21 | if (_asset.parts) {
22 | return _asset.parts
23 | .map((part: OriginalAssetPart) => {
24 | const path = normalizePath(part.body as string, width);
25 | if (part.color) {
26 | return ``;
27 | }
28 | return ``;
29 | })
30 | .join("");
31 | } else if (_asset.bodies) {
32 | return _asset.bodies
33 | .map((body0: string) => {
34 | const path = normalizePath(body0, width);
35 | return ``;
36 | })
37 | .join("");
38 | } else {
39 | const path = normalizePath(_asset.body || "", width);
40 | return ``;
41 | }
42 | })();
43 | asset.parts = (() => {
44 | if (_asset.parts) {
45 | return _asset.parts.map((part: OriginalAssetPart) => {
46 | part.color = part.color || "";
47 | part.body = compressPath(part.body as string, width);
48 | return part;
49 | });
50 | } else if (_asset.bodies) {
51 | return _asset.bodies.map((body0: string) => {
52 | const body = compressPath(body0, width);
53 | return { body, color: "" };
54 | });
55 | } else {
56 | return [
57 | {
58 | color: "",
59 | body: compressPath(_asset.body || "", width),
60 | },
61 | ];
62 | }
63 | })();
64 | //asset.svgPath = svgPath;
65 | asset.svgPart = `${svgPath}`;
66 | asset.svg =
67 | '";
70 | asset.image =
71 | "data:image/svg+xml;base64," + Buffer.from(asset.svg).toString("base64");
72 | return asset;
73 | };
74 |
75 | export const loadAssets = (_resource: OriginalAssetDataSet) => {
76 | return _resource.assets.map((asset: OriginalAssetData) => {
77 | return createAsset(
78 | asset,
79 | _resource.group || "",
80 | _resource.category || "",
81 | _resource.width || 24
82 | );
83 | });
84 | };
85 |
--------------------------------------------------------------------------------
/src/utils/currency.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 |
3 | export const aBillion = 1000000000;
4 |
5 | export const weiToGwei = (amount: BigNumber) => {
6 | return amount.div(BigNumber.from(aBillion));
7 | };
8 |
9 | export const weiToEther = (amount: BigNumber) => {
10 | const amountInGwei = weiToGwei(amount);
11 | return amountInGwei.toNumber() / aBillion;
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/fetchTokens.ts:
--------------------------------------------------------------------------------
1 | import { ethers, BigNumber } from "ethers";
2 | import { Token } from "@/models/token";
3 | import { identityTransform, Remix } from "@/models/point";
4 | import { weiToEther } from "@/utils/currency";
5 |
6 | export const fetchTokens = async (
7 | count: number,
8 | tokens: Token[],
9 | tokensPerAsset: number,
10 | style: number,
11 | assetStoreRO: ethers.Contract,
12 | tokenRO: ethers.Contract,
13 | callback: (tokens: Token[]) => void
14 | ) => {
15 | const length = Math.min(4, count);
16 | const offset = count - length;
17 | const promises = Array(length)
18 | .fill({})
19 | .map(async (_, index) => {
20 | if (tokens[index]) {
21 | return tokens[index]; // we already have it
22 | }
23 | const tokenId = (offset + index) * tokensPerAsset;
24 | console.log("*** Fetching", tokenId);
25 | const result = await tokenRO.functions.assetIdOfToken(tokenId);
26 | const assetId = result[0].toNumber();
27 | const svgPart = await assetStoreRO.functions.generateSVGPart(
28 | assetId,
29 | "item"
30 | );
31 | const svg = await tokenRO.functions.generateSVG(
32 | svgPart[0],
33 | style,
34 | "item"
35 | );
36 | const image =
37 | "data:image/svg+xml;base64," + Buffer.from(svg[0]).toString("base64");
38 | return { image, tokenId: (offset + index) * tokensPerAsset };
39 | });
40 |
41 | // Sequential version of callback(await Promise.all(promises));
42 | const updateTokens: Token[] = [];
43 | let i;
44 | for (i = 0; i < promises.length; i++) {
45 | const token = await promises[i];
46 | updateTokens.push(token);
47 | callback(
48 | updateTokens.map((token) => {
49 | return token;
50 | })
51 | );
52 | }
53 | };
54 |
55 | export const fetchTokensRemix = async (
56 | count: number,
57 | tokens: Remix[],
58 | tokensPerAsset: number,
59 | style: number,
60 | tokenRO: ethers.Contract,
61 | callback: (tokens: Remix[]) => void
62 | ) => {
63 | const promises = Array(count)
64 | .fill({})
65 | .map(async (_, index): Promise => {
66 | if (tokens[index]) {
67 | return tokens[index]; // we already have it
68 | }
69 |
70 | const tokenId = index * tokensPerAsset;
71 |
72 | try {
73 | const [svgPart, svgTag] = await tokenRO.functions.generateSVGPart(
74 | tokenId
75 | );
76 | const svg = await tokenRO.functions.generateSVG(svgPart, style, svgTag);
77 | const image =
78 | "data:image/svg+xml;base64," + Buffer.from(svg[0]).toString("base64");
79 | return {
80 | image,
81 | tokenId: index * tokensPerAsset,
82 | svgPart,
83 | svgTag,
84 | transform: identityTransform,
85 | };
86 | } catch (e: any) {
87 | console.error("failed to generateSVG");
88 | return null;
89 | }
90 | });
91 |
92 | // Sequential version of callback(await Promise.all(promises));
93 | const updateTokens: Remix[] = [];
94 | let i;
95 | for (i = 0; i < promises.length; i++) {
96 | const token = await promises[i];
97 | if (token) {
98 | updateTokens.push(token);
99 | callback(updateTokens.map((token) => token));
100 | }
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/src/utils/mintUtils.ts:
--------------------------------------------------------------------------------
1 | import { ref } from "vue";
2 | import {
3 | AssetData,
4 | OriginalAssetData,
5 | MintSelectionAsset,
6 | } from "@/models/asset";
7 |
8 | import { ethers } from "ethers";
9 |
10 | export const assetsReduce = (
11 | prev: { [key: string]: AssetData },
12 | asset: AssetData
13 | ) => {
14 | prev[asset.name] = asset;
15 | return prev;
16 | };
17 |
18 | export const assetFilter = (asset: OriginalAssetData) => {
19 | return !asset.registered;
20 | };
21 |
22 | export const useOnSelect = (initTokenPer: number, tokenRO: ethers.Contract) => {
23 | const tokensPerAsset = ref(initTokenPer); // hard-coded only for MaterialToken
24 | const selection = ref(null);
25 | const onSelect = async (asset: OriginalAssetData, tag: string | null) => {
26 | /* We no longer unselect on selecting the same one
27 | if (selection.value && selection.value.asset.name == asset.name && asset.name != "") {
28 | selection.value = null;
29 | return;
30 | }
31 | */
32 | selection.value = {
33 | isLoading: true,
34 | asset,
35 | };
36 | const promises = Array(tokensPerAsset.value - 1)
37 | .fill("")
38 | .map((_, index) => {
39 | return tokenRO.functions.generateSVG(
40 | asset.svgPart,
41 | index,
42 | tag || "item"
43 | );
44 | });
45 | const images = (await Promise.all(promises)).map((result) => {
46 | return (
47 | "data:image/svg+xml;base64," + Buffer.from(result[0]).toString("base64")
48 | );
49 | });
50 | selection.value = { images, asset };
51 | };
52 | return { selection, onSelect, tokensPerAsset };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/networks.ts:
--------------------------------------------------------------------------------
1 | import { ChainIds } from "../utils/MetaMask";
2 | import { addresses as mainnet } from "../../generated/addresses_mainnet";
3 | import { addresses as localhost } from "../../generated/addresses_localhost";
4 | import { addresses as rinkeby } from "../../generated/addresses_rinkeby";
5 | import { addresses as goerli } from "../../generated/addresses_goerli";
6 | import { addresses as matic } from "../../generated/addresses_matic";
7 | import { kamon_addresses as kamon_mainnet } from "../../generated/addresses_kamon_mainnet";
8 | import { kamon_addresses as kamon_localhost } from "../../generated/addresses_kamon_localhost";
9 | import { kamon_addresses as kamon_rinkeby } from "../../generated/addresses_kamon_rinkeby";
10 | import { kamon_addresses as kamon_goerli } from "../../generated/addresses_kamon_goerli";
11 | import { token_addresses as flag_mainnet } from "../../generated/addresses_flag_mainnet";
12 | import { token_addresses as flag_localhost } from "../../generated/addresses_flag_localhost";
13 | import { token_addresses as flag_rinkeby } from "../../generated/addresses_flag_rinkeby";
14 | import { token_addresses as flag_goerli } from "../../generated/addresses_flag_goerli";
15 | import { token_addresses as draw_localhost } from "../../generated/addresses_draw_localhost";
16 | import { token_addresses as draw_rinkeby } from "../../generated/addresses_draw_rinkeby";
17 | import { token_addresses as draw_goerli } from "../../generated/addresses_draw_goerli";
18 |
19 | export const getContractAddresses = (network: string) => {
20 | if (network == "localhost") {
21 | return {
22 | network,
23 | chainId: ChainIds.Localhost,
24 | storeAddress: localhost.storeAddress,
25 | materialAddress: localhost.tokenAddress,
26 | kamonAddress: kamon_localhost.kamonAddress,
27 | flagAddress: flag_localhost.emojiFlagAddress,
28 | drawAddress: draw_localhost.customTokenAddress,
29 | composerAddress: draw_localhost.composerAddress,
30 | registryAddress: draw_localhost.registryAddress,
31 | tokenAddress: "",
32 | };
33 | }
34 | if (network == "rinkeby") {
35 | return {
36 | network,
37 | EtherscanBase: "https://rinkeby.etherscan.io/address",
38 | chainId: ChainIds.RinkebyTestNet,
39 | storeAddress: rinkeby.storeAddress,
40 | materialAddress: rinkeby.tokenAddress,
41 | kamonAddress: kamon_rinkeby.kamonAddress,
42 | flagAddress: flag_rinkeby.emojiFlagAddress,
43 | drawAddress: draw_rinkeby.customTokenAddress,
44 | composerAddress: draw_rinkeby.composerAddress,
45 | registryAddress: draw_rinkeby.registryAddress,
46 | tokenAddress: "",
47 | };
48 | }
49 | if (network == "goerli") {
50 | return {
51 | network,
52 | EtherscanBase: "https://rinkeby.etherscan.io/address",
53 | chainId: ChainIds.Goerli,
54 | storeAddress: goerli.storeAddress,
55 | materialAddress: goerli.tokenAddress,
56 | kamonAddress: kamon_goerli.kamonAddress,
57 | flagAddress: flag_goerli.emojiFlagAddress,
58 | drawAddress: draw_goerli.customTokenAddress,
59 | composerAddress: draw_goerli.composerAddress,
60 | registryAddress: draw_goerli.registryAddress,
61 | tokenAddress: "",
62 | };
63 | }
64 | if (network == "mainnet") {
65 | return {
66 | network,
67 | EtherscanBase: "https://etherscan.io/address",
68 | chainId: ChainIds.Mainnet,
69 | storeAddress: mainnet.storeAddress,
70 | materialAddress: mainnet.tokenAddress,
71 | kamonAddress: kamon_mainnet.kamonAddress,
72 | flagAddress: flag_mainnet.emojiFlagAddress,
73 | drawAddress: "to be determined", // MEMO: don't forget to change the category in Draw.vue as well
74 | composerAddress: "to be determined",
75 | registryAddress: "to be determined",
76 | tokenAddress: "",
77 | };
78 | }
79 | if (network == "matic") {
80 | return {
81 | network,
82 | EtherscanBase: "https://polygonscan.com/address",
83 | chainId: ChainIds.Polygon,
84 | storeAddress: matic.storeAddress,
85 | materialAddress: matic.tokenAddress,
86 | kamonAddress: "to be determined",
87 | flagAddress: "to be determined",
88 | drawAddress: "to be determined", // MEMO: don't forget to change the category in Draw.vue as well
89 | composerAddress: "to be determined",
90 | registryAddress: "to be determined",
91 | tokenAddress: "",
92 | };
93 | }
94 | console.error("**** unexpected");
95 | };
96 |
--------------------------------------------------------------------------------
/src/utils/pathUtils.ts:
--------------------------------------------------------------------------------
1 | import { AssetBase } from "@/models/asset";
2 |
3 | export const assetBase: AssetBase = {
4 | width: 24,
5 | height: 24,
6 | minter: "",
7 | name: "",
8 | };
9 |
10 | const regexNum = /[+-]?(\d*\.\d*|\d+)/;
11 | const regexNumG = /[+-]?(\d*\.\d*|\d+)/g;
12 | const regexFloatG = /[+-]?(\d*\.\d*|\d+)e-\d+/g;
13 | const regexDivG = /[,\s]+/g;
14 |
15 | // T is number or string
16 | const reduceFun = (
17 | width: number,
18 | func1: (val: number) => T,
19 | func2: (item: string) => T[]
20 | ) => {
21 | return (
22 | prev: { isArc: boolean; offset: number; numArray: T[] },
23 | item: string
24 | ) => {
25 | if (regexNum.test(item)) {
26 | let value = Math.round((parseFloat(item) * 1024) / width);
27 | if (prev.isArc) {
28 | const off7 = prev.offset % 7;
29 | if (off7 >= 2 && off7 <= 4) {
30 | // we don't want to normalize 'angle', and two flags for 'a' or 'A'
31 | value = Math.round(parseFloat(item));
32 | }
33 | prev.offset++;
34 | }
35 | prev.numArray.push(func1(value));
36 | } else {
37 | const codes = func2(item);
38 | codes.map((code) => {
39 | prev.numArray.push(code);
40 | });
41 | const ch = item.substring(-1);
42 | prev.isArc = ch == "a" || ch == "A";
43 | if (prev.isArc) {
44 | prev.offset = 0;
45 | }
46 | }
47 | return prev;
48 | };
49 | };
50 |
51 | const prepareBody = (body: string) => {
52 | let ret = body.replace(regexFloatG, (str: string) => {
53 | return "0";
54 | });
55 | ret = ret.replace(regexNumG, (str: string) => {
56 | return ` ${parseFloat(str)} `;
57 | });
58 | const items = ret.split(regexDivG);
59 | return items;
60 | };
61 | export const normalizePath = (body: string, width: number) => {
62 | const items = prepareBody(body);
63 |
64 | const func1 = (value: number) => {
65 | return value.toString();
66 | };
67 | const func2 = (item: string) => {
68 | return [item];
69 | };
70 | const { numArray } = items.reduce(reduceFun(width, func1, func2), {
71 | isArc: false,
72 | offset: 0,
73 | numArray: [],
74 | });
75 | return numArray.join(" ");
76 | };
77 | export const compressPath = (body: string, width: number) => {
78 | const items = prepareBody(body);
79 |
80 | const func1 = (value: number) => {
81 | return value + 0x100 + 1024;
82 | };
83 | const func2 = (item: string) => {
84 | return item.split("").map((char) => {
85 | return char.charCodeAt(0);
86 | });
87 | };
88 | const { numArray } = items.reduce(reduceFun(width, func1, func2), {
89 | isArc: false,
90 | offset: 0,
91 | numArray: [],
92 | });
93 |
94 | // 12-bit middle-endian compression
95 | const bytes = new Uint8Array((numArray.length * 3 + 1) / 2);
96 | numArray.map((value: number, index) => {
97 | const offset = Math.floor(index / 2) * 3;
98 | if (index % 2 == 0) {
99 | bytes[offset] = value % 0x100; // low 8 bits in the first byte
100 | bytes[offset + 1] = (value >> 8) & 0x0f; // hight 4 bits in the low 4 bits of middle byte
101 | } else {
102 | bytes[offset + 2] = value % 0x100; // low 8 bits in the third byte
103 | bytes[offset + 1] |= (value >> 8) * 0x10; // high 4 bits in the high 4 bits of middle byte
104 | }
105 | });
106 |
107 | return bytes;
108 | };
109 |
--------------------------------------------------------------------------------
/src/utils/resources.ts:
--------------------------------------------------------------------------------
1 | import { assetBase, compressPath } from "./pathUtils";
2 | import {
3 | OriginalAssetData,
4 | OriginalAssetDataSet,
5 | OriginalAssetPart,
6 | } from "@/models/asset";
7 |
8 | export const createAsset = (
9 | _asset: OriginalAssetData,
10 | group: string,
11 | category: string,
12 | _width: number
13 | ) => {
14 | const asset = Object.assign({}, assetBase);
15 | asset.group = group;
16 | asset.category = category;
17 | asset.name = _asset.name;
18 | const width = _asset.width || _width;
19 | if (_asset.parts) {
20 | asset.parts = _asset.parts.map((part: OriginalAssetPart) => {
21 | part.color = part.color || "";
22 | part.body = compressPath(part.body as string, width);
23 | return part;
24 | });
25 | } else {
26 | asset.parts = [
27 | {
28 | color: "",
29 | body: compressPath(_asset.body || "", width),
30 | },
31 | ];
32 | }
33 | return asset;
34 | };
35 |
36 | export const loadAssets = (_resource: OriginalAssetDataSet) => {
37 | return _resource.assets.map((asset: OriginalAssetData) => {
38 | return createAsset(
39 | asset,
40 | _resource.group || "",
41 | _resource.category || "",
42 | _resource.width || 24
43 | );
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/src/utils/undo.ts:
--------------------------------------------------------------------------------
1 | import { ref, computed, Ref } from "vue";
2 |
3 | import { Drawing } from "@/models/point";
4 |
5 | export const useUndoStack = (drawing: Ref) => {
6 | const undoStack = ref([]);
7 | const undoIndex = ref(0);
8 |
9 | const recordState = () => {
10 | const array = undoStack.value.filter((state, index) => {
11 | return index < undoIndex.value;
12 | });
13 | array.push(drawing.value);
14 | undoStack.value = array;
15 | undoIndex.value = undoStack.value.length;
16 | };
17 |
18 | const isRedoable = computed(() => {
19 | return undoIndex.value + 1 < undoStack.value.length;
20 | });
21 |
22 | const isUndoable = computed(() => {
23 | return undoIndex.value > 0;
24 | });
25 |
26 | const _undo = () => {
27 | console.log("undo", isUndoable.value);
28 | if (!isUndoable.value) {
29 | return null;
30 | }
31 | if (!isRedoable.value) {
32 | recordState();
33 | undoIndex.value -= 1;
34 | }
35 | drawing.value = undoStack.value[undoIndex.value - 1];
36 | undoIndex.value -= 1;
37 | };
38 | const _redo = () => {
39 | if (!isRedoable.value) {
40 | return null;
41 | }
42 | drawing.value = undoStack.value[undoIndex.value + 1];
43 | undoIndex.value += 1;
44 | };
45 |
46 | return {
47 | recordState,
48 | isRedoable,
49 | isUndoable,
50 | _undo,
51 | _redo,
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/views/Assets.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
29 |
--------------------------------------------------------------------------------
/src/views/Emoji.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
65 |
--------------------------------------------------------------------------------
/src/views/Kamons.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
68 |
--------------------------------------------------------------------------------
/src/views/Materials.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
64 |
--------------------------------------------------------------------------------
/storage.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 | service firebase.storage {
3 | match /b/{bucket}/o {
4 | match /{allPaths=**} {
5 | allow read, write: if request.auth!=null;
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | './public/**/*.html',
4 | './src/**/*.{js,jsx,ts,tsx,vue}',
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | module.exports = {
4 | configureWebpack: {
5 | plugins: [
6 | new webpack.ProvidePlugin({
7 | Buffer: ['buffer', 'Buffer'],
8 | }),
9 | /*
10 | new webpack.ProvidePlugin({
11 | process: 'process/browser',
12 | }),
13 | */
14 | ],
15 | resolve: {
16 | fallback: {
17 | "buffer": require.resolve('buffer/'),
18 | "http": 'agent-base',
19 | "https": 'agent-base',
20 | "stream": false,
21 | "crypto": false,
22 | "os": false,
23 | "url": false,
24 | "assert": false,
25 | }
26 | }
27 | }
28 | };
29 |
--------------------------------------------------------------------------------