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-177 Create a splash screen #100

Merged
merged 1 commit into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 11 additions & 5 deletions src/attack_flow_builder/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<div id="app-body" ref="body" :style="gridLayout">
<div class="frame center">
<BlockDiagram id="block-diagram"/>
<SplashMenu id="splash-menu" />
</div>
<div class="frame right">
<div class="resize-handle" @pointerdown="startResize($event, Handle.Right)"></div>
Expand All @@ -23,14 +24,16 @@ import Configuration from "@/assets/builder.config"
import { clamp } from "./assets/scripts/BlockDiagram";
import { PointerTracker } from "./assets/scripts/PointerTracker";
import { mapMutations, mapState } from 'vuex';
import { LoadFile, LoadSettings } from './store/Commands/AppCommands';
import * as App from './store/Commands/AppCommands';
import { defineComponent, markRaw, ref } from 'vue';
// Components
import SplashMenu from "@/components/Controls/SplashMenu.vue";
import AppTitleBar from "@/components/Elements/AppTitleBar.vue";
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 { ShowSplashMenu } from "./store/Commands/AppCommands/ShowSplashMenu";
const Handle = {
None : 0,
Expand Down Expand Up @@ -156,20 +159,22 @@ export default defineComponent({
settings = require("../public/settings.json");
}
// Load settings
this.execute(new LoadSettings(this.context, settings));
this.execute(new App.LoadSettings(this.context, settings));
// Load empty file
this.execute(await LoadFile.fromNew(this.context));
this.execute(await App.LoadFile.fromNew(this.context));
// Load file from query parameters, if possible
let params = new URLSearchParams(window.location.search);
let src = params.get("src");
if(src) {
try {
// TODO: Incorporate loading dialog
this.execute(await LoadFile.fromUrl(this.context, src));
this.execute(await App.LoadFile.fromUrl(this.context, src));
} catch(ex) {
console.error(`Failed to load file from url: '${ src }'`);
console.error(ex);
}
} else {
this.execute(new ShowSplashMenu(this.context));
}
},
mounted() {
Expand All @@ -193,7 +198,8 @@ export default defineComponent({
AppTitleBar,
BlockDiagram,
AppFooterBar,
EditorSidebar
EditorSidebar,
SplashMenu,
},
});
</script>
Expand Down
Binary file added src/attack_flow_builder/src/assets/afb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/attack_flow_builder/src/assets/builder.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,35 @@ const config: AppConfiguration = {
application_name: "Attack Flow Builder",
file_type_name: "Attack Flow",
file_type_extension: "afb",
menu_icon: "./ctid-small.png",
splash: {
product: "./afb.png",
organization: "./ctid.png",
buttons: [
{
action: "new",
name: "New Flow",
description: "Create a new, blank flow",
},
{
action: "open",
name: "Open Flow",
description: "Open an existing flow from your computer"
},
{
action: "link",
name: "Example Flows",
description: "View a list of example flows",
url: "https://center-for-threat-informed-defense.github.io/attack-flow/example_flows/"
},
{
action: "link",
name: "Builder Help",
description: "View help for Attack Flow Builder",
url: "https://center-for-threat-informed-defense.github.io/attack-flow/builder/"
},
],
},
schema: {
page_template: "flow",
templates: [
Expand Down
Binary file added src/attack_flow_builder/src/assets/ctid-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/attack_flow_builder/src/assets/ctid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
188 changes: 188 additions & 0 deletions src/attack_flow_builder/src/components/Controls/SplashMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<template>
<div class="splash-menu-control" :class="{
hidden: !isShowingSplash
}">
<div class="splash-menu-container">
<div class="header">
<img class="product" :src="product" />
<img class="organization" v-if="organization" :src="organization" />
</div>
<div class="section-grid">
<div class="button" v-for="button of buttons" @click="handleClick(button)">
<h1 class="name">
{{ button.name }}
<NewFlow class="icon" v-if="button.action === 'new'" />
<OpenFlow class="icon" v-else-if="button.action === 'open'" />
<Link class="icon" v-else-if="button.action === 'link'" />
</h1>
<p class="description">{{ button.description }}</p>
</div>
</div>
</div>
</div>
</template>

<script lang="ts">
const Images = require.context("../../assets", false);
import Configuration from "@/assets/builder.config"
import { mapGetters, mapMutations, mapState } from 'vuex';
// Dependencies
import { defineComponent } from 'vue';
import { ApplicationStore, SplashButton, SplashButtonAction } from "@/store/StoreTypes";
import * as App from "@/store/Commands/AppCommands";
// Components
import NewFlow from "@/components/Icons/NewFlow.vue";
import OpenFlow from "@/components/Icons/OpenFlow.vue";
import Link from "@/components/Icons/Link.vue";

export default defineComponent({
name: 'SplashMenu',
data() {
let organization;
if (Configuration.splash.organization) {
organization = Images(Configuration.splash.organization);
}
return {
product: Images(Configuration.splash.product),
organization,
buttons: Configuration.splash.buttons,
}
},
computed: {
...mapState("ApplicationStore", {
ctx(state: ApplicationStore): ApplicationStore {
return state;
}
}),

...mapGetters("ApplicationStore", ["isShowingSplash"]),
},
methods: {
/**
* Application Store mutations
*/
...mapMutations("ApplicationStore", ["execute"]),

async handleClick(button: SplashButton) {
switch (button.action) {
case SplashButtonAction.New:
this.execute(await App.LoadFile.fromNew(this.ctx));
this.execute(new App.HideSplashMenu(this.ctx));
break;
case SplashButtonAction.Open:
this.execute(await App.LoadFile.fromFileSystem(this.ctx));
this.execute(new App.HideSplashMenu(this.ctx));
break;
case SplashButtonAction.Link:
if (button.url !== undefined) {
this.execute(new App.OpenHyperlink(this.ctx, button.url));
}
break;
default:
throw new Error(`Unknown splash button action: ${button.action}`);
}
}
},
components: {
Link,
NewFlow,
OpenFlow,
},
});
</script>

<style scoped>
/** === Main Control === */

.splash-menu-control {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}

.hidden {
visibility: hidden;
}

.splash-menu-container {
border: solid 1px #474747;
border-radius: 5px;
width: 500px;
margin: 30px 0px;
background: #242424;
overflow: hidden;
box-shadow: #000000 0px 0px 10px 3px;
}

/** === Header === */

.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 25px;
border-bottom: solid 1px #474747;
background: #383838;
pointer-events: none;
user-select: none;
}

.product,
.organization {
height: 50px;
}

/** === Sections === */

.section-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 25px;
}

/** === Buttons === */

.button {
width: calc(50% - 20px);
padding: 25px;
border: solid 1px #383838;
border-radius: 3px;
box-sizing: border-box;
user-select: none;
margin: 10px;
}

.button:hover {
background: #383838;
;
}

.button .name {
color: #726de2;
font-size: 12pt;
font-weight: 700;
margin: 0px 0px 8px 0px;
}

.button .name .icon {
position: relative;
fill: #726de2;
width: 18px;
height: 18px;
top: 3px;
left: 3px;
}

.button .description {
color: #d9d9d9;
font-size: 10.5pt;
font-weight: 400;
}
</style>
20 changes: 10 additions & 10 deletions src/attack_flow_builder/src/components/Elements/AppTitleBar.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<template>
<TitleBar class="app-title-bar-element" :menus="menus" @select="onItemSelect">
<template v-slot:icon>
<span class="logo">AFB</span>
<img alt="Logo" title="Logo" class="logo" :src="menuIcon">
</template>
</TitleBar>
</template>

<script lang="ts">
const Images = require.context("../../assets", false);
import Configuration from "@/assets/builder.config"
// Dependencies
import { ContextMenu } from "@/assets/scripts/ContextMenuTypes";
import { CommandEmitter } from "@/store/Commands/Command";
Expand All @@ -17,6 +19,11 @@ import TitleBar from "@/components/Controls/TitleBar.vue";

export default defineComponent({
name: "AppTitleBar",
data() {
return {
menuIcon: Images(Configuration.menu_icon),
};
},
computed: {

/**
Expand Down Expand Up @@ -77,17 +84,10 @@ export default defineComponent({
</script>

<style scoped>

/** === App Logo === */

.logo {
margin: 0px 8px;
padding: 2px 4px;
color: #f0f1f2;
font-size: 7pt;
font-weight: 600;
border-radius: 3px;
background: #726de2;
margin: 2px 8px 0px 10px;
height: 16px;
}

</style>
12 changes: 12 additions & 0 deletions src/attack_flow_builder/src/components/Icons/Link.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 640 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z" />
</svg>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({ name: "Link" });
</script>
12 changes: 12 additions & 0 deletions src/attack_flow_builder/src/components/Icons/NewFlow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 384 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128z" />
</svg>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({ name: "NewFlow" });
</script>
12 changes: 12 additions & 0 deletions src/attack_flow_builder/src/components/Icons/OpenFlow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 576 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="M384 480h48c11.4 0 21.9-6 27.6-15.9l112-192c5.8-9.9 5.8-22.1 .1-32.1S555.5 224 544 224H144c-11.4 0-21.9 6-27.6 15.9L48 357.1V96c0-8.8 7.2-16 16-16H181.5c4.2 0 8.3 1.7 11.3 4.7l26.5 26.5c21 21 49.5 32.8 79.2 32.8H416c8.8 0 16 7.2 16 16v32h48V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H87.7 384z" />
</svg>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({ name: "OpenFlow" });
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AppCommand } from "../AppCommand";
import { ApplicationStore } from "@/store/StoreTypes";

export class HideSplashMenu extends AppCommand {

/**
* Display the find dialog
* @param context
* The application context.
*/
constructor(context: ApplicationStore) {
super(context);
}

/**
* Executes the command.
*/
public execute(): void {
this._context.splashIsVisible = false;
}

}
Loading
Loading