[gh-47] Implemented proper offline style, and redone the favicon support.

This commit is contained in:
Andrey Shchekin 2017-01-02 12:37:40 +13:00
Родитель d29934f70e
Коммит d5bc089c25
19 изменённых файлов: 231 добавлений и 112 удалений

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

@ -0,0 +1,17 @@
/*
npm install text-to-svg && npm install svgo
*/
const SVGO = require('svgo');
const TextToSVG = require('text-to-svg');
const svgo = new SVGO();
const text = TextToSVG
.loadSync(`${process.env.WINDIR}\\Fonts\\consola.ttf`)
.getSVG('?.', { x: 7, y: 105, fontSize: 90, attributes: { fill: '#fff' } })
.replace(/<\/?svg[^>]*>/g, '');
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect fill="#4684ee" stroke="#fff" width="100%" height="100%"></rect>
${text}
</svg>`
svgo.optimize(svg, optimized => console.log(optimized.data));

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

@ -101,11 +101,11 @@
<Private>True</Private>
</Reference>
<Reference Include="MirrorSharp.Common, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\#packages\MirrorSharp.Common.0.9.0-pre-20161227-3\lib\net45\MirrorSharp.Common.dll</HintPath>
<HintPath>..\#packages\MirrorSharp.Common.0.9.0-pre-20161230\lib\net45\MirrorSharp.Common.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MirrorSharp.Owin, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\#packages\MirrorSharp.Owin.0.9.0-pre-20161227-3\lib\net45\MirrorSharp.Owin.dll</HintPath>
<HintPath>..\#packages\MirrorSharp.Owin.0.9.0-pre-20161230\lib\net45\MirrorSharp.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">

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

@ -17,8 +17,8 @@
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net46" />
<package id="Microsoft.Owin.Cors" version="3.0.1" targetFramework="net46" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net46" />
<package id="MirrorSharp.Common" version="0.9.0-pre-20161227-3" targetFramework="net46" />
<package id="MirrorSharp.Owin" version="0.9.0-pre-20161227-3" targetFramework="net46" />
<package id="MirrorSharp.Common" version="0.9.0-pre-20161230" targetFramework="net46" />
<package id="MirrorSharp.Owin" version="0.9.0-pre-20161230" targetFramework="net46" />
<package id="Owin" version="1.0" targetFramework="net46" />
<package id="System.AppContext" version="4.1.0" targetFramework="net46" />
<package id="System.Buffers" version="4.0.0" targetFramework="net46" />

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

@ -184,14 +184,13 @@
<None Include="browserslist" />
<None Include="js\helpers\warn.js" />
<None Include="js\ui\components\app-codemirror.js" />
<None Include="js\ui\components\app-favicon-replace-href.js" />
<None Include="js\ui\components\app-loader.js" />
<None Include="js\ui\components\app-mirrorsharp-diagnostic.js" />
<None Include="js\ui\components\app-mirrorsharp.js" />
<Content Include="less\imports\offline.less" />
<None Include="webpack.config.js" />
<None Include=".brackets.json" />
<None Include="gulpfile.js" />
<None Include="favicon-error.svg" />
<None Include="favicon.svg" />
<Content Include="index.html" />
<None Include="js\app.js" />
@ -206,6 +205,7 @@
<None Include="js\ui\hooks\app-mobile-codemirror-fullscreen.js" />
<None Include="js\ui\hooks\registry.js" />
<None Include="js\ui\index.js" />
<Content Include="js\ui\components\app-favicon-manager.js" />
<Content Include="Web.config">
<SubType>Designer</SubType>
</Content>

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

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect fill="#dc3912" stroke="#fff" width="100%" height="100%"></rect>
<text x="7" y="105" fill="#fff" style="font-family: Consolas; font-size: 90">?.</text>
</svg>

До

Ширина:  |  Высота:  |  Размер: 230 B

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

@ -1,4 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect fill="#4684ee" stroke="#fff" width="100%" height="100%"></rect>
<text x="7" y="105" fill="#fff" style="font-family: Consolas; font-size: 90">?.</text>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><rect fill="#4684ee" stroke="#fff" width="100%" height="100%"/><path fill="#fff" d="M47.52 62.42q0 7.12-3.96 10.79-3.95 3.67-11.47 4.15l-.35 10.81h-6.55l-.57-16.83h5.14q2.68 0 4.49-.57 1.8-.57 2.9-1.63 1.1-1.05 1.58-2.57.48-1.52.48-3.41 0-3.2-1.32-5.71-1.31-2.5-3.69-4.22-2.37-1.71-5.64-2.59-3.28-.88-7.15-.88h-1.09v-6.85h1.18q4.44 0 8.11.83 3.67.84 6.55 2.26 2.88 1.43 5.01 3.37 2.13 1.93 3.56 4.08 1.43 2.16 2.11 4.46.68 2.31.68 4.51M28.4 94.19q1.23 0 2.29.46 1.05.46 1.82 1.25t1.21 1.87q.44 1.08.44 2.26 0 1.19-.44 2.25-.44 1.05-1.21 1.82t-1.82 1.23q-1.06.46-2.29.46-1.19 0-2.24-.46-1.05-.46-1.85-1.23-.79-.77-1.23-1.82-.44-1.06-.44-2.25 0-1.18.44-2.26.44-1.08 1.23-1.87.8-.79 1.85-1.25 1.05-.46 2.24-.46zm52.47-3.03q1.5 0 2.84.57 1.34.57 2.33 1.58.98 1.01 1.56 2.35.57 1.34.57 2.88 0 1.49-.57 2.81-.58 1.32-1.56 2.31-.99.99-2.33 1.56-1.34.57-2.84.57-1.54 0-2.85-.57-1.32-.57-2.31-1.56-.99-.99-1.56-2.31-.57-1.32-.57-2.81 0-1.54.57-2.88.57-1.34 1.56-2.35.99-1.01 2.31-1.58 1.31-.57 2.85-.57z"/></svg>

До

Ширина:  |  Высота:  |  Размер: 230 B

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

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

@ -1,13 +1,14 @@
/* global require:false */
'use strict';
const fs = require('fs');
const gulp = require('gulp');
const g = require('gulp-load-plugins')();
const webpack = require('webpack-stream');
const assign = require('object-assign');
const pipe = require('multipipe');
gulp.task('less', function () {
gulp.task('less', () => {
return gulp
.src('./less/app.less')
// this doesn't really work properly, e.g. https://github.com/ai/autoprefixer-core/issues/27
@ -20,7 +21,7 @@ gulp.task('less', function () {
.pipe(gulp.dest('wwwroot'));
});
gulp.task('js', function () {
gulp.task('js', () => {
var config = require('./webpack.config.js');
assign(config.output, { filename: 'app.min.js' });
return gulp
@ -29,43 +30,43 @@ gulp.task('js', function () {
.pipe(gulp.dest('wwwroot'));
});
gulp.task('favicons', function () {
function rename(suffix, extension) {
return g.rename(path => {
path.extension = extension || path.extension;
const parts = path.basename.split('-');
if (parts.length > 1) {
path.dirname = parts[1];
path.basename = parts[0];
}
path.basename += suffix || '';
return;
});
}
gulp.task('favicons', () => {
return gulp
.src('./favicon*.svg')
.src('./favicon.svg')
.pipe(g.mirror(
rename(),
pipe(g.svg2png({ width: 16, height: 16 }), rename('-16', 'png')),
pipe(g.svg2png({ width: 32, height: 32 }), rename('-32', 'png')),
pipe(g.svg2png({ width: 64, height: 64 }), rename('-64', 'png')),
pipe(g.svg2png({ width: 96, height: 96 }), rename('-96', 'png')),
pipe(g.svg2png({ width: 128, height: 128 }), rename('-128', 'png')),
pipe(g.svg2png({ width: 196, height: 196 }), rename('-196', 'png')),
pipe(g.svg2png({ width: 256, height: 256 }), rename('-256', 'png'))
g.noop(),
pipe(g.svg2png({ width: 16, height: 16 }), g.rename({suffix:'-16'})),
pipe(g.svg2png({ width: 32, height: 32 }), g.rename({suffix:'-32'})),
pipe(g.svg2png({ width: 64, height: 64 }), g.rename({suffix:'-64'})),
pipe(g.svg2png({ width: 96, height: 96 }), g.rename({suffix:'-96'})),
pipe(g.svg2png({ width: 128, height: 128 }), g.rename({suffix:'-128'})),
pipe(g.svg2png({ width: 196, height: 196 }), g.rename({suffix:'-196'})),
pipe(g.svg2png({ width: 256, height: 256 }), g.rename({suffix:'-256'}))
))
.pipe(gulp.dest('wwwroot/favicons'));
});
gulp.task('html', function () {
return gulp
.src('./index.html')
.pipe(g.htmlReplace({ js: 'app.min.js', css: 'app.min.css' }))
.pipe(gulp.dest('wwwroot'));
});
gulp.task('watch', function () {
gulp.task('html', () => {
const faviconSvg = fs.readFileSync('favicon.svg', 'utf8');
// http://codepen.io/jakob-e/pen/doMoML
const faviconSvgUrlSafe = faviconSvg
.replace(/"/g, '\'')
.replace(/%/g, '%25')
.replace(/#/g, '%23')
.replace(/{/g, '%7B')
.replace(/}/g, '%7D')
.replace(/</g, '%3C')
.replace(/>/g, '%3E')
.replace(/\s+/g,' ');
return gulp
.src('./index.html')
.pipe(g.htmlReplace({ js: 'app.min.js', css: 'app.min.css' }))
.pipe(g.replace('{build:favicon-svg}', faviconSvgUrlSafe))
.pipe(gulp.dest('wwwroot'));
});
gulp.task('watch', () => {
gulp.watch('less/**/*.less', ['less']);
gulp.watch('js/**/*.js', ['js']);
gulp.watch('favicon*.svg', ['favicons']);

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

@ -3,26 +3,24 @@
<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="icon" type="image/svg+xml" href="favicons/favicon.svg">
<link rel="icon" type="image/png" href="favicons/favicon-256.png" sizes="256x256">
<link rel="icon" type="image/png" href="favicons/favicon-196.png" sizes="196x196">
<link rel="icon" type="image/png" href="favicons/favicon-128.png" sizes="128x128">
<link rel="icon" type="image/png" href="favicons/favicon-96.png" sizes="96x96">
<link rel="icon" type="image/png" href="favicons/favicon-64.png" sizes="64x64">
<link rel="icon" type="image/png" href="favicons/favicon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="favicons/favicon-16.png" sizes="16x16">
<app-favicon-replace-href v-bind:if="!result.success"
regexp="favicons"
with="favicons/error"></app-favicon-replace-href>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,{build:favicon-svg}">
<link rel="icon" type="image/png" href="favicon-256.png" sizes="256x256">
<link rel="icon" type="image/png" href="favicon-196.png" sizes="196x196">
<link rel="icon" type="image/png" href="favicon-128.png" sizes="128x128">
<link rel="icon" type="image/png" href="favicon-96.png" sizes="96x96">
<link rel="icon" type="image/png" href="favicon-64.png" sizes="64x64">
<link rel="icon" type="image/png" href="favicon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="favicon-16.png" sizes="16x16">
<app-favicon-manager default-color="#4684ee" v-bind:color="status.color"></app-favicon-manager>
<!-- build:css -->
<!-- endbuild -->
</head>
<body data-mobile-codemirror-fullscreen-class="mobile-editor-focus">
<main v-bind:class="{failed: !result.success}">
<main class="root-status-{{status.name}}">
<div class="section-group">
<section class="code">
<header>
@ -43,6 +41,7 @@
</select>
</div>
</div>
<div class="offline-only">[connection lost, reconnecting…]</div>
<div class="select-wrapper option-branch option _unsupported">
<select v-model="branch">
@ -52,7 +51,7 @@
</optgroup>
</select>
</div>
<app-mobile-shelf class="mobile-menu"
toggle=".code .mobile-menu-button"
container=".code"
@ -61,9 +60,10 @@
<div class="content">
<app-mirrorsharp v-bind:value="code"
v-bind:server-options="serverOptions"
v-on:after-text-change="code = $arguments[0]()"
v-on:after-slow-update="applyUpdateResult"
v-on:server-error="applyServerError"></app-mirrorsharp>
v-on:text-change="code = $arguments[0]()"
v-on:slow-update-result="applyUpdateResult"
v-on:server-error="applyServerError"
v-on:connection-change="applyConnectionChange"></app-mirrorsharp>
<!--app-codemirror v-bind:value="code"
v-bind:mode="codeMirrorModes[options.language]"
v-bind:lint="lintCodeAsync"
@ -158,7 +158,7 @@
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="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<!-- build:js -->
<!-- endbuild -->

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

@ -30,6 +30,10 @@ function applyServerError(message) {
};
}
function applyConnectionChange(connectionState) {
this.online = (connectionState === 'open');
}
async function createAppAsync() {
const data = Object.assign({
codeMirrorModes: {
@ -41,6 +45,7 @@ async function createAppAsync() {
branchGroups: [],
branch: null,
online: true,
loading: false,
result: {
@ -80,9 +85,16 @@ async function createAppAsync() {
optimize: this.options.release ? 'release' : 'debug',
'x-target-language': this.options.target
};
},
status: function() {
if (!this.online)
return { name: 'offline', color: '#aaa' };
if (!this.result.success)
return { name: 'error', color: '#dc3912' };
return { name: 'default', color: '#4684ee' };
}
},
methods: { applyUpdateResult, applyServerError }
methods: { applyUpdateResult, applyServerError, applyConnectionChange }
};
}

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

@ -0,0 +1,68 @@
import Vue from 'vue';
Vue.component('app-favicon-manager', {
props: {
color: String,
defaultColor: String
},
ready: function() {
const favicons = Array.from(document.querySelectorAll('link[rel=icon]'));
let faviconSvg;
let faviconSvgUrl;
const faviconsBySizes = {};
const cacheDefault = {};
const cache = { [this.defaultColor]: cacheDefault };
for (let favicon of favicons) {
if (favicon.getAttribute('type') === 'image/svg+xml') {
faviconSvg = favicon;
faviconSvgUrl = favicon.getAttribute('href');
cacheDefault.svg = faviconSvgUrl;
continue;
}
const size = favicon.getAttribute('sizes').match(/^\d+/)[0];
cacheDefault[size] = favicon.getAttribute('href');
faviconsBySizes[size] = favicon;
}
const loadImage = src => {
const img = new Image();
const promise = new Promise(resolve => img.onload = () => resolve(img));
img.src = src;
return promise;
};
const generateDataUrls = async color => {
const recoloredSvgUrl = faviconSvgUrl.replace(encodeURIComponent(this.defaultColor), encodeURIComponent(color));
const urls = {};
urls.svg = recoloredSvgUrl;
await Promise.all(Object.keys(faviconsBySizes).map(async size => {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
// Firefox bug #700533, SVG needs specific dimensions
const finalSvgUrl = recoloredSvgUrl.replace('viewBox', encodeURIComponent(`width="${size}" height="${size}" viewBox`));
const img = await loadImage(finalSvgUrl);
context.drawImage(img, 0, 0);
urls[size] = canvas.toDataURL('image/png');
}));
return urls;
}
this.$watch('color', async color => {
var urls = cache[color];
if (!urls) {
urls = await generateDataUrls(color);
cache[color] = urls;
if (this.color !== color) // changed while we were awaiting urls
return;
}
faviconSvg.href = urls.svg;
for (let size in faviconsBySizes) {
faviconsBySizes[size].setAttribute('href', urls[size]);
}
});
}
});

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

@ -1,31 +0,0 @@
import Vue from 'vue';
const savedHrefKey = 'app-favicon-replace-href::saved';
Vue.component('app-favicon-replace-href', {
props: {
if: Boolean,
regexp: String,
with: String
},
ready: function() {
const favicons = document.querySelectorAll('link[rel=icon]');
this.$watch('if', value => {
if (value) {
for (let favicon of favicons) {
const href = favicon.href;
favicon[savedHrefKey] = href;
favicon.href = href.replace(new RegExp(this.regexp), this.with);
}
}
else {
for (let favicon of favicons) {
const saved = favicon[savedHrefKey];
if (saved)
favicon.href = saved;
}
}
});
}
});

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

@ -12,9 +12,12 @@ Vue.component('app-mirrorsharp', {
textarea.value = this.value;
const instance = mirrorsharp(textarea, {
serviceUrl: "ws://" + window.location.host + "/mirrorsharp",
afterSlowUpdate: result => this.$emit('after-slow-update', result),
afterTextChange: getText => this.$emit('after-text-change', getText),
onServerError: message => this.$emit('server-error', message)
on: {
slowUpdateResult: result => this.$emit('slow-update-result', result),
connectionChange: type => this.$emit('connection-change', type),
textChange: getText => this.$emit('text-change', getText),
serverError: message => this.$emit('server-error', message)
}
});
if (this.serverOptions)
instance.sendServerOptions(this.serverOptions);

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

@ -5,7 +5,7 @@ import hooks from './hooks/registry';
import './filters/app-date';
import './filters/app-trim';
import './components/app-codemirror';
import './components/app-favicon-replace-href';
import './components/app-favicon-manager';
import './components/app-loader';
import './components/app-mirrorsharp';
import './components/app-mirrorsharp-diagnostic';
@ -27,7 +27,7 @@ export default function(app) {
// ReSharper disable once ConstructorCallNotUsed
// jshint -W031
new Vue({
el: 'body',
el: 'html',
data: app.data,
computed: app.computed,
methods: app.methods,

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

@ -89,6 +89,7 @@ section {
padding-left: 7px;
color: #fff;
background-color: @primary-color;
font-family: 'Roboto Condensed', sans-serif;
}
h1 {
@ -96,7 +97,6 @@ section {
margin-top: auto;
margin-bottom: auto;
font-family: 'Roboto Condensed', sans-serif;
font-size: 100%;
font-weight: normal;
}
@ -170,7 +170,7 @@ section.info-only {
}
.errors header,
.failed .code header {
.root-status-error .code header {
background-color: @error-color;
}
@ -178,6 +178,14 @@ section.info-only {
background-color: @warning-color;
}
.root-status-offline {
.code, .decompiled {
header {
background-color: @offline-color;
}
}
}
.commit-message {
white-space: pre-line;
}
@ -203,6 +211,7 @@ section.info-only {
@import 'imports/loader.less';
@import 'imports/mobile.less';
@import 'imports/codemirror.less';
@import 'imports/offline.less';
._unsupported {
pointer-events: none;

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

@ -3,7 +3,7 @@
@import (inline) '../../node_modules/codemirror/lib/codemirror.css';
@import (inline) '../../node_modules/codemirror/addon/lint/lint.css';
@import (inline) '../../node_modules/codemirror/addon/hint/show-hint.css';
@import (inline) '../../node_modules/codemirror-addon-lint-fix/addon/lint-fix/lint-fix.css';
@import (inline) '../../node_modules/codemirror-addon-lint-fix/dist/lint-fix.css';
@import (inline) '../../node_modules/mirrorsharp/css/mirrorsharp.css';
textarea, .CodeMirror {

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

@ -1,6 +1,7 @@
@primary-color: #4684ee;
@error-color: #dc3912;
@warning-color: #ff9900;
@offline-color: #aaa;
.code-text() {
font-family: Consolas, Menlo, Monaco, monospace;

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

@ -1,5 +1,12 @@
@import 'common.less';
.mobile-compact(@rules) {
@media screen and (orientation: portrait) and (max-device-width: 700px),
screen and (orientation: portrait) and (max-width: 700px) {
@rules();
}
}
.mobile-menu-button, .mobile-menu {
display: none;
}
@ -33,9 +40,7 @@
}
}
@media screen and (orientation: portrait) and (max-device-width: 700px),
screen and (orientation: portrait) and (max-width: 700px) {
.mobile-compact({
.mobile-menu-button {
display: inline-block;
vertical-align: middle;
@ -131,7 +136,7 @@
main + footer {
font-size: 70%;
}
}
});
@media screen and (orientation: landscape) and (max-device-width: 600px) {
body.mobile-editor-focus {

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

@ -0,0 +1,38 @@
@import 'mobile.less';
/* TODO: https://github.com/ashmind/mirrorsharp/issues/48 */
.CodeMirror.mirrorsharp-connection-has-issue {
padding-top: 0;
}
.mirrorsharp-connection-has-issue .mirrorsharp-connection-issue {
display: none;
}
.offline-only {
display: none;
.code header & {
margin-left: 10px;
}
}
.root-status-offline {
.languages-and-modes {
display: none;
.mobile-compact({
/* if it's in menu, no reason to hide it */
display: block;
});
}
header .offline-only {
display: inline-block;
margin-top: auto;
margin-bottom: auto;
}
select {
opacity: 0.5;
pointer-events: none;
}
}

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

@ -1,6 +1,6 @@
{
"name": "_",
"version": "0.0.0",
"version": "0.0.0",
"private": true,
"devDependencies": {
"babel-core": "^6.17.0",
@ -12,21 +12,24 @@
"gulp-autoprefixer": "^3.1.0",
"gulp-clean-css": "^2.0.6",
"gulp-concat": "^2.6.0",
"gulp-debug": "^3.0.0",
"gulp-html-replace": "^1.5.5",
"gulp-less": "^3.0.5",
"gulp-load-plugins": "^1.2.2",
"gulp-mirror": "^1.0.0",
"gulp-noop": "^1.0.0",
"gulp-plumber": "^0.6.6",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.5.4",
"gulp-sourcemaps": "^1.6.0",
"gulp-svg2png": "^2.0.2",
"gulp-svg2png": "https://github.com/ashmind/gulp-svg2png.git#gh-36-temp",
"gulp-uglify": "^1.5.1",
"less-plugin-autoprefix": "^1.5.1",
"less-plugin-clean-css": "^1.5.1",
"multipipe": "^1.0.1",
"object-assign": "^4.0.1",
"svg2png": "3.0.0",
"rimraf": "^2.5.4",
"svg2png": "3.0.0",
"uglify-js": "https://github.com/mishoo/UglifyJS2#harmony",
"webpack-stream": "^3.1.0"
},