First word lookup draft

This commit is contained in:
Mr. Stallion 2021-01-20 18:02:15 -06:00
parent a56b9baed6
commit 00dcc5f571
7 changed files with 217 additions and 20 deletions

View File

@ -89,6 +89,12 @@
</select>
</div>
</modal>
<modal :buttons="false" ref="wordDefinitionViewer" dialogClass="word-definition-viewer">
<word-definition :expression="wordDefinitionLookup" ref="characterPage"></word-definition>
<template slot="title">
{{wordDefinitionLookup}}
</template>
</modal>
<logs ref="logsDialog"></logs>
</div>
</template>
@ -116,10 +122,12 @@
// import { BetterSqliteStore } from '../learn/store/better-sqlite3';
// import { Sqlite3Store } from '../learn/store/sqlite3';
import CharacterPage from '../site/character_page/character_page.vue';
import WordDefinition from '../learn/dictionary/WordDefinition.vue';
import {defaultHost, GeneralSettings, nativeRequire} from './common';
import { fixLogs /*SettingsStore, Logs as FSLogs*/ } from './filesystem';
import * as SlimcatImporter from './importer';
import _ from 'lodash';
import { EventBus } from '../chat/preview/event-bus';
// import Bluebird from 'bluebird';
// import Connection from '../fchat/connection';
// import Notifications from './notifications';
@ -171,7 +179,7 @@
log.info('init.chat.keytar.load.done');
@Component({
components: {chat: Chat, modal: Modal, characterPage: CharacterPage, logs: Logs}
components: {chat: Chat, modal: Modal, characterPage: CharacterPage, logs: Logs, 'word-definition': WordDefinition}
})
export default class Index extends Vue {
showAdvanced = false;
@ -190,6 +198,7 @@
adName = '';
fixCharacters: ReadonlyArray<string> = [];
fixCharacter = '';
wordDefinitionLookup = '';
shouldShowSpinner = false;
@ -242,6 +251,17 @@
@Hook('mounted')
onMounted(): void {
log.debug('init.chat.mounted');
EventBus.$on(
'word-definition',
(data: any) => {
this.wordDefinitionLookup = data.lookupWord;
if (!!data.lookupWord) {
(<Modal>this.$refs.wordDefinitionViewer).show();
}
}
);
}
@ -311,7 +331,6 @@
dt.connect();
}*/
}
async login(): Promise<void> {

View File

@ -94,7 +94,7 @@
async mounted(): Promise<void> {
log.debug('init.window.mounting');
// top bar devtools
browserWindow.webContents.openDevTools({ mode: 'detach' });
// browserWindow.webContents.openDevTools({ mode: 'detach' });
updateSupportedLanguages(browserWindow.webContents.session.availableSpellCheckerLanguages);
@ -255,7 +255,7 @@
);
// tab devtools
view.webContents.openDevTools();
// view.webContents.openDevTools();
// console.log('ADD TAB LANGUAGES', getSafeLanguages(this.settings.spellcheckLang), this.settings.spellcheckLang);
view.webContents.session.setSpellCheckerLanguages(getSafeLanguages(this.settings.spellcheckLang));

View File

@ -52,8 +52,8 @@ import {Logs, SettingsStore} from './filesystem';
import Notifications from './notifications';
import * as SlimcatImporter from './importer';
import Index from './Index.vue';
import log from 'electron-log';
import { DefinitionDictionary } from '../learn/dictionary/definition-dictionary'; // tslint:disable-line: match-default-export-name
import log from 'electron-log'; // tslint:disable-line: match-default-export-name
import { WordPosSearch } from '../learn/dictionary/word-pos-search';
log.debug('init.chat');
@ -107,7 +107,7 @@ function openIncognito(url: string): void {
}
const webContents = electron.remote.getCurrentWebContents();
const wordDef = new DefinitionDictionary(electron.remote.app.getAppPath());
const wordPosSearch = new WordPosSearch();
webContents.on('context-menu', (_, props) => {
const hasText = props.selectionText.trim().length > 0;
@ -185,15 +185,16 @@ webContents.on('context-menu', (_, props) => {
click: () => electron.ipcRenderer.send('dictionary-remove', props.selectionText)
}, {type: 'separator'});
if (props.selectionText) {
const lookupWord = props.selectionText || wordPosSearch.getLastClickedWord();
if (lookupWord) {
menuTemplate.unshift(
{ type: 'separator' },
{
label: `Look up '${props.selectionText}'`,
label: `Look up '${lookupWord}'`,
click: async() => {
const words = await wordDef.getDefinition(props.selectionText);
// console.log('WORDS', words);
EventBus.$emit('word-definition', { lookupWord, x: props.x, y: props.y });
}
}
);

View File

@ -86,8 +86,7 @@ export class CacheManager {
this.queue.push(entry);
console.log('Added to queue', entry.name, entry.added.toISOString());
// console.log('Added to queue', entry.name, entry.added.toISOString());
// console.log('AddProfileForFetching', name, this.queue.length);
}
@ -239,11 +238,11 @@ export class CacheManager {
const scheduleNextFetch = () => {
this.profileTimer = setTimeout(
async() => {
const d = Date.now();
// const d = Date.now();
const next = this.consumeNextInQueue();
if (next) {
console.log('Next in queue', next.name, (Date.now() - d) / 1000.0);
// console.log('Next in queue', next.name, (Date.now() - d) / 1000.0);
try {
// tslint:disable-next-line: binary-expression-operand-order
@ -259,7 +258,7 @@ export class CacheManager {
this.queue.push(next); // return to queue
}
console.log('Completed', next.name, (Date.now() - d) / 1000.0);
// console.log('Completed', next.name, (Date.now() - d) / 1000.0);
}
scheduleNextFetch();

View File

@ -0,0 +1,65 @@
<template>
<ul>
<li v-for="definition in definitions">
<p><i>({{definition.type}}.)</i> {{definition.definition}}</p>
<small>{{definition.synonyms.join(', ').replace(/_/g, ' ')}}</small>
</li>
</ul>
</template>
<script lang="ts">
import Vue from 'vue';
import { Definition, DefinitionDictionary } from './definition-dictionary';
import electron from 'electron';
import { Component, Prop, Watch } from '@f-list/vue-ts';
// import { EventBus } from '../../chat/preview/event-bus';
@Component({})
export default class WordDefinition extends Vue {
private wordDef = new DefinitionDictionary(electron.remote.app.getAppPath());
@Prop
readonly expression?: string;
public definitions: Definition[] = [];
// @Hook('mounted')
// mounted(): void {
//
// }
@Watch('expression')
async onWordChange(newExpression: string): Promise<void> {
this.definitions = await this.wordDef.getDefinition(newExpression);
}
}
</script>
<style lang="scss">
.word-definition-viewer {
ul {
margin-left: 0;
padding-left: 0;
li {
list-style: none;
padding-left: 0;
margin-left: 0;
p {
margin-bottom: 0.1em;
color: var(--input-color);
}
small {
display: block;
margin-bottom: 1.2em;
color: var(--secondary);
}
}
}
}
</style>

View File

@ -13,6 +13,7 @@ import WordNet from './wordnet/wordnet';
export interface Definition {
definition: string;
synonyms: string[];
type: string;
}
@ -23,8 +24,6 @@ export class DefinitionDictionary {
constructor(basePath: string) {
const dataDir = path.join(basePath, 'assets', 'wordnet-dictionary');
console.log('MOO MOO', process.cwd(), dataDir);
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
this.wordnet = new WordNet(
@ -68,7 +67,8 @@ export class DefinitionDictionary {
(r) => (
{
definition: r.def,
synonyms: r.synonyms
synonyms: r.synonyms,
type: r.pos
} as any as Definition
)
);

View File

@ -0,0 +1,113 @@
import _ from 'lodash';
export interface FoundWord {
word: string;
start: number;
clickedRect: DOMRect;
}
export class WordPosSearch {
private listener?: (e: MouseEvent) => void;
private lastClicked: FoundWord | null = null;
constructor() {
this.start();
}
stop() {
if (this.listener) {
document.removeEventListener('contextmenu', this.listener);
}
delete this.listener;
}
start() {
this.stop();
this.listener = this.generateListener();
document.addEventListener(
'contextmenu',
this.listener
);
}
generateListener(): ((e: MouseEvent) => void) {
return (e: MouseEvent): void => {
try {
if (!e.target) {
return;
}
this.lastClicked = _.reduce(
(e.target as any).childNodes || [],
(accum: FoundWord | null, node) => (accum ? accum : this.findClickedWord(node as any, e.clientX, e.clientY)),
null
);
} catch (err) {
console.log('wordpos.event', err);
}
};
}
findClickedWord(parentElt: Element, x: number, y: number): FoundWord | null {
if (!parentElt.textContent) {
return null;
}
const range = document.createRange();
const words = parentElt.textContent.split(/[\s\.\<\>\,\?\!\;\:\@\$\*\(\)]/);
let start = 0;
let end = 0;
for(let i = 0; i < words.length; i++) {
const word = words[i];
end = start + word.length;
try {
range.setStart(parentElt, start);
range.setEnd(parentElt, end);
// not getBoundingClientRect as word could wrap
const rects = range.getClientRects();
const clickedRect = this.isClickInRects(x, y, rects);
if (clickedRect) {
return { word, start, clickedRect };
}
start = end + 1;
} catch (e) {
// console.log('MM', 'word', word, 'start', start, 'end', end, 'i', i, words);
// console.error(e);
return null;
}
}
return null;
}
isClickInRects(x: number, y: number, rects: DOMRectList): DOMRect | null {
for(let i = 0; i < rects.length; ++i) {
const r = rects[i];
if ((r.left < x) && (r.right > x) && (r.top < y) && (r.bottom > y)) {
return r;
}
}
return null;
}
getLastClickedWord(): string | null {
return this.lastClicked ? this.lastClicked.word : null;
}
}