Added fuzzy search to the editor. Missing proper index handling of the plan display, I'd like to keep original indices on the filtered results

merge-notes
isark 2 years ago
parent c754789ffb
commit a81bcc908f

@ -20,7 +20,9 @@
"@angular/platform-browser": "^16.1.4", "@angular/platform-browser": "^16.1.4",
"@angular/platform-browser-dynamic": "^16.1.4", "@angular/platform-browser-dynamic": "^16.1.4",
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"@types/natural-compare": "^1.4.1",
"fuzzr": "github:isark2/fuzzr#v0.3.1", "fuzzr": "github:isark2/fuzzr#v0.3.1",
"natural-compare": "^1.4.0",
"ngx-moveable": "^0.48.1", "ngx-moveable": "^0.48.1",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"tslib": "^2.6.0", "tslib": "^2.6.0",

@ -93,9 +93,9 @@ fn main() {
app.manage(tx); app.manage(tx);
app.manage(Mutex::new(Storage::default())); app.manage(Mutex::new(Storage::default()));
// app.get_window("Overlay") app.get_window("Overlay")
// .expect("Could not get main overlay window") .expect("Could not get main overlay window")
// .open_devtools(); .open_devtools();
Ok(()) Ok(())
}) })

@ -10,6 +10,7 @@ import { ConfigService } from "./services/config.service";
import { ColorPickerComponent } from './color-picker/color-picker.component'; import { ColorPickerComponent } from './color-picker/color-picker.component';
import { MatButtonModule } from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
import { EditorComponent } from "./editor/editor.component"; import { EditorComponent } from "./editor/editor.component";
import { initFuzzr } from "./fuzzr/fuzzr";
export function initializeApp(configService: ConfigService) { export function initializeApp(configService: ConfigService) {
return (): Promise<any> => { return (): Promise<any> => {

@ -1,6 +1,7 @@
<div cdkDropListGroup *ngIf="areas" class="editor-container"> <div cdkDropListGroup *ngIf="areas" class="editor-container">
<div class="container"> <div class="container">
<h2>available items</h2> <input type="text" [(ngModel)]="areaSearchString">
<h2>Campaign zones</h2>
<div <div
cdkDropList cdkDropList
@ -8,18 +9,24 @@
class="list areas" class="list areas"
cdkDropListSortingDisabled cdkDropListSortingDisabled
(cdkDropListDropped)="dropHandler($event)"> (cdkDropListDropped)="dropHandler($event)">
<div class="box" *ngFor="let item of areas" cdkDrag>Name: {{item.name}}<br> in act {{item.act}}</div> <div class="box" *ngFor="let item of filterAreas(areaSearchString)" cdkDrag>
Name: {{item.name}}<br> in act {{item.act}}
</div>
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<h2>Shopping basket</h2> <input type="text" [(ngModel)]="planSearchString">
<h2>Plan</h2>
<div <div
cdkDropList cdkDropList
[cdkDropListData]="plan.plan" [cdkDropListData]="plan.plan"
class="list" class="list"
(cdkDropListDropped)="dropHandler($event)"> (cdkDropListDropped)="dropHandler($event)">
<div class="box" *ngFor="let item of plan.plan" cdkDrag>{{item.area_key}}</div> <div class="box" *ngFor="let item of filterPlanElements(planSearchString); let index = index" cdkDrag>
<div class="area">{{areasMap?.get(item.area_key)?.name}}</div>
<div class="index">#{{index}}</div>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -1,68 +1,68 @@
:host { :host {
height: 100%; height: 100%;
} }
.container { .container {
width: 400px; width: 400px;
max-width: 100%; max-width: 100%;
margin: 0 25px 25px 0; margin: 0 25px 25px 0;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
flex-grow: 1 1 auto; flex-grow: 1 1 auto;
} }
.list { .list {
border: solid 1px #ccc; border: solid 1px #ccc;
min-height: 60px; min-height: 60px;
background: white; background: white;
border-radius: 4px; border-radius: 4px;
display: block; display: block;
padding-bottom: 50px; padding-bottom: 50px;
overflow: auto; overflow: auto;
max-height: 100%; max-height: 100%;
} }
.box { .box {
padding: 20px 10px; padding: 10px 5px;
border-bottom: solid 1px #ccc; border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
cursor: move; cursor: move;
background: white; background: white;
font-size: 14px; font-size: 14px;
} }
.cdk-drag-preview { .cdk-drag-preview {
box-sizing: border-box; box-sizing: border-box;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12); 0 3px 14px 2px rgba(0, 0, 0, 0.12);
} }
.cdk-drag-placeholder { .cdk-drag-placeholder {
opacity: 0; opacity: 0;
} }
.cdk-drag-animating { .cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); transition: transform 75ms cubic-bezier(0, 0, 0.2, 1);
} }
.box:last-child { .box:last-child {
border: none; border: none;
} }
.list.cdk-drop-list-dragging .box:not(.cdk-drag-placeholder) { .list.cdk-drop-list-dragging .box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); transition: transform 125ms cubic-bezier(0, 0, 0.2, 1);
} }
.editor-container { .editor-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100%; height: 100%;
} }

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { import {
CdkDrag, CdkDrag,
@ -6,13 +6,14 @@ import {
CdkDropList, CdkDropList,
CdkDropListGroup, CdkDropListGroup,
moveItemInArray, moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop'; } from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { WorldArea } from '../models/world-area'; import { WorldArea } from '../models/world-area';
import { Plan, PlanElement } from '../models/plan'; import { Plan, PlanElement } from '../models/plan';
import { WorldAreaService } from '../services/world-area.service'; import { WorldAreaService } from '../services/world-area.service';
import { FormsModule } from '@angular/forms';
import { Fuzzr } from '../fuzzr/fuzzr';
@Component({ @Component({
selector: 'plan-editor', selector: 'plan-editor',
@ -21,6 +22,7 @@ import { WorldAreaService } from '../services/world-area.service';
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule,
FormsModule,
CdkDropListGroup, CdkDropListGroup,
CdkDropList, CdkDropList,
CdkDrag CdkDrag
@ -30,6 +32,10 @@ export class EditorComponent implements OnInit {
areas?: WorldArea[]; areas?: WorldArea[];
planAreas: WorldArea[]; planAreas: WorldArea[];
plan: Plan; plan: Plan;
areasMap?: Map<String, WorldArea>;
areaSearchString: string = "";
planSearchString: string = "";
planFuzzer: Fuzzr;
constructor(public worldAreaService: WorldAreaService) { constructor(public worldAreaService: WorldAreaService) {
this.plan = { this.plan = {
@ -37,13 +43,19 @@ export class EditorComponent implements OnInit {
current: 0, current: 0,
} }
this.planFuzzer = new Fuzzr(this.plan.plan, {
toString: (e: PlanElement) => {
return this.areasMap?.get(e.area_key)?.name;
}
});
this.planAreas = []; this.planAreas = [];
} }
ngOnInit(): void { ngOnInit(): void {
this.worldAreaService.getWorldAreas().subscribe(worldAreas => { this.worldAreaService.getWorldAreas().subscribe(worldAreas => {
this.areas = [...worldAreas.values()]; this.areas = [...worldAreas.values()];
console.log(this.areas); this.areasMap = worldAreas;
}); });
} }
@ -62,4 +74,23 @@ export class EditorComponent implements OnInit {
notes: undefined, notes: undefined,
}; };
} }
filterAreas(searchString: string) {
if (searchString !== "") {
return this.worldAreaService.matcher?.search(searchString).map(({ item }) => {
return item[1];
});
} else {
return this.areas;
}
}
filterPlanElements(searchString: string) {
if (searchString !== "") {
return this.planFuzzer.search(searchString).map(({item}) => item);
} else {
return this.plan.plan;
}
}
} }

@ -0,0 +1,8 @@
import init, { Fuzzr } from 'fuzzr/pkg';
export function initFuzzr() {
return init("/fuzzr/pkg/fuzzr_bg.wasm")
}
export { Fuzzr };

@ -8,4 +8,5 @@ export interface Plan {
export interface PlanElement { export interface PlanElement {
area_key: string; area_key: string;
notes?: string; notes?: string;
index?: number;
} }

@ -1,30 +1,27 @@
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import init, { Fuzzr } from 'fuzzr/pkg';
import { WorldArea } from '../models/world-area'; import { WorldArea } from '../models/world-area';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { Observable, ReplaySubject, Subject, filter, from, map } from 'rxjs'; import { Observable, ReplaySubject, Subject, filter, from, map } from 'rxjs';
import naturalCompare from 'natural-compare';
import { Fuzzr } from '../fuzzr/fuzzr';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class WorldAreaService { export class WorldAreaService {
private worldAreas?: Map<string, WorldArea>; private worldAreas?: Map<string, WorldArea>;
private matcher?: Fuzzr; public matcher?: Fuzzr;
private worldAreasSubject = new ReplaySubject<Map<string, WorldArea>>(); private worldAreasSubject = new ReplaySubject<Map<string, WorldArea>>();
private matcherSubject = new ReplaySubject<Fuzzr>();
constructor(private zone: NgZone) { constructor(private zone: NgZone) {
from(invoke<Map<string, WorldArea>>('load_world_areas')).subscribe((data) => { from(invoke<Map<string, WorldArea>>('load_world_areas')).subscribe((data) => {
this.worldAreas = new Map(Object.entries(data)); const entries = Object.entries(data).sort((a, b) => naturalCompare(a[1].named_id, b[1].named_id));
console.log("pre next worldareas"); this.worldAreas = new Map(entries);
this.zone.run(() => this.worldAreasSubject.next(this.worldAreas!)); this.zone.run(() => this.worldAreasSubject.next(this.worldAreas!));
console.log("post next worldareas"); this.matcher = new Fuzzr(this.worldAreas, {
from(init("/fuzzr/pkg/fuzzr_bg.wasm")).subscribe(() => { toString: (e: [string, WorldArea]) => e[1].name
this.matcher = new Fuzzr(this.worldAreas); })
this.zone.run(() => this.matcherSubject.next(this.matcher!));
});
}); });
} }
@ -32,11 +29,5 @@ export class WorldAreaService {
return this.worldAreasSubject.asObservable().pipe( return this.worldAreasSubject.asObservable().pipe(
filter(worldAreas => !!worldAreas), filter(worldAreas => !!worldAreas),
); );
} }
getMatcher(): Observable<Fuzzr> {
return this.matcherSubject.asObservable().pipe(
filter(matcher => !!matcher),
);
}
} }

@ -1,6 +1,10 @@
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module"; import { AppModule } from "./app/app.module";
import { initFuzzr } from "./app/fuzzr/fuzzr";
initFuzzr().then(() => {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
});
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

@ -19,7 +19,8 @@
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
"useDefineForClassFields": false, "useDefineForClassFields": false,
"lib": ["ES2022", "dom"] "lib": ["ES2022", "dom"],
"esModuleInterop": true
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,

@ -2555,6 +2555,11 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
"@types/natural-compare@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@types/natural-compare/-/natural-compare-1.4.1.tgz#fc2b11ea100d380b0de7af15768bef209157bfb9"
integrity sha512-9dr4UakpvN0QUvwNefk9+o14Sr1pPPIDWkgCxPkHcg3kyjtc9eKK1ng6dZ23vRwByloCqXYtZ1T5nJxkk3Ib3A==
"@types/node@*", "@types/node@>=10.0.0": "@types/node@*", "@types/node@>=10.0.0":
version "20.4.2" version "20.4.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
@ -5339,6 +5344,11 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
needle@^3.1.0: needle@^3.1.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44" resolved "https://registry.yarnpkg.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44"

Loading…
Cancel
Save