added support for grouping
This commit is contained in:
14
src/App.vue
14
src/App.vue
@@ -4,11 +4,7 @@
|
|||||||
<TheHeader class="row"
|
<TheHeader class="row"
|
||||||
github-url="https://github.com/undergroundwires/privacy.sexy" />
|
github-url="https://github.com/undergroundwires/privacy.sexy" />
|
||||||
<!-- <TheSearchBar> </TheSearchBar> -->
|
<!-- <TheSearchBar> </TheSearchBar> -->
|
||||||
<!-- <div style="display: flex; justify-content: space-between;"> -->
|
<TheScripts class="row"/>
|
||||||
<!-- <TheGrouper></TheGrouper> -->
|
|
||||||
<TheSelector class="row" />
|
|
||||||
<!-- </div> -->
|
|
||||||
<CardList />
|
|
||||||
<TheCodeArea class="row" theme="xcode" />
|
<TheCodeArea class="row" theme="xcode" />
|
||||||
<TheCodeButtons class="row" />
|
<TheCodeButtons class="row" />
|
||||||
<TheFooter />
|
<TheFooter />
|
||||||
@@ -24,9 +20,7 @@ import TheFooter from './presentation/TheFooter.vue';
|
|||||||
import TheCodeArea from './presentation/TheCodeArea.vue';
|
import TheCodeArea from './presentation/TheCodeArea.vue';
|
||||||
import TheCodeButtons from './presentation/TheCodeButtons.vue';
|
import TheCodeButtons from './presentation/TheCodeButtons.vue';
|
||||||
import TheSearchBar from './presentation/TheSearchBar.vue';
|
import TheSearchBar from './presentation/TheSearchBar.vue';
|
||||||
import TheSelector from './presentation/Scripts/Selector/TheSelector.vue';
|
import TheScripts from './presentation/Scripts/TheScripts.vue';
|
||||||
import TheGrouper from './presentation/Scripts/TheGrouper.vue';
|
|
||||||
import CardList from './presentation/Scripts/Cards/CardList.vue';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -34,9 +28,7 @@ import CardList from './presentation/Scripts/Cards/CardList.vue';
|
|||||||
TheCodeArea,
|
TheCodeArea,
|
||||||
TheCodeButtons,
|
TheCodeButtons,
|
||||||
TheSearchBar,
|
TheSearchBar,
|
||||||
TheGrouper,
|
TheScripts,
|
||||||
CardList,
|
|
||||||
TheSelector,
|
|
||||||
TheFooter,
|
TheFooter,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ export default class CardList extends StatefulVue {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
font-family: $main-font;
|
font-family: $main-font;
|
||||||
.card {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card__expander" v-on:click.stop>
|
<div class="card__expander" v-on:click.stop>
|
||||||
<font-awesome-icon :icon="['fas', 'times']" class="close-button" v-on:click="onSelected(false)"/>
|
<font-awesome-icon :icon="['fas', 'times']" class="close-button" v-on:click="onSelected(false)"/>
|
||||||
<CardListItemScripts :categoryId="categoryId"></CardListItemScripts>
|
<ScriptsTree :categoryId="categoryId"></ScriptsTree>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator';
|
import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator';
|
||||||
import CardListItemScripts from './CardListItemScripts.vue';
|
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
CardListItemScripts,
|
ScriptsTree,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class CardListItem extends StatefulVue {
|
export default class CardListItem extends StatefulVue {
|
||||||
@@ -124,10 +124,6 @@ export default class CardListItem extends StatefulVue {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: $light-gray;
|
|
||||||
font-size: 1.5em;
|
|
||||||
|
|
||||||
.close-button {
|
.close-button {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { ICategory } from './../../../domain/ICategory';
|
|
||||||
import { IApplicationState, IUserSelection } from '@/application/State/IApplicationState';
|
|
||||||
import { INode } from './../SelectableTree/INode';
|
|
||||||
|
|
||||||
export class ScriptNodeParser {
|
|
||||||
public static parseNodes(categoryId: number, state: IApplicationState): INode[] | undefined {
|
|
||||||
const category = state.app.findCategory(categoryId);
|
|
||||||
if (!category) {
|
|
||||||
throw new Error(`Category with id ${categoryId} does not exist`);
|
|
||||||
}
|
|
||||||
const tree = this.parseNodesRecursively(category, state.selection);
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parseNodesRecursively(parentCategory: ICategory, selection: IUserSelection): INode[] {
|
|
||||||
const nodes = new Array<INode>();
|
|
||||||
if (parentCategory.subCategories && parentCategory.subCategories.length > 0) {
|
|
||||||
for (const subCategory of parentCategory.subCategories) {
|
|
||||||
const subCategoryNodes = this.parseNodesRecursively(subCategory, selection);
|
|
||||||
nodes.push(
|
|
||||||
{
|
|
||||||
id: `cat${subCategory.id}`,
|
|
||||||
text: subCategory.name,
|
|
||||||
selected: false,
|
|
||||||
children: subCategoryNodes,
|
|
||||||
documentationUrls: subCategory.documentationUrls,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parentCategory.scripts && parentCategory.scripts.length > 0) {
|
|
||||||
for (const script of parentCategory.scripts) {
|
|
||||||
nodes.push( {
|
|
||||||
id: script.id,
|
|
||||||
text: script.name,
|
|
||||||
selected: selection.isSelected(script),
|
|
||||||
children: undefined,
|
|
||||||
documentationUrls: script.documentationUrls,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<CardList v-if="isGrouped">
|
|
||||||
</CardList>
|
|
||||||
<SelectableTree></SelectableTree>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';
|
|
||||||
import { Category } from '@/domain/Category';
|
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
|
|
||||||
/** Shows content of single category or many categories */
|
|
||||||
@Component
|
|
||||||
export default class CategoryTree extends StatefulVue {
|
|
||||||
@Prop() public data!: Category | Category[];
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
4
src/presentation/Scripts/Grouping/Grouping.ts
Normal file
4
src/presentation/Scripts/Grouping/Grouping.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum Grouping {
|
||||||
|
Cards = 1,
|
||||||
|
None = 0,
|
||||||
|
}
|
||||||
62
src/presentation/Scripts/Grouping/TheGrouper.vue
Normal file
62
src/presentation/Scripts/Grouping/TheGrouper.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<span class="part">Group by:</span>
|
||||||
|
<span class="part">
|
||||||
|
<span
|
||||||
|
class="part"
|
||||||
|
v-bind:class="{ 'disabled': isGrouped, 'enabled': !isGrouped}"
|
||||||
|
@click="!isGrouped ? toggleGrouping() : undefined">Cards</span>
|
||||||
|
<span class="part">|</span>
|
||||||
|
<span class="part"
|
||||||
|
v-bind:class="{ 'disabled': !isGrouped, 'enabled': isGrouped}"
|
||||||
|
@click="isGrouped ? toggleGrouping() : undefined">None</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
|
import { IApplicationState } from '@/application/State/IApplicationState';
|
||||||
|
import { Grouping } from './Grouping';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class TheGrouper extends StatefulVue {
|
||||||
|
public currentGrouping: Grouping;
|
||||||
|
public isGrouped = true;
|
||||||
|
|
||||||
|
public toggleGrouping() {
|
||||||
|
this.currentGrouping = this.currentGrouping === Grouping.None ? Grouping.Cards : Grouping.None;
|
||||||
|
this.isGrouped = this.currentGrouping === Grouping.Cards;
|
||||||
|
this.$emit('groupingChanged', this.currentGrouping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/presentation/styles/colors.scss";
|
||||||
|
@import "@/presentation/styles/fonts.scss";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-family: $normal-font;
|
||||||
|
.part {
|
||||||
|
display: flex;
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
font-weight:bold;
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled {
|
||||||
|
color:$gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
63
src/presentation/Scripts/ScriptsTree/ScriptNodeParser.ts
Normal file
63
src/presentation/Scripts/ScriptsTree/ScriptNodeParser.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { IApplicationState, IUserSelection } from '@/application/State/IApplicationState';
|
||||||
|
import { ICategory, IScript } from '@/domain/ICategory';
|
||||||
|
import { INode } from './SelectableTree/INode';
|
||||||
|
|
||||||
|
export function parseAllCategories(state: IApplicationState): INode[] | undefined {
|
||||||
|
const nodes = new Array<INode>();
|
||||||
|
for (const category of state.app.categories) {
|
||||||
|
const children = parseCategoryRecursively(category, state.selection);
|
||||||
|
nodes.push(convertCategoryToNode(category, children));
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseSingleCategory(categoryId: number, state: IApplicationState): INode[] | undefined {
|
||||||
|
const category = state.app.findCategory(categoryId);
|
||||||
|
if (!category) {
|
||||||
|
throw new Error(`Category with id ${categoryId} does not exist`);
|
||||||
|
}
|
||||||
|
const tree = parseCategoryRecursively(category, state.selection);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCategoryRecursively(
|
||||||
|
parentCategory: ICategory,
|
||||||
|
selection: IUserSelection): INode[] {
|
||||||
|
if (!parentCategory) { throw new Error('parentCategory is undefined'); }
|
||||||
|
if (!selection) { throw new Error('selection is undefined'); }
|
||||||
|
|
||||||
|
const nodes = new Array<INode>();
|
||||||
|
if (parentCategory.subCategories && parentCategory.subCategories.length > 0) {
|
||||||
|
for (const subCategory of parentCategory.subCategories) {
|
||||||
|
const subCategoryNodes = parseCategoryRecursively(subCategory, selection);
|
||||||
|
nodes.push(convertCategoryToNode(subCategory, subCategoryNodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parentCategory.scripts && parentCategory.scripts.length > 0) {
|
||||||
|
for (const script of parentCategory.scripts) {
|
||||||
|
nodes.push(convertScriptToNode(script, selection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertCategoryToNode(
|
||||||
|
category: ICategory, children: readonly INode[]): INode {
|
||||||
|
return {
|
||||||
|
id: `${category.id}`,
|
||||||
|
text: category.name,
|
||||||
|
selected: false,
|
||||||
|
children,
|
||||||
|
documentationUrls: category.documentationUrls,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertScriptToNode(script: IScript, selection: IUserSelection): INode {
|
||||||
|
return {
|
||||||
|
id: `${script.id}`,
|
||||||
|
text: script.name,
|
||||||
|
selected: selection.isSelected(script),
|
||||||
|
children: undefined,
|
||||||
|
documentationUrls: script.documentationUrls,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span id="container">
|
||||||
<span v-if="nodes != null && nodes.length > 0">
|
<span v-if="nodes != null && nodes.length > 0">
|
||||||
<SelectableTree
|
<SelectableTree
|
||||||
:nodes="nodes"
|
:nodes="nodes"
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { IApplicationState, IUserSelection } from '@/application/State/IApplicationState';
|
import { IApplicationState, IUserSelection } from '@/application/State/IApplicationState';
|
||||||
import { IFilterMatches } from '@/application/State/Filter/IFilterMatches';
|
import { IFilterMatches } from '@/application/State/Filter/IFilterMatches';
|
||||||
import { ScriptNodeParser } from './ScriptNodeParser';
|
import { parseAllCategories, parseSingleCategory } from './ScriptNodeParser';
|
||||||
import SelectableTree, { FilterPredicate } from './../SelectableTree/SelectableTree.vue';
|
import SelectableTree, { FilterPredicate } from './SelectableTree/SelectableTree.vue';
|
||||||
import { INode } from './../SelectableTree/INode';
|
import { INode } from './SelectableTree/INode';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
SelectableTree,
|
SelectableTree,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class CardListItemScripts extends StatefulVue {
|
export default class ScriptsTree extends StatefulVue {
|
||||||
@Prop() public categoryId!: number;
|
@Prop() public categoryId?: number;
|
||||||
|
|
||||||
public nodes?: INode[] = null;
|
public nodes?: INode[] = null;
|
||||||
public selectedNodeIds?: string[] = null;
|
public selectedNodeIds?: string[] = null;
|
||||||
@@ -42,15 +42,17 @@
|
|||||||
public async mounted() {
|
public async mounted() {
|
||||||
// React to state changes
|
// React to state changes
|
||||||
const state = await this.getCurrentStateAsync();
|
const state = await this.getCurrentStateAsync();
|
||||||
this.reactToChanges(state);
|
state.selection.changed.on(this.handleSelectionChanged);
|
||||||
|
state.filter.filterRemoved.on(this.handleFilterRemoved);
|
||||||
|
state.filter.filtered.on(this.handleFiltered);
|
||||||
// Update initial state
|
// Update initial state
|
||||||
await this.updateNodesAsync(this.categoryId);
|
await this.initializeNodesAsync(this.categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkNodeAsync(node: INode) {
|
public async checkNodeAsync(node: INode) {
|
||||||
if (node.children != null && node.children.length > 0) {
|
if (node.children != null && node.children.length > 0) {
|
||||||
return; // only interested in script nodes
|
return; // only interested in script nodes
|
||||||
}
|
}
|
||||||
const state = await this.getCurrentStateAsync();
|
const state = await this.getCurrentStateAsync();
|
||||||
if (node.selected) {
|
if (node.selected) {
|
||||||
state.selection.addSelectedScript(node.id);
|
state.selection.addSelectedScript(node.id);
|
||||||
@@ -60,40 +62,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('categoryId')
|
@Watch('categoryId')
|
||||||
public async updateNodesAsync(categoryId: |number) {
|
public async initializeNodesAsync(categoryId?: number) {
|
||||||
this.nodes = categoryId ?
|
const state = await this.getCurrentStateAsync();
|
||||||
await ScriptNodeParser.parseNodes(categoryId, await this.getCurrentStateAsync())
|
if (categoryId) {
|
||||||
: undefined;
|
this.nodes = parseSingleCategory(categoryId, state);
|
||||||
|
} else {
|
||||||
|
this.nodes = parseAllCategories(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public filterPredicate(node: INode): boolean {
|
public filterPredicate(node: INode): boolean {
|
||||||
return this.matches.scriptMatches.some((script: IScript) => script.id === node.id);
|
return this.matches.scriptMatches.some((script: IScript) => script.id === node.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private reactToChanges(state: IApplicationState) {
|
private handleSelectionChanged(selectedScripts: ReadonlyArray<IScript>) {
|
||||||
// Update selection data
|
this.nodes = this.nodes.map((node: INode) => updateNodeSelection(node, selectedScripts));
|
||||||
const updateNodeSelection = (node: INode, selectedScripts: ReadonlyArray<IScript>): INode => {
|
}
|
||||||
return {
|
|
||||||
id: node.id,
|
private handleFilterRemoved() {
|
||||||
text: node.text,
|
this.filterText = '';
|
||||||
selected: selectedScripts.some((script) => script.id === node.id),
|
}
|
||||||
children: node.children ? node.children.map((child) => updateNodeSelection(child, selectedScripts)) : [],
|
|
||||||
documentationUrls: node.documentationUrls,
|
private handleFiltered(matches: IFilterMatches) {
|
||||||
};
|
this.filterText = matches.query;
|
||||||
};
|
this.matches = matches;
|
||||||
state.selection.changed.on(
|
|
||||||
(selectedScripts: ReadonlyArray<IScript>) =>
|
|
||||||
this.nodes = this.nodes.map((node: INode) => updateNodeSelection(node, selectedScripts)),
|
|
||||||
);
|
|
||||||
// Update search / filter data
|
|
||||||
state.filter.filterRemoved.on(() =>
|
|
||||||
this.filterText = '');
|
|
||||||
state.filter.filtered.on((matches: IFilterMatches) => {
|
|
||||||
this.filterText = matches.query;
|
|
||||||
this.matches = matches;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateNodeSelection(node: INode, selectedScripts: ReadonlyArray<IScript>): INode {
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
text: node.text,
|
||||||
|
selected: selectedScripts.some((script) => script.id === node.id),
|
||||||
|
children: node.children ? node.children.map((child) => updateNodeSelection(child, selectedScripts)) : [],
|
||||||
|
documentationUrls: node.documentationUrls,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -2,25 +2,27 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="part select">Select:</div>
|
<div class="part select">Select:</div>
|
||||||
<div class="part">
|
<div class="part">
|
||||||
<SelectableOption
|
<div class="part">
|
||||||
label="None"
|
<SelectableOption
|
||||||
:enabled="isNoneSelected"
|
label="None"
|
||||||
@click="selectNoneAsync()">
|
:enabled="isNoneSelected"
|
||||||
</SelectableOption>
|
@click="selectNoneAsync()">
|
||||||
</div>
|
</SelectableOption>
|
||||||
<div class="part"> | </div>
|
</div>
|
||||||
<div class="part">
|
<div class="part"> | </div>
|
||||||
<SelectableOption
|
<div class="part">
|
||||||
label="Recommended"
|
<SelectableOption
|
||||||
:enabled="isRecommendedSelected"
|
label="Recommended"
|
||||||
@click="selectRecommendedAsync()" />
|
:enabled="isRecommendedSelected"
|
||||||
</div>
|
@click="selectRecommendedAsync()" />
|
||||||
<div class="part"> | </div>
|
</div>
|
||||||
<div class="part">
|
<div class="part"> | </div>
|
||||||
<SelectableOption
|
<div class="part">
|
||||||
label="All"
|
<SelectableOption
|
||||||
:enabled="isAllSelected"
|
label="All"
|
||||||
@click="selectAllAsync()" />
|
:enabled="isAllSelected"
|
||||||
|
@click="selectAllAsync()" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -94,7 +96,6 @@ export default class TheSelector extends StatefulVue {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items:flex-start;
|
|
||||||
.part {
|
.part {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right:5px;
|
margin-right:5px;
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
Group by: <span
|
|
||||||
v-bind:class="{ 'disabled': isGrouped, 'enabled': !isGrouped}"
|
|
||||||
@click="changeGrouping()" >Cards</Span> |
|
|
||||||
<span class="action"
|
|
||||||
v-bind:class="{ 'disabled': !isGrouped, 'enabled': isGrouped}"
|
|
||||||
@click="changeGrouping()">None</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { IApplicationState } from '@/application/State/IApplicationState';
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export default class TheGrouper extends StatefulVue {
|
|
||||||
public isGrouped = true;
|
|
||||||
|
|
||||||
public changeGrouping() {
|
|
||||||
this.isGrouped = !this.isGrouped;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "@/presentation/styles/colors.scss";
|
|
||||||
@import "@/presentation/styles/fonts.scss";
|
|
||||||
|
|
||||||
.container {
|
|
||||||
// text-align:left;
|
|
||||||
font:$normal-font;
|
|
||||||
|
|
||||||
}
|
|
||||||
.enabled {
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
font-weight:bold;
|
|
||||||
text-decoration:underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disabled {
|
|
||||||
color:$gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
67
src/presentation/Scripts/TheScripts.vue
Normal file
67
src/presentation/Scripts/TheScripts.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="help-container">
|
||||||
|
<TheSelector class="left" />
|
||||||
|
<TheGrouper class="right"
|
||||||
|
v-on:groupingChanged="onGroupingChanged($event)" />
|
||||||
|
</div>
|
||||||
|
<CardList v-if="showCards" />
|
||||||
|
<ScriptsTree v-if="showList" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';
|
||||||
|
import { Category } from '@/domain/Category';
|
||||||
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
|
import TheGrouper from '@/presentation/Scripts/Grouping/TheGrouper.vue';
|
||||||
|
import TheSelector from '@/presentation/Scripts/Selector/TheSelector.vue';
|
||||||
|
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
|
||||||
|
import CardList from '@/presentation/Scripts/Cards/CardList.vue';
|
||||||
|
import { Grouping } from './Grouping/Grouping';
|
||||||
|
|
||||||
|
/** Shows content of single category or many categories */
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
TheGrouper,
|
||||||
|
TheSelector,
|
||||||
|
ScriptsTree,
|
||||||
|
CardList,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class TheScripts extends StatefulVue {
|
||||||
|
public showCards = true;
|
||||||
|
public showList = false;
|
||||||
|
|
||||||
|
@Prop() public data!: Category | Category[];
|
||||||
|
|
||||||
|
public onGroupingChanged(group: Grouping) {
|
||||||
|
switch (group) {
|
||||||
|
case Grouping.Cards:
|
||||||
|
this.showCards = true;
|
||||||
|
this.showList = false;
|
||||||
|
break;
|
||||||
|
case Grouping.None:
|
||||||
|
this.showCards = false;
|
||||||
|
this.showList = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Unknown grouping');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.help-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
// Overrides base styling for LiquorTree
|
// Overrides base styling for LiquorTree
|
||||||
@import "@/presentation/styles/colors.scss";
|
@import "@/presentation/styles/colors.scss";
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
background-color: $slate;
|
||||||
|
}
|
||||||
|
|
||||||
.tree-node > .tree-content > .tree-anchor > span {
|
.tree-node > .tree-content > .tree-anchor > span {
|
||||||
color: $white !important;
|
color: $white !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $light-gray;
|
||||||
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node {
|
.tree-node {
|
||||||
|
|||||||
Reference in New Issue
Block a user