Skip to content

Commit 40fa080

Browse files
Merge pull request #35 from HALFpipe/dev/import
Add import button
2 parents fc99854 + c4b382c commit 40fa080

File tree

9 files changed

+2567
-3153
lines changed

9 files changed

+2567
-3153
lines changed

package-lock.json

Lines changed: 2402 additions & 3099 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,40 @@
2525
"./src/model/model.ts"
2626
],
2727
"dependencies": {
28-
"@babel/core": "^7.17.8",
29-
"@babel/preset-env": "^7.16.11",
28+
"@babel/core": "^7.17.10",
29+
"@babel/preset-env": "^7.17.10",
3030
"@types/d3-format": "^3.0.1",
3131
"@types/inflected": "^1.1.29",
3232
"@types/resize-observer-browser": "^0.1.7",
33-
"autoprefixer": "^10.4.4",
34-
"babel-loader": "^8.2.3",
35-
"browserslist": "^4.20.2",
33+
"autoprefixer": "^10.4.7",
34+
"babel-loader": "^8.2.5",
35+
"browserslist": "^4.20.3",
3636
"clean-webpack-plugin": "^4.0.0",
3737
"collections": "^5.1.13",
38-
"core-js": "^3.21.1",
38+
"core-js": "^3.22.5",
3939
"d3-format": "^3.1.0",
40-
"got": "^12.0.2",
40+
"got": "^12.0.4",
4141
"html-webpack-plugin": "^5.5.0",
4242
"inflected": "^2.1.0",
4343
"node-sass": "^7.0.1",
44-
"postcss": "^8.4.12",
44+
"postcss": "^8.4.13",
4545
"postcss-inline-svg": "^5.0.0",
4646
"postcss-loader": "^6.2.1",
4747
"postcss-normalize": "^10.0.1",
4848
"postcss-reporter": "^7.0.5",
4949
"postcss-value-parser": "^4.2.0",
5050
"raw-loader": "^4.0.2",
5151
"sass-loader": "^12.6.0",
52-
"ts-loader": "^9.2.8",
53-
"typescript": "^4.6.2",
54-
"webpack": "^5.70.0",
52+
"ts-loader": "^9.3.0",
53+
"typescript": "^4.6.4",
54+
"webpack": "^5.72.1",
5555
"webpack-cli": "^4.9.2"
5656
},
5757
"devDependencies": {
58-
"@types/jest": "^27.4.1",
59-
"jest": "^27.5.1",
60-
"prettier": "^2.6.0",
61-
"ts-jest": "^27.1.3",
62-
"webpack-dev-server": "^4.7.4"
58+
"@types/jest": "^27.5.0",
59+
"jest": "^28.1.0",
60+
"prettier": "^2.6.2",
61+
"ts-jest": "^28.0.2",
62+
"webpack-dev-server": "^4.9.0"
6363
}
6464
}

src/model/__tests__/database.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Database } from "../database";
2+
import { Img } from "../dataclass";
3+
4+
describe("Database", () => {
5+
const path = "report.svg";
6+
7+
const img1 = Img.load({sub: "01", desc: "skull_strip_report", hash: "1", path});
8+
img1.index = 0;
9+
10+
const img2 = Img.load({sub: "02", desc: "skull_strip_report", hash: "2", path});
11+
img2.index = 1;
12+
13+
const imgsArray = [img1, img2];
14+
15+
const database = new Database();
16+
database.put(imgsArray);
17+
18+
it("gets closest image", () => {
19+
let closestImg = database.closest({"sub": "01", "type": "tsnr_rpt"});
20+
expect(closestImg.sub).toBe("01");
21+
expect(closestImg.type).toBe("skull_strip_report");
22+
23+
closestImg = database.closest({"sub": "03", "type": "tsnr_rpt"});
24+
expect(closestImg.sub).toBe("01");
25+
expect(closestImg.type).toBe("skull_strip_report");
26+
});
27+
28+
it("finds exact match", () => {
29+
let [ exactImg ] = database.findAll({sub: "01", type: "skull_strip_report"});
30+
expect(exactImg.hash).toBe("1");
31+
32+
let result = database.findAll({sub: "03"});
33+
expect(result.length).toBe(0);
34+
});
35+
36+
});

src/model/database.ts

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,73 @@ export class Database {
2323

2424
put(imgsArray: Array<Img>): void {
2525
this.imgsArray = imgsArray;
26+
2627
for (const img of this.imgsArray) {
27-
for (const k of entities) {
28-
const v = img[k];
29-
const valuedict = this.indexSets[k];
30-
if (!(v in valuedict)) {
31-
valuedict[v] = new FastSet<number>();
28+
for (const key of entities) {
29+
const value = img[key];
30+
31+
const valueMap = this.indexSets[key];
32+
33+
if (!(value in valueMap)) {
34+
valueMap[value] = new FastSet<number>();
3235
}
33-
valuedict[v].add(img.index);
36+
37+
valueMap[value].add(img.index);
3438
}
3539
}
3640
}
3741

38-
closest(obj: Tagged, entities: Entity[]): Img {
39-
let set: FastSet<number> | null = null;
40-
for (const k of entities) {
41-
if (obj[k] !== null && obj[k] in this.indexSets[k]) {
42-
if (set === null) {
43-
set = this.indexSets[k][obj[k]];
44-
} else {
45-
const candidateSet = set.intersection(this.indexSets[k][obj[k]]);
46-
if (candidateSet.length === 0) {
47-
break;
48-
} else {
49-
set = candidateSet;
50-
}
42+
matches(obj: Tagged, exact: boolean, basedOnEntities?: Entity[]): number[] | null {
43+
basedOnEntities = basedOnEntities || [...entities];
44+
45+
let matches: FastSet<number> | null = null;
46+
for (const key of basedOnEntities) {
47+
const value = obj[key];
48+
49+
if (value === null) {
50+
continue;
51+
}
52+
if (!(value in this.indexSets[key])) {
53+
continue;
54+
}
55+
56+
let indexSet = this.indexSets[key][value];
57+
if (matches === null) {
58+
matches = indexSet;
59+
} else {
60+
indexSet = matches.intersection(indexSet);
61+
if (!exact && indexSet.length === 0) {
62+
break;
5163
}
64+
65+
matches = indexSet;
5266
}
5367
}
54-
if (set !== null) {
55-
return this.imgsArray[set.sorted()[0]];
68+
69+
if (matches === null) {
70+
return null;
71+
}
72+
73+
return matches.toArray().sort();
74+
}
75+
76+
findAll(obj: Tagged): Img[] {
77+
const matches = this.matches(obj, true);
78+
79+
if (matches === null) {
80+
return Array();
5681
}
57-
return this.imgsArray[0]; // return default if no match
82+
83+
return matches.map((index) => this.imgsArray[index]);
84+
}
85+
86+
closest(obj: Tagged, basedOnEntities?: Entity[]): Img {
87+
const matches = this.matches(obj, false, basedOnEntities);
88+
89+
if (matches === null || matches.length < 1) {
90+
return this.imgsArray[0]; // default if no match
91+
}
92+
93+
return this.imgsArray[matches[0]];
5894
}
5995
}

src/model/dataclass/img.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import {
1111
import { keyPath } from "../key-path";
1212
import { Indexed, Tagged } from "../types";
1313

14+
interface Loadable extends Tagged {
15+
path: string;
16+
hash: string;
17+
desc: string;
18+
};
19+
1420
export class Img implements Indexed, Tagged {
1521
index: number;
1622

@@ -60,7 +66,7 @@ export class Img implements Indexed, Tagged {
6066
return Img.relatedImgsMap.get(this.keyPath);
6167
}
6268

63-
static async load(obj: Tagged): Promise<Img | null> {
69+
static load(obj: Loadable): Img | null {
6470
if (!("sub" in obj)) {
6571
console.warn("Val obj missing 'sub':", obj);
6672
return null;

src/model/model.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class Model {
6666

6767
static async load(): Promise<Model> {
6868
const model = new Model();
69-
69+
7070
const reportDfds: Array<Deferred> = new Array<Deferred>();
7171
for (let i = 0; i < 4; i++) {
7272
reportDfds.push(new Deferred());
@@ -95,15 +95,15 @@ export class Model {
9595
model.preprocStatuses[sub].push(preprocStatus);
9696

9797
} else if ("path" in element) { // reportimgs.js
98-
const img = await Img.load(element);
98+
const img = Img.load(element);
9999
if (img !== null) {
100100
model.addImg(img);
101101
}
102102

103103
} else if ("node" in element) { // reporterror.js
104104
const nodeError = await NodeError.load(element);
105105
const sub = nodeError.sub;
106-
106+
107107
if (!(sub in model.preprocStatuses)) {
108108
model.nodeErrors[sub] = new Array<NodeError>();
109109
}

src/view-model/ratings-view-model.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export class RatingsViewModel {
124124
let scanKeyPath: string = "";
125125
let scan: Scan;
126126
let imgRatingProperty: RatingProperty;
127-
for (const [i, img] of this.model.imgsArray.entries()) {
127+
for (const img of this.model.imgsArray.values()) {
128128
if (!this.model.ratingPropertiesByHash.has(img.hash)) {
129129
throw new Error(`RatingProperty not found for img '${img.hash}'`);
130130
}
@@ -180,10 +180,6 @@ export class RatingsViewModel {
180180
}
181181
}
182182

183-
// get img(): Img {
184-
// return
185-
// }
186-
187183
set(hash: string, rating: Rating): void {
188184
if (!this.model.ratingPropertiesByHash.has(hash)) {
189185
throw new Error(`Unknown hash '${hash}'`);

src/view/render.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export class Attribute {
99
}
1010

1111
// inspired by hyperscript
12-
export function h(tag: string, attrs: Attribute[], children: Node[]): HTMLElement {
13-
let element: HTMLElement = document.createElement(tag);
12+
export function h<K extends keyof HTMLElementTagNameMap>(tag: K, attrs: Attribute[], children: Node[]): HTMLElementTagNameMap[K] {
13+
let element = document.createElement(tag);
1414

1515
for (let attr of attrs) {
1616
element.setAttribute(attr.key, attr.value);

src/view/sidebar.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,20 @@ export class Sidebar extends HTMLElement {
3434
this.viewModel = viewModel;
3535

3636
const importButton = h(
37-
"button",
37+
"a",
3838
[new Attribute("class", "dropdown-item")],
3939
[t("Import...")]
4040
);
41+
const fileInput = h(
42+
"input",
43+
[
44+
new Attribute("type", "file"),
45+
new Attribute("hidden", ""),
46+
new Attribute("accept", ".json"),
47+
],
48+
[]
49+
);
50+
4151
const exportButton = h(
4252
"a",
4353
[
@@ -47,6 +57,7 @@ export class Sidebar extends HTMLElement {
4757
],
4858
[t("Export")]
4959
);
60+
5061
const viewTypeButton: { [key in ViewType]?: HTMLElement } = {};
5162
for (const viewType of viewTypes) {
5263
viewTypeButton[viewType] = h(
@@ -91,7 +102,7 @@ export class Sidebar extends HTMLElement {
91102
"ul",
92103
[new Attribute("class", "dropdown-menu")],
93104
[
94-
// h("li", [], [importButton]),
105+
h("li", [], [importButton, fileInput]),
95106
h("li", [], [exportButton]),
96107
h("li", [], [this.viewTypeButtonGroup]),
97108
h("li", [], [this.sortKeyButtonGroup]),
@@ -107,9 +118,35 @@ export class Sidebar extends HTMLElement {
107118
menuButton.classList.toggle("active");
108119
});
109120

110-
// importButton.addEventListener("click", () => {
111-
// //
112-
// });
121+
importButton.addEventListener("click", (e) => {
122+
e.preventDefault();
123+
fileInput.click();
124+
});
125+
fileInput.addEventListener("change", () => {
126+
const [ file ] = fileInput.files;
127+
const reader = new FileReader();
128+
129+
reader.addEventListener("load", () => {
130+
const database = viewModel.model.database;
131+
132+
const objs = JSON.parse(reader.result as string);
133+
for (const obj of objs) {
134+
const { rating } = obj;
135+
delete obj["rating"];
136+
137+
if (rating == "none") {
138+
continue;
139+
}
140+
141+
for (const img of database.findAll(obj)) {
142+
viewModel.ratingsViewModel.set(img.hash, rating);
143+
}
144+
}
145+
});
146+
147+
reader.readAsText(file);
148+
});
149+
113150
exportButton.addEventListener("click", () => {
114151
const objs = new Array();
115152
for (const [hash, ratingProperty] of viewModel.model.ratingPropertiesByHash) {

0 commit comments

Comments
 (0)