Skip to content

Commit

Permalink
[FEATURE] Implement storage selector
Browse files Browse the repository at this point in the history
  • Loading branch information
andreaskienast authored Apr 3, 2019
2 parents 63d3667 + 0367bf7 commit 71eb3b2
Show file tree
Hide file tree
Showing 22 changed files with 17,948 additions and 7,022 deletions.
19 changes: 13 additions & 6 deletions Build/Vue/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import {AjaxRoutes} from '@/enums/AjaxRoutes';
import {StorageInterface} from '@/interfaces/StorageInterface';
import {Component, Vue} from 'vue-property-decorator';
import {CreateElement, VNode} from 'vue';
import TreePanel from '@/components/TreePanel';
import ContentPanel from '@/components/ContentPanel';
import {Action} from 'vuex-class';
import {AjaxRoutes} from '@/enums/AjaxRoutes';
import {Action, State} from 'vuex-class';

@Component
export default class App extends Vue {
@Action(AjaxRoutes.damGetStoragesAndMounts)
getStorages!: Function;

@Action(AjaxRoutes.damGetFolderItems)
fetchData: any;

@State
activeStorage!: StorageInterface;

// lifecycle method
mounted(): void {
// root content area - dummy
this.fetchData('1:/');
this.getStorages();
}

private render(h: CreateElement): VNode {
private render(h: CreateElement): VNode | null {
if (!this.activeStorage) {
return null;
}

return (
<div id='app' class='module'>
Expand Down
5 changes: 4 additions & 1 deletion Build/Vue/src/components/Breadcrumb/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default class Breadcrumb extends Vue {
@State
current!: String;

@State
showTree!: boolean;

@Action(AjaxRoutes.damGetFolderItems)
fetchData: any;

Expand All @@ -39,7 +42,7 @@ export default class Breadcrumb extends Vue {
});
return (
<div class='breadcrumb'>
<StorageSelector />
{!this.showTree ? <StorageSelector/> : ''}
{items}
</div>
);
Expand Down
12 changes: 12 additions & 0 deletions Build/Vue/src/components/ContentPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {StorageInterface} from '@/interfaces/StorageInterface';
import {Component, Prop, Vue} from 'vue-property-decorator';
import {VNode} from 'vue';
import DocHeader from '@/components/DocHeader';
Expand Down Expand Up @@ -42,6 +43,9 @@ export default class ContentPanel extends Vue {
@Action(AjaxRoutes.damGetFolderItems)
fetchData: any;

@State
activeStorage!: StorageInterface;

@State
items: any;

Expand All @@ -58,6 +62,14 @@ export default class ContentPanel extends Vue {
super(props);
}

get browsableIdentifier(): string {
return this.activeStorage.identifier + ':/';
}

mounted(): void {
this.fetchData(this.browsableIdentifier);
}

private renderFileTiles(): VNode | null {
return this.files ? <Tiles items={this.files} /> : null;
}
Expand Down
24 changes: 24 additions & 0 deletions Build/Vue/src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {VNode} from 'vue';
import {Component, Prop, Vue} from 'vue-property-decorator';
import Icons from 'TYPO3/CMS/Backend/Icons';

@Component
export default class Icon extends Vue {
@Prop()
identifier!: string;

private icon: VNode | null = null;

constructor(props: any) {
super(props);
}

async mounted(): Promise<any> {
const iconRaw = await Icons.getIcon(this.$props.identifier, Icons.sizes.small, null, null, 'inline');
this.icon = <span domPropsInnerHTML={iconRaw}></span>;
}

private render(): VNode | null {
return this.icon;
}
}
104 changes: 94 additions & 10 deletions Build/Vue/src/components/StorageSelector/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,114 @@
import Icon from '@/components/Icon';
import {Mutations} from '@/enums/Mutations';
import {StorageInterface} from '@/interfaces/StorageInterface';
import {Component, Vue} from 'vue-property-decorator';
import {VNode} from 'vue';
import {Action} from 'vuex-class';
import {Action, State} from 'vuex-class';

@Component
export default class StorageSelector extends Vue {
@Action(Mutations.SET_STORAGE)
setStorage!: Function;

@State
storages!: Array<StorageInterface>;

@State
activeStorage!: StorageInterface;

constructor(props: any) {
super(props);
}

private render(): VNode {
private static toggleDropdown(e: Event): void {
const me = (e.target as HTMLElement);
const dropdown = me.closest('.component-dropdown');
if (dropdown === null) {
return;
}

let isActive = dropdown.classList.contains('component-dropdown-active');
dropdown.classList.toggle('component-dropdown-active', !isActive);
dropdown.classList.toggle('component-dropdown-inactive', isActive);

const dropdownToggle = dropdown.querySelector('.component-dropdown-toggle');
if (dropdownToggle !== null) {
dropdownToggle.setAttribute('aria-expanded', (!isActive).toString());
}
}

private static getBrowsableIdentifier(identifier: number): string {
return identifier + ':/';
}

private render(): VNode | null {
if (!this.storages.length) {
return null;
}

const options = this.storages.map(this.generateOption, this);
return (
<div class='storage-selector' onchange={this.updateStorage}>
<select class='form-control'>
<option value='1:/'>fileadmin/ (auto-created)</option>
<option value='2:/'>also fileadmin/ (but another ID)</option>
</select>
</div>
<span class='component-dropdown component-dropdown-inactive'>
<button
type='button'
title={TYPO3.lang['StorageSelector.title']}
aria-label={TYPO3.lang['StorageSelector.label']}
class='component-dropdown-toggle'
aria-haspopup='true'
aria-expanded='false'
onclick={StorageSelector.toggleDropdown}
>
<span class='component-dropdown-toggle-icon' role='presentation'>
<Icon identifier={this.activeStorage.icon} />
</span>
<span class='component-dropdown-toggle-text'>
{this.activeStorage.name}
</span>
<span class='component-dropdown-toggle-icon' role='presentation'>
<Icon identifier='apps-pagetree-expand' />
</span>
</button>
<div class='component-dropdown-container'>
<ul class='component-dropdown-menu' role='menu'>
{options}
</ul>
</div>
</span>
);
}

private generateOption(storage: StorageInterface): VNode {
return(
<li class='component-dropdown-menu-item'>
<a
class='component-dropdown-menu-link'
title='{storage.storageName}'
href='#'
data-identifier={storage.identifier}
onclick={this.updateStorage}
>
<span class='component-dropdown-menu-link-icon' role='presentation'>
<Icon identifier={storage.icon} />
</span>
<span class='component-dropdown-menu-link-text'>{storage.storageName}</span>
</a>
</li>
);
}

private updateStorage(e: Event): void {
const selectedStorage = (e.target as HTMLSelectElement).selectedOptions[0].value;
this.setStorage(selectedStorage);
const me = (e.target as HTMLElement);
const link = me.closest('a');
if (link === null || typeof link.dataset.identifier === 'undefined') {
return;
}

const storageId = parseInt(link.dataset.identifier, 10);
if (storageId !== this.activeStorage.identifier) {
this.setStorage({
id: storageId,
browsableIdentifier: StorageSelector.getBrowsableIdentifier(storageId),
});
}
}
}
21 changes: 12 additions & 9 deletions Build/Vue/src/components/Tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export default class Tree extends Vue {
fetchTreeData: any;

@State
storage!: StorageInterface;
activeStorage!: StorageInterface;

@State
treeFolders!: Array<FolderTreeNode>;

private draggableService: DraggableService;

Expand All @@ -29,22 +32,22 @@ export default class Tree extends Vue {
this.draggableService = new DraggableService(configuration);
}

get browsableIdentifier(): string {
return this.activeStorage.identifier + ':/';
}

mounted(): void {
this.fetchTreeData(this.storage.identifier);
this.draggableService.makeDraggable();
this.fetchTreeData(this.browsableIdentifier);
}

private render(h: CreateElement): VNode|null {
if (!this.storage.folders) {
return null;
}

const nodes = [this.storage.folders].map(this.generateNodes, this);
private render(h: CreateElement): VNode | null {
const nodes = [this.treeFolders].map(this.generateNodes, this);
return(
<div>
<ul class='list-tree list-tree-root'>
<li class='list-tree-control-open'>
<TreeRootNode></TreeRootNode>
<TreeRootNode storage={this.activeStorage}></TreeRootNode>
{nodes}
</li>
</ul>
Expand Down
12 changes: 6 additions & 6 deletions Build/Vue/src/components/TreePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import {VNode} from 'vue';
import DocHeader from '@/components/DocHeader';
import Tree from '@/components/Tree';
import StorageSelector from '@/components/StorageSelector';
import {StorageInterface} from '@/interfaces/StorageInterface';
import {State} from 'vuex-class';

@Component
export default class TreePanel extends Vue {
@State
showTree!: boolean;

@State
activeStorage!: StorageInterface;

constructor(props: any) {
super(props);
}
Expand All @@ -19,17 +23,13 @@ export default class TreePanel extends Vue {
}

private render(): VNode|null {
if (!this.shallShowTree) {
return null;
}

return (
<div class='typo3-filelist-treepanel'>
<div class='typo3-filelist-treepanel' v-show={this.shallShowTree}>
<DocHeader>
<template slot='bottomBarLeft'><StorageSelector/></template>
</DocHeader>
<div class=''>
<Tree/>
{this.activeStorage ? <Tree storage={this.activeStorage}/> : ''}
</div>
</div>
);
Expand Down
17 changes: 11 additions & 6 deletions Build/Vue/src/components/TreeRootNode/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import Icon from '@/components/Icon';
import {AjaxRoutes} from '@/enums/AjaxRoutes';
import {StorageInterface} from '@/interfaces/StorageInterface';
import {CreateElement, VNode} from 'vue';
import {Action, State} from 'vuex-class';
import {Component, Vue} from 'vue-property-decorator';
import {Action} from 'vuex-class';
import {Component, Prop, Vue} from 'vue-property-decorator';

@Component
export default class TreeNode extends Vue {
@Action(AjaxRoutes.damGetFolderItems)
fetchData: any;

@State
@Prop()
storage!: StorageInterface;

constructor(props: any) {
super(props);
}

get browsableIdentifier(): string {
return this.storage.identifier + ':/';
}

private render(h: CreateElement): VNode {
return(
<span class='list-tree-group' data-identifier={this.storage.identifier}>
<a href='#' data-identifier={this.storage.identifier} onclick={() => this.fetchData(this.storage.identifier)}>
<img src={this.storage.icon} width='16' height='16' /> {this.storage.title}
<span class='list-tree-group' data-identifier={this.browsableIdentifier}>
<a href='#' data-identifier={this.browsableIdentifier} onclick={() => this.fetchData(this.browsableIdentifier)}>
<Icon identifier={this.storage.icon} /> {this.storage.storageName}
</a>
</span>
);
Expand Down
1 change: 1 addition & 0 deletions Build/Vue/src/enums/Mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export enum Mutations {
CHANGE_SORTING = 'CHANGE_SORTING',
TOGGLE_TREE = 'TOGGLE_TREE',
SET_STORAGE = 'SET_STORAGE',
FETCH_STORAGES = 'FETCH_STORAGES',
FETCH_TREE_DATA = 'FETCH_TREE_DATA',
}
11 changes: 6 additions & 5 deletions Build/Vue/src/interfaces/StorageInterface.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import FolderTreeNode from '@/interfaces/FolderTreeNode';

export interface StorageInterface {
folders: Array<FolderTreeNode>;
title: string;
identifier: string;
type: string;
identifier: number;
name: string;
storageName: string;
storageType: string;
storageOnline: boolean;
icon: string;
}
Loading

0 comments on commit 71eb3b2

Please sign in to comment.