From e5867beeb95d45b649d1a707e14a9a1fa69f9c7c Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Wed, 21 Mar 2018 18:44:27 +0100 Subject: [PATCH] [init] all --- .gitignore | 7 + .htaccess | 3 + package.json | 46 +++++++ public_html/.htaccess | 4 + public_html/asset/svg/menu.add.svg | 44 +++++++ public_html/asset/svg/menu.add@hover.svg | 44 +++++++ public_html/asset/svg/menu.group.svg | 13 ++ public_html/index.html | 33 +++++ webpack.config.js | 85 ++++++++++++ webpack/common.js | 40 ++++++ webpack/lib/api-client.js | 161 +++++++++++++++++++++++ webpack/lib/gstore.js | 14 ++ webpack/main.js | 25 ++++ webpack/page/home.js | 35 +++++ webpack/routes.js | 11 ++ webpack/scss/constants.scss | 30 +++++ webpack/scss/container.scss | 45 +++++++ webpack/scss/dialog.scss | 44 +++++++ webpack/scss/layout.scss | 45 +++++++ webpack/scss/menu.scss | 119 +++++++++++++++++ webpack/vue/channel.vue | 18 +++ webpack/vue/dialog.vue | 28 ++++ webpack/vue/menu.vue | 33 +++++ webpack/vue/wrapper.vue | 43 ++++++ 24 files changed, 970 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 package.json create mode 100644 public_html/.htaccess create mode 100644 public_html/asset/svg/menu.add.svg create mode 100644 public_html/asset/svg/menu.add@hover.svg create mode 100644 public_html/asset/svg/menu.group.svg create mode 100644 public_html/index.html create mode 100644 webpack.config.js create mode 100644 webpack/common.js create mode 100644 webpack/lib/api-client.js create mode 100644 webpack/lib/gstore.js create mode 100644 webpack/main.js create mode 100644 webpack/page/home.js create mode 100644 webpack/routes.js create mode 100644 webpack/scss/constants.scss create mode 100644 webpack/scss/container.scss create mode 100644 webpack/scss/dialog.scss create mode 100644 webpack/scss/layout.scss create mode 100644 webpack/scss/menu.scss create mode 100644 webpack/vue/channel.vue create mode 100644 webpack/vue/dialog.vue create mode 100644 webpack/vue/menu.vue create mode 100644 webpack/vue/wrapper.vue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..644e6b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vscode +.sass-cache +*.map +/node_modules +/public_html/css +/public_html/js +/package-lock.json \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..a80b524 --- /dev/null +++ b/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine on + +RewriteRule ^(.*)$ public_html/$1 [QSA,L] diff --git a/package.json b/package.json new file mode 100644 index 0000000..8ef9806 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "ptut-vhost", + "description": "PTUT", + "version": "1.0.0", + "author": "xdrm-brackets SeekDaSky ", + "license": "MIT", + "private": true, + "scripts": { + "bundle:clean": "exit 0", + "bundle:prod": "cross-env NODE_ENV=production webpack --progress --hide-modules", + "bundle:dev": "cross-env NODE_ENV=development webpack --progress --hide-modules", + "bundle:watch": "cross-env NODE_ENV=development webpack --progress --watch --hide-modules", + "scss": "node-sass -r --output-style compressed --output ./public_html/css ./webpack/scss", + "watch-css": "node-sass -w -r --output-style compressed --output ./public_html/css ./webpack/scss", + "dev": "npm run bundle:clean; npm run bundle:dev; npm run watch-css", + "devjs": "npm run bundle:clean; npm run bundle:watch", + "build": "npm run bundle:clean; npm run bundle:prod; npm run scss" + }, + "dependencies": { + "uglifyjs-webpack-plugin": "^1.2.3", + "vue": "^2.5.9", + "vue-router": "^2.5.3" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ], + "devDependencies": { + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "cross-env": "^5.0.5", + "babel-preset-env": "^1.6.0", + "babel-preset-stage-3": "^6.24.1", + "css-loader": "^0.28.7", + "extract-text-webpack-plugin": "^3.0.2", + "file-loader": "^1.1.4", + "node-sass": "^4.7.2", + "sass-loader": "^6.0.6", + "vue-loader": "^13.0.5", + "vue-svg-loader": "^0.5.0", + "vue-template-compiler": "^2.5.9", + "webpack": "^3.8.1", + "webpack-dev-server": "^2.9.5" + } +} diff --git a/public_html/.htaccess b/public_html/.htaccess new file mode 100644 index 0000000..760dba5 --- /dev/null +++ b/public_html/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine on + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php?url=/$1 [QSA,L] diff --git a/public_html/asset/svg/menu.add.svg b/public_html/asset/svg/menu.add.svg new file mode 100644 index 0000000..675e93d --- /dev/null +++ b/public_html/asset/svg/menu.add.svg @@ -0,0 +1,44 @@ + +image/svg+xml \ No newline at end of file diff --git a/public_html/asset/svg/menu.add@hover.svg b/public_html/asset/svg/menu.add@hover.svg new file mode 100644 index 0000000..5dd5e08 --- /dev/null +++ b/public_html/asset/svg/menu.add@hover.svg @@ -0,0 +1,44 @@ + +image/svg+xml \ No newline at end of file diff --git a/public_html/asset/svg/menu.group.svg b/public_html/asset/svg/menu.group.svg new file mode 100644 index 0000000..b829d50 --- /dev/null +++ b/public_html/asset/svg/menu.group.svg @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/public_html/index.html b/public_html/index.html new file mode 100644 index 0000000..267d507 --- /dev/null +++ b/public_html/index.html @@ -0,0 +1,33 @@ + + + + + xdrm-brackets + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..27273d6 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,85 @@ +var path = require('path'); +var webpack = require('webpack'); +var UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + +const ExtractTextPlugin = require("extract-text-webpack-plugin"); + +const extractSass = new ExtractTextPlugin({ + filename: "[name].css", + disable: process.env.NODE_ENV === "development" +}); + +var mod_common = { + rules: [ + { + test: /\.css$/, + use: [ + 'vue-style-loader', + 'css-loader' + ], + }, { + test: /\.vue$/, + loader: 'vue-loader', + options: { + loaders: {} // other vue-loader options go here + } + }, { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/ + }, { + test: /\.(png|jpg|gif)$/, + loader: 'file-loader', + options: { name: '[name].[ext]?[hash]' } + }, { + test: /\.svg$/, + loader: 'vue-svg-loader', // `vue-svg` for webpack 1.x + options: { + // optional [svgo](https://github.com/svg/svgo) options + svgo: { + plugins: [ + {removeDoctype: true}, + {removeComments: true} + ] + } + } + } + ] +}; + + + +module.exports = { + + name: "main", + entry: './webpack/main.js', + output: { + path: path.resolve(__dirname, './public_html/js'), + publicPath: '/js/', + filename: 'bundle.js' + }, + module: mod_common, + devtool: (process.env.NODE_ENV==='development') ? '#eval-source-map' : false + +} + + + +if (process.env.NODE_ENV === 'production') { + + // http://vue-loader.vuejs.org/en/workflow/production.html + module.exports.plugins = (module.exports.plugins || []).concat([ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: '"production"' + } + }), + new UglifyJSPlugin({ + sourceMap: true + }), + new webpack.LoaderOptionsPlugin({ + minimize: true + }) + ]) + +} diff --git a/webpack/common.js b/webpack/common.js new file mode 100644 index 0000000..92313ac --- /dev/null +++ b/webpack/common.js @@ -0,0 +1,40 @@ +import {GlobalStore} from './lib/gstore' +import VueRouter from 'vue-router' +import routes from './routes' + + +window.gs = new GlobalStore(); + +/* (1) Global data +---------------------------------------------------------*/ +/* (1) Get Full URI */ +gs.set('URI', document.URL.replace(/^(?:[^\/]+\/\/|[^\/]+\/)/, '').split('/').filter(function(v,i){ return !!i && v.length; })); + +/* (2) Store routes */ +gs.set('routes', routes[0]); + +/* (3) Init. vue router */ +gs.set('router', new VueRouter({ + routes: gs.get.routes +})); + + + + +/* (2) Main components +---------------------------------------------------------*/ +/* (1) Menu - channel list */ +gs.set('channel', { + list: { + me: { label: '0 online', icon: 'group' }, + test1: { label: null, icon: '' }, + test2: { label: null, icon: '' }, + add: { label: null, icon: 'add', add: 1 } + }, + + active: 'me' +}); + +/* (2) Set current active menu item from URL */ +if( gs.get.URI.length > 1 && gs.get.channel.list.hasOwnProperty(gs.get.URI[gs.get.URI.length-1]) ) + gs.get.channel.active = gs.get.URI[gs.get.URI.length-1]; \ No newline at end of file diff --git a/webpack/lib/api-client.js b/webpack/lib/api-client.js new file mode 100644 index 0000000..02b5769 --- /dev/null +++ b/webpack/lib/api-client.js @@ -0,0 +1,161 @@ +/* classe API */ +export class APIClient{ + + constructor(target){ + + this.target = target; + + this.xhr = []; // tableau d'objets pour les requêtes ajax + this.buffer = null; // Contiendra le buffer pour debugger si erreur de parsage + this.error = { // error constants + '-1': 'Invalid target format: "METHOD module/method"', + '-2': 'XHR error', + '-3': 'Invalid JSON response', + }; + + } + + + + /* transaction avec le serveur (http://{host}/api/) + * + * @param pTarget URI cible, format "HTTP_METHOD uri/uri/uri" + * @param pArgs Le tableu d'arguments passé en POST (attribut<->postfield) à http://{host}/api/ + * @param pHandler Fonction qui s'éxécutera lors de la réponse (1 argument -> réponse) + * @param pToken Optionnel, token d'auth pour l'api + * + *************************************************************************************************** + * + * @usecase + * 1. api.call( + * 2. "PUT newspaper/article/4" + * 2. { content: "New article content" }, + * 3. function(resp){ + * 4. alert(resp.error); + * 5. } + * 6. ); + * + */ + call(pTarget, pArgs, pHandler, pToken=null){ + + + /* (1) Check @pHandler (for dispatching errors) + ---------------------------------------------------------*/ + /* (1) Check if is a Function */ + if( !(pHandler instanceof Function) ) + throw new Error("3rd argument must be a function, but is of type '"+typeof(pHandler)+"' !"); + + + + /* (2) Check @pTarget + ---------------------------------------------------------*/ + /* (1) Check format */ + if( !/^([A-Z]+) (.+)/i.test(pTarget) ){ + pHandler({ error: -1, ErrorDescription: this.error['-1'] }); + return false; + } + + /* (2) Store locally data */ + var lHttpMethod = RegExp.$1; + var lUri = RegExp.$2; + + + + /* (3) Create form data + ---------------------------------------------------------*/ + /* (1) Create virtual form */ + var lForm = new FormData(); + + /* (2) Add attributes */ + for( var key in pArgs ){ + + // {2.1} If a file -> send as it // + if( pArgs[key] instanceof File ) + lForm.append(key, pArgs[key]); + + // {2.2} Else -> JSON stringify // + else + lForm.append(key, JSON.stringify(pArgs[key])); + + } + + + + /* (4) Create XHR request + ---------------------------------------------------------*/ + /* (1) Clean ended requests */ + for( var i = this.xhr.length-1 ; i >= 0 ; i-- ){ + + if( this.xhr[i] != null ) + break; + + this.xhr.pop(); + + } + + /* (2) Push a new entry -> fetch its index */ + i = this.xhr.push(null) - 1; + + /* (3) Create XHR object */ + this.xhr[i] = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttpRequest'); + + + + /* (5) Bind response event + ---------------------------------------------------------*/ + var self = this; // to access the buffer + + this.xhr[i].onreadystatechange = function(i){ + + /* (1) If request over */ + if( this.xhr[i].readyState == 4 ){ + + /* (2) Update buffer (for debug) */ + self.buffer = this.xhr[i].responseText; + + /* (3) If request success */ + if( [0, 200, 417].indexOf(this.xhr[i].status) > -1 ){ + + + /* (3.1) Create default response (if JSON error) */ + var response = {error:-3, ErrorDescription: self.error['-3']}; + + /* (3.2) Try to parse JSON */ + try{ response = JSON.parse(this.xhr[i].responseText); }catch(e){} + + /* (3.3) Launch @pHandler with response */ + pHandler(response); + + /* (4) If request error */ + }else + pHandler({ error:-2, ErrorDescription: self.error['-2'] }); + + /* (5) Notify current xhr instance is done */ + this.xhr[i] = null; + + } + + }.bind(this, i); + + + /* (6) Finish & send request + ---------------------------------------------------------*/ + /* (1) Open the XHR */ + this.xhr[i].open(lHttpMethod, this.target+lUri, true); + + /* (2) Manage optional token */ + if( pToken != null ) + this.xhr[i].setRequestHeader('Authorization', 'Digest '+pToken); + + /* (3) Custom header to notify we're using Ajax */ + this.xhr[i].setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + /* (4) Make the call */ + this.xhr[i].send( lForm ); + + return true; + + } + + +} \ No newline at end of file diff --git a/webpack/lib/gstore.js b/webpack/lib/gstore.js new file mode 100644 index 0000000..d6bc681 --- /dev/null +++ b/webpack/lib/gstore.js @@ -0,0 +1,14 @@ +/* GlobalStore for VueJS */ +export class GlobalStore{ + + constructor(){ + + this.get = {}; + + } + + set(field, value){ + this.get[field] = value; + } + +} \ No newline at end of file diff --git a/webpack/main.js b/webpack/main.js new file mode 100644 index 0000000..3bd0100 --- /dev/null +++ b/webpack/main.js @@ -0,0 +1,25 @@ +/* (1) Imports +---------------------------------------------------------*/ +/* (1) NPM libs */ +import Vue from 'vue' +import VueRouter from 'vue-router' +import routes from './routes' + +/* (2) Vues */ +import wrapper from './vue/wrapper.vue' + +/* (3) Data */ +require('./common.js'); + + + + +/* (2) Initialisation +---------------------------------------------------------*/ +/* (1) Render view */ +Vue.use(VueRouter); +new Vue({ + el: '#vue', + router: gs.get.router, + render(h){ return h(wrapper); } +}) \ No newline at end of file diff --git a/webpack/page/home.js b/webpack/page/home.js new file mode 100644 index 0000000..c6a590a --- /dev/null +++ b/webpack/page/home.js @@ -0,0 +1,35 @@ +/* (1) Imports +---------------------------------------------------------*/ +/* (1) NPM libs */ +import Vue from 'vue' +import VueRouter from 'vue-router' +import routes from '../routes/home' + +/* (2) Vues */ +import wrapper_vue from '../vue/wrapper.vue' + +/* (3) Data */ +require('../data/common'); +require('../data/home'); + + + + +/* (2) Initialisation +---------------------------------------------------------*/ +/* (1) Init Router */ +const router = new VueRouter({ + mode: 'history', + routes: require('./routes.js') +}); + +/* (2) Store router in gstore */ +gstore.add('router', router); + +/* (3) Render view */ +Vue.use(VueRouter); +new Vue({ + el: '#WRAPPER', + router, + render: h => h(wrapper_vue) +}); \ No newline at end of file diff --git a/webpack/routes.js b/webpack/routes.js new file mode 100644 index 0000000..05e935d --- /dev/null +++ b/webpack/routes.js @@ -0,0 +1,11 @@ +export default{ 0: [ + + { + path: '/channel/:id', + component: require('./vue/channel.vue').default + }, { + path: '*', + redirect: '/channel/me' + } + +] } \ No newline at end of file diff --git a/webpack/scss/constants.scss b/webpack/scss/constants.scss new file mode 100644 index 0000000..73818a4 --- /dev/null +++ b/webpack/scss/constants.scss @@ -0,0 +1,30 @@ + +/* (1) Dimensions */ +$menu-width: 4.3em; +$dialog-width: 15.2em; +$header-height: 3em; + +/* (2) Main colors */ +$menu-bg: #202225; +$dialog-header-bg: #2f3136; +$dialog-bg: #2f3136; +$container-bg: #36393e; +$header-bg: #36393f; + + +/* (1) Header specific +---------------------------------------------------------*/ +/* (1) Header border-bottom */ +$bb-height: 1px; +$bb-offset: 2px; + + +/* (2) Menu Specific +---------------------------------------------------------*/ +/* (1) Menu item size */ +$menu-item-size: #{$menu-width * .72}; +$menu-item-space: .3em; + +/* (2) Default menu item background */ +$menu-item-bg: #2f3136; + diff --git a/webpack/scss/container.scss b/webpack/scss/container.scss new file mode 100644 index 0000000..93a4b03 --- /dev/null +++ b/webpack/scss/container.scss @@ -0,0 +1,45 @@ +@import 'constants'; + +#WRAPPER > div.container{ + + display: block; + position: absolute; + top: 0; + left: #{$menu-width + $dialog-width}; + width: calc( 100% - #{$menu-width + $dialog-width} ); + height: 100%; + + background-color: $container-bg; + + + /* (1) Container HEADER */ + & > div.header{ + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: $header-height; + + border-bottom: #{$bb-height} solid darken($container-bg, 5%); + box-shadow: 0 #{$bb-offset - $bb-height} 0 darken($container-bg, 2%); + + z-index: 200; + + } + + /* (2) Container BODY */ + & > div.body{ + display: block; + position: absolute; + top: calc( #{$header-height} + #{$bb-offset} ); + left: 0; + width: 100%; + height: calc( 100% - #{$header-height} - #{$bb-offset} ); + + background-color: $container-bg; + + z-index: 100; + } + +} \ No newline at end of file diff --git a/webpack/scss/dialog.scss b/webpack/scss/dialog.scss new file mode 100644 index 0000000..36cadc1 --- /dev/null +++ b/webpack/scss/dialog.scss @@ -0,0 +1,44 @@ +@import 'constants'; + +#WRAPPER > div.dialog{ + + display: block; + position: absolute; + top: 0; + left: $menu-width; + width: $dialog-width; + height: 100%; + + background-color: $dialog-bg; + + /* (1) Container HEADER */ + & > div.header{ + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: $header-height; + + border-bottom: #{$bb-height} solid darken($dialog-header-bg, 5%); + box-shadow: 0 #{$bb-offset - $bb-height} 0 darken($dialog-header-bg, 2%); + + z-index: 200; + + } + + /* (2) Container BODY */ + & > div.body{ + display: block; + position: absolute; + top: calc( #{$header-height} + #{$bb-offset} ); + left: 0; + width: 100%; + height: calc( 100% - #{$header-height} - #{$bb-offset} ); + + background-color: $dialog-bg; + + z-index: 100; + } + +} \ No newline at end of file diff --git a/webpack/scss/layout.scss b/webpack/scss/layout.scss new file mode 100644 index 0000000..8983fc1 --- /dev/null +++ b/webpack/scss/layout.scss @@ -0,0 +1,45 @@ + +*{ + margin: 0; + padding: 0; +} + +body{ + + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + margin: 0; + padding: 0; + + color: #fff; + + font-family: 'Source Sans Pro'; + font-size: 16px; + + + + & > #WRAPPER{ + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + margin: 0; + padding: 0; + + background-color: #f00; + } + +} + + + + + diff --git a/webpack/scss/menu.scss b/webpack/scss/menu.scss new file mode 100644 index 0000000..dbfe4a4 --- /dev/null +++ b/webpack/scss/menu.scss @@ -0,0 +1,119 @@ +@import 'constants'; + +#WRAPPER > div.menu{ + + display: flex; + position: absolute; + top: 0; + left: 0; + width: $menu-width; + height: 100%; + + background-color: $menu-bg; + + flex-direction: column; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; + + overflow: hidden; + + /* (1) Menu Item */ + & > span.channel{ + + display: block; + position: relative; + width: $menu-item-size; + height: $menu-item-size; + + margin: #{$menu-item-space} 0; + + border-radius: 50% / 50%; + + background: $menu-item-bg center center no-repeat; + background-size: auto 55%; + + overflow: hidden; + + cursor: pointer; + + transition: border-radius .4s cubic-bezier(.49, .4, .26, 1.93), + background-color .4s ease-in-out, + border-color .2s ease-in-out; + + // {1} First item have margin-top // + &:first-child{ + margin-top: #{$menu-item-space * 1.8}; + } + + // {2} [data-add] have no background color // + &[data-add]{ + background-color: #1e2124; + border: 1px dashed #535552; + + &:hover{ border-color: #ddd; } + } + + + // {3} Hover + current channel (except [data-add])// + &:not([data-add='1']):hover, + &:not([data-add='1']).active{ + border-radius: 30% / 30%; + + // if [data-special=1] -> colorize + &[data-special='1']{ background-color: #7289da; } + + } + + // {4} sub label // + &[data-sub]:not([data-sub='']){ + + display: inline-block; + position: relative; + + margin-bottom: #{$menu-item-space + 2em}; + + overflow: visible; + + // show sub-text + &:before{ + content: attr(data-sub); + + display: inline-block; + position: absolute; + top: calc( 100% + 1em ); + left: 1em; + width: calc( 100% - 2em ); + height: calc( 2em + #{$menu-item-space} ); + + font-size: .6em; + color: #777; + text-indent: -.7em; + text-align: center; + white-space: nowrap; + text-transform: uppercase; + + border-bottom: 2px solid #{$menu-item-bg}; + + overflow: visible; + + } + + } + + // {5} Specific icons // + &[data-icon='group']{ + background-image: url('../asset/svg/menu.group.svg'); + } + + &[data-icon='add']{ + background-image: url('../asset/svg/menu.add.svg'); + &:hover{ background-image: url('../asset/svg/menu.add@hover.svg'); } + } + + + + } + + +} \ No newline at end of file diff --git a/webpack/vue/channel.vue b/webpack/vue/channel.vue new file mode 100644 index 0000000..c0eeb50 --- /dev/null +++ b/webpack/vue/channel.vue @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/webpack/vue/dialog.vue b/webpack/vue/dialog.vue new file mode 100644 index 0000000..188bdac --- /dev/null +++ b/webpack/vue/dialog.vue @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/webpack/vue/menu.vue b/webpack/vue/menu.vue new file mode 100644 index 0000000..fe5cc66 --- /dev/null +++ b/webpack/vue/menu.vue @@ -0,0 +1,33 @@ + \ No newline at end of file diff --git a/webpack/vue/wrapper.vue b/webpack/vue/wrapper.vue new file mode 100644 index 0000000..93284d4 --- /dev/null +++ b/webpack/vue/wrapper.vue @@ -0,0 +1,43 @@ + \ No newline at end of file