-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the capability to search for text with in a flow's objects. - New menu items / shortcuts for find/next/previous. - Display a find dialog inspired by the VS Code finder - Shows total result count and current result index - Arrows to move back and forth between items in the result set - Zoom the camera to the currently selected item in the result set
- Loading branch information
Showing
17 changed files
with
682 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
src/attack_flow_builder/src/assets/scripts/BlockDiagram/Utilities/Debouncer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export default class Debouncer { | ||
private timer: number | null; | ||
private seconds: number; | ||
|
||
/** | ||
* Creates a {@link Debouncer}. | ||
* @param duration | ||
* The number of seconds that the debouncer waits before calling its target function. | ||
*/ | ||
constructor(seconds: number) { | ||
this.timer = null; | ||
this.seconds = seconds; | ||
} | ||
|
||
/** | ||
* Debounce a function call. | ||
* @param fn | ||
* The function to call. This is only called if not superseded by another call before the debounce | ||
* duration elapses. | ||
*/ | ||
public call(fn: TimerHandler) { | ||
if (this.timer !== null) { | ||
clearTimeout(this.timer); | ||
} | ||
|
||
this.timer = setTimeout(fn, this.seconds * 1000); | ||
} | ||
} |
229 changes: 229 additions & 0 deletions
229
src/attack_flow_builder/src/components/Elements/FindDialog.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
<template> | ||
<div class="find-dialog-container" :class="{ | ||
hidden: | ||
!isShowingFindDialog | ||
}"> | ||
<input type="text" class="query" placeholder="Find" v-model="query" @keyup="runQuery" ref="query"> | ||
<div class="results"> | ||
<span v-if="totalResults === 0">No results</span> | ||
<span v-if="totalResults !== 0">{{ currentResultIndex + 1 }} of {{ totalResults }}</span> | ||
</div> | ||
<div class="control" @pointerdown="findPrevious()" :class="{ | ||
disabled: | ||
totalResults === 0 | ||
}"> | ||
<UpArrow /> | ||
</div> | ||
<div class="control" @pointerdown="findNext()" :class="{ | ||
disabled: | ||
totalResults === 0 | ||
}"> | ||
<DownArrow /> | ||
</div> | ||
<div class="control" @pointerdown="hideFindDialog()"> | ||
<Close color="#8c8c8c" /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import * as Store from "@/store/StoreTypes"; | ||
// Dependencies | ||
import { defineComponent } from "vue"; | ||
import { mapGetters, mapMutations, mapState } from "vuex"; | ||
import * as App from "@/store/Commands/AppCommands"; | ||
import * as Page from "@/store/Commands/PageCommands"; | ||
import { FindResult } from "@/store/Finder"; | ||
import Debouncer from "@/assets/scripts/BlockDiagram/Utilities/Debouncer"; | ||
// Components | ||
import Close from "@/components/Icons/Close.vue"; | ||
import DownArrow from "@/components/Icons/DownArrow.vue"; | ||
import UpArrow from "@/components/Icons/UpArrow.vue"; | ||
export default defineComponent({ | ||
name: "FindDialog", | ||
data() { | ||
return { | ||
query: "", | ||
lastQuery: "", | ||
debouncer: new Debouncer(0.4), | ||
totalResults: 0, | ||
currentResultIndex: 0, | ||
currentDiagramObject: null as any, | ||
} | ||
}, | ||
computed: { | ||
...mapState("ApplicationStore", { | ||
ctx(state: Store.ApplicationStore): Store.ApplicationStore { | ||
return state; | ||
}, | ||
editor(state: Store.ApplicationStore): Store.PageEditor { | ||
return state.activePage; | ||
} | ||
}), | ||
...mapGetters("ApplicationStore", ["isShowingFindDialog", "currentFindResult"]), | ||
}, | ||
components: { | ||
Close, | ||
DownArrow, | ||
UpArrow, | ||
}, | ||
watch: { | ||
isShowingFindDialog: { | ||
handler(isShowingFindDialog, oldValue) { | ||
if (isShowingFindDialog) { | ||
this.focus(); | ||
} else { | ||
this.blur(); | ||
} | ||
} | ||
}, | ||
currentFindResult: { | ||
handler(newResult, oldResult) { | ||
if (newResult !== null) { | ||
this.currentResultIndex = newResult.index; | ||
this.totalResults = newResult.totalResults; | ||
if (this.currentDiagramObject !== newResult.diagramObject) { | ||
this.currentDiagramObject = newResult.diagramObject; | ||
this.execute(new Page.UnselectDescendants(this.editor.page)); | ||
this.execute(new Page.SelectObject(this.currentDiagramObject)); | ||
this.execute(new Page.MoveCameraToSelection(this.ctx, this.editor.page)) | ||
} | ||
} else { | ||
this.totalResults = 0; | ||
} | ||
} | ||
} | ||
}, | ||
methods: { | ||
/** | ||
* Application Store mutations | ||
*/ | ||
...mapMutations("ApplicationStore", ["execute"]), | ||
/** | ||
* Focus the input field and highlight existing text | ||
*/ | ||
focus() { | ||
const queryInput = this.$refs.query as HTMLInputElement; | ||
queryInput.focus(); | ||
queryInput.select(); | ||
}, | ||
/** | ||
* Blur the input field. | ||
*/ | ||
blur() { | ||
const queryInput = this.$refs.query as HTMLInputElement; | ||
queryInput.blur(); | ||
}, | ||
/** | ||
* Update find results with the current query. | ||
*/ | ||
runQuery(event: KeyboardEvent) { | ||
if (event.key === "Escape") { | ||
this.hideFindDialog(); | ||
} else if (this.query !== this.lastQuery) { | ||
this.debouncer.call(() => { | ||
this.lastQuery = this.query; | ||
this.ctx.finder.runQuery(this.ctx.activePage, this.query); | ||
}); | ||
} | ||
}, | ||
/** | ||
* Focus the next item in the result set. | ||
*/ | ||
findNext() { | ||
this.execute(new App.MoveToNextFindResult(this.ctx)); | ||
}, | ||
/** | ||
* Focus the previous item in the result set. | ||
*/ | ||
findPrevious() { | ||
this.execute(new App.MoveToPreviousFindResult(this.ctx)); | ||
}, | ||
/** | ||
* Hide the find dialog. | ||
*/ | ||
hideFindDialog() { | ||
this.execute(new App.HideFindDialog(this.ctx)); | ||
}, | ||
} | ||
}); | ||
</script> | ||
|
||
<style scoped> | ||
.find-dialog-container { | ||
position: absolute; | ||
top: 31px; | ||
transition: top 0.3s ease-out; | ||
z-index: 1; | ||
overflow: hidden; | ||
display: flex; | ||
align-items: center; | ||
background-color: #242424; | ||
border: solid 1px #303030; | ||
color: #d9d9d9; | ||
padding: 2px 5px; | ||
} | ||
.hidden { | ||
/* visibility: hidden; */ | ||
top: -13px; | ||
} | ||
.query { | ||
color: #d9d9d9; | ||
background-color: #2e2e2e; | ||
border: 1px solid #3d3d3d; | ||
border-radius: 2px; | ||
height: 20px; | ||
width: 180px; | ||
margin: 6px 10px 6px 6px; | ||
padding: 2px 5px; | ||
} | ||
.query:focus { | ||
outline: none; | ||
} | ||
.results { | ||
white-space: nowrap; | ||
font-size: 10pt; | ||
margin-right: 1em; | ||
width: 60px; | ||
} | ||
.control { | ||
font-size: 14pt; | ||
/* margin: 0; */ | ||
border-radius: 4px; | ||
padding: 2px 5px; | ||
} | ||
.control * { | ||
fill: #8c8c8c; | ||
} | ||
.control.disabled:hover { | ||
background: none; | ||
} | ||
.control.disabled * { | ||
fill: #3d3d3d; | ||
} | ||
.control:hover { | ||
background-color: #383838; | ||
} | ||
.control * { | ||
position: relative; | ||
top: 1px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<template> | ||
<svg width="16" height="16" viewBox="0 0 384 512" :fill="color" | ||
xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> | ||
<path | ||
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" /> | ||
</svg> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
export default defineComponent({ | ||
name: "Close", | ||
props: { | ||
color: { | ||
type: String, | ||
default: "#737373" | ||
} | ||
} | ||
}); | ||
</script> |
20 changes: 20 additions & 0 deletions
20
src/attack_flow_builder/src/components/Icons/DownArrow.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<template> | ||
<svg width="16" height="16" viewBox="0 0 384 512" :fill="color" | ||
xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> | ||
<path | ||
d="M169.4 470.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 370.8 224 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 306.7L54.6 265.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" /> | ||
</svg> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
export default defineComponent({ | ||
name: "DownArrow", | ||
props: { | ||
color: { | ||
type: String, | ||
default: "#737373" | ||
} | ||
} | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<template> | ||
<svg width="16" height="16" viewBox="0 0 384 512" :fill="color" | ||
xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> | ||
<path | ||
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z" /> | ||
</svg> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
export default defineComponent({ | ||
name: "UpArrow", | ||
props: { | ||
color: { | ||
type: String, | ||
default: "#737373" | ||
} | ||
} | ||
}); | ||
</script> |
Oops, something went wrong.