Skip to content
This repository has been archived by the owner on Aug 15, 2023. It is now read-only.

Commit

Permalink
complete loading and error message prompting
Browse files Browse the repository at this point in the history
  • Loading branch information
gifnksm committed May 30, 2015
1 parent 2de524c commit 9d6c39a
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 111 deletions.
2 changes: 2 additions & 0 deletions etc/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ div[data-nicovideo-thumbinfo-popup-content-type="nicovideo-thumbinfo"].content {
& {
width: 600px;
}
}

> div.video-data, > div.loading-message, > div.error-message {
> img.thumbnail {
float: left;
width: 130px;
Expand Down
4 changes: 2 additions & 2 deletions src/nico_thumbinfo/actions/NicoThumbinfoActionCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ class NicoThumbinfoActionCreator {
}

if (result instanceof ErrorInfo) {
if (result.errorCode === ErrorCode.Community &&
if (result.code === ErrorCode.Community &&
requestKey.type === VideoKey.Type.ThreadId) {
return new ErrorInfo(ErrorCode.CommunitySubThread, result.errorDetail);
return new ErrorInfo(ErrorCode.CommunitySubThread, result.detail);
}
return result;
}
Expand Down
139 changes: 106 additions & 33 deletions src/nico_thumbinfo/components/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {DataAttributeName} from "../../components/constants";

import {ThumbType} from "../models/constants";
import ErrorInfo from "../models/ErrorInfo";
import ErrorInfo, {ErrorCode} from "../models/ErrorInfo";
import VideoKey from "../models/VideoKey";

import VideoData from "../stores/VideoData";
Expand All @@ -21,6 +21,7 @@ import TagList from "./TagList";
import Description from "./Description";
import LastResBody from "./LastResBody";

import {Option, Some, None} from "option-t";
import * as React from "react";

module Base {
Expand Down Expand Up @@ -61,14 +62,7 @@ class Base extends React.Component<Base.Props, Base.State> {
this.props.store.removeChangeListener(this._onChange.bind(this));
}

private _renderLoadingMessage(): React.ReactNode {
const RD = React.DOM;
return RD.div(null, "loading...");
}

private _renderVideoData(videoData: VideoData): React.ReactNode {
const RD = React.DOM;

private _renderThumbnail(videoData: VideoData): React.ReactNode {
let deleted = videoData.thumbType.map(type => {
switch (type) {
case ThumbType.Deleted:
Expand All @@ -81,11 +75,27 @@ class Base extends React.Component<Base.Props, Base.State> {
return false;
}).unwrapOr(false);

return React.createElement(Thumbnail,
{url: videoData.thumbnailUrl,
deleted: deleted})
}

private _renderLoadingMessage(videoData: VideoData): React.ReactNode {
const RD = React.DOM;
return RD.div({className: "loading-message"},
this._renderThumbnail(videoData),
RD.h1({className: "title"},
"取得中: \"",
RD.a({href: videoData.watchUrl}, videoData.key.id),
"\"..."));
}

private _renderVideoData(videoData: VideoData): React.ReactNode {
const RD = React.DOM;

return RD.div(
{className: "video-data"},
React.createElement(Thumbnail,
{url: videoData.thumbnailUrl,
deleted: deleted}),
this._renderThumbnail(videoData),
React.createElement(HeaderList, {videoData: videoData}),
React.createElement(Title, {title: videoData.title,
watchUrl: videoData.watchUrl,
Expand All @@ -97,33 +107,98 @@ class Base extends React.Component<Base.Props, Base.State> {
React.createElement(LastResBody, {value: videoData.lastResBody}));
}

private _renderErrorMessage(errors: ErrorInfo[]): React.ReactNode {
private _renderErrorMessage(videoData: VideoData, errors: ErrorInfo[]): React.ReactNode {
const RD = React.DOM;
return RD.div(null, "error",
errors.map(e => `${e.errorCode}: ${e.errorDetail}`));
return RD.div({className: "error-message"},
this._renderThumbnail(videoData),
RD.h1({className: "title"},
"取得失敗: \"",
RD.a({href: videoData.watchUrl}, videoData.key.id),
"\""),
this._renderErrorSummary(errors));
}

render() {
private _errorCode2Summary(code: ErrorCode): string {
switch (code) {
case ErrorCode.UrlFetch:
return "APIの呼び出しに失敗";
case ErrorCode.HttpStatus:
return "APIのレスポンスコードが異常";
case ErrorCode.ServerMaintenance:
return "メンテナンスまたはサーバダウン";
case ErrorCode.Invalid:
return "APIのレスポンス内容が異常";
case ErrorCode.Deleted:
return "削除済み";
case ErrorCode.DeletedByUploader:
return "投稿者削除";
case ErrorCode.DeletedByAdmin:
return "利用規約違反削除";
case ErrorCode.DeletedByContentHolder:
return "権利者削除";
case ErrorCode.DeletedAsPrivate:
return "非表示";
case ErrorCode.AccessLocked:
return "APIアクセス過多";
case ErrorCode.Community:
return "コミュニティー動画"
case ErrorCode.CommunitySubThread:
return "コミュニティー動画(サブスレッド)"
case ErrorCode.NotFound:
return "動画が見つかりません";
case ErrorCode.NotLoggedIn:
return "ログインしていません";
case ErrorCode.Unknown:
return "未知のエラー";
default:
return "バグ";
}
}

private _renderErrorSummary(errors: ErrorInfo[]): React.ReactNode {
const RD = React.DOM;
let organizer = this.state.organizer;
let errors = organizer.getErrors();
let map = new Map<ErrorCode, ErrorInfo[]>();
for (let e of errors) {
let es = map.get(e.code);
if (es === undefined) {
es = [];
map.set(e.code, es);
}
es.push(e);
}

let list: string[] = [];
map.forEach((es, code) => {
let msg = this._errorCode2Summary(code);
let detail: string[] = [];
for (let e of es) {
if (e.detail !== undefined) {
detail.push(e.detail);
}
}
if (detail.length !== 0) {
msg = `${msg} (${detail.join(", ")})`;
}
list.push(msg)
})

let videoData: React.ReactNode = null;
let progressMessage: React.ReactNode = null;
let errorMessage: React.ReactNode = null;
return RD.ul({className: "error-summary"},
list.map(msg => RD.li(null, msg)));
}

let loading = false;
if (errors.length === 0 && organizer.videoData.isEmpty) {
loading = true;
}
render() {
const RD = React.DOM;
let organizer = this.state.organizer;
let content: React.ReactNode = null;

if (loading) {
progressMessage = this._renderLoadingMessage();
if (organizer.numStopped === 0) {
content = this._renderLoadingMessage(organizer.videoData);
} else {
if (organizer.videoData.isEmpty) {
errorMessage = this._renderErrorMessage(errors);
if (organizer.numCompleted === 0) {
content = this._renderErrorMessage(organizer.videoData,
organizer.getErrors());
} else {
videoData = this._renderVideoData(organizer.videoData);
content = this._renderVideoData(organizer.videoData);
}
}

Expand All @@ -132,9 +207,7 @@ class Base extends React.Component<Base.Props, Base.State> {
[DataAttributeName.PopupContent]: DataAttributeValue.PopupContent,
className: "content"
},
videoData,
progressMessage,
errorMessage
content
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/nico_thumbinfo/components/Thumbnail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Thumbnail extends React.Component<Thumbnail.Props, Thumbnail.State> {
attr.src = this.props.url.unwrap();
}
} else {
if (this.props.url.isNone) {
if (this.props.url.isNone || this.state.loadFailed) {
return null;
}
attr.src = this.props.url.unwrap();
Expand Down
14 changes: 7 additions & 7 deletions src/nico_thumbinfo/models/ErrorInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export const enum ErrorCode {
}

export default class ErrorInfo {
private _errorCode: ErrorCode;
private _errorDetail: string;
private _code: ErrorCode;
private _detail: string;

constructor(errorCode: ErrorCode, errorDetail?: string) {
this._errorCode = errorCode;
this._errorDetail = errorDetail;
constructor(code: ErrorCode, detail?: string) {
this._code = code;
this._detail = detail;
}

get errorCode() { return this._errorCode; }
get errorDetail() { return this._errorDetail; }
get code() { return this._code; }
get detail() { return this._detail; }
}
8 changes: 3 additions & 5 deletions src/nico_thumbinfo/models/RawVideoData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,15 @@ export default class RawVideoData {
this._dataSource = source;
}

static createWatchPage(key: VideoKey): RawVideoData {
return new RawVideoData(key, DataSource.WatchPage);
}

static createV3VideoArray(key: VideoKey): RawVideoData {
return new RawVideoData(key, DataSource.V3VideoArray);
}

static createGetThumbinfo(key: VideoKey): RawVideoData {
return new RawVideoData(key, DataSource.GetThumbinfo);
}
static createInitialDummy(key: VideoKey): RawVideoData {
return new RawVideoData(key, DataSource.InitialDummy);
}

get key() { return this._key; }
get dataSource() { return this._dataSource; }
Expand Down
5 changes: 3 additions & 2 deletions src/nico_thumbinfo/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"use strict";

export const enum DataSource {
// 優先度の高い順に並べること (RawVideoData のマージ時に使われる)
Merge,
WatchPage,
V3VideoArray,
GetThumbinfo
GetThumbinfo,
InitialDummy // Key のみから生成されたダミー情報
}

export const enum ThumbType {
Expand Down
6 changes: 4 additions & 2 deletions src/nico_thumbinfo/models/parser/V3VideoArrayParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ module V3VideoArrayParser {

let videoInfos = docElem.getElementsByTagName("video_info");
if (videoInfos.length === 0) {
return new ErrorInfo(ErrorCode.NotFound,
`XML Format Error: There is no "video_info" elements.`)
return new ErrorInfo(ErrorCode.NotFound);
}
if (videoInfos.length > 1) {
return new ErrorInfo(ErrorCode.Invalid,
Expand Down Expand Up @@ -347,6 +346,9 @@ module V3VideoArrayParser {
.mapOr(false, main_category => main_category === name);

tag.isCategory = new Some(isCategory);
if (isCategory) {
tag.isLocked = new Some(true); // カテゴリタグは必ずロックされている
}

return tag;
}
Expand Down
6 changes: 3 additions & 3 deletions src/nico_thumbinfo/stores/GetThumbinfoFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ export default class GetThumbinfoFetcher {

this._errorStack.set(payload);

if (payload.errorCode === ErrorCode.CommunitySubThread ||
payload.errorCode === ErrorCode.Deleted) {
if (payload.code === ErrorCode.CommunitySubThread ||
payload.code === ErrorCode.Deleted) {
// コミュニティ動画の場合、getflv の optional_thread_id により、
// 元動画の情報を取得できる可能性がある
// 削除済み動画の場合、getflv の deleted/error により、
Expand All @@ -188,7 +188,7 @@ export default class GetThumbinfoFetcher {
}

if (payload instanceof ErrorInfo) {
if (payload.errorCode !== ErrorCode.Unknown) {
if (payload.code !== ErrorCode.Unknown) {
this._errorStack.set(payload);
} else {
console.warn("Unknown getflv error:", action);
Expand Down
3 changes: 0 additions & 3 deletions src/nico_thumbinfo/stores/VideoData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,4 @@ export default class VideoData {
get watchUrl(): string {
return `http://www.nicovideo.jp/watch/${this._key.id}`;
}
get isEmpty(): boolean {
return this._rawData.length == 0;
}
}
28 changes: 27 additions & 1 deletion src/nico_thumbinfo/stores/VideoDataOrganizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,30 @@ import NicoThumbinfoAction from "../actions/NicoThumbinfoAction";

import ErrorInfo from "../models/ErrorInfo";
import VideoKey from "../models/VideoKey";
import RawVideoData from "../models/RawVideoData";

import {Option, Some, None} from "option-t";

export default class VideoDataOrganizer {
private _key: VideoKey;
private _videoData: VideoData;
private _initialDummy: RawVideoData;
private _getThumbinfoFetcher: GetThumbinfoFetcher;
private _v3VideoArrayFetcher: V3VideoArrayFetcher;

constructor(key: VideoKey) {
this._key = key;
this._videoData = new VideoData(key);
this._initialDummy = RawVideoData.createInitialDummy(key);
this._getThumbinfoFetcher = new GetThumbinfoFetcher(key);
this._v3VideoArrayFetcher = new V3VideoArrayFetcher(key);

if (key.type === VideoKey.Type.VideoId) {
let index = key.id.replace(/^../, "");
this._initialDummy.thumbnailUrl = new Some(`http://tn-skr.smilevideo.jp/smile?i=${index}`);
}

this._updateVideoData();
}

handleAction(action: NicoThumbinfoAction): boolean {
Expand All @@ -42,7 +54,19 @@ export default class VideoDataOrganizer {

get key() { return this._key; }
get videoData() { return this._videoData; }

get numStopped() { return this.numCompleted + this.numErrored; }
get numCompleted() {
let num = 0;
if (this._getThumbinfoFetcher.isCompleted) { num++; }
if (this._v3VideoArrayFetcher.isCompleted) { num++; }
return num;
}
get numErrored() {
let num = 0;
if (this._getThumbinfoFetcher.isErrored) { num++; }
if (this._v3VideoArrayFetcher.isErrored) { num++; }
return num;
}
getErrors() {
let errors: ErrorInfo[] = [];
this._getThumbinfoFetcher.errorInfo.map(e => errors.push(e));
Expand All @@ -53,6 +77,8 @@ export default class VideoDataOrganizer {
private _updateVideoData() {
this._videoData.clear();

this._videoData.pushRawVideoData(this._initialDummy);

this._getThumbinfoFetcher.videoData.map(videoData => {
this._videoData.pushRawVideoData(videoData);
});
Expand Down

0 comments on commit 9d6c39a

Please sign in to comment.