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

Basic flow editor and connect dots tool #179

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
21 changes: 20 additions & 1 deletion src/components/grapher/Dot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,41 @@ import Vue from "vue";
import StuntSheet from "@/models/StuntSheet";
import DotAppearance from "@/models/DotAppearance";

const nextSSDotAppearance = new DotAppearance({
filled: true,
fill: "purple",
color: "purple",
});

const nextSSUnconnectedDotAppearance = new DotAppearance({
...nextSSDotAppearance,
fill: "deeppink",
color: "deeppink",
});

/**
* Renders a single dot
*/
export default Vue.extend({
name: "Dot",
props: {
isNextSS: Boolean,
dotTypeIndex: Number,
label: String,
labeled: Boolean,
selected: Boolean,
isConnected: Boolean,
},
computed: {
radius(): number {
return 0.7;
return this.$props.isNextSS ? 0.5 : 0.7;
},
dotAppearance(): DotAppearance {
if (this.$props.isNextSS) {
return this.$props.isConnected
? nextSSDotAppearance
: nextSSUnconnectedDotAppearance;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have the appearance of the connected dots mimic the starting dot's appearance? It might make situations like this where multiple different dot types have paths that overlap a bit clearer:
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea... Let's re-visit this once we get this in the hands of STUNT.

const currentSS: StuntSheet = this.$store.getters.getSelectedStuntSheet;
return currentSS.dotAppearances[this.dotTypeIndex];
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/grapher/Grapher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<!-- Note:Inside svg, 1px = 1 eight-to-five step -->
<g class="grapher--wrapper" data-test="grapher--wrapper">
<GrapherField />
<GrapherFlow />
<GrapherDots />
<GrapherTool />
</g>
Expand All @@ -20,6 +21,7 @@
<script lang="ts">
import Vue from "vue";
import GrapherField from "./GrapherField.vue";
import GrapherFlow from "./GrapherFlow.vue";
import GrapherDots from "./GrapherDots.vue";
import GrapherTool from "./GrapherTool.vue";
import svgPanZoom from "svg-pan-zoom";
Expand All @@ -33,6 +35,7 @@ export default Vue.extend({
name: "Grapher",
components: {
GrapherField,
GrapherFlow,
GrapherDots,
GrapherTool,
},
Expand Down
46 changes: 46 additions & 0 deletions src/components/grapher/GrapherDots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,22 @@
:label="label"
:labeled="showDotLabels"
:selected="selectedDotIds.includes(dot.id)"
:isNextSS="false"
:isConnected="dot.nextDotId !== null"
/>
<template v-if="isViewModeFlow">
<Dot
v-for="[label, dot, isConnected, isSelected] in nextSSDots"
:key="`${dot.id}-dots--dot`"
class="grapher-dots--dot"
:transform="`translate(${dot.xAtBeat(0)}, ${dot.yAtBeat(0)})`"
:label="label"
:labeled="showDotLabels"
:selected="isSelected"
:isNextSS="true"
:isConnected="isConnected"
/>
</template>
</g>
</template>

Expand All @@ -20,6 +35,8 @@ import Vue from "vue";
import StuntSheetDot from "@/models/StuntSheetDot";
import Dot from "./Dot.vue";
import Show from "@/models/Show";
import { CalChartState } from "@/store";
import { VIEW_MODES } from "@/store/constants";

/**
* Renders the field, the dots of the current stunt sheet, and pending dots
Expand All @@ -46,6 +63,35 @@ export default Vue.extend({
return this.$store.state.beat;
},
},
isViewModeFlow(): boolean {
return (this.$store.state as CalChartState).viewMode === VIEW_MODES.FLOW;
},
nextSSDots(): [string | null, StuntSheetDot, boolean, boolean][] {
/**
* Returns an array with contents:
* 0: Previous dot's label
* 1: StuntSheetDot from the next stuntsheet
* 2: Boolean indicating if the dot is connected to the current stunt sheet
* 3: Boolean indicating if the dot is connected to a selected dot on the current stunt sheet
*/
const { show, selectedSS } = this.$store.state as CalChartState;
if (selectedSS + 1 >= show.stuntSheets.length) {
return [];
}

return show.stuntSheets[selectedSS + 1].stuntSheetDots.map((dot) => {
const prevDotWithLabel = this.dotsWithLabels.find(
([, prevDot]) => prevDot.nextDotId === dot.id
);
return [
prevDotWithLabel ? prevDotWithLabel[0] : null,
dot,
!!prevDotWithLabel,
!!prevDotWithLabel &&
this.selectedDotIds.includes(prevDotWithLabel[1].id),
];
});
},
},
});
</script>
Expand Down
47 changes: 47 additions & 0 deletions src/components/grapher/GrapherFlow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<g v-if="isViewModeFlow">
<g v-for="cachedFlow in selectedDotsFlows" :key="cachedFlow">
<polyline
class="grapher--flow-line"
v-if="cachedFlow && cachedFlow.length > 1"
:points="
cachedFlow.map((flowBeat) => `${flowBeat.x},${flowBeat.y}`).join(` `)
"
/>
</g>
</g>
</template>

<script lang="ts">
import { FlowBeat } from "@/models/util/FlowBeat";
import { CalChartState } from "@/store";
import { VIEW_MODES } from "@/store/constants";
import Vue from "vue";

/**
* Renders each selected dots' cachedFlow.
*/
export default Vue.extend({
name: "GrapherFlow",
computed: {
isViewModeFlow(): boolean {
return this.$store.state.viewMode === VIEW_MODES.FLOW;
},
selectedDotsFlows(): (FlowBeat[] | null)[] {
const { selectedDotIds, show, selectedSS } = this.$store
.state as CalChartState;
return show.stuntSheets[selectedSS].stuntSheetDots
.filter((dot) => selectedDotIds.includes(dot.id))
.map((dot) => dot.cachedFlow);
},
},
});
</script>

<style scoped lang="scss">
.grapher--flow-line {
fill: none;
stroke: $founders-rock;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐻

stroke-width: 0.75;
}
</style>
21 changes: 17 additions & 4 deletions src/components/menu-bottom/MenuBottom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import BaseTool, { ToolConstructor } from "@/tools/BaseTool";
import ToolBoxSelect from "@/tools/ToolBoxSelect";
import ToolLassoSelect from "@/tools/ToolLassoSelect";
import ToolSingleDot from "@/tools/ToolSingleDot";
import ToolConnectDots from "@/tools/ToolConnectDots";
import { Mutations } from "@/store/mutations";
import { VIEW_MODES } from "@/store/constants";

interface ToolData {
label: string;
icon: string;
icon: string; // See https://materialdesignicons.com/
tool: ToolConstructor;
forceViewMode?: VIEW_MODES;
"data-test": string;
}

Expand All @@ -59,8 +62,16 @@ export default Vue.extend({
label: "Add and Remove Single Dot",
icon: "plus-minus",
tool: ToolSingleDot,
forceViewMode: VIEW_MODES.STUNTSHEET,
"data-test": "add-rm",
},
{
label: "Connect Dots between Stuntsheets",
icon: "transit-connection-horizontal",
tool: ToolConnectDots,
forceViewMode: VIEW_MODES.FLOW,
"data-test": "connect-dots",
},
],
toolSelectedIndex: 0, // Assume that 0 is the pan/zoom tool
}),
Expand All @@ -70,14 +81,16 @@ export default Vue.extend({
methods: {
setTool(toolIndex: number): void {
this.$data.toolSelectedIndex = toolIndex;
const ToolConstructor: ToolConstructor = this.$data.toolDataList[
toolIndex
].tool;
const toolItem = this.$data.toolDataList[toolIndex];
const ToolConstructor: ToolConstructor = toolItem.tool;
const tool: BaseTool = new ToolConstructor();
this.$store.commit(Mutations.SET_TOOL_SELECTED, tool);
if (!tool.supportsSelection) {
this.$store.commit(Mutations.CLEAR_SELECTED_DOTS);
}
if (toolItem.forceViewMode) {
this.$store.commit(Mutations.SET_VIEW_MODE, toolItem.forceViewMode);
}
},
},
});
Expand Down
4 changes: 4 additions & 0 deletions src/components/menu-right/MenuRight.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
<template>
<div class="menu-right">
<ViewModeForm />
<hr />
<SelectedDotEditor />
<hr />
<DotTypeEditorWrapper />
</div>
</template>

<script lang="ts">
import ViewModeForm from "./ViewModeForm.vue";
import SelectedDotEditor from "./SelectedDotEditor.vue";
import DotTypeEditorWrapper from "./DotTypeEditorWrapper.vue";
import Vue from "vue";

export default Vue.extend({
name: "MenuRight",
components: {
ViewModeForm,
SelectedDotEditor,
DotTypeEditorWrapper,
},
Expand Down
39 changes: 39 additions & 0 deletions src/components/menu-right/ViewModeForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<div>
<b-field label="View Mode">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we put "View Mode" under the view menu bar?

<b-select v-model="viewMode">
<option
v-for="viewModeOption in viewModeOptions"
:key="viewModeOption"
:value="viewModeOption"
>
{{ viewModeOption }}
</option>
</b-select>
</b-field>
</div>
</template>

<script lang="ts">
import { CalChartState } from "@/store";
import { VIEW_MODES } from "@/store/constants";
import { Mutations } from "@/store/mutations";
import Vue from "vue";

export default Vue.extend({
name: "ViewModeForm",
computed: {
viewMode: {
get(): VIEW_MODES {
return (this.$store.state as CalChartState).viewMode;
},
set(viewModeOption: VIEW_MODES): void {
this.$store.commit(Mutations.SET_VIEW_MODE, viewModeOption);
},
},
viewModeOptions(): string[] {
return Object.values(VIEW_MODES);
},
},
});
</script>
11 changes: 11 additions & 0 deletions src/store/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Defines the different ways that the data can be viewed and interacted with in the UI
*/
export enum VIEW_MODES {
// "Stuntsheet" mode is for viewing and updating a specific stuntsheet.
// Used for drawing and editing formations.
STUNTSHEET = "Stuntsheet",
// "Flow" mode is for viewing and updating the transition between two stuntsheets.
// Used for creating and editing continuities.
FLOW = "Flow",
}
4 changes: 4 additions & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Vue from "vue";
import Vuex, { Store } from "vuex";
import { VIEW_MODES } from "./constants";
import { mutations } from "./mutations";
import getters from "./getters";
import Show from "@/models/Show";
Expand All @@ -17,6 +18,7 @@ Vue.use(Vuex);
* @property initialShowState - Beginning spot for show
* @property selectedSS - Index of stuntsheet currently in view
* @property beat - The point in time the show is in
* @property viewMode - Defines the possible UI interactions
* @property fourStepGrid - View setting to toggle the grapher grid
* @property grapherSvgPanZoom - Initialized upon mounting Grapher
* @property invertedCTMMatrix - Used to calculate clientX/Y to SVG X/Y
Expand All @@ -32,6 +34,8 @@ export class CalChartState extends Serializable<CalChartState> {

beat = 0;

viewMode: VIEW_MODES = VIEW_MODES.STUNTSHEET;

fourStepGrid = true;

yardlines = true;
Expand Down
23 changes: 23 additions & 0 deletions src/store/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DotAppearance from "@/models/DotAppearance";
import { MARCH_TYPES } from "@/models/util/constants";
import ContETFStatic from "@/models/continuity/ContETFStatic";
import ContGateTurn from "@/models/continuity/ContGateTurn";
import { VIEW_MODES } from "./constants";

export enum Mutations {
// Show mutations:
Expand All @@ -34,6 +35,8 @@ export enum Mutations {
SET_STUNT_SHEET_BEATS = "Set Stund Sheet beats",
ADD_DOT_TYPE = "Add Marcher type",
ADD_CONTINUITY = "Add Continuity",
// StuntSheetDot mutations:
SET_DOT_NEXT_DOT_ID = "setDotNextDotId",
// Continuity mutations:
UPDATE_DOT_TYPE_MARCH_STYLE = "Update Marcher Step Style",
UPDATE_DOT_TYPE_DURATION = "Update Marcher Duration",
Expand All @@ -44,6 +47,7 @@ export enum Mutations {
UPDATE_DOT_TYPE_ANGLE = "Update Marcher Angle",

// View mutations:
SET_VIEW_MODE = "setViewMode",
SET_SELECTED_SS = "setSelectedSS",
SET_BEAT = "setBeat",
INCREMENT_BEAT = "incrementBeat",
Expand Down Expand Up @@ -219,6 +223,22 @@ export const mutations: MutationTree<CalChartState> = {
currentSS.calculateIssuesDeep(state.selectedSS);
},

// Show -> StuntSheet -> StuntSheetDot
[Mutations.SET_DOT_NEXT_DOT_ID](
state,
{ dotId, nextDotId }: { dotId: number; nextDotId: number | null }
) {
const getSelectedStuntSheet = getters.getSelectedStuntSheet as (
state: CalChartState
) => StuntSheet;
const currentSS = getSelectedStuntSheet(state);
const currentDot = currentSS.stuntSheetDots.find((dot) => dot.id === dotId);
if (currentDot) {
currentDot.nextDotId = nextDotId;
state.show.generateFlows(state.selectedSS);
}
},

// Show -> StuntSheet -> BaseCont
[Mutations.UPDATE_DOT_TYPE_MARCH_STYLE](
state,
Expand Down Expand Up @@ -395,6 +415,9 @@ export const mutations: MutationTree<CalChartState> = {
},

// View Settings
[Mutations.SET_VIEW_MODE](state, viewMode: VIEW_MODES): void {
state.viewMode = viewMode;
},
[Mutations.SET_FOUR_STEP_GRID](state, enabled: boolean): void {
state.fourStepGrid = enabled;
},
Expand Down
Loading