├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ ├── logo.png
│ ├── icon-fullscreen-close.svg
│ ├── icon-fullscreen-open.svg
│ └── icon-close.svg
├── main.js
├── components
│ ├── ActiveFilters.vue
│ ├── DarkmodeToggle.vue
│ ├── FilterAside.vue
│ └── GalleryComponent.vue
├── App.vue
└── store
│ └── index.js
├── babel.config.js
├── .gitignore
├── jsconfig.json
├── vue.config.js
├── config.js
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nftchef/art-engine-viewer/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nftchef/art-engine-viewer/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import store from './store'
4 |
5 | createApp(App).use(store).mount('#app')
6 |
--------------------------------------------------------------------------------
/src/assets/icon-fullscreen-close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon-fullscreen-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon-close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@/*": [
9 | "src/*"
10 | ]
11 | },
12 | "lib": [
13 | "esnext",
14 | "dom",
15 | "dom.iterable",
16 | "scripthost"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const config = require("./config");
2 | console.log({ config });
3 | process.env.VUE_APP_IMAGE_TYPE = config.imageType;
4 |
5 | const { defineConfig } = require("@vue/cli-service");
6 | module.exports = defineConfig({
7 | transpileDependencies: true,
8 | });
9 |
10 | const path = require("path");
11 | module.exports = {
12 | chainWebpack: (config) => {
13 | //TODO: make paths dynamic
14 | config.resolve.alias.set(
15 | "#BUILD",
16 | path.resolve(__dirname, "../Art Engine/build")
17 | );
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | // ***** FOR LOCAL DEV: SET THE PATH TO YOUR BUILD FOLDER, RELATIVE TO THIS PROJECT ****// this path is
2 | // |
3 | // |
4 | // v
5 | const buildPath = "../Art Engine/build";
6 |
7 | /** TO UPLOAD TO A LIVE SERVER, SET EMBED PATH TO TRUE */
8 | const hosted = false;
9 | const hostedPath = "/build";
10 | // set the image type of your images from your build folder
11 | const imageType = "png";
12 |
13 | module.exports = {
14 | buildPath,
15 | imageType,
16 | hosted,
17 | hostedPath,
18 | };
19 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "explorer",
3 | "version": "0.1.2",
4 | "private": true,
5 | "scripts": {
6 | "build": "vue-cli-service build",
7 | "lint": "vue-cli-service lint",
8 | "dev": "vue-cli-service serve"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.8.3",
12 | "sass": "^1.49.9",
13 | "sass-loader": "^12.6.0",
14 | "vue": "^3.2.13",
15 | "vuex": "^4.0.0"
16 | },
17 | "devDependencies": {
18 | "@babel/core": "^7.12.16",
19 | "@babel/eslint-parser": "^7.12.16",
20 | "@vue/cli-plugin-babel": "~5.0.0",
21 | "@vue/cli-plugin-eslint": "~5.0.0",
22 | "@vue/cli-plugin-vuex": "~5.0.0",
23 | "@vue/cli-service": "~5.0.0",
24 | "eslint": "^7.32.0",
25 | "eslint-plugin-vue": "^8.0.3"
26 | },
27 | "eslintConfig": {
28 | "root": true,
29 | "env": {
30 | "node": true
31 | },
32 | "extends": [
33 | "plugin:vue/vue3-essential",
34 | "eslint:recommended"
35 | ],
36 | "parserOptions": {
37 | "parser": "@babel/eslint-parser"
38 | },
39 | "rules": {}
40 | },
41 | "browserslist": [
42 | "> 1%",
43 | "last 2 versions",
44 | "not dead",
45 | "not ie 11"
46 | ]
47 | }
--------------------------------------------------------------------------------
/src/components/ActiveFilters.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
FILTERS
5 | {{ activeFilters.length }}
7 |
8 |
{{ filter.trait_type }}:{{ filter.value }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
32 |
33 |
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🧪 Art Engine Explorer (alpha)
2 | A local web app intended to be used in conjunction with the Art-Engine. This is a quick (desktop) viewer that allows you to visualize and
3 |
4 |
5 |
6 |
7 | # Controls
8 | Use `arrow keys` to scroll through items when the detail modal or fullscreen modes are open
9 | Use `Escape` to close teh detail modal or Fullscreen mode
10 |
11 | ## Roadmap
12 | [] Add rarity calculations
13 | [] Add "jump to number" input
14 | [] Integrate into the main art-engine repo as a submodule
15 | [] Properly configure the config.js file to allow for custom paths to the build dir
16 |
17 | # Project setup
18 | **⚠️ Important!!**
19 | - As of this writing, this repo should be installed _next_ to the Art Engine folder so the _Viewer_ project can easily the `build` folders from the Art Engine generator.
20 | - As of this writing, the viewer relies on the `.edition` metadata property for indexing images.
21 |
22 |
23 | Clone or Download this project _next to_ the generator folder.
24 | This build expects the art-engine folder to be named `Art Engine`
25 | [To adjust the path of the build folder, see vue.config.js AND App.vue in_this_project]
26 |
27 | If you are building JPEG images, change the config in `config.js` in the viewer config file.
28 |
29 | ```
30 | yarn
31 |
32 | // or using npm
33 | npm install
34 | ```
35 |
36 | ### Once installed, run `yarn dev`
37 | ```
38 | yarn dev
39 |
40 | // if using npm
41 | // npm run dev
42 | ```
43 |
44 | ## Building for production
45 | This application uses standard Vue-cli building and bundling. For more information on deploying a live version (After revealing all your NFT art of course), see the Vue.js documentation https://vuejs.org/guide/introduction.html
46 | ### Compiles and minifies for production
47 | ```
48 | yarn build
49 | ```
50 |
51 | ### Lints and fixes files
52 | ```
53 | yarn lint
54 | ```
55 |
56 | ### Customize configuration
57 | See [Configuration Reference](https://cli.vuejs.org/config/).
58 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
52 |
53 |
92 |
--------------------------------------------------------------------------------
/src/components/DarkmodeToggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
16 |
17 |
18 |
19 |
darkmode
22 |
23 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
47 |
48 |
102 |
--------------------------------------------------------------------------------
/src/components/FilterAside.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Filter
4 |
5 |
10 |
15 |
16 |
17 | {{ category }}
19 |
43 |
44 |
45 |
46 |
47 |
84 |
85 |
131 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex";
2 |
3 | export default createStore({
4 | state: {
5 | metadata: [],
6 | filters: [],
7 | results: [],
8 | allTraits: [],
9 | traitTypes: [],
10 | currentDetailIndex: 0,
11 | darkmode: false,
12 | },
13 | getters: {},
14 | mutations: {
15 | SET_RESULTS(state, _results) {
16 | state.results = _results;
17 | },
18 | SET_FILTERS(state, _filters) {
19 | state.filters = _filters;
20 | },
21 | SET_METADATA(state, _metadata) {
22 | state.metadata = _metadata;
23 | },
24 | SET_TRAITS(state, _traitsData) {
25 | state.allTraits = _traitsData;
26 | },
27 | SET_TRAIT_TYPES(state, _traits) {
28 | state.traitTypes = _traits;
29 | },
30 | SET_CURRENT_INDEX(state, _index) {
31 | state.currentDetailIndex = _index;
32 | },
33 | SET_DARKMODE(state, _on) {
34 | state.darkmode = _on;
35 | },
36 | },
37 | actions: {
38 | INITIALIZE_TRAITS({ commit }, metadata) {
39 | console.log("init traits,", metadata);
40 | commit("SET_METADATA", metadata);
41 | commit("SET_RESULTS", metadata);
42 |
43 | const allTraits = metadata.reduce((acc, item) => {
44 | // loop over each item in the entire metadata. this could be
45 | // time intensive.
46 | // Initialize the trait if it has not been initialized yet in the acc
47 | // initialize the value at count 1 it it has not been init yet
48 | // sum al the counts
49 | item.attributes.forEach((attribute) => {
50 | acc[attribute.trait_type] = {
51 | ...acc[attribute.trait_type],
52 | [attribute.value]: {
53 | filterState: false,
54 | count: acc[attribute.trait_type]
55 | ? acc[attribute.trait_type][attribute.value]
56 | ? acc[attribute.trait_type][attribute.value].count + 1
57 | : 1
58 | : 1,
59 | },
60 | };
61 | });
62 | return acc;
63 | }, {});
64 |
65 | const traitTypes = Object.keys(allTraits);
66 | commit("SET_TRAIT_TYPES", traitTypes);
67 | commit("SET_TRAITS", allTraits);
68 | },
69 |
70 | SET_AVAILABLE_ATTRIBUTES({ commit, state }) {
71 | const allTraits = state.results.reduce((acc, item) => {
72 | // loop over each item in the entire metadata. this could be
73 | // time intensive.
74 | // Initialize the trait if it has not been initialized yet in the acc
75 | // initialize the value at count 1 it it has not been init yet
76 | // sum al the counts
77 | item.attributes.forEach((attribute) => {
78 | acc[attribute.trait_type] = {
79 | ...acc[attribute.trait_type],
80 | [attribute.value]: {
81 | filterState: false,
82 | count: acc[attribute.trait_type]
83 | ? acc[attribute.trait_type][attribute.value]
84 | ? acc[attribute.trait_type][attribute.value].count + 1
85 | : 1
86 | : 1,
87 | },
88 | };
89 | });
90 | return acc;
91 | }, {});
92 |
93 | commit("SET_TRAITS", allTraits);
94 | },
95 | /**
96 | *
97 | * @param {context} param0 Unwraped context params for commit,state
98 | * @param {Array} filters [...{}] filters of types and value pairs
99 | */
100 | UPDATE_RESULTS({ commit, state }, filters) {
101 | // TODO: do the filtering of the metadata here
102 | const subset = state.metadata.filter((item) => {
103 | return item.attributes.includes(
104 | (attribute) =>
105 | attribute.trait_type === filters.trait_type &&
106 | attribute.value === attribute.value
107 | );
108 | });
109 |
110 | commit("SET_RESULTS", subset);
111 | },
112 |
113 | ADD_FILTER({ commit, dispatch, state }, filter) {
114 | const filters = [...state.filters, { ...filter, filterState: true }];
115 | const updateState = {
116 | ...state.allTraits,
117 | };
118 | updateState[filter.trait_type][filter.value].filterState = true;
119 |
120 | commit("SET_TRAITS", updateState);
121 | commit("SET_FILTERS", filters);
122 | dispatch("FILTER");
123 | },
124 |
125 | REMOVE_FILTER({ commit, dispatch, state }, traitFilter) {
126 | const filters = state.filters.filter(
127 | (trait) =>
128 | trait.trait_type !== traitFilter.trait_type &&
129 | trait.value !== traitFilter.value
130 | );
131 | const updateState = {
132 | ...state.allTraits,
133 | };
134 | updateState[traitFilter.trait_type][
135 | traitFilter.value
136 | ].filterState = false;
137 | commit("SET_TRAITS", updateState);
138 | commit("SET_FILTERS", filters);
139 | dispatch("FILTER");
140 | dispatch("SET_AVAILABLE_ATTRIBUTES");
141 | },
142 |
143 | FILTER({ commit, state, dispatch }) {
144 | if (!state.filters.length || state.filters.length === 0) {
145 | // clear all filter results and show all
146 | commit("SET_RESULTS", state.metadata);
147 | return;
148 | }
149 |
150 | // console.log("active filters", state.filters);
151 |
152 | const results = state.metadata.filter((item) => {
153 | return state.filters.every((filter) => {
154 | // if the items attributes match any of the filters, return true
155 | return item.attributes.some((attribute) => {
156 | return (
157 | attribute.trait_type === filter.trait_type &&
158 | attribute.value === filter.value
159 | );
160 | });
161 | });
162 |
163 | // return matches.length > 0;
164 | });
165 | // console.log({ results });
166 | commit("SET_RESULTS", results);
167 | dispatch("SET_AVAILABLE_ATTRIBUTES");
168 | },
169 |
170 | // Set the metadata array index of the current selected item
171 | CURRENT_DETAIL_INDEX({ commit }, _index) {
172 | commit("SET_CURRENT_INDEX", _index);
173 | },
174 |
175 | TOGGLE_DARK_MODE({ commit, state }) {
176 | commit("SET_DARKMODE", !state.darkmode);
177 | },
178 | },
179 | modules: {},
180 | });
181 |
--------------------------------------------------------------------------------
/src/components/GalleryComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ results.length }}/ {{ metadata.length }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 | {{ item.name }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
62 |
67 |
68 |
69 |
70 |
71 |
183 |
184 |
288 |
--------------------------------------------------------------------------------