Merge pull request #1007 from mozilla/comp

converting some things to choo/component
This commit is contained in:
Danny Coates 2018-11-16 12:35:16 -08:00 committed by GitHub
commit 91a8c66e0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 373 additions and 396 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ dist
.nyc_output
.tox
.pytest_cache
*.iml
android/app/src/main/assets
ios/send-ios/assets/ios.js
ios/send-ios/assets/vendor.js

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -24,7 +24,7 @@ import html from 'choo/html';
import Raven from 'raven-js';
import assets from '../common/assets';
import header from '../app/ui/header';
import Header from '../app/ui/header';
import locale from '../common/locales';
import storage from '../app/storage';
import controller from '../app/controller';
@ -59,7 +59,7 @@ function body(main) {
>
<img src="${assets.get('preferences.png')}" />
</a>
${header(state, emit)} ${main(state, emit)}
${state.cache(Header, 'header').render()} ${main(state, emit)}
</body>
`;

View file

@ -1,196 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JVM 1.8" useProjectSettings="false">
<compilerSettings />
<compilerArguments>
<option name="destination" value="$MODULE_DIR$/build/tmp/kotlin-classes/debug" />
<option name="noStdlib" value="true" />
<option name="noReflect" value="true" />
<option name="moduleName" value="app_debug" />
<option name="jvmTarget" value="1.8" />
<option name="addCompilerBuiltIns" value="true" />
<option name="loadBuiltInsFromDependencies" value="true" />
<option name="languageVersion" value="1.2" />
<option name="apiVersion" value="1.2" />
<option name="pluginOptions">
<array>
<option value="plugin:org.jetbrains.kotlin.android:experimental=false" />
<option value="plugin:org.jetbrains.kotlin.android:enabled=true" />
<option value="plugin:org.jetbrains.kotlin.android:defaultCacheImplementation=hashMap" />
</array>
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-android-extensions/1.2.60/69596054ff0e04bb3f38cd906dce6854a9d3438d/kotlin-android-extensions-1.2.60.jar" />
</array>
</option>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debug/processDebugResources/r" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debugAndroidTest/processDebugAndroidTestResources/r" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotation_processor_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/apk_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/build-info" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-libraries" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/checkDebugClasspath" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/compatible_screen_manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_main_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_split_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javac" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/processed_res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shader_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/kotlin" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: net.java.dev.jna:jna-4.5.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:runner-1.0.2" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:27.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.2.61@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.squareup:javawriter:2.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-1.1.2" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.google.code.findbugs:jsr305:2.0.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.delight-im:Android-AdvancedWebView-v3.0.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-core-3.0.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-android:0.23.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment-27.1.1" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: junit:junit:4.12@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.61@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-core:1.3@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:monitor-1.0.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: org.mozilla.components:service-firefox-accounts-0.26.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-idling-resource-3.0.2" level="project" />
<orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-solver:1.1.2@jar" level="project" />
<orderEntry type="library" name="Gradle: org.mozilla.fxa_client:fxa_client-0.5.1" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core-common:0.23.4@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-library:1.3@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-integration:1.3@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:atomicfu-common:0.10.3@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.2.61@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: net.sf.kxml:kxml2:2.3.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" />
</component>
</module>

View file

@ -35,8 +35,7 @@ dependencies {
}
task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') {
commandLine 'node'
args '../generateAndLinkBundle.js'
commandLine './buildAssets.sh'
}
tasks.withType(JavaCompile) {

7
android/app/buildAssets.sh Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
npm run build
rm -rf src/main/assets
mkdir -p src/main/assets
cp -R ../../dist/* src/main/assets
sed -i '' 's/url(/url(\/android_asset/g' src/main/assets/app.*.css

View file

@ -1,10 +0,0 @@
const child_process = require('child_process');
const path = require('path');
child_process.execSync('npm run build');
child_process.execSync(
`cp -R ${path.resolve(__dirname, '../dist/*')} ${path.resolve(
__dirname,
'app/src/main/assets'
)}`
);

View file

@ -18,6 +18,7 @@ module.exports = function(state, emit) {
}
const archives = state.storage.files
.filter(archive => !archive.expired)
.map(archive => archiveTile(state, emit, archive))
.reverse();

View file

@ -1,5 +1,7 @@
import 'fast-text-encoding'; // MS Edge support
import 'fluent-intl-polyfill';
import choo from 'choo';
import nanotiming from 'nanotiming';
import routes from './routes';
import capabilities from './capabilities';
import locale from '../common/locales';
@ -14,7 +16,10 @@ import './main.css';
import User from './user';
(async function start() {
const app = routes();
const app = routes(choo());
if (process.env.NODE_ENV === 'production') {
nanotiming.disabled = true;
}
if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) {
Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install();
}

View file

@ -1,37 +1,8 @@
const choo = require('choo');
const html = require('choo/html');
const nanotiming = require('nanotiming');
const download = require('./ui/download');
const footer = require('./ui/footer');
const fxPromo = require('./ui/fxPromo');
const header = require('./ui/header');
const body = require('./ui/body');
nanotiming.disabled = true;
function banner(state, emit) {
if (state.promo && !state.route.startsWith('/unsupported/')) {
return fxPromo(state, emit);
}
}
function body(main) {
return function(state, emit) {
const b = html`<body class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest">
${banner(state, emit)}
${header(state, emit)}
${main(state, emit)}
${footer(state)}
</body>`;
if (state.layout) {
// server side only
return state.layout(state, b);
}
return b;
};
}
module.exports = function() {
const app = choo();
module.exports = function(app = choo()) {
app.route('/', body(require('./ui/home')));
app.route('/download/:id', body(download));
app.route('/download/:id/:key', body(download));

View file

@ -1,3 +1,5 @@
import assets from '../common/assets';
import { version } from '../package.json';
import Keychain from './keychain';
import { downloadStream } from './api';
import { transformStream } from './streams';
@ -8,11 +10,11 @@ let noSave = false;
const map = new Map();
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(precache());
});
self.addEventListener('activate', event => {
self.clients.claim();
event.waitUntil(self.clients.claim());
});
async function decryptStream(id) {
@ -77,11 +79,32 @@ async function decryptStream(id) {
}
}
async function precache() {
const oldCaches = await caches.keys();
for (const c of oldCaches) {
if (c !== version) {
await caches.delete(c);
}
}
const cache = await caches.open(version);
const images = assets.match(/.*\.(png|svg|jpg)$/);
await cache.addAll(images);
return self.skipWaiting();
}
async function cachedOrFetch(req) {
const cache = await caches.open(version);
const cached = await cache.match(req);
return cached || fetch(req);
}
self.onfetch = event => {
const req = event.request;
const match = /\/api\/download\/([A-Fa-f0-9]{4,})/.exec(req.url);
if (match) {
event.respondWith(decryptStream(match[1]));
} else {
event.respondWith(cachedOrFetch(req));
}
};

View file

@ -1,58 +1,103 @@
const html = require('choo/html');
const Component = require('choo/component');
module.exports = function(state, emit) {
if (!state.capabilities.account) {
return null;
class Account extends Component {
constructor(name, state, emit) {
super(name);
this.state = state;
this.emit = emit;
this.enabled = state.capabilities.account;
this.local = state.components[name] = {};
this.setState();
}
const user = state.user;
if (!user.loggedIn) {
return html`<button
class="p-2 border rounded border-white text-white hover:bg-white hover:text-blue md:text-blue md:border-blue md:hover:text-white md:hover:bg-blue"
onclick=${login}>
${state.translate('signInMenuOption')}
</button>`;
}
return html`<div class="relative h-8">
<input
type="image"
alt="${user.email}"
class="w-8 h-8 rounded-full text-white"
src="${user.avatar}"
onclick=${avatarClick}/>
<ul
id="accountMenu"
class="invisible list-reset absolute pin-t pin-r mt-10 pt-2 pb-2 bg-white shadow-md whitespace-no-wrap outline-none z-50"
onblur="${hideMenu}"
tabindex="-1">
<li class="p-2 text-grey-dark">${user.email}</li>
<li>
<a class="block px-4 py-2 text-grey-darkest hover:bg-blue hover:text-white cursor-pointer" onclick=${logout}>
${state.translate('logOut')}
</a>
</li>
</ul>
</div>`;
function avatarClick(event) {
avatarClick(event) {
event.preventDefault();
const menu = document.getElementById('accountMenu');
menu.classList.toggle('invisible');
menu.focus();
}
function hideMenu(event) {
hideMenu(event) {
event.stopPropagation();
const menu = document.getElementById('accountMenu');
menu.classList.add('invisible');
}
function login(event) {
login(event) {
event.preventDefault();
emit('login');
this.emit('login');
}
function logout(event) {
logout(event) {
event.preventDefault();
emit('logout');
this.emit('logout');
}
};
changed() {
return this.local.loggedIn !== this.state.user.loggedIn;
}
setState() {
const changed = this.changed();
if (changed) {
this.local.loggedIn = this.state.user.loggedIn;
}
return changed;
}
update() {
return this.setState();
}
createElement() {
if (!this.enabled) {
return html`
<div></div>
`;
}
const user = this.state.user;
const translate = this.state.translate;
if (!this.local.loggedIn) {
return html`
<div>
<button
class="p-2 border rounded border-white text-white hover:bg-white hover:text-blue md:text-blue md:border-blue md:hover:text-white md:hover:bg-blue"
onclick="${e => this.login(e)}"
>
${translate('signInMenuOption')}
</button>
</div>
`;
}
return html`
<div class="relative h-8">
<input
type="image"
alt="${user.email}"
class="w-8 h-8 rounded-full text-white"
src="${user.avatar}"
onclick="${e => this.avatarClick(e)}"
/>
<ul
id="accountMenu"
class="invisible list-reset absolute pin-t pin-r mt-10 pt-2 pb-2 bg-white shadow-md whitespace-no-wrap outline-none z-50"
onblur="${e => this.hideMenu(e)}"
tabindex="-1"
>
<li class="p-2 text-grey-dark">${user.email}</li>
<li>
<a
class="block px-4 py-2 text-grey-darkest hover:bg-blue hover:text-white cursor-pointer"
onclick="${e => this.logout(e)}"
>
${translate('logOut')}
</a>
</li>
</ul>
</div>
`;
}
}
module.exports = Account;

28
app/ui/body.js Normal file
View file

@ -0,0 +1,28 @@
const html = require('choo/html');
const Promo = require('./promo');
const Header = require('./header');
const Footer = require('./footer');
function banner(state) {
if (state.promo && !state.route.startsWith('/unsupported/')) {
return state.cache(Promo, 'promo').render();
}
}
module.exports = function body(main) {
return function(state, emit) {
const b = html`
<body
class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest"
>
${banner(state, emit)} ${state.cache(Header, 'header').render()}
${main(state, emit)} ${state.cache(Footer, 'footer').render()}
</body>
`;
if (state.layout) {
// server side only
return state.layout(state, b);
}
return b;
};
};

View file

@ -1,52 +1,74 @@
const html = require('choo/html');
const Component = require('choo/component');
const version = require('../../package.json').version;
const { browserName } = require('../utils');
module.exports = function(state) {
const browser = browserName();
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
const footer = html`<footer class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 font-medium text-xs text-grey-dark md:items-center justify-between bg-grey-lightest">
<a class="mozilla-logo pb-10 md:pb-0 m-2"
href="https://www.mozilla.org/">
Mozilla
</a>
<ul class="list-reset flex flex-col md:flex-row items-start md:items-center md:justify-end">
<li class="m-2"><a
href="https://www.mozilla.org/about/legal">
${state.translate('footerLinkLegal')}
</a></li>
<li class="m-2"><a
href="/legal">
${state.translate('footerLinkTerms')}
</a></li>
<li class="m-2"><a
href="https://www.mozilla.org/privacy/websites/#cookies">
${state.translate('footerLinkCookies')}
</a></li>
<li class="m-2"><a
href="https://www.mozilla.org/about/legal/report-infringement/">
${state.translate('reportIPInfringement')}
</a></li>
<li class="m-2"><a
href="https://github.com/mozilla/send">GitHub
</a></li>
<li class="m-2"><a
href="https://twitter.com/FxTestPilot">Twitter
</a></li>
<li class="m-2"><a href="${feedbackUrl}"
rel="noreferrer noopener"
class="feedback-link"
alt="Feedback"
target="_blank">
${state.translate('siteFeedback')}
</a></li>
</ul>
</footer>`;
// HACK
// We only want to render this once because we
// toggle the targets of the links with utils/openLinksInNewTab
footer.isSameNode = function(target) {
return target && target.nodeName && target.nodeName === 'FOOTER';
};
return footer;
};
class Footer extends Component {
constructor(name, state) {
super(name);
this.state = state;
}
update() {
return false;
}
createElement() {
const translate = this.state.translate;
const browser = browserName();
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
return html`
<footer
class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 font-medium text-xs text-grey-dark md:items-center justify-between bg-grey-lightest"
>
<a
class="mozilla-logo pb-10 md:pb-0 m-2"
href="https://www.mozilla.org/"
>
Mozilla
</a>
<ul
class="list-reset flex flex-col md:flex-row items-start md:items-center md:justify-end"
>
<li class="m-2">
<a href="https://www.mozilla.org/about/legal">
${translate('footerLinkLegal')}
</a>
</li>
<li class="m-2">
<a href="/legal"> ${translate('footerLinkTerms')} </a>
</li>
<li class="m-2">
<a href="https://www.mozilla.org/privacy/websites/#cookies">
${translate('footerLinkCookies')}
</a>
</li>
<li class="m-2">
<a href="https://www.mozilla.org/about/legal/report-infringement/">
${translate('reportIPInfringement')}
</a>
</li>
<li class="m-2">
<a href="https://github.com/mozilla/send">GitHub </a>
</li>
<li class="m-2">
<a href="https://twitter.com/FxTestPilot">Twitter </a>
</li>
<li class="m-2">
<a
href="${feedbackUrl}"
rel="noreferrer noopener"
class="feedback-link"
alt="Feedback"
target="_blank"
>
${translate('siteFeedback')}
</a>
</li>
</ul>
</footer>
`;
}
}
module.exports = Footer;

View file

@ -1,19 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function() {
return html`
<div class="w-full flex-none flex flex-row items-center content-center justify-center text-sm bg-grey-light leading-tight text-grey-darkest px-4 py-3">
<div class="flex items-center mx-auto">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="w-6"
alt="Firefox"/>
<span class="ml-3">Send is brought to you by the all-new Firefox.
<a
class="text-blue"
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com">Download Firefox now </a>
</span>
</div>
</div>`;
};

View file

@ -1,25 +1,34 @@
const html = require('choo/html');
const account = require('./account');
const Component = require('choo/component');
const Account = require('./account');
module.exports = function(state, emit) {
const header = html`
<header class="relative flex-none flex flex-row items-center justify-between bg-blue md:bg-white w-full px-6 h-16 md:shadow z-20">
<a
class="header-logo"
href="/">
<h1 class="text-white md:text-black font-normal">Firefox <b>Send</b></h1>
</a>
${account(state, emit)}
<div class="invisible absolute pin-t pin-l mt-12 w-full flex flex-col items-center pointer-events-none">
<div class="border rounded bg-grey-darkest text-white mt-2 p-2">Your upload has finished.<button class="border border-blue rounded-sm bg-blue text-white inline-block p-1 ml-2">Copy Link</button><button class="text-white inline-block p-1 ml-2"></button></div>
${state.toast ? state.toast() : ''}
</div>
</header>`;
// HACK
// We only want to render this once because we
// toggle the targets of the links with utils/openLinksInNewTab
// header.isSameNode = function(target) {
// return target && target.nodeName && target.nodeName === 'HEADER';
// };
return header;
};
class Header extends Component {
constructor(name, state, emit) {
super(name);
this.state = state;
this.emit = emit;
this.account = state.cache(Account, 'account');
}
update() {
this.account.render();
return false;
}
createElement() {
return html`
<header
class="relative flex-none flex flex-row items-center justify-between bg-blue md:bg-white w-full px-6 h-16 md:shadow z-20"
>
<a class="header-logo" href="/">
<h1 class="text-white md:text-black font-normal">
Firefox <b>Send</b>
</h1>
</a>
${this.account.render()}
</header>
`;
}
}
module.exports = Header;

View file

@ -5,9 +5,9 @@ const modal = require('./modal');
const intro = require('./intro');
module.exports = function(state, emit) {
const archives = state.storage.files.map(archive =>
archiveTile(state, emit, archive)
);
const archives = state.storage.files
.filter(archive => !archive.expired)
.map(archive => archiveTile(state, emit, archive));
let left = '';
if (state.uploading) {
left = archiveTile.uploading(state, emit);
@ -23,11 +23,12 @@ module.exports = function(state, emit) {
: list(archives, 'list-reset h-full overflow-y-scroll', 'mb-3');
return html`
<main class="main relative">
${state.modal && modal(state, emit)}
<section class="h-full w-full p-6 md:flex md:flex-row z-10">
<div class="md:mr-6 md:w-1/2">${left}</div>
<div class="md:w-1/2 mt-6 md:mt-0">${right}</div>
</section>
</main>`;
<main class="main relative">
${state.modal && modal(state, emit)}
<section class="h-full w-full p-6 md:flex md:flex-row z-10">
<div class="md:mr-6 md:w-1/2">${left}</div>
<div class="md:w-1/2 mt-6 md:mt-0">${right}</div>
</section>
</main>
`;
};

39
app/ui/promo.js Normal file
View file

@ -0,0 +1,39 @@
const html = require('choo/html');
const Component = require('choo/component');
const assets = require('../../common/assets');
class Promo extends Component {
constructor(name) {
super(name);
}
update() {
return false;
}
createElement() {
return html`
<div
class="w-full flex-none flex flex-row items-center content-center justify-center text-sm bg-grey-light leading-tight text-grey-darkest px-4 py-3"
>
<div class="flex items-center mx-auto">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="w-6"
alt="Firefox"
/>
<span class="ml-3"
>Send is brought to you by the all-new Firefox.
<a
class="text-blue"
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com"
>Download Firefox now </a
>
</span>
</div>
</div>
`;
}
}
module.exports = Promo;

View file

@ -184,8 +184,17 @@ async function streamToArrayBuffer(stream, size) {
}
function list(items, ulStyle = '', liStyle = '') {
const lis = items.map(i => html`<li class="${liStyle}">${i}</li>`);
return html`<ul class="${ulStyle}">${lis}</ul>`;
const lis = items.map(
i =>
html`
<li class="${liStyle}">${i}</li>
`
);
return html`
<ul class="${ulStyle}">
${lis}
</ul>
`;
}
function secondsToL10nId(seconds) {
@ -199,6 +208,9 @@ function secondsToL10nId(seconds) {
}
function timeLeft(milliseconds) {
if (milliseconds < 1) {
return { id: 'linkExpiredAlt' };
}
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

View file

@ -34,7 +34,56 @@ const serviceWorker = {
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
devtool: 'source-map'
devtool: 'source-map',
module: {
rules: [
{
include: [require.resolve('./assets/cryptofill')],
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.(png|jpg)$/,
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]'
}
},
{
test: /\.svg$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]'
}
},
{
loader: 'svgo-loader',
options: {
plugins: [
{ removeViewBox: false }, // true causes stretched images
{ convertStyleToAttrs: true }, // for CSP, no unsafe-eval
{ removeTitle: true } // for smallness
]
}
}
]
},
{
// loads all assets from assets/ for use by common/assets.js
test: require.resolve('./build/generate_asset_map.js'),
use: ['babel-loader', 'val-loader']
}
]
},
plugins: [new webpack.IgnorePlugin(/\.\.\/dist/)]
};
const web = {
@ -185,6 +234,7 @@ const web = {
from: '*.*'
}
]),
new webpack.EnvironmentPlugin(['NODE_ENV']),
new webpack.IgnorePlugin(/\.\.\/dist/), // used in common/*.js
new webpack.IgnorePlugin(/require-from-string/), // used in common/locales.js
new webpack.HashedModuleIdsPlugin(),