Skip to content

Commit

Permalink
Merge branch 'web_worker'
Browse files Browse the repository at this point in the history
  • Loading branch information
connorhsm committed May 14, 2024
2 parents 3a41614 + 2901b45 commit 0b5d789
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 103 deletions.
5 changes: 3 additions & 2 deletions process/src/GameData.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const SpriteProcessor = require('./SpriteProcessor');
const ObjectFilters = require('./ObjectFilters');
const ObjectBadges = require('./ObjectBadges');
const SitemapGenerator = require('./SitemapGenerator');
const readFileNormalized = require('./readFileNormalized');

class GameData {
constructor(processDir, dataDir, staticDir) {
Expand Down Expand Up @@ -90,7 +91,7 @@ class GameData {
});
this.eachFileInDir("objects", ".txt", (path, filename) => {
if (filename.startsWith("groundHeat")) {
const content = fs.readFileSync(path, "utf8");
const content = readFileNormalized(path);
Biome.applyGroundHeat(this.biomes, filename, content);
}
});
Expand Down Expand Up @@ -233,7 +234,7 @@ class GameData {

eachFileContent(dirName, extension, callback) {
this.eachFileInDir(dirName, extension, (path, filename) => {
callback(fs.readFileSync(path, "utf8"), filename);
callback(readFileNormalized(path, "utf8"), filename);
});
}

Expand Down
9 changes: 9 additions & 0 deletions process/src/readFileNormalized.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";
const fs = require("fs");

/** read file and normalize line endings of CR to LF */
function readFileNormalized(path) {
return fs.readFileSync(path, "utf8").replaceAll('\r\n', '\n');
}

module.exports = readFileNormalized;
212 changes: 115 additions & 97 deletions src/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
}
/* Search Button */
.v-select .dropdown-toggle .search-icon {
.v-select .dropdown-toggle .search-icon, .v-select .dropdown-toggle .spin {
position: absolute;
top: 9px;
left: 10px;
Expand Down Expand Up @@ -323,13 +323,35 @@
.v-select .selected-tag {
position: absolute;
}
.spin {
transform: rotate(0);
animation: spin 1.1s infinite linear;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

<template>
<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix']">
<img src="../assets/search.svg" width="19" height="20" class="search-icon" />

<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix']">
<img v-if="!processing" src="../assets/search.svg" width="19" height="20" class="search-icon"/>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-loader spin" width="19" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 6l0 -3" />
<path d="M16.25 7.75l2.15 -2.15" />
<path d="M18 12l3 0" />
<path d="M16.25 16.25l2.15 2.15" />
<path d="M12 18l0 3" />
<path d="M7.75 16.25l-2.15 2.15" />
<path d="M6 12l-3 0" />
<path d="M7.75 7.75l-2.15 -2.15" />
</svg>
<span class="selected-tag" v-for="option in valueAsArray" v-bind:key="option.index">
<slot name="selected-option" v-bind="option">
{{ getOptionLabel(option) }}
Expand Down Expand Up @@ -380,15 +402,28 @@
</div>

<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
<li v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
<ul ref="dropdownMenu" v-if="dropdownOpen && search.length > 0" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
<li v-if="!processing && maybeInaccurate" class="no-options">
<slot name="no-options" style="display: inline-block">
<pp>Showing inaccurate results, check spelling.</pp>
<span style="color: #5897fb;" title="Unfortunately, our conventional string search did not yield any matching results. As a result, we are displaying results generated by a predictive engine. Please be aware that these results may not be entirely accurate.">
<svg style="margin-bottom: -4px" width="20" height="20" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
</svg>
</span>
</slot>
</li>
<li v-if="!processing" v-for="(option, index) in displayed_options" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
<a @mousedown.prevent="select(option)">
<slot name="option" v-bind="option">
{{ getOptionLabel(option) }}
</slot>
</a>
</li>
<li v-if="!filteredOptions.length" class="no-options">
<li v-if="processing" class="no-options">
<slot name="no-options">Searching...</slot>
</li>
<li v-else-if="displayed_options.length === 0" class="no-options">
<slot name="no-options">Sorry, no matching options.</slot>
</li>
</ul>
Expand Down Expand Up @@ -657,7 +692,12 @@
search: '',
open: false,
mutableValue: null,
mutableOptions: []
mutableOptions: [],
worker: null,
worker_promises: [], // keep track of multiple pending search requests.
displayed_options: [],
processing: false,
maybeInaccurate: false, // if results are from guessing
}
},
Expand Down Expand Up @@ -726,12 +766,77 @@
this.mutableValue = this.value
this.mutableOptions = this.options.slice(0)
this.mutableLoading = this.loading
if (this.useGuessingEngine && typeof Worker !== "undefined") {
this.prepareEngine();
} else if (this.useGuessingEngine) {
console.error("can't use guessing engine, web worker not supported on this browser.")
}
this.$watch('mutableOptions', this.doSearch)
this.$watch('taggable', this.doSearch)
this.$watch('filterable', this.doSearch)
// debounce will only trigger the search after user stopped typing for 250ms
this.$watch('search', _.debounce(this.doSearch, 250))
this.$on('option:created', this.maybePushTag)
},
methods: {
async doSearch() {
this.processing = true;
try {
if (!this.filterable && !this.taggable) {
return this.mutableOptions.slice()
}
let options = this.mutableOptions.filter((option) => {
if (typeof option === 'object' && option.hasOwnProperty(this.label)) {
return option[this.label].toLowerCase().indexOf(this.search.toLowerCase()) > -1
} else if (typeof option === 'object' && !option.hasOwnProperty(this.label)) {
return console.warn(`[vue-select warn]: Label key "option.${this.label}" does not exist in options object.\nhttp://sagalbot.github.io/vue-select/#ex-labels`)
}
return option.toLowerCase().indexOf(this.search.toLowerCase()) > -1
})
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
options.unshift(this.search)
}
if (options.length === 0 && this.useGuessingEngine) {
const query = this.search.toLowerCase()
options = await new Promise(resolve => {
this.worker_promises.push(resolve);
this.worker.postMessage({
signal: 'search',
data: query,
meta: {limit: 30, promise_index: this.worker_promises.length - 1}
})
})
this.maybeInaccurate = true;
} else {
this.maybeInaccurate = false;
}
this.displayed_options = _.take(options, 30);
} finally {
this.processing = false;
}
},
prepareEngine() {
this.worker = new Worker(new URL("../search.worker.js", import.meta.url))
this.worker.onmessageerror = this.worker.onerror = function () {
console.error("web worker unexpected error!");
}
this.worker.onmessage = ({data: {signal, data, meta}}) => {
meta = meta || {};
if (signal === 'log') {
console.log("WebWorkerLog: " + data)
} else if (signal === 'search_result') {
const result = data.map(index => this.mutableOptions[index]);
this.worker_promises[meta.promise_index](result)
} else {
console.error("unknown signal from web worker: " + signal);
}
}
this.$watch('mutableOptions', () => {
this.worker.postMessage({signal: 'set_search_list', data: this.mutableOptions.map(opt => opt[this.label])})
}, {immediate: true})
},
/**
* Select a given option.
* @param {Object|String} option
Expand Down Expand Up @@ -922,63 +1027,6 @@
if (this.pushTags) {
this.mutableOptions.push(option)
}
},
similarText (first, second, percent) {
// discuss at: https://locutus.io/php/similar_text/
// original by: Rafał Kukawski (https://blog.kukawski.pl)
// bugfixed by: Chris McMacken
// bugfixed by: Jarkko Rantavuori original by findings in stackoverflow (https://stackoverflow.com/questions/14136349/how-does-similar-text-work)
// improved by: Markus Padourek (taken from https://www.kevinhq.com/2012/06/php-similartext-function-in-javascript_16.html)
// example 1: similar_text('Hello World!', 'Hello locutus!')
// returns 1: 8
// example 2: similar_text('Hello World!', null)
// returns 2: 0
if (first === null ||
second === null ||
typeof first === 'undefined' ||
typeof second === 'undefined') {
return 0
}
first += ''
second += ''
let pos1 = 0
let pos2 = 0
let max = 0
const firstLength = first.length
const secondLength = second.length
let p
let q
let l
let sum
for (p = 0; p < firstLength; p++) {
for (q = 0; q < secondLength; q++) {
for (l = 0; (p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++) { // eslint-disable-line max-len
// @todo: ^-- break up this crazy for loop and put the logic in its body
}
if (l > max) {
max = l
pos1 = p
pos2 = q
}
}
}
sum = max
if (sum) {
if (pos1 && pos2) {
sum += this.similarText(first.substr(0, pos1), second.substr(0, pos2))
}
if ((pos1 + max < firstLength) && (pos2 + max < secondLength)) {
sum += this.similarText(
first.substr(pos1 + max, firstLength - pos1 - max),
second.substr(pos2 + max,
secondLength - pos2 - max))
}
}
if (!percent) {
return sum
}
return (sum * 200) / (firstLength + secondLength)
}
},
Expand Down Expand Up @@ -1038,36 +1086,6 @@
}
},
/**
* The currently displayed options, filtered
* by the search elements value. If tagging
* true, the search text will be prepended
* if it doesn't already exist.
*
* @return {array}
*/
filteredOptions() {
if (!this.filterable && !this.taggable) {
return this.mutableOptions.slice()
}
let options = this.mutableOptions.filter((option) => {
if (typeof option === 'object' && option.hasOwnProperty(this.label)) {
return option[this.label].toLowerCase().indexOf(this.search.toLowerCase()) > -1
} else if (typeof option === 'object' && !option.hasOwnProperty(this.label)) {
return console.warn(`[vue-select warn]: Label key "option.${this.label}" does not exist in options object.\nhttp://sagalbot.github.io/vue-select/#ex-labels`)
}
return option.toLowerCase().indexOf(this.search.toLowerCase()) > -1
})
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
options.unshift(this.search)
}
if(options.length === 0 && this.useGuessingEngine) {
const query = this.search.toLowerCase()
options = [...this.mutableOptions].sort((b, a) => this.similarText(a[this.label], query) - this.similarText(b[this.label], query))
}
return _.take(options, 30);
},
/**
* Check if there aren't any options selected.
* @return {Boolean}
Expand Down
8 changes: 4 additions & 4 deletions src/mixins/typeAheadPointer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
},

watch: {
filteredOptions() {
displayed_options() {
this.typeAheadPointer = 0
}
},
Expand All @@ -32,7 +32,7 @@ module.exports = {
* @return {void}
*/
typeAheadDown() {
if (this.typeAheadPointer < this.filteredOptions.length - 1) {
if (this.typeAheadPointer < this.displayed_options.length - 1) {
this.typeAheadPointer++
if( this.maybeAdjustScroll ) {
this.maybeAdjustScroll()
Expand All @@ -46,8 +46,8 @@ module.exports = {
* @return {void}
*/
typeAheadSelect() {
if( this.filteredOptions[ this.typeAheadPointer ] ) {
this.select( this.filteredOptions[ this.typeAheadPointer ] );
if( this.displayed_options[ this.typeAheadPointer ] ) {
this.select( this.displayed_options[ this.typeAheadPointer ] );
} else if (this.taggable && this.search.length){
this.select(this.search)
}
Expand Down
Loading

0 comments on commit 0b5d789

Please sign in to comment.