merge-notes
parent
640a2bfaba
commit
6f2f7f27fc
@ -0,0 +1 @@
|
||||
{"plan":[{"area_key":"1_1_town","notes":""},{"area_key":"1_1_1","notes":""},{"area_key":"1_1_2","notes":""},{"area_key":"1_1_2a","notes":""},{"area_key":"1_1_3","notes":""},{"area_key":"1_1_4_0","notes":""},{"area_key":"1_1_4_1","notes":""}],"current":0}
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,189 @@
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom},
|
||||
path::PathBuf,
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use notify::{event, Config, PollWatcher, RecursiveMode, Watcher, Event};
|
||||
use regex::Regex;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
|
||||
struct LogFileReader {
|
||||
tx: Sender<String>,
|
||||
file_path: PathBuf,
|
||||
contents: Vec<u8>,
|
||||
position: u64,
|
||||
}
|
||||
|
||||
impl Drop for LogFileReader {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("LogFileReader dropped");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn share_mode(options: &mut OpenOptions) {
|
||||
options.share_mode(0x00000004 | 0x00000001 | 0x00000002);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn share_mode(options: &mut OpenOptions) {}
|
||||
|
||||
impl LogFileReader {
|
||||
fn new(file: &PathBuf, tx: Sender<String>) -> Result<Self, std::io::Error> {
|
||||
let mut lfr = LogFileReader {
|
||||
tx,
|
||||
file_path: file.to_path_buf(),
|
||||
contents: Vec::new(),
|
||||
position: 0,
|
||||
};
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
|
||||
options.read(true).write(false);
|
||||
|
||||
share_mode(&mut options);
|
||||
|
||||
let mut file = options.open(file)?;
|
||||
lfr.position = file
|
||||
.seek(SeekFrom::End(0))
|
||||
.expect("Could not start at end of file");
|
||||
|
||||
Ok(lfr)
|
||||
}
|
||||
|
||||
fn on_write_handler(&mut self) {
|
||||
self.contents.truncate(0);
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
|
||||
options.read(true).write(false);
|
||||
|
||||
share_mode(&mut options);
|
||||
|
||||
let mut file = options
|
||||
.open(&self.file_path)
|
||||
.expect("Could not re-open file");
|
||||
|
||||
let _ = file.seek(SeekFrom::Start(self.position));
|
||||
|
||||
self.position += file.read_to_end(&mut self.contents).unwrap() as u64;
|
||||
|
||||
for line in String::from_utf8_lossy(&self.contents).lines() {
|
||||
let line = line.to_string();
|
||||
if !line.is_empty() {
|
||||
self.tx.send(line).expect("Could not send line to receiver");
|
||||
}
|
||||
}
|
||||
|
||||
// do_process(contents)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entered_strings_receiver(path: PathBuf) -> (Receiver<String>, PollWatcher) {
|
||||
let (tx, rx) = channel();
|
||||
let mut lfr = LogFileReader::new(&path, tx).unwrap();
|
||||
|
||||
|
||||
|
||||
(rx, watch_file_for_writes(path, Box::new(move || lfr.on_write_handler())))
|
||||
}
|
||||
|
||||
|
||||
const POE_STEAM_APP_ID: u32 = 238960;
|
||||
pub fn get_poe_path() -> Option<PathBuf> {
|
||||
if let Some(mut steam_dir) = steamlocate::SteamDir::locate() {
|
||||
if let Some(app) = steam_dir.app(&POE_STEAM_APP_ID) {
|
||||
let mut path = app.path.clone();
|
||||
path.push("logs");
|
||||
path.push("Client.txt");
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn entered_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = String> + 'a {
|
||||
rx.iter().filter_map(|str| {
|
||||
if str.contains("You have entered ") {
|
||||
Some(str)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator of strings with the id
|
||||
pub fn area_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = String> + 'a {
|
||||
// Generating level 68 area "1_SideArea5_6" with seed 4103532853
|
||||
let reg = Regex::new("Generating level ([0-9]+) area \"(.+)\"").unwrap();
|
||||
let area_id_location = 2;
|
||||
rx.try_iter().filter_map(move |str| {
|
||||
let caps = reg.captures(&str);
|
||||
if let Some(caps) = caps {
|
||||
Some(caps[area_id_location].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn filter_func(str : String) -> Option<String> {
|
||||
let reg = Regex::new("Generating level ([0-9]+) area \"(.+)\"").unwrap();
|
||||
let area_id_location = 2;
|
||||
|
||||
let caps = reg.captures(&str);
|
||||
if let Some(caps) = caps {
|
||||
Some(caps[area_id_location].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blocking_area_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = String> + 'a {
|
||||
// Generating level 68 area "1_SideArea5_6" with seed 4103532853
|
||||
|
||||
rx.iter()
|
||||
}
|
||||
|
||||
fn watch_file_for_writes(path: PathBuf, mut on_write_handler: Box<dyn FnMut() + Send>) -> PollWatcher
|
||||
|
||||
{
|
||||
|
||||
// Create a watcher object, delivering debounced events.
|
||||
// The notification back-end is selected based on the platform.
|
||||
let config = Config::default().with_poll_interval(Duration::from_millis(150));
|
||||
let mut watcher = PollWatcher::new(
|
||||
move |e: Result<Event, _>| match e {
|
||||
Ok(e2) => {
|
||||
match e2.kind {
|
||||
|
||||
notify::EventKind::Modify(_) => {
|
||||
on_write_handler()
|
||||
},
|
||||
_ => (),
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
e => log::error!("Error watching for file something something: {e:?}"),
|
||||
},
|
||||
config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//let mut watcher = watcher(tx, Duration::from_millis(100)).unwrap();
|
||||
|
||||
// Add a path to be watched. All files and directories at that path and
|
||||
// below will be monitored for changes.
|
||||
watcher
|
||||
.watch(path.as_path(), RecursiveMode::NonRecursive)
|
||||
.unwrap();
|
||||
watcher
|
||||
}
|
@ -1,25 +1,23 @@
|
||||
|
||||
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
|
||||
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[RecordKeyChord]'
|
||||
selector: '[RecordKeyChord]',
|
||||
standalone: true
|
||||
})
|
||||
export class RecordKeyChord {
|
||||
@Output() finishedKeyChord = new EventEmitter<string[]>;
|
||||
@Output() chord = new EventEmitter<string[]>;
|
||||
|
||||
chordStack: string[] = [];
|
||||
|
||||
@HostListener('window:keydown', ['$event'])
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
console.log("keydown:", event);
|
||||
this.chordStack.push(event.key);
|
||||
|
||||
this.chord.next(this.chordStack);
|
||||
}
|
||||
|
||||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
console.log("keyup:", event);
|
||||
this.finishedKeyChord.next(this.chordStack);
|
||||
this.chordStack = [];
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
<div mat-dialog-content>
|
||||
<p>What's your favorite animal?</p>
|
||||
<mat-form-field>
|
||||
<mat-label>data.title</mat-label>
|
||||
<input matInput [(ngModel)]="data.chord" readonly RecordKeyChord (chord)="onChord($event)">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancel</button>
|
||||
<button mat-button [mat-dialog-close]="data.chord" cdkFocusInitial>Save</button>
|
||||
</div>
|
Loading…
Reference in new issue