Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AF-147: Search inside a flow #99

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/attack_flow_builder/public/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"paste": "Control+V",
"delete": "Backspace",
"duplicate": "Control+D",
"find": "Control+F",
"find_next": "F3",
"find_previous": "Shift+F3",
"select_all": "Control+A"
},
"layout": {
Expand Down
19 changes: 17 additions & 2 deletions src/attack_flow_builder/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<AppHotkeyBox id="main">
<AppTitleBar id="app-title-bar"/>
<FindDialog ref="findDialog" id="find-dialog" :style="findDialogLayout" />
<div id="app-body" ref="body" :style="gridLayout">
<div class="frame center">
<BlockDiagram id="block-diagram"/>
Expand All @@ -22,7 +23,7 @@ import Configuration from "@/assets/builder.config"
// Dependencies
import { clamp } from "./assets/scripts/BlockDiagram";
import { PointerTracker } from "./assets/scripts/PointerTracker";
import { mapMutations, mapState } from 'vuex';
import { mapGetters, mapMutations, mapState } from 'vuex';
import { LoadFile, LoadSettings } from './store/Commands/AppCommands';
import { defineComponent, markRaw, ref } from 'vue';
// Components
Expand All @@ -31,6 +32,7 @@ import AppHotkeyBox from "@/components/Elements/AppHotkeyBox.vue";
import BlockDiagram from "@/components/Elements/BlockDiagram.vue";
import AppFooterBar from "@/components/Elements/AppFooterBar.vue";
import EditorSidebar from "@/components/Elements/EditorSidebar.vue";
import FindDialog from "@/components/Elements/FindDialog.vue";
const Handle = {
None : 0,
Expand Down Expand Up @@ -81,6 +83,18 @@ export default defineComponent({
return {
gridTemplateColumns: `minmax(0, 1fr) ${ r }px`
}
},
/**
* Compute the location of the find dialog
* @returns
* The current grid layout.
*/
findDialogLayout(): { right: string } {
let r = this.frameSize[Handle.Right] + 25;
return {
right: `${r}px`
}
}
},
Expand Down Expand Up @@ -193,7 +207,8 @@ export default defineComponent({
AppTitleBar,
BlockDiagram,
AppFooterBar,
EditorSidebar
EditorSidebar,
FindDialog
},
});
</script>
Expand Down
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);
}
}
234 changes: 234 additions & 0 deletions src/attack_flow_builder/src/components/Elements/FindDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
<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 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) {
mikecarenzo marked this conversation as resolved.
Show resolved Hide resolved
if (event.key === "Escape") {
this.hideFindDialog();
} else if (event.key === "Enter") {
if (event.shiftKey) {
this.execute(new App.MoveToPreviousFindResult(this.ctx));
} else {
this.execute(new App.MoveToNextFindResult(this.ctx));
}
} 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>
20 changes: 20 additions & 0 deletions src/attack_flow_builder/src/components/Icons/Close.vue
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 src/attack_flow_builder/src/components/Icons/DownArrow.vue
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>
20 changes: 20 additions & 0 deletions src/attack_flow_builder/src/components/Icons/UpArrow.vue
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>
Loading