Added A way to get Game-icons.net icons into the database (it's not secure yet), plus some icon ui

This commit is contained in:
Stefan Zermatten
2019-01-28 16:26:39 +02:00
parent 77d2f87373
commit 4584499019
13 changed files with 302 additions and 7 deletions

View File

@@ -51,4 +51,3 @@ static-html
aldeed:collection2@3.0.0
aldeed:schema-index
akryum:vue-component
autopublish@1.0.7

View File

@@ -12,7 +12,6 @@ akryum:vue-router2@0.2.2
aldeed:collection2@3.0.1
aldeed:schema-index@3.0.0
allow-deny@1.1.0
autopublish@1.0.7
autoupdate@1.5.0
babel-compiler@7.2.4
babel-runtime@1.3.0

View File

@@ -0,0 +1,50 @@
import SimpleSchema from 'simpl-schema';
let Icons = new Mongo.Collection('icons');
iconsSchema = new SimpleSchema({
name: {
type: String,
unique: true,
index: 1,
},
description: {
type: String,
optional: true,
},
tags: {
type: Array,
optional: true,
index: 1,
},
'tags.$': {
type: String,
},
shape: {
type: String,
},
});
if (Meteor.isServer) {
Icons._ensureIndex({
'name': 'text',
'description': 'text',
'tags': 'text',
});
}
Icons.attachSchema(iconsSchema);
const writeIcons = new ValidatedMethod({
name: 'writeIcons',
validate: null,
run(icons){
if (Meteor.isServer){
this.unblock();
Icons.rawCollection().insert(icons, {ordered: false});
}
}
});
export { writeIcons };
export default Icons;

View File

@@ -0,0 +1,26 @@
import Icons from '/imports/api/icons/Icons.js';
Meteor.publish("sampleIcons", function(){
return Icons.find({}, {limit: 50});
});
Meteor.publish("searchIcons", function(searchValue) {
// Don't publish anything if there's no search value
if (!searchValue) {
return [];
}
return Icons.find(
{ $text: {$search: searchValue} },
{
// relevant documents have a higher score.
fields: {
score: { $meta: "textScore" }
},
// `score` property specified in the projection fields above.
sort: {
score: { $meta: "textScore" }
}
}
);
});

View File

@@ -1,5 +1,6 @@
import "./characterList.js";
import "./library.js";
import "./singleCharacter.js";
import "./user.js";
import "./users.js";
import './characterList.js';
import './library.js';
import './singleCharacter.js';
import './user.js';
import './users.js';
import './icons.js';

View File

@@ -44,8 +44,10 @@
import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
import HealthBar from '/imports/ui/components/HealthBar.Story.vue';
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
import IconSearch from '/imports/ui/components/IconSearch.Story.vue';
import SkillListTile from '/imports/ui/components/SkillListTile.Story.vue';
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
export default {
components: {
AbilityListTile,
@@ -54,6 +56,7 @@
DialogStack,
HealthBar,
HitDiceListTile,
IconSearch,
SkillListTile,
ToolbarLayout,
},

View File

@@ -0,0 +1,16 @@
<template lang="html">
<icon-search/>
</template>
<script>
import IconSearch from '/imports/ui/components/IconSearch.vue';
export default {
components: {
IconSearch,
},
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,76 @@
<template lang="html">
<v-autocomplete
v-model="model"
:search-input.sync="searchString"
:items="items"
:loading="!$subReady.searchIcons || isLoading"
item-text="name"
item-value="_id"
label="Search icons"
hide-no-data
@input="input"
>
<template
slot="item"
slot-scope="{ item, tile }"
>
<v-list-tile-avatar>
<svg class="avatar" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#000" :d="item.shape"/></svg>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title v-text="item.name"></v-list-tile-title>
</v-list-tile-content>
</template>
</v-autocomplete>
</template>
<script>
import Icons from '/imports/api/icons/Icons.js';
export default {
data(){ return {
model: this.value,
searchString: null,
serverSearchString: null,
isLoading: false,
}},
props: {
value: String,
},
watch: {
searchString(string){
this.isLoading = true;
this.searchServer(string)
},
value(newValue){
this.model = newValue;
},
},
methods: {
searchServer: _.debounce(function(string){
this.serverSearchString = string;
}, 200),
input(e){
this.$emit('input', e);
}
},
meteor: {
$subscribe: {
searchIcons() {
this.isLoading = false;
return [this.serverSearchString];
},
},
items(){
return Icons.find({}, { sort: [['score', 'desc']] }).fetch();
},
},
}
</script>
<style lang="css" scoped>
.avatar {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,83 @@
<template lang="html">
<toolbar-layout>
<div slot="toolbar">
DiceCloud
</div>
<div class="content">
<v-card class="ma-4">
<v-card-text>
<v-layout column align-center>
<upload-btn
:fileChangedCallback="fileChanged"
/>
<v-text-field
label="Search"
@click:append="updateSearchString"
@keydown.enter="updateSearchString"
ref="iconSearchField"
append-icon="search"
/>
<v-container grid-list-md fill-height>
<v-layout row wrap>
<v-flex xs3 md2 xl1 v-for="icon in icons" :key="icon._id._str || icon._id">
<v-card>
<v-card-title class="title">{{icon.name}}</v-card-title>
<v-card-text>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#000" :d="icon.shape"/></svg>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-layout>
</v-card-text>
</v-card>
</div>
</toolbar-layout>
</template>
<script>
import UploadButton from 'vuetify-upload-button';
import ToolbarLayout from "/imports/ui/layouts/ToolbarLayout.vue";
import importIcons from '/imports/ui/icons/importIcons.js';
import Icons from '/imports/api/icons/Icons.js';
import { _ } from 'meteor/underscore';
export default {
data(){ return {
searchString: '',
}},
methods: {
fileChanged (file) {
importIcons(file);
},
updateSearchString(){
this.searchString = this.$refs.iconSearchField.internalValue;
},
},
components: {
ToolbarLayout,
'upload-btn': UploadButton
},
meteor: {
$subscribe: {
searchIcons() {
return [this.searchString];
},
},
icons(){
return Icons.find({}, { sort: [["score", "desc"]] });
},
},
};
</script>
<style lang="css" scoped>
svg {
height: 64px;
width: 64px;
}
.v-card {
height: 100%;
}
</style>

View File

@@ -0,0 +1,31 @@
import { writeIcons } from '/imports/api/icons/Icons.js';
/*
* Import a SVG sprite file. All the icons must contain one id and one path with a
* single 'd' attribute.
*
* A svg sprite file can be created by downloading the entire archive of
* https://game-icons.net/ then using the search function with *.svg to copy
* all the individual files into a single directory, and then using the npm
* sprite-generator to run `svg-sprite-generate -d icons -o sprite.svg` to save
* the sprite file.
*/
export default function importIcons(file){
let id, d, icons = [];
let reader = new FileReader();
reader.onload = function(){
reader.result.match(/i?d="([^"])+"/gi).forEach(s => {
if (s[0] === 'i'){
id = s.slice(4, -1);
} else if (s[0] === 'd'){
d = s.slice(3, -1);
icons.push ({_id: Random.id(), name: id, shape: d});
}
});
writeIcons.call(icons);
};
reader.readAsText(file);
};

View File

@@ -49,6 +49,7 @@ RouterFactory.configure(factory => {
//Development routes
if (Meteor.isDevelopment){
let StoryBook = require('/imports/ui/StoryBook.vue').default;
let IconAdmin = require('/imports/ui/icons/IconAdmin.vue').default;
factory.addRoutes([
{
path: '/storybook/:component',
@@ -58,6 +59,10 @@ RouterFactory.configure(factory => {
path: '/storybook',
name: 'storybook',
component: StoryBook,
}, {
path: '/icon-admin',
name: "iconAdmin",
component: IconAdmin,
},
]);
}

5
app/package-lock.json generated
View File

@@ -2149,6 +2149,11 @@
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-1.4.2.tgz",
"integrity": "sha512-HnMBBtO5FqTPzr6FXVlxnYulGMrHVlcmBL7SSLE/AQnNbdUpT6Jw9D/Kdw9fbK5mdn7JSWMcfWpPJUrqYTtK1w=="
},
"vuetify-upload-button": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/vuetify-upload-button/-/vuetify-upload-button-1.2.2.tgz",
"integrity": "sha512-WQmAevQl4F2musSLpPzyT3fCBCksFiL7V6Jh8qD1fxGxHjAvjGywJKWjPLMzXDHkJfo6q2OPJtynPU0QXN5hGA=="
},
"vuex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz",

View File

@@ -29,6 +29,7 @@
"vue-meteor-tracker": "^2.0.0-beta.5",
"vue-router": "^3.0.2",
"vuetify": "^1.4.2",
"vuetify-upload-button": "^1.2.2",
"vuex": "^3.0.1"
},
"devDependencies": {}