First steps of UI rewrite, broken.

This commit is contained in:
Andrey Shchekin 2016-04-17 15:44:51 +12:00
Родитель e37166cc59
Коммит 978afa88be
13 изменённых файлов: 540 добавлений и 26 удалений

Просмотреть файл

@ -6,5 +6,13 @@
"spaceUnits": 4,
"brackets-git.gitPath": "git"
}
},
"language": {
"javascript": {
"linting.prefer": [
"JSHint"
],
"linting.usePreferredOnly": true
}
}
}

Просмотреть файл

@ -1,22 +1,17 @@
{
"browser": true,
"devel": true,
"jquery": true,
"globals": {
"moment": false,
"angular": false
},
"esnext": true,
"camelcase": true,
"eqeqeq": true,
"indent": 2,
"eqnull": true,
"indent": 4,
"noempty": true,
"nonew": true,
"plusplus": true,
"quotmark": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"laxbreak": true
}

Просмотреть файл

@ -21,7 +21,7 @@
<action type="Rewrite" url="wwwroot/index.html" />
</rule>
<rule name="App" stopProcessing="true">
<match url="^((.+)\.(?:html|js|css))$" />
<match url="^((.+)\.(?:html|js|css|js\.map|json))$" />
<action type="Rewrite" url="wwwroot/{R:1}" />
</rule>
</rules>

Просмотреть файл

@ -1,34 +1,40 @@
var gulp = require('gulp');
var concat = require('gulp-concat');
var plumber = require('gulp-plumber');
var uglify = require('gulp-uglify');
var less = require('gulp-less');
var htmlreplace = require('gulp-html-replace');
var webpack = require('webpack-stream');
var assign = require('object-assign');
gulp.task('less', function () {
return gulp
.src('./legacy/app.less')
.pipe(less())
.pipe(gulp.dest('wwwroot'));
return gulp
.src('./legacy/app.less')
.pipe(less())
.pipe(gulp.dest('wwwroot'));
});
gulp.task('js', function () {
return gulp
.src(['./legacy/external/**/*.js', './legacy/app.js', './legacy/**/*.js'])
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(gulp.dest('wwwroot'));
var config = require('./webpack.config.js');
return gulp
.src('./js/app.js')
.pipe(webpack(assign({}, config, {
output: { filename: "app.min.js" }
})))
.pipe(gulp.dest('wwwroot'));
});
gulp.task('html', function () {
return gulp
.src('./legacy/index.html')
.pipe(htmlreplace({ js: 'app.js', css: 'app.css' }))
.pipe(gulp.dest('wwwroot'));
return gulp
.src('./index.html')
.pipe(htmlreplace({ js: 'app.min.js', css: 'app.css' }))
.pipe(gulp.dest('wwwroot'));
});
gulp.task('watch', function () {
gulp.watch('**/*.less', ['less']);
gulp.task('watch', ['default'], function () {
gulp.watch('legacy/**/*.less', ['less']);
gulp.watch('js/**/*.js', ['js']);
gulp.watch('index.html', ['html']);
});
gulp.task('default', ['less', 'js', 'html']);

166
source/Web/index.html Normal file
Просмотреть файл

@ -0,0 +1,166 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Try Roslyn</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta name="theme-color" content="#4684ee">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.0.3/codemirror.min.css"/>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.0.3//addon/lint/lint.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/3.0.0/normalize.min.css" />
<!-- build:css -->
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="loader/loader.css"/>
<!-- endbuild -->
</head>
<body data-app-mobile-codemirror-focus="mobile-editor-focus">
<main v-bind:class="{failed: !result.success}">
<div class="section-group">
<section class="code" data-app-mobile-codemirror-focus="mobile-editor-focus">
<header>
<input class="mobile-menu-button" type="button" />
<h1>Code</h1>
<div class="languages-and-modes">
<div class="select-wrapper option-language option">
<select v-model="options.language">
<option value="csharp">C#</option>
<option value="vbnet">VB.NET</option>
</select>
</div>
<div class="select-wrapper option-mode option">
<select v-model="options.mode">
<option value="regular">Standard</option>
<option value="script">Script</option>
</select>
</div>
</div>
<div class="select-wrapper option-branch option">
<select data-v-model="branch">
<option value="">Release (NuGet)</option>
<option v-for="branch in branches" value="{{branch.id}}">{{branch.displayText}}</option>
</select>
</div>
</header>
<div class="mobile-menu" data-app-mobile-shelf="{
toggle: '.code .mobile-menu-button',
container: '.code',
openClass: 'mobile-menu-open'
}"></div>
<div class="content">
<app-codemirror v-bind:value="code"
v-bind:mode="codeMirrorModes[options.language]"
v-bind:lint="processCodeAsync"
v-bind:options="{
lineNumbers: true,
indentUnit: 4,
gutters: ['CodeMirror-linenumbers','CodeMirror-lint-markers']
}"></app-codemirror>
</div>
</section>
<section v-if="branch" class="info-only non-code">
<header>
<h1>Branch {{branch.name}}, last commit</h1>
</header>
<div class="content">
<div>
<a href="https://github.com/dotnet/roslyn/commit/{{branch.commits[0].hash}}" target="_blank">{{branch.commits[0].hash}}</a>
at {{branch.commits[0].date | date:'dd MMM yyyy'}}
by {{branch.commits[0].author}}
</div>
<div class="commit-message">{{branch.commits[0].message | trim}}</div>
</div>
</section>
</div>
<div class="section-group results">
<section v-show="result.success" class="decompiled" data-app-mobile-codemirror-focus="mobile-editor-focus">
<header>
<input class="mobile-menu-button" type="button"/>
<h1>Decompiled</h1>
<!--span class="loader-container" data-rv-show="loading" data-ng-include="'loader/loader.html'">
</span-->
<div class="select-wrapper option-target-language option">
<select v-model="options.target">
<option value="csharp">C#</option>
<option value="vbnet">VB.NET</option>
<option value="il">IL</option>
</select>
</div>
<div class="select-wrapper option-optimizations option">
<select v-model="options.optimizations" data-app-boolean-model>
<option value="false">Debug</option>
<option value="true">Release</option>
</select>
</div>
</header>
<div class="mobile-menu" data-app-mobile-shelf="{
toggle: '.decompiled .mobile-menu-button',
container: '.decompiled',
openClass: 'mobile-menu-open'
}"></div>
<div class="content">
<app-codemirror v-bind:value="result.decompiled"
v-bind:mode="codeMirrorModes[options.target]"
v-bind:options="{ readOnly: true, indentUnit: 4 }"></app-codemirror>
</div>
</section>
<section class="errors" v-show="result.errors">
<header>
<h1>Errors</h1>
<!--span class="loader-container" data-ng-show="loading" data-ng-include="'loader/loader.html'">
</span-->
</header>
<div class="content">
<ul>
<li data-rv-each-error="result.errors">
<app-diagnostic bind="error"></app-diagnostic>
</li>
</ul>
</div>
</section>
<section class="warnings" v-show="warningsVisible">
<header>
<h1>Warnings</h1>
</header>
<div class="content">
<ul>
<li data-rv-each-warning="result.warnings">
<app-diagnostic bind="warning"></app-diagnostic>
</li>
</ul>
</div>
</section>
</div>
</main>
<footer>
Built by Andrey Shchekin (<a href="http://twitter.com/ashmind">@ashmind</a>). See <a href="http://github.com/ashmind/TryRoslyn">TryRoslyn</a> on GitHub.
</footer>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.4/hammer.min.js"></script>
<!-- build:js -->
<!-- endbuild -->
<!--script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-1782671-10', 'tryroslyn.azurewebsites.net');
ga('send', 'pageview');
</script-->
</body>
</html>

99
source/Web/js/app.js Normal file
Просмотреть файл

@ -0,0 +1,99 @@
import CodeMirror from 'codemirror';
import getBranchesAsync from './server/get-branches-async.js';
import sendCodeAsync from './server/send-code-async.js';
import uiAsync from './ui/main.js';
import urlState from './state/url.js';
let pendingRequest;
async function processCodeAsync(code) {
this.code = code;
if (code === undefined || code === '')
return [];
urlState.save(this.code, this.options);
if (pendingRequest) {
pendingRequest.abort();
pendingRequest = null;
}
const branchUrl = this.branch ? this.branch.url : null;
this.loading = true;
const resultPromise = sendCodeAsync(code, this.options, branchUrl);
pendingRequest = resultPromise;
try {
this.result = await resultPromise;
}
catch (ex) {
console.log(ex);
const error = ex.response.data;
let report = error.exceptionMessage || error.message;
if (error.stackTrace)
report += '\r\n' + error.stackTrace;
this.result = {
success: false,
errors: [ report ]
};
}
if (pendingRequest === resultPromise)
pendingRequest = null;
this.loading = false;
return convertToAnnotations(
this.result.errors,
this.result.warnings
);
}
function convertToAnnotations(errors, warnings) {
const annotations = [];
const pushAnnotations = array => {
if (!array)
return;
for (let item of array) {
annotations.push({
severity: item.severity.toLowerCase(),
message: item.message,
from: CodeMirror.Pos(item.start.line, item.start.column),
to: CodeMirror.Pos(item.end.line, item.end.column)
});
}
}
pushAnnotations(errors);
pushAnnotations(warnings);
return annotations;
}
(async function init() {
const application = Object.assign({
codeMirrorModes: {
csharp: 'text/x-csharp',
vbnet: 'text/x-vb',
il: ''
},
branches: (await getBranchesAsync()),
branch: null,
result: {
success: true,
decompiled: ''
}
}, urlState.load() || {
options: {
language: 'csharp',
mode: 'regular',
target: 'csharp',
optimizations: null
},
code: ''
});
application.processCodeAsync = processCodeAsync.bind(application);
await uiAsync(application);
})();

Просмотреть файл

@ -0,0 +1,10 @@
import $ from 'jquery';
export default async function getBranchesAsync() {
try {
return await $.get('!branches.json');
}
catch(e) {
return [];
}
}

Просмотреть файл

@ -0,0 +1,10 @@
import $ from 'jquery';
export default function sendCodeAsync(code, options, branchUrl) {
let url = 'api/compilation';
if (branchUrl)
url = branchUrl.replace(/\/?$/, '/') + url;
const data = Object.assign({ code: code }, options);
return $.post(url, data);
}

101
source/Web/js/state/url.js Normal file
Просмотреть файл

@ -0,0 +1,101 @@
import $ from 'jquery';
import LZString from 'lz-string';
let lastHash;
function save(code, options) {
let hash = LZString.compressToBase64(code);
const flags = stringifyFlags(options);
if (flags)
hash = 'f:' + flags + '/' + hash;
if (options.branch)
hash = 'b:' + options.branch + '/' + hash;
lastHash = hash;
window.location.hash = hash;
}
function loadInternal(onlyIfChanged) {
let hash = window.location.hash;
if (!hash)
return null;
hash = hash.replace(/^#/, '');
if (!hash || (onlyIfChanged && hash === lastHash))
return null;
lastHash = hash;
const match = /(?:b:([^\/]+)\/)?(?:f:([^\/]+)\/)?(.+)/.exec(hash);
if (match === null)
return null;
const result = {
options: Object.assign({ branch: match[1] }, parseFlags(match[2]))
};
try {
result.code = LZString.decompressFromBase64(match[3]);
}
catch (e) {
return null;
}
return result;
}
function load() {
return loadInternal(false);
}
function onchange(callback) {
$(window).on('hashchange', () => {
const loaded = loadInternal(true);
if (loaded !== null)
callback(loaded);
});
}
var targetMap = { csharp: '', vbnet: '>vb', il: '>il' };
var targetMapReverse = (() => {
const result = {};
for (let key in targetMap) {
result[targetMap[key]] = key;
}
return result;
})();
function stringifyFlags(options) {
return [
options.language === 'vbnet' ? 'vb' : '',
targetMap[options.target],
options.mode === 'script' ? 's' : '',
options.optimizations ? 'r' : ''
].join('');
}
function parseFlags(flags) {
if (!flags)
return {};
let target = targetMapReverse[''];
for (var key in targetMapReverse) {
if (key === '')
continue;
if (flags.indexOf(key) > -1)
target = targetMapReverse[key];
}
return {
language: /(^|[a-z])vb/.test(flags) ? 'vbnet' : 'csharp',
target: target,
mode: flags.indexOf('s') > -1 ? 'script' : 'regular',
optimizations: flags.indexOf('r') > -1
};
}
export default {
save,
load,
onchange
};

Просмотреть файл

@ -0,0 +1,54 @@
import Vue from 'vue';
import CodeMirror from 'codemirror';
import 'codemirror/addon/lint/lint';
import 'codemirror/mode/clike/clike';
import 'codemirror/mode/vb/vb';
function buildGetAnnotations(data) {
return async (cm, updateLinting) => {
updateLinting(await data.lint(data.value));
};
}
Vue.component('app-codemirror', {
props: {
value: String,
mode: String,
lint: Function,
options: Object
},
ready: function() {
const textarea = this.$el;
textarea.value = this.value;
const options = Object.assign(
{},
this.options,
this.mode !== undefined ? { mode: this.mode } : {},
this.lint !== undefined ? {
lint: { async: true, getAnnotations: buildGetAnnotations(this) }
} : {}
);
const instance = CodeMirror.fromTextArea(textarea, options);
this.$watch('mode', value => instance.setOption('mode', value));
let settingValue = false;
this.$watch('value', value => {
value = value != null ? value : '';
if (instance.getValue() === value)
return;
settingValue = true;
instance.setValue(value);
settingValue = false;
});
instance.on('change', () => {
if (settingValue)
return;
this.value = instance.getValue();
});
},
template: '<textarea></textarea>'
});

23
source/Web/js/ui/main.js Normal file
Просмотреть файл

@ -0,0 +1,23 @@
import Vue from 'vue';
import $ from 'jquery';
import './app-codemirror.js';
export default function(model) {
return new Promise(function(resolve, reject) {
$(function() {
try {
// jshint -W031
new Vue({
el: 'body',
data: model
});
}
catch (e) {
reject(e);
return;
}
resolve();
});
});
}

Просмотреть файл

@ -1,10 +1,25 @@
{
"devDependencies": {
"babel-core": "^6.4.5",
"babel-loader": "^6.2.4",
"babel-plugin-syntax-async-functions": "^6.3.13",
"babel-plugin-transform-regenerator": "^6.4.4",
"babel-plugin-transform-runtime": "^6.7.5",
"babel-preset-es2015": "^6.3.13",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-html-replace": "^1.5.5",
"gulp-less": "^3.0.5",
"gulp-plumber": "^0.6.6",
"gulp-uglify": "^1.5.1"
"gulp-uglify": "^1.5.1",
"object-assign": "^4.0.1",
"webpack-stream": "^3.1.0"
},
"dependencies": {
"codemirror": "^5.13.4",
"dateformat": "^1.0.12",
"lz-string": "^1.4.4",
"regenerator": "^0.8.42",
"vue": "^1.0.21"
}
}

Просмотреть файл

@ -0,0 +1,27 @@
/* globals module:false, require:false */
const webpack = require('webpack');
module.exports = {
externals: {
jquery: 'jQuery'
},
devtool: 'source-map',
plugins: [
//new webpack.optimize.UglifyJsPlugin()
],
entry: [
'regenerator/runtime',
'./js/app.js'
],
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015'],
plugins: ['syntax-async-functions', 'transform-regenerator']
}
}]
}
};