Skip to content

Commit d1589ea

Browse files
committed
frontend for adding notes
* deck selection during addin note * fields - header, occlusions, images, notes and tags * basic tools implementation using fabric.js - rectangle - ellipse - group - delete - multiple select - undo, redo
1 parent 5735d30 commit d1589ea

30 files changed

+1279
-18
lines changed

build/configure/src/web.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,18 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> {
334334
":sass"
335335
],
336336
)?;
337+
build_page(
338+
"image-occlusion",
339+
true,
340+
inputs![
341+
//
342+
":ts:lib",
343+
":ts:components",
344+
":ts:sveltelib",
345+
":ts:tag-editor",
346+
":sass"
347+
],
348+
)?;
337349

338350
Ok(())
339351
}

ftl/core/editing.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,12 @@ editing-close-html-tags = Auto-close HTML tags
6969

7070
editing-html-editor = HTML Editor
7171
72+
## Image Occlusion editing
73+
7274
editing-image-occlusion = Image Occlusion
75+
editing-masks = Masks
76+
editing-notes = Notes
77+
editing-hide-all-guess-one = Hide All, Guess One
78+
editing-hide-one-guess-one = Hide One, Guess One
79+
editing-question-mask-color = Question Mask Color
80+
editing-answer-mask-color = Answer Mask Color

rslib/src/image_occlusion/imagedata.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ impl Collection {
1212
let mut metadata = ImageClozeMetadata {
1313
..Default::default()
1414
};
15-
15+
let deck_name = self.get_current_deck().unwrap();
16+
metadata.deck_id = deck_name.id.0;
1617
metadata.data = read(path)?;
17-
metadata.deck_id = 1;
18-
1918
Ok(metadata)
2019
}
2120

@@ -38,9 +37,7 @@ impl Collection {
3837
.to_string();
3938

4039
let mgr = MediaManager::new(&self.media_folder, &self.media_db)?;
41-
let mut ctx = mgr.dbctx();
42-
let actual_image_name_after_adding =
43-
mgr.add_file(&mut ctx, &image_filename, &image_bytes)?;
40+
let actual_image_name_after_adding = mgr.add_file(&image_filename, &image_bytes)?;
4441

4542
let image_tag = format!(
4643
"<img id='img' src='{}'></img>",
@@ -72,8 +69,7 @@ impl Collection {
7269
Some(nt) => nt,
7370
None => {
7471
self.add_io_notetype(&name)?;
75-
self.get_notetype_by_name(&name)?
76-
.ok_or(AnkiError::NotFound { source: todo!() })?
72+
self.get_notetype_by_name(&name).unwrap().unwrap()
7773
}
7874
};
7975
if nt.fields.len() < 4 {
@@ -99,7 +95,7 @@ impl Collection {
9995
let notes_field = self.tr.notetypes_notes_field().to_string();
10096

10197
notetype.set_modified(usn);
102-
98+
10399
notetype.add_field(&occlusion_field);
104100
notetype.add_field(&header_field);
105101
notetype.add_field(&image_field);

rslib/src/image_occlusion/io_back_template.html

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,9 @@
2626
var aColor = ioCloze.dataset.answerMaskColor;
2727

2828
if (cloze.children.length) {
29-
if (cloze.children[0].className == "cloze" && cloze.children[0].innerText == "[prompted]") {
29+
if (cloze.children[0].className.includes("cloze-inactive")) {
3030
draw(shape, qColor);
3131
}
32-
} else {
33-
if (cloze.innerText.trim() == "hidden") {
34-
draw(shape, aColor);
35-
}
3632
}
3733
}
3834
}

rslib/src/image_occlusion/io_front_template.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@
2525

2626
if (cloze.children.length) {
2727
if (cloze.children[0].className == "cloze" && cloze.children[0].innerText == "[prompted]") {
28-
draw(shape, qColor);
29-
}
30-
} else {
31-
if (cloze.innerText.trim() == "hidden") {
3228
draw(shape, aColor);
29+
} else if (cloze.innerText.trim() == "hidden") {
30+
draw(shape, qColor);
3331
}
3432
}
3533
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script>
2+
import IconButton from "../components/IconButton.svelte";
3+
import { cursorTools, undoRedoTools, zoomTools } from "./tools/bottom-tools";
4+
5+
export let activeTool = "cursor";
6+
export let canvas;
7+
export let instance;
8+
</script>
9+
10+
<div class="bottom-toolbar">
11+
{#each undoRedoTools as undoRedoTool}
12+
<IconButton
13+
class="bottom-tool-icon"
14+
iconSize={110}
15+
on:click={() => {
16+
undoRedoTool.action(canvas);
17+
}}
18+
>
19+
{@html undoRedoTool.icon}
20+
</IconButton>
21+
{/each}
22+
23+
{#if activeTool === "cursor"}
24+
{#each cursorTools as cursorBottomTool}
25+
<IconButton
26+
class="bottom-tool-icon"
27+
iconSize={110}
28+
on:click={() => {
29+
cursorBottomTool.action(canvas);
30+
}}
31+
>
32+
{@html cursorBottomTool.icon}
33+
</IconButton>
34+
{/each}
35+
{/if}
36+
37+
{#if activeTool === "magnify"}
38+
{#each zoomTools as zoomBottomTool}
39+
<IconButton
40+
class="bottom-tool-icon"
41+
iconSize={110}
42+
on:click={() => {
43+
zoomBottomTool.action(instance);
44+
}}
45+
>
46+
{@html zoomBottomTool.icon}
47+
</IconButton>
48+
{/each}
49+
{/if}
50+
</div>
51+
52+
<style lang="scss">
53+
:global(.bottom-toolbar) {
54+
position: fixed;
55+
bottom: 0px;
56+
left: 46px;
57+
z-index: 99;
58+
width: 100%;
59+
}
60+
61+
:global(.bottom-tool-icon) {
62+
border-radius: 100px !important;
63+
width: 40px;
64+
height: 40px !important;
65+
line-height: 1px;
66+
margin: 6px;
67+
}
68+
</style>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts">
2+
import { getAnswerMaskColor, getQuestionMaskColor } from "./tools/lib";
3+
import * as tr from "@tslib/ftl";
4+
5+
const setColor = (type: string, e: any) => {
6+
localStorage.setItem(type, e.target.value);
7+
};
8+
</script>
9+
10+
<div class="color-dialog">
11+
<div class="color-btn-container">
12+
<label for="ques-color">{tr.editingQuestionMaskColor()}</label>
13+
<input
14+
type="color"
15+
id="ques-color"
16+
value={getQuestionMaskColor()}
17+
on:change={(e) => setColor("ques-color", e)}
18+
/>
19+
</div>
20+
<div class="color-btn-container">
21+
<label for="ans-color">{tr.editingAnswerMaskColor()}</label>
22+
<input
23+
type="color"
24+
id="ans-color"
25+
value={getAnswerMaskColor()}
26+
on:change={(e) => setColor("ans-color", e)}
27+
/>
28+
</div>
29+
</div>
30+
31+
<style lang="scss">
32+
.color-dialog {
33+
display: flex;
34+
flex-direction: column;
35+
position: fixed;
36+
z-index: 1;
37+
left: 40px;
38+
top: 200px;
39+
overflow: auto;
40+
background: white;
41+
padding: 16px;
42+
}
43+
44+
.color-btn-container {
45+
display: inline-grid;
46+
text-align: left;
47+
justify-items: left;
48+
margin: 2px;
49+
}
50+
</style>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!--
2+
Copyright: Ankitects Pty Ltd and contributors
3+
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
4+
-->
5+
<script lang="ts">
6+
import Col from "../components/Col.svelte";
7+
import Row from "../components/Row.svelte";
8+
import * as tr from "@tslib/ftl";
9+
import type { Decks } from "../lib/proto";
10+
import Select from "../components/Select.svelte";
11+
import SelectOption from "../components/SelectOption.svelte";
12+
13+
export let deckNameIds: Decks.DeckNameId[];
14+
export let deckId: number;
15+
$: label = deckNameIds.find((d) => d.id === deckId)?.name.replace(/^.+::/, "...");
16+
</script>
17+
18+
<Row --cols={2}>
19+
<Col --col-size={1}>
20+
{tr.decksDeck()}
21+
</Col>
22+
<Col --col-size={1}>
23+
<Select bind:value={deckId} {label}>
24+
{#each deckNameIds as { id, name }}
25+
<SelectOption value={id}>{name}</SelectOption>
26+
{/each}
27+
</Select>
28+
</Col>
29+
</Row>

ts/image-occlusion/Header.svelte

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!--
2+
Copyright: Ankitects Pty Ltd and contributors
3+
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
4+
-->
5+
<script lang="ts">
6+
import type { Decks } from "../lib/proto";
7+
import StickyContainer from "../components/StickyContainer.svelte";
8+
import DeckSelector from "./DeckSelector.svelte";
9+
10+
export let deckNameIds: Decks.DeckNameId[];
11+
export let deckId: number | null;
12+
</script>
13+
14+
<StickyContainer --sticky-border="var(--border)" --sticky-borders="0px 0 1px">
15+
{#if deckId}
16+
<DeckSelector {deckNameIds} bind:deckId />
17+
{/if}
18+
</StickyContainer>
19+
20+
<style lang="scss">
21+
</style>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<!--
2+
Copyright: Ankitects Pty Ltd and contributors
3+
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
4+
-->
5+
<script lang="ts">
6+
import { generate } from "./generate";
7+
import StickyFooter from "./StickyFooter.svelte";
8+
import Notes from "./Notes.svelte";
9+
import type { Decks } from "../lib/proto";
10+
import MasksEditor from "./MasksEditor.svelte";
11+
import Container from "../components/Container.svelte";
12+
import * as tr from "@tslib/ftl";
13+
14+
export let path: string;
15+
export let data: string;
16+
export let deckNameIds: Decks.DeckNameId[];
17+
export let deckId: number | null;
18+
19+
async function hideAllGuessOne(): Promise<void> {
20+
generate(path, "hidden", deckId!);
21+
}
22+
23+
async function hideOneGuessOne(): Promise<void> {
24+
generate(path, "shown", deckId!);
25+
}
26+
27+
let items = [
28+
{ label: tr.editingMasks(), value: 1 },
29+
{ label: tr.editingNotes(), value: 2 },
30+
];
31+
32+
let activeTabValue = 1;
33+
const tabChange = (tabValue) => () => (activeTabValue = tabValue);
34+
</script>
35+
36+
<Container class="image-occlusion">
37+
<ul>
38+
{#each items as item}
39+
<li class={activeTabValue === item.value ? "active" : ""}>
40+
<span on:click={tabChange(item.value)}>{item.label}</span>
41+
</li>
42+
{/each}
43+
</ul>
44+
45+
<div hidden={activeTabValue != 1}>
46+
<MasksEditor {path} {data} />
47+
</div>
48+
49+
<div hidden={activeTabValue != 2}>
50+
<Notes {deckNameIds} bind:deckId />
51+
<StickyFooter {hideAllGuessOne} {hideOneGuessOne} />
52+
</div>
53+
</Container>
54+
55+
<style lang="scss">
56+
:global(.image-occlusion) {
57+
--gutter-inline: 0.5rem;
58+
59+
:global(.row) {
60+
// rows have negative margins by default
61+
--bs-gutter-x: 0;
62+
// ensure equal spacing between tall rows like
63+
// dropdowns, and short rows like checkboxes
64+
min-height: 2.5em;
65+
align-items: center;
66+
}
67+
68+
:global(.col) {
69+
// cols have negative margins by default
70+
--bs-gutter-x: 0;
71+
padding: unset !important;
72+
}
73+
}
74+
ul {
75+
display: flex;
76+
flex-wrap: wrap;
77+
padding-left: 0;
78+
list-style: none;
79+
border-bottom: 1px solid #dee2e6;
80+
}
81+
li {
82+
margin-bottom: -1px;
83+
}
84+
85+
span {
86+
border: 1px solid transparent;
87+
border-top-left-radius: 0.25rem;
88+
border-top-right-radius: 0.25rem;
89+
display: block;
90+
padding: 0.5rem 1rem;
91+
cursor: pointer;
92+
}
93+
94+
span:hover {
95+
border-color: #e9ecef #e9ecef #dee2e6;
96+
}
97+
98+
li.active > span {
99+
color: #495057;
100+
background-color: #fff;
101+
border-color: #dee2e6 #dee2e6 #fff;
102+
}
103+
</style>

0 commit comments

Comments
 (0)