Skip to content

Commit

Permalink
Feature/media list (#83)
Browse files Browse the repository at this point in the history
* Implement open/close in the tree component

# Conflicts:
#	media/media/js/app.js

* Create components for the different media types

* WIP functional item component

* WIP Added simple breadcrumb

# Conflicts:
#	administrator/components/com_media/resources/components/app.vue

* Use dummy icons

* Add example buttons

* Simplify the tree item open property

* WIP list view styling

* Fix breadcrumb

* Adjust app layout

* Add breadcrumb navigation

* Implement folder navigation

* Show image preview

* Add media-item border

* Add media-toolbar

* Toolbar styling

* Fix extension check

* WIP

* Simple tree styling

* Tree view animation

* Styling

* Fix tree change detection

* Tree styling

* WIP fixed toolbar

* Browser item styling

* Export Event inline

* Code style & styling

* Disable text selection

* Use a store as a single source of truth

* use com_media to store static assets

* Extract styles

* Make travis happy

* Remove file mixin

* Extract styles

* Use currentDirectory from app state throughout the app

* Move currentDirContents to the store

* Create api service

* Create MediaTree data structure

* Adjust base styles for 3.7.0

* Code style
  • Loading branch information
dneukirchen authored and laoneo committed Feb 21, 2017
1 parent 85d671d commit 25261c5
Show file tree
Hide file tree
Showing 26 changed files with 795 additions and 176 deletions.
5 changes: 3 additions & 2 deletions administrator/components/com_media/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
"author": "",
"license": "GPL-3.0",
"scripts": {
"build": "browserify ./resources/main.js | uglifyjs -c warnings=false -m > ../../../media/media/js/app.js"
"build": "browserify ./resources/main.js | uglifyjs -c warnings=false -m > ../../../media/com_media/js/mediamanager.js"
},
"dependencies": {
"vue": "^2.0.1"
"vue": "^2.0.1",
"vuex": "^2.1.2"
},
"devDependencies": {
"babel-core": "^6.0.0",
Expand Down
84 changes: 84 additions & 0 deletions administrator/components/com_media/resources/app/Api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const path = require('path');

/**
* Api class for communication with the server
*/
class Api {

/**
* Store constructor
*/
constructor() {
this._baseUrl = '/administrator/index.php?option=com_media&format=json';
}

/**
* Get the contents of a directory from the server
* @param dir
* @returns {Promise}
*/
getContents(dir) {
// Wrap the jquery call into a real promise
return new Promise((resolve, reject) => {
const url = this._baseUrl + '&task=api.files&path=' + dir;
jQuery.getJSON(url)
.success((json) => resolve(this._normalizeArray(json.data)))
.fail((xhr, status, error) => {
reject(xhr)
})
}).catch(this._handleError);
}

/**
* Normalize array data
* @param data
* @returns {{directories, files}}
* @private
*/
_normalizeArray(data) {

// Directories
const directories = data.filter(item => (item.type === 'dir'))
.map(directory => {
directory.directory = path.dirname(directory.path);
directory.directories = [];
directory.files = [];
return directory;
});

// Files
const files = data.filter(item => (item.type === 'file'))
.map(file => {
file.directory = path.dirname(file.path);
return file;
});

return {
directories: directories,
files: files,
}
}

/**
* Handle errors
* @param error
* @private
*/
_handleError(error) {
alert(error.status + ' ' + error.statusText);
switch (error.status) {
case 404:
break;
case 401:
case 403:
case 500:
window.location.href = '/administrator';
default:
window.location.href = '/administrator';
}

throw error;
}
}

export let api = new Api();
17 changes: 0 additions & 17 deletions administrator/components/com_media/resources/app/Event.js

This file was deleted.

84 changes: 26 additions & 58 deletions administrator/components/com_media/resources/components/app.vue
Original file line number Diff line number Diff line change
@@ -1,76 +1,44 @@
<template>
<div>
<div class="row-fluid">
<div class="span3 media-sidebar">
<media-tree :tree="tree" :dir="dir"></media-tree>
</div>
<div class="span9 media-browser">
<media-browser :content="content"></media-browser>
<div class="media-container" :style="{minHeight: fullHeight}">
<media-toolbar></media-toolbar>
<div class="media-main">
<div class="media-sidebar">
<media-tree :root="'/'"></media-tree>
</div>
<media-browser></media-browser>
</div>
</div>
</template>

<script>
export default {
name: 'media-app',
methods: {
/* Set the full height on the app container */
setFullHeight() {
this.fullHeight = window.innerHeight - this.$el.offsetTop + 'px';
},
},
data() {
return {
// The current selected directory
dir: '/',
// The content of the selected directory
content: [],
// The tree structure
tree: {path: '/', children: []},
// The api base url
baseUrl: 'https://github.com/gitapi/repos/joomla/joomla-cms/contents'
}
// The full height of the app in px
fullHeight: '',
};
},
methods: {
getContent() {
let url = this.baseUrl + this.dir;
jQuery.getJSON(url, (content) => {
// Update the current directory content
this.content = content;
// Find the directory node by path and update its children
this._updateLeafByPath(this.tree, this.dir, content);
}).error(() => {
alert("Error loading directory content.");
})
},
// TODO move to a mixin
_updateLeafByPath(obj, path, data) {
// Set the node children
if (obj.path && obj.path === path) {
this.$set(obj, 'children', data);
return true;
}
// Loop over the node children
if (obj.children && obj.children.length) {
for(let i=0; i < obj.children.length; i++) {
if(this._updateLeafByPath(obj.children[i], path, data)) {
return true;
}
}
}
mounted() {
// Initial load the data
this.$store.dispatch('getContents', this.$store.state.selectedDirectory);
return false;
}
},
created() {
// Listen to the directory changed event
Media.Event.listen('dirChanged', (dir) => {
this.dir = dir;
// Set the full height and add event listener when dom is updated
this.$nextTick(() => {
this.setFullHeight();
// Add the global resize event listener
window.addEventListener('resize', this.setFullHeight)
});
},
mounted() {
// Load the tree data
this.getContent();
beforeDestroy() {
// Add the global resize event listener
window.removeEventListener('resize', this.setFullHeight)
},
watch: {
dir: function () {
this.getContent();
}
}
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<ul class="media-breadcrumb">
<li>
<a @click.stop.prevent="goTo('/')">Home</a>
</li>
<li v-for="crumb in crumbs">
<span class="divider material-icons">keyboard_arrow_right</span>
<a @click.stop.prevent="goTo(crumb.path)">{{ crumb.name }}</a>
</li>
</ul>
</template>

<script>
export default {
name: 'media-breadcrumb',
computed: {
/* Get the crumbs from the current directory path */
crumbs () {
const items = [];
this.$store.state.selectedDirectory.split('/')
.filter(crumb => crumb.length !== 0)
.forEach(crumb => {
items.push({
name: crumb,
path: this.$store.state.selectedDirectory.split(crumb)[0] + crumb,
});
});
return items;
},
/* Whether or not the crumb is the last element in the list */
isLast(item) {
return this.crumbs.indexOf(item) === this.crumbs.length - 1;
}
},
methods: {
/* Go to a path */
goTo: function (path) {
this.$store.dispatch('getContents', path);
},
},
}
</script>
Original file line number Diff line number Diff line change
@@ -1,45 +1,19 @@
<template>
<ul class="media-browser">
<media-browser-item v-for="item in content" :item="item"></media-browser-item>
</ul>
<div class="media-browser">
<div class="media-browser-items">
<media-browser-item v-for="item in items" :item="item"></media-browser-item>
</div>
</div>
</template>

<script>
export default {
name: 'media-browser',
props: ['content'],
computed: {
contents: function () {
return this.content
.filter((item) => {
// Hide hidden files
return item.name.indexOf('.') !== 0;
})
.sort((a, b) => {
// Sort by type and alphabetically
if (a.type !== b.type) {
return (a.type === 'dir') ? -1 : 1;
} else {
return (a.name.toUpperCase() < b.name.toUpperCase()) ? -1 : 1;
}
})
/* Filter the contents of the currently selected directory */
items() {
return this.$store.getters.getSelectedDirectoryContents;
}
}
}
</script>
<style>
/** TODO: move styles to dedicated css file **/
.media-browser ul {
margin: 0;
padding: 0;
list-style: none;
}
.media-browser ul li {
display: inline-block;
float: left;
width: 100px;
height: 100px;
margin-bottom: 9px;
margin-right: 9px;
}
</style>
</script>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<div class="media-browser-item-directory"
@click="select(item)"
@dblclick="goTo(item.path)">
<div class="media-browser-item-preview">
<span class="icon material-icons">folder</span>
</div>
<div class="media-browser-item-info">
{{ item.name }}
</div>
</div>
</template>
<script>
export default {
name: 'media-browser-item-directory',
props: ['item'],
methods: {
/* Go to a path */
goTo: function (path) {
this.$store.dispatch('getContents', path);
},
/* Select the directory */
select: function(item) {
// TODO implement
}
}
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div class="media-browser-item-file">
<div class="media-browser-item-preview">
<span class="icon material-icons">insert_drive_file</span>
</div>
<div class="media-browser-item-info">{{ item.name }}</div>
</div>
</template>

<script>
export default {
name: 'media-browser-item-file',
props: ['item'],
}
</script>
Loading

0 comments on commit 25261c5

Please sign in to comment.