Commit d85b70e3 by Lee Jaebin

Merge branch 'features/1.0.500' into feature/1.0.500_34190

# Conflicts:
#	ABVJE_BL/src/jp/agentec/abook/abv/bl/acms/client/AcmsClient.java
#	ABVJE_BL/src/jp/agentec/abook/abv/bl/acms/type/AcmsApis.java
#	ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/appinfo/AppDefType.java
#	ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/home/activity/OperationListActivity.java
parents 10472ee2 471da81d
...@@ -23,6 +23,7 @@ import jp.agentec.abook.abv.bl.acms.client.json.GroupsJSON; ...@@ -23,6 +23,7 @@ import jp.agentec.abook.abv.bl.acms.client.json.GroupsJSON;
import jp.agentec.abook.abv.bl.acms.client.json.LogSendFlagJSON; import jp.agentec.abook.abv.bl.acms.client.json.LogSendFlagJSON;
import jp.agentec.abook.abv.bl.acms.client.json.NewAppStoreLoginJSON; import jp.agentec.abook.abv.bl.acms.client.json.NewAppStoreLoginJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationGroupMasterJSON; import jp.agentec.abook.abv.bl.acms.client.json.OperationGroupMasterJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ApertureMasterDataJSON;
import jp.agentec.abook.abv.bl.acms.client.json.RequirePasswordChangeJSON; import jp.agentec.abook.abv.bl.acms.client.json.RequirePasswordChangeJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ResultJSON; import jp.agentec.abook.abv.bl.acms.client.json.ResultJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ServerTimeZoneJSON; import jp.agentec.abook.abv.bl.acms.client.json.ServerTimeZoneJSON;
...@@ -42,6 +43,7 @@ import jp.agentec.abook.abv.bl.acms.client.parameters.ContentReadingLogParameter ...@@ -42,6 +43,7 @@ import jp.agentec.abook.abv.bl.acms.client.parameters.ContentReadingLogParameter
import jp.agentec.abook.abv.bl.acms.client.parameters.EnqueteReplyParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.EnqueteReplyParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.EnterpriseLoginParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.EnterpriseLoginParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.EnterpriseNewLoginParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.EnterpriseNewLoginParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetApertureMasterDataParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetContentParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.GetContentParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetEnqueteReplyParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.GetEnqueteReplyParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetOperationDataParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.GetOperationDataParameters;
...@@ -716,6 +718,19 @@ public class AcmsClient implements AcmsClientResponseListener { ...@@ -716,6 +718,19 @@ public class AcmsClient implements AcmsClientResponseListener {
return json; return json;
} }
/**
* ACMSから、絞り検索マスタデータ情報を取得します。
* @param param {@link AcmsParameters} オブジェクトです。
* @return 絞り検索マスタデータを格納したリストを返します。
* @throws NetworkDisconnectedException
* @throws AcmsException
*/
public ApertureMasterDataJSON getApertureMasterData(GetApertureMasterDataParameters param) throws NetworkDisconnectedException, AcmsException {
HttpResponse response = send(AcmsApis.ApiGetApertureMasterData, param);
return new ApertureMasterDataJSON(response.httpResponseBody);
}
/**********************************************************************************************/ /**********************************************************************************************/
/** 以下、共用メソッド---------------------------------------------------------------------- **/ /** 以下、共用メソッド---------------------------------------------------------------------- **/
/**********************************************************************************************/ /**********************************************************************************************/
......
package jp.agentec.abook.abv.bl.acms.client.json;
import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.JSONValidationException;
import org.json.adf.JSONArray;
import org.json.adf.JSONObject;
/**
* {@link AcmsClient#contentVersion} の戻り値です。
* @author Taejin Hong
* @version 1.0.0
*/
public class ApertureMasterDataJSON extends AcmsCommonJSON {
private static final String apertureLastEditDate = "apertureLastEditDate";
private static final String ApertureData = "apertureData";
public String lastEditDate;
public JSONObject apertureData;
public ApertureMasterDataJSON(String jsonString) throws AcmsException {
super(jsonString);
}
@Override
protected void parse(JSONObject json) throws JSONValidationException {
// 絞り検索の日付を取得
if(json.has(apertureLastEditDate)) {
lastEditDate = json.getString(apertureLastEditDate);
}
// 絞り検索のデータを取得
if (json.has(ApertureData)) {
JSONArray apertureList = json.getJSONArray(ApertureData);
for (int i = 0; i < apertureList.length(); i++) {
apertureData = apertureList.getJSONObject(i);
}
}
}
}
package jp.agentec.abook.abv.bl.acms.client.parameters;
/**
* Created by leej on 2018/09/10.
*/
public class GetApertureMasterDataParameters extends AcmsParameters {
public GetApertureMasterDataParameters(String sid) {
super(sid);
}
}
\ No newline at end of file
...@@ -154,6 +154,8 @@ public class AcmsApis { ...@@ -154,6 +154,8 @@ public class AcmsApis {
public static final String ApiSendRoutineTaskData = "routineTaskData"; public static final String ApiSendRoutineTaskData = "routineTaskData";
// 作業種別データ取得 // 作業種別データ取得
public static final String ApiOperationGroupMaster = "operationGroupMaster"; public static final String ApiOperationGroupMaster = "operationGroupMaster";
// 絞り検索マスタデータ取得
public static final String ApiGetApertureMasterData = "getApertureMasterData";
// download // download
/** /**
...@@ -197,7 +199,8 @@ public class AcmsApis { ...@@ -197,7 +199,8 @@ public class AcmsApis {
apiValue = Constant.ApiValue.nuapi; apiValue = Constant.ApiValue.nuapi;
} else if (methodName.equals(ApiOperationList) || methodName.equals(ApiWorkingGroupList) || methodName.equals(ApiSendTaskData) || methodName.equals(ApiGetOperationData) || } else if (methodName.equals(ApiOperationList) || methodName.equals(ApiWorkingGroupList) || methodName.equals(ApiSendTaskData) || methodName.equals(ApiGetOperationData) ||
methodName.equals(ApiGetTaskFile) || methodName.equals(ApiSceneEntry) || methodName.equals(ApiTaskContentEntry) || methodName.equals(ApiGetTaskFile) || methodName.equals(ApiSceneEntry) || methodName.equals(ApiTaskContentEntry) ||
methodName.equals(ApiSendPushMessage) || methodName.equals(ApiGetPushMessages) || methodName.equals(ApiSendRoutineTaskData) || methodName.equals(ApiOperationGroupMaster)) { methodName.equals(ApiSendPushMessage) || methodName.equals(ApiGetPushMessages) || methodName.equals(ApiSendRoutineTaskData) ||
methodName.equals(ApiOperationGroupMaster) || methodName.equals(ApiGetApertureMasterData)) {
apiValue = Constant.ApiValue.checkapi; apiValue = Constant.ApiValue.checkapi;
} }
......
...@@ -180,6 +180,13 @@ public class ABVEnvironment { ...@@ -180,6 +180,13 @@ public class ABVEnvironment {
public boolean enableToastMessage = true; public boolean enableToastMessage = true;
// 絞り検索マスタ参照パス
public static final String ApertureMasterDataDirFormat = "%s/ABook/operation/apertureMaster";
// 絞り検索マスタファイル名
public static final String ApertureMasterDataFileName = "apertureMaster.json";
private ABVEnvironment() { private ABVEnvironment() {
} }
...@@ -784,4 +791,11 @@ public class ABVEnvironment { ...@@ -784,4 +791,11 @@ public class ABVEnvironment {
return reportDate; return reportDate;
} }
} }
// MasterDataに対したJSONファイルの位置
// 経路:root/files/operation/apertureMaster
public String getMasterFilePath() {
return String.format(ApertureMasterDataDirFormat, rootDirectory);
}
} }
...@@ -123,4 +123,9 @@ public class ABookKeys { ...@@ -123,4 +123,9 @@ public class ABookKeys {
// taskReport.Jsonに一時保存フラグを保持するためのキー // taskReport.Jsonに一時保存フラグを保持するためのキー
public static final String LOCAL_SAVE_FLG = "localSaveFlg"; public static final String LOCAL_SAVE_FLG = "localSaveFlg";
// 編集画面を
public static final String CMD_EDIT_ATTACHED = "editAttached"; //再編集のためのcmdパラメタ
public static final String EDITABLE = "editable"; //commonAttachedDataUrl()で編集可否を判別するパラメタをWebからもらう。
public static final String FILE_PATH = "filePath"; //再編集する場合、クリックしたイマージのパスのパラメタ
} }
...@@ -39,6 +39,10 @@ public class ABVDataCache { ...@@ -39,6 +39,10 @@ public class ABVDataCache {
private Date lastPresentTime = DateTimeUtil.getCurrentDate(); // 最後の通信時に取得したサーバの時間 private Date lastPresentTime = DateTimeUtil.getCurrentDate(); // 最後の通信時に取得したサーバの時間
private static final int SEVER_ALERT_INTERVAL = 30; //システム日付チェック前後期間(分) private static final int SEVER_ALERT_INTERVAL = 30; //システム日付チェック前後期間(分)
// Serverから取得したapertureMasterDataのfetchDateを一時的に保存するための変数
private String tempApertureMasterDataFetchDate = null;
/** /**
* 未指定ジャンルのID * 未指定ジャンルのID
* @since 1.0.0 * @since 1.0.0
...@@ -233,6 +237,8 @@ public class ABVDataCache { ...@@ -233,6 +237,8 @@ public class ABVDataCache {
defaultCategoryId = -1; defaultCategoryId = -1;
defaultGroupId = -1; defaultGroupId = -1;
urlPath = null; urlPath = null;
// 絞り検索の取得日付を初期化
tempApertureMasterDataFetchDate = null;
} }
public void setLastPresentTime(Date lasttime) { public void setLastPresentTime(Date lasttime) {
...@@ -371,4 +377,20 @@ public class ABVDataCache { ...@@ -371,4 +377,20 @@ public class ABVDataCache {
} }
return null; return null;
} }
/**
* tempApertureMasterDataFetchDateを更新する
* @param date
*/
public void setTempApertureMasterDataFetchDate(String date){
this.tempApertureMasterDataFetchDate = date;
}
/**
* tempApertureMasterDataFetchDateをreturn
* @return tempApertureMasterDataFetchDate
*/
public String getTempApertureMasterDataFetchDate(){
return tempApertureMasterDataFetchDate;
}
} }
...@@ -23,6 +23,7 @@ import jp.agentec.abook.abv.bl.data.dao.ContentDao; ...@@ -23,6 +23,7 @@ import jp.agentec.abook.abv.bl.data.dao.ContentDao;
import jp.agentec.abook.abv.bl.dto.ContentDto; import jp.agentec.abook.abv.bl.dto.ContentDto;
import jp.agentec.abook.abv.bl.dto.EnqueteDto; import jp.agentec.abook.abv.bl.dto.EnqueteDto;
import jp.agentec.abook.abv.bl.logic.AbstractLogic; import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.ApertureMasterDataLogic;
import jp.agentec.abook.abv.bl.logic.CategoryLogic; import jp.agentec.abook.abv.bl.logic.CategoryLogic;
import jp.agentec.abook.abv.bl.logic.ContentLogic; import jp.agentec.abook.abv.bl.logic.ContentLogic;
import jp.agentec.abook.abv.bl.logic.ContentReadingLogLogic; import jp.agentec.abook.abv.bl.logic.ContentReadingLogLogic;
...@@ -63,6 +64,11 @@ public class ContentRefresher { ...@@ -63,6 +64,11 @@ public class ContentRefresher {
private static final int refreshSleepInterval = 500; private static final int refreshSleepInterval = 500;
private static final int maxRefreshWorker = 2; private static final int maxRefreshWorker = 2;
// masterDataを取得するため登録
private ApertureMasterDataLogic apertureMasterDataLogic = AbstractLogic.getLogic(ApertureMasterDataLogic.class);
public static ContentRefresher getInstance() { public static ContentRefresher getInstance() {
if (instance == null) { if (instance == null) {
synchronized (ContentRefresher.class) { synchronized (ContentRefresher.class) {
...@@ -156,6 +162,13 @@ public class ContentRefresher { ...@@ -156,6 +162,13 @@ public class ContentRefresher {
contractLogic.initializeContractServiceOption(); // サービスオプション関連処理 contractLogic.initializeContractServiceOption(); // サービスオプション関連処理
groupLogic.initializeGroups(); // グループ設定(グループ変更の場合、FetchDateをクリアする) groupLogic.initializeGroups(); // グループ設定(グループ変更の場合、FetchDateをクリアする)
categoryLogic.initializeCategories(); // カテゴリ設定 categoryLogic.initializeCategories(); // カテゴリ設定
// 絞り検索マスタデータの最新更新された時のFetchDateを一時に保存する。
Logger.d(TAG, "before fetchDate : " + ABVDataCache.getInstance().getTempApertureMasterDataFetchDate());
// CMSでメンテナンスされる絞り検索マスタデータをアプリから取得できるようにJSONファイルを生成する。
apertureMasterDataLogic.initializeApertureMasterData(ABVDataCache.getInstance().getTempApertureMasterDataFetchDate());
Logger.d(TAG, "after fetchDate : " + ABVDataCache.getInstance().getTempApertureMasterDataFetchDate());
if (interrupt) { // この時点で停止要求が来た場合先には進まない。(ServiceOption/Group/Categoryの更新は1セットで行う(トランザクションはそれぞれ別)) if (interrupt) { // この時点で停止要求が来た場合先には進まない。(ServiceOption/Group/Categoryの更新は1セットで行う(トランザクションはそれぞれ別))
Logger.d(TAG, "stop refresh worker before content update."); Logger.d(TAG, "stop refresh worker before content update.");
......
package jp.agentec.abook.abv.bl.logic;
import org.json.adf.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.ApertureMasterDataJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetApertureMasterDataParameters;
import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.adf.util.DateTimeFormat;
import jp.agentec.adf.util.DateTimeUtil;
import jp.agentec.adf.util.FileUtil;
public class ApertureMasterDataLogic extends AbstractLogic {
private static final java.lang.String TAG = "RefineMasterDataLogic";
/**
* グループ情報をサーバから受信し、ローカルに保存します。既存のデータは上書きされます。また、サーバにないグループがローカルにある場合、そのグループは削除されます。
* 絞り検索マスタデータ情報をサーバから受信し、ローカルにJSONで保存する。
* 日付を比べてサーバーからローカルのデータより最新のデータを受信した場合、受信した情報を保存する。
* ローカルの日付はない場合、受信した情報を保存する。
* @param lastFetchDateString
*/
public void initializeApertureMasterData(String lastFetchDateString) {
try {
GetApertureMasterDataParameters param = new GetApertureMasterDataParameters(cache.getMemberInfo().sid);
//サーバーから絞り検索マスタデータを取得
ApertureMasterDataJSON masterDataJson = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).getApertureMasterData(param);
//サーバーデータの日付
String lastEditDateString = masterDataJson.lastEditDate;
if(lastFetchDateString != null) {
Date lastFetchDate = DateTimeUtil.toDate(lastFetchDateString, "UTC", DateTimeFormat.yyyyMMddHHmmss_hyphen);
Date lastEditDate = DateTimeUtil.toDate(lastEditDateString, "UTC", DateTimeFormat.yyyyMMddHHmmss_hyphen);
if (lastFetchDate == null || lastEditDate.after(lastFetchDate)) {
createApertureMasterDataJson(masterDataJson.apertureData);
cache.setTempApertureMasterDataFetchDate(lastEditDateString);
}
}
else{
createApertureMasterDataJson(masterDataJson.apertureData);
cache.setTempApertureMasterDataFetchDate(lastEditDateString);
}
} catch (Exception e) {
Logger.e(TAG, "apertureMasterDataSend error : ", e);
}
}
/**
* apertureMaster.jsonファイル作成
* @param json
* @throws IOException
*/
private void createApertureMasterDataJson(JSONObject json) throws IOException {
// マスタデータの保存されるパス
String masterDataJsonPath = ABVEnvironment.getInstance().getMasterFilePath() + File.separator + ABVEnvironment.getInstance().ApertureMasterDataFileName;
FileUtil.createFile(masterDataJsonPath, json.toString());
}
}
...@@ -839,6 +839,9 @@ public class OperationLogic extends AbstractLogic { ...@@ -839,6 +839,9 @@ public class OperationLogic extends AbstractLogic {
jsonObject.put("attachedPath", "../../../../files/ABook/operation/" + operationId); jsonObject.put("attachedPath", "../../../../files/ABook/operation/" + operationId);
jsonObject.put("attachedMoviePath", ABVEnvironment.getInstance().getAttachedMoviesFilePath(contentId)); jsonObject.put("attachedMoviePath", ABVEnvironment.getInstance().getAttachedMoviesFilePath(contentId));
// 絞り検索マスタデータのパス
jsonObject.put("masterPath",ABVEnvironment.getInstance().getMasterFilePath() + File.separator + ABVEnvironment.getInstance().ApertureMasterDataFileName);
FileUtil.createFile(contentPath + "/content.json", jsonObject.toString()); FileUtil.createFile(contentPath + "/content.json", jsonObject.toString());
} }
......
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; }
.drawing-board-utils-hidden, .drawing-board-controls-hidden { display: none !important; }
.drawing-board { position: relative; display: block; }
.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; }
.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; }
.drawing-board-canvas { cursor: crosshair; z-index: 20; }
.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; }
.drawing-board-control > button, .drawing-board-control-colors-rainbows, .drawing-board-control-size .drawing-board-control-inner, .drawing-board-control-size-dropdown { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; overflow: hidden; border: none; background-color: #eee; padding: 2px 4px; border: 1px solid #ccc; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); height: 28px; }
.drawing-board-control > button { cursor: pointer; min-width: 28px; line-height: 14px; }
.drawing-board-control > button:hover, .drawing-board-control > button:focus { background-color: #ddd; }
.drawing-board-control > button:active, .drawing-board-control > button.active { box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); background-color: #ddd; }
.drawing-board-control > button[disabled] { color: gray; }
.drawing-board-control > button[disabled]:hover, .drawing-board-control > button[disabled]:focus, .drawing-board-control > button[disabled]:active, .drawing-board-control > button[disabled].active { background-color: #eee; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); cursor: default; }
/*.drawing-board-controls { margin: 0 auto; text-align: center; font-size: 0; display: table; border-spacing: 9.33333px 0; position: relative; min-height: 28px; }*/
.drawing-board-controls { margin: 0 auto; text-align: center; font-size: 0; display: table; border-spacing: 4px 0; position: relative; min-height: 28px; }
.drawing-board-controls[data-align="left"] { margin: 0; left: -9.33333px; }
.drawing-board-controls[data-align="right"] { margin: 0 0 0 auto; right: -9.33333px; }
.drawing-board-canvas-wrapper + .drawing-board-controls, .drawing-board-controls + .drawing-board-canvas-wrapper { margin-top: 5px; }
.drawing-board-controls-hidden { height: 0; min-height: 0; padding: 0; margin: 0; border: 0; }
.drawing-board-control { display: table-cell; border-collapse: separate; vertical-align: middle; font-size: 16px; height: 100%; }
.drawing-board-control-inner { position: relative; height: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
.drawing-board-control > button { margin: 0; vertical-align: middle; }
.drawing-board-control-colors { font-size: 0; line-height: 0; }
.drawing-board-control-colors-current { border: 1px solid #ccc; cursor: pointer; display: inline-block; width: 26px; height: 26px; }
/*.drawing-board-control-colors-rainbows { display: inline-block; margin-left: 5px; position: absolute; left: 0; top: 33px; margin-left: 0; z-index: 100; width: 250px; height: auto; padding: 4px; }*/
.drawing-board-control-colors-rainbows { display: inline-block; margin-left: 5px; position: absolute; left: 0; top: 33px; margin-left: 0; z-index: 100; width: 180px; height: 42px; padding: 4px; }
/*.drawing-board-control-colors-rainbow { height: 18px; }*/
.drawing-board-control-colors-rainbow { height: 42px; }
/*.drawing-board-control-colors-picker:first-child { margin-right: 5px; }*/
/*.drawing-board-control-colors-picker { display: inline-block; width: 18px; height: 18px; cursor: pointer; }*/
.drawing-board-control-colors-picker { display: inline-block; width: 30px; height: 30px; cursor: pointer; }
/*.drawing-board-control-colors-picker[data-color="rgba(255, 255, 255, 1)"] { width: 17px; height: 16px; border: 1px solid #ccc; border-bottom: none; }*/
/*.drawing-board-control-colors-picker:hover { width: 16px; height: 16px; border: 1px solid #555; }*/
.drawing-board-control-colors-picker:hover { width: 28px; height: 28px; border: 1px solid #555; }
.drawing-board-control-drawingmode > button { margin-right: 2px; }
.drawing-board-control-drawingmode > button:last-child { margin-right: 0; }
.drawing-board-control-drawingmode-pencil-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAK5JREFUeNpiZCADREREOAApEN7AQobmBCA1H8ZnokQzEBxgokBz4ooVKw4wUqB5AYjBSIRmkMYEbJoJGkBIMwiw4NAoAHVyAJLwAnTNWF0A1bwfiA3QNCdis4yRCM0bgJoDcXkTPRrRNV8A+RtfODEi2ayAZIgAVLMj0PYP+AyAuaAAqhEEHInVjBwL8VBb90M1GhKbQlmgqQzk/AdAvBBKEw1YoBoSscUxMQAgwACOZ0Ns854p+gAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-pencil-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-drawingmode-eraser-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAMtJREFUeNqck8ENgzAMRZN0gQwSqYwAW+TWMlHVCaC3bMEKrZQFeuuREfpTxciEACmWvowcvQ92zEkUhLX2aowR3vtPeqZKYKQOGvBcpeeyEKYYocY599w1yMBZE/knvDCRB+CZiToIh9BQJRlch0mL8mjRQi8jrCMcPqsuhX97wOA7ig1yXwrTIt2gBxWR2w2TGUwGb+jMiysmC3jaA7TRMZDfyhBnkoWnf4FAMmJXquPCrM5FJm8kgxd0ifC4NVGV6Z1msguH+AowAL10YsD/SG0CAAAAAElFTkSuQmCC'); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-eraser-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-drawingmode-filler-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnNJREFUeNp0k0trE1EUx89MJpNJooYYXBgDNtCKdRPwlbqoCKUtaNVNA0Uo7UbMxoVPEARTXEi+QWfnwn6DEAlEkrSLttTGRiULEQlJ8yChmbzI++E50yTUJA78uMy953/u/557LmOz2WDEZ2m1WrckSRJSqdR2tVrdHQyYebwHtVoNuFHqTqczhQnWKaBYLDoKhcIuzgHDMKBSqeD20qd+LNdsNocSoFhRr9ctpVLJigl4xIIJQizLAmG4cAPa7bYcy9Iug5TL5UYikbD6/X7Rbre/IUcYe3WUW5ZsnQQzW9LpNOPz+UQc5aBM5mgdh7vI9FCCAesW2tnr9YqZTAby+bw8f3AQRP6853n+Ph5hemSCntjj8YjZbFYWx2IxeS2RSEMwuA87O79eqdXquVolK+GxnP0EPbHb7RZJSGABIR6PA11zJHKIR2MhHA5DIPDj7eH3j95KpfK60Wg8Yntil8slkqgnpioLghacTidoNDpEC3q9HnheCc3s1jZeLcW943pirPw/4lKpBkqlDubnl/riycnLsLy88EKj0fhzuRyZv8RFo1E6wpBYkiqy7Z54YmIcVlYeyOKC4mYwJ0nHRaQuM5vNT6hB/iceG7sIq6sPnwmC4MerDkby40AOCCoiddie1Wp92W7zQ2KTyQSLizNP8T0EsPLBbxEDnCj0GkM2qIEwyZRCobizsfH5A1ZXFhuN52F29vpz3HkL574mk8lj24Y5wsHkvjjoX0BOIWc5jruHzbK2ufmzEwpFO3jnDhQv4JoROYdoERVyGjEgZ8iBDlF3FzXo4go6utZ9lftY4N/dXisjR0i1G0ublv8KMAA0ZoUlicxrhwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-filler-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-navigation > button { font-family: Helvetica, Arial, sans-serif; font-size: 14px; font-weight: bold; margin-right: 2px; }
.drawing-board-control-navigation > button:last-child { margin-right: 0; }
.drawing-board-control-size[data-drawing-board-type="range"] .drawing-board-control-inner { width: 75px; }
.drawing-board-control-size[data-drawing-board-type="dropdown"] .drawing-board-control-inner { overflow: visible; }
.drawing-board-control-size-range-input { position: relative; width: 100%; z-index: 100; margin: 0; padding: 0; border: 0; }
.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span, .drawing-board-control-size-dropdown span { display: block; background: #333; opacity: .8; }
.drawing-board-control-size-range-current { display: inline-block; opacity: .15; position: absolute; pointer-events: none; left: 50%; top: 50%; z-index: 50; }
.drawing-board-control-size-dropdown-current { display: block; height: 100%; width: 40px; overflow: hidden; position: relative; }
.drawing-board-control-size-dropdown-current span { position: absolute; left: 50%; top: 50%; }
/*.drawing-board-control-size-dropdown { position: absolute; left: 0px; top: 33px; height: auto; list-style-type: none; margin: 0; padding: 0; z-index: 100; width: 40px;}*/
.drawing-board-control-size-dropdown { position: absolute; left: -6px; top: 33px; height: auto; list-style-type: none; margin: 0; padding: 0; z-index: 100; width: 60px;}
.drawing-board-control-size-dropdown li { display: block; padding: 4px; margin: 3px 0; min-height: 16px; }
.drawing-board-control-size-dropdown li:hover { background: #ccc; }
.drawing-board-control-size-dropdown span { margin: auto; display: inline-block; vertical-align: middle;}
.drawing-board-control-download-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJ1JREFUeNpiYMADIiIiGkAYnxomBvygHorJNoAgGI4GAEM9AYSJFWdEU2QApM5DuYlAPB8L23DFihUX4AZA4xkUVY5AiQNAPkhhAg4XLwCqSQSqcQCy9wNxI4YXQApACnFpJioQsRiCVTPeWIBqmADCuDSDAAu+KAJqLCQUjcgGGAADh9joN0A24AOU3U9GOvrAghRYAqRqBukFCDAAdsY3kbu0QI0AAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-download-button:before { content: ""; display: block; width: 0; height: 100%; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>AIS Edit Tool</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<meta name="viewport" content="height=device-height">
<link rel="stylesheet" href="./css/drawingboard.css">
<style>
.board {
margin: 0 auto;
}
</style>
<style>
#ais-edit-tool {
height: 90vh; /* For 90% screen height */
width: 90vw; /* For 90% screen width */
}
</style>
<style>
.drawing-board-canvas-wrapper {
border: 1px solid gray;
}
#overlay{
position:initial;
display:block;
width:100%;
height:100%;
z-index:30;
}
</style>
</head>
<body>
<noscript>JavaScript is required :(</noscript>
<div class="container">
<div>
<div class="board" id="ais-edit-tool">
</div>
</div>
</div>
<script src="./js/jquery.min.js"></script>
<script src="./js/simple-undo.js"></script>
<script src="./js/utils.js"></script>
<script src="./js/drawingboard.js"></script>
<script src="./js/controls/control.js"></script>
<script src="./js/controls/color.js"></script>
<script src="./js/controls/drawingmode.js"></script>
<script src="./js/controls/navigation.js"></script>
<script src="./js/controls/size.js"></script>
<script src="./js/controls/download.js"></script>
<script>
var aisEditTool = new DrawingBoard.Board('ais-edit-tool', {
controls: [
'Color',
{ Size: { type: 'dropdown' } },
{ DrawingMode: { filler: false } },
'Download'
],
color: '#ff0000',
background: false,
size: 3,
webStorage: 'false',
controlsPosition: 'center',
enlargeYourContainer: true
});
//Canvasのサイズを写真の横縦割合に合わせて変更
function resizeCanvasToRatio(ratio){
aisEditTool.resizeCanvasToRatio(ratio);
}
//CanvasのBackgroundをイメージファイルのurlに設定
function setBackground(url){
aisEditTool.setBackground(url);
}
//Canvasの修正可否を返す。
function isCanvasEdited(){
return aisEditTool.isCanvasEdited();
}
//パスを取得してoverlayイメージをCanvasの中に入れる。(iOS)
function createOverlayImageElement(imageSrc) {
aisEditTool.createOverlayImageElement(imageSrc);
}
//接続しているクライアントがPCかmobileかをはんだんする
function checkPC() {
var filter = "win16|win32|win64|mac|macintel";
if ( navigator.platform ) {
if ( filter.indexOf( navigator.platform.toLowerCase() ) < 0 ) {
//mobile
return false;
} else {
//pc
return true;
}
}
}
//このペースのロード完了後でイメージをロードする
document.addEventListener("DOMContentLoaded", function(event) {
console.log('DOMContentLoaded');
if(checkPC()){
parent.loadDlgImage(); //call parent, parent call setBackground
}
});
</script>
</body>
</html>
DrawingBoard.Control.Color = DrawingBoard.Control.extend({
name: 'colors',
initialize: function() {
this.initTemplate();
var that = this;
this.$el.on('click', '.drawing-board-control-colors-picker', function(e) {
var color = $(this).attr('data-color');
that.board.setColor(color);
that.$el.find('.drawing-board-control-colors-current')
.css('background-color', color)
.attr('data-color', color);
that.board.ev.trigger('color:changed', color);
that.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
e.preventDefault();
});
this.$el.on('click', '.drawing-board-control-colors-current', function(e) {
that.$el.find('.drawing-board-control-colors-rainbows').toggleClass('drawing-board-utils-hidden');
e.preventDefault();
});
$('body').on('click', function(e) {
var $target = $(e.target);
var $relatedButton = $target.hasClass('drawing-board-control-colors-current') ? $target : $target.closest('.drawing-board-control-colors-current');
var $myButton = that.$el.find('.drawing-board-control-colors-current');
var $popup = that.$el.find('.drawing-board-control-colors-rainbows');
if ( (!$relatedButton.length || $relatedButton.get(0) !== $myButton.get(0)) && !$popup.hasClass('drawing-board-utils-hidden') )
$popup.addClass('drawing-board-utils-hidden');
});
},
initTemplate: function() {
var tpl = '<div class="drawing-board-control-inner">' +
'<div class="drawing-board-control-colors-current" style="background-color: {{color}}" data-color="{{color}}"></div>' +
'<div class="drawing-board-control-colors-rainbows">{{rainbows}}</div>' +
'</div>';
var oneColorTpl = '<div class="drawing-board-control-colors-picker" data-color="{{color}}" style="background-color: {{color}}"></div>';
var rainbows = '';
// $.each([0.75, 0.5, 0.25], $.proxy(function(key, val) {
$.each([0.5], $.proxy(function(key, val) {
var i = 0;
var additionalColor = null;
rainbows += '<div class="drawing-board-control-colors-rainbow">';
// if (val == 0.25) additionalColor = this._rgba(0, 0, 0, 1);
// if (val == 0.5) additionalColor = this._rgba(150, 150, 150, 1);
// if (val == 0.75) additionalColor = this._rgba(255, 255, 255, 1);
// rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: additionalColor.toString() });
// while (i <= 330) {
// rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: this._hsl2Rgba(this._hsl(i-60, 1, val)).toString() });
// i+=30;
// }
// white, black, red, blue, yellow
var white = this._rgba(255,255,255,1);
var black = this._rgba(0,0,0,1);
var red = this._rgba(255,0,0,1);
var blue = this._rgba(0,0,255,1);
var yellow = this._rgba(255,255,0,1);
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: white.toString()});
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: black.toString()});
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: red.toString()});
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: blue.toString()});
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: yellow.toString()});
rainbows += '</div>';
}, this));
this.$el.append( $( DrawingBoard.Utils.tpl(tpl, {color: this.board.color, rainbows: rainbows }) ) );
this.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
},
onBoardReset: function(opts) {
this.board.setColor(this.$el.find('.drawing-board-control-colors-current').attr('data-color'));
},
_rgba: function(r, g, b, a) {
return { r: r, g: g, b: b, a: a, toString: function() { return "rgba(" + r +", " + g + ", " + b + ", " + a + ")"; } };
},
_hsl: function(h, s, l) {
return { h: h, s: s, l: l, toString: function() { return "hsl(" + h +", " + s*100 + "%, " + l*100 + "%)"; } };
},
_hex2Rgba: function(hex) {
var num = parseInt(hex.substring(1), 16);
return this._rgba(num >> 16, num >> 8 & 255, num & 255, 1);
},
//conversion function (modified a bit) taken from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
_hsl2Rgba: function(hsl) {
var h = hsl.h/360, s = hsl.s, l = hsl.l, r, g, b;
function hue2rgb(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
if (s === 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = Math.floor( (hue2rgb(p, q, h + 1/3)) * 255);
g = Math.floor( (hue2rgb(p, q, h)) * 255);
b = Math.floor( (hue2rgb(p, q, h - 1/3)) * 255);
}
return this._rgba(r, g, b, 1);
}
});
\ No newline at end of file
DrawingBoard.Control = function(drawingBoard, opts) {
this.board = drawingBoard;
this.opts = $.extend({}, this.defaults, opts);
this.$el = $(document.createElement('div')).addClass('drawing-board-control');
if (this.name)
this.$el.addClass('drawing-board-control-' + this.name);
this.board.ev.bind('board:reset', $.proxy(this.onBoardReset, this));
this.initialize.apply(this, arguments);
return this;
};
DrawingBoard.Control.prototype = {
name: '',
defaults: {},
initialize: function() {
},
addToBoard: function() {
this.board.addControl(this);
},
onBoardReset: function(opts) {
}
};
//extend directly taken from backbone.js
DrawingBoard.Control.extend = function(protoProps, staticProps) {
var parent = this;
var child;
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
$.extend(child, parent, staticProps);
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
if (protoProps) $.extend(child.prototype, protoProps);
child.__super__ = parent.prototype;
return child;
};
\ No newline at end of file
DrawingBoard.Control.Download = DrawingBoard.Control.extend({
name: 'download',
initialize: function() {
this.$el.append('<button class="drawing-board-control-download-button"></button>');
this.$el.on('click', '.drawing-board-control-download-button', $.proxy(function(e) {
this.board.downloadImg();
e.preventDefault();
}, this));
}
});
\ No newline at end of file
DrawingBoard.Control.DrawingMode = DrawingBoard.Control.extend({
name: 'drawingmode',
defaults: {
pencil: true,
eraser: true,
filler: true
},
initialize: function() {
this.prevMode = this.board.getMode();
$.each(["pencil", "eraser", "filler"], $.proxy(function(k, value) {
if (this.opts[value]) {
this.$el.append('<button class="drawing-board-control-drawingmode-' + value + '-button" data-mode="' + value + '"></button>');
}
}, this));
this.$el.on('click', 'button[data-mode]', $.proxy(function(e) {
var value = $(e.currentTarget).attr('data-mode');
var mode = this.board.getMode();
if (mode !== value) this.prevMode = mode;
var newMode = mode === value ? this.prevMode : value;
this.board.setMode( newMode );
e.preventDefault();
}, this));
this.board.ev.bind('board:mode', $.proxy(function(mode) {
this.toggleButtons(mode);
}, this));
this.toggleButtons( this.board.getMode() );
},
toggleButtons: function(mode) {
this.$el.find('button[data-mode]').each(function(k, item) {
var $item = $(item);
$item.toggleClass('active', mode === $item.attr('data-mode'));
});
}
});
DrawingBoard.Control.Navigation = DrawingBoard.Control.extend({
name: 'navigation',
defaults: {
back: true,
forward: true,
reset: true
},
initialize: function() {
var el = '';
if (this.opts.back) el += '<button class="drawing-board-control-navigation-back">&larr;</button>';
if (this.opts.forward) el += '<button class="drawing-board-control-navigation-forward">&rarr;</button>';
if (this.opts.reset) el += '<button class="drawing-board-control-navigation-reset">&times;</button>';
this.$el.append(el);
if (this.opts.back) {
var $back = this.$el.find('.drawing-board-control-navigation-back');
this.board.ev.bind('historyNavigation', $.proxy(this.updateBack, this, $back));
this.$el.on('click', '.drawing-board-control-navigation-back', $.proxy(function(e) {
this.board.goBackInHistory();
e.preventDefault();
}, this));
this.updateBack($back);
}
if (this.opts.forward) {
var $forward = this.$el.find('.drawing-board-control-navigation-forward');
this.board.ev.bind('historyNavigation', $.proxy(this.updateForward, this, $forward));
this.$el.on('click', '.drawing-board-control-navigation-forward', $.proxy(function(e) {
this.board.goForthInHistory();
e.preventDefault();
}, this));
this.updateForward($forward);
}
if (this.opts.reset) {
this.$el.on('click', '.drawing-board-control-navigation-reset', $.proxy(function(e) {
this.board.reset({ background: true });
e.preventDefault();
}, this));
}
},
updateBack: function($back) {
if (this.board.history.canUndo()) {
$back.removeAttr('disabled');
} else {
$back.attr('disabled', 'disabled');
}
},
updateForward: function($forward) {
if (this.board.history.canRedo()) {
$forward.removeAttr('disabled');
} else {
$forward.attr('disabled', 'disabled');
}
}
});
\ No newline at end of file
DrawingBoard.Control.Size = DrawingBoard.Control.extend({
name: 'size',
defaults: {
type: "auto",
// dropdownValues: [1, 3, 6, 10, 20, 30, 40, 50],
dropdownValues: [3, 6, 10, 20, 30, 45],
min: 1,
max: 50
},
types: ['dropdown', 'range'],
initialize: function() {
if (this.opts.type == "auto")
this.opts.type = this._iHasRangeInput() ? 'range' : 'dropdown';
var tpl = $.inArray(this.opts.type, this.types) > -1 ? this['_' + this.opts.type + 'Template']() : false;
if (!tpl) return false;
this.val = this.board.opts.size;
this.$el.append( $( tpl ) );
this.$el.attr('data-drawing-board-type', this.opts.type);
this.updateView();
var that = this;
if (this.opts.type == "range") {
this.$el.on('change', '.drawing-board-control-size-range-input', function(e) {
that.val = $(this).val();
that.updateView();
that.board.ev.trigger('size:changed', that.val);
e.preventDefault();
});
}
if (this.opts.type == "dropdown") {
this.$el.on('click', '.drawing-board-control-size-dropdown-current', $.proxy(function(e) {
this.$el.find('.drawing-board-control-size-dropdown').toggleClass('drawing-board-utils-hidden');
}, this));
this.$el.on('click', '[data-size]', function(e) {
that.val = parseInt($(this).attr('data-size'), 0);
that.updateView();
that.board.ev.trigger('size:changed', that.val);
e.preventDefault();
});
}
},
_rangeTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<input type="range" min="{{min}}" max="{{max}}" value="{{size}}" step="1" class="drawing-board-control-size-range-input">' +
'<span class="drawing-board-control-size-range-current"></span>' +
'</div>';
return DrawingBoard.Utils.tpl(tpl, {
min: this.opts.min,
max: this.opts.max,
size: this.board.opts.size
});
},
_dropdownTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<div class="drawing-board-control-size-dropdown-current"><span></span></div>' +
'<ul class="drawing-board-control-size-dropdown">';
$.each(this.opts.dropdownValues, function(i, size) {
tpl += DrawingBoard.Utils.tpl(
'<li data-size="{{size}}"><span style="width: {{size}}px; height: {{size}}px; border-radius: {{size}}px;"></span></li>',
{ size: size }
);
});
tpl += '</ul></div>';
return tpl;
},
onBoardReset: function(opts) {
this.updateView();
},
updateView: function() {
var val = this.val;
this.board.ctx.lineWidth = val;
this.$el.find('.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span').css({
width: val + 'px',
height: val + 'px',
borderRadius: val + 'px',
marginLeft: -1*val/2 + 'px',
marginTop: -1*val/2 + 'px'
});
this.$el.find('.drawing-board-control-inner').attr('title', val);
if (this.opts.type == 'dropdown') {
var closest = null;
$.each(this.opts.dropdownValues, function(i, size) {
if (closest === null || Math.abs(size - val) < Math.abs(closest - val))
closest = size;
});
this.$el.find('.drawing-board-control-size-dropdown').addClass('drawing-board-utils-hidden');
}
},
_iHasRangeInput: function() {
var inputElem = document.createElement('input'),
smile = ':)',
docElement = document.documentElement,
inputElemType = 'range',
available;
inputElem.setAttribute('type', inputElemType);
available = inputElem.type !== 'text';
inputElem.value = smile;
inputElem.style.cssText = 'position:absolute;visibility:hidden;';
if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
docElement.appendChild(inputElem);
defaultView = document.defaultView;
available = defaultView.getComputedStyle &&
defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
(inputElem.offsetHeight !== 0);
docElement.removeChild(inputElem);
}
return !!available;
}
});
\ No newline at end of file
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
(function() {
'use strict';
/**
* SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
*
* options are: {
* * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
* * `maxLength` : the maximum number of items in history
* * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
* }
*
*/
var SimpleUndo = function(options) {
var settings = options ? options : {};
var defaultOptions = {
provider: function() {
throw new Error("No provider!");
},
maxLength: 30,
onUpdate: function() {}
};
this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
this.initialItem = null;
this.clear();
};
function truncate (stack, limit) {
while (stack.length > limit) {
stack.shift();
}
}
SimpleUndo.prototype.initialize = function(initialItem) {
this.stack[0] = initialItem;
this.initialItem = initialItem;
};
SimpleUndo.prototype.clear = function() {
this.stack = [this.initialItem];
this.position = 0;
this.onUpdate();
};
SimpleUndo.prototype.save = function() {
this.provider(function(current) {
truncate(this.stack, this.maxLength);
this.position = Math.min(this.position,this.stack.length - 1);
this.stack = this.stack.slice(0, this.position + 1);
this.stack.push(current);
this.position++;
this.onUpdate();
}.bind(this));
};
SimpleUndo.prototype.undo = function(callback) {
if (this.canUndo()) {
var item = this.stack[--this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.redo = function(callback) {
if (this.canRedo()) {
var item = this.stack[++this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.canUndo = function() {
return this.position > 0;
};
SimpleUndo.prototype.canRedo = function() {
return this.position < this.count();
};
SimpleUndo.prototype.count = function() {
return this.stack.length - 1; // -1 because of initial item
};
//exports
// node module
if (typeof module != 'undefined') {
module.exports = SimpleUndo;
}
// browser global
if (typeof window != 'undefined') {
window.SimpleUndo = SimpleUndo;
}
})();
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
DrawingBoard.Utils = {};
/*!
* Tim (lite)
* github.com/premasagar/tim
*//*
A tiny, secure JavaScript micro-templating script.
*/
DrawingBoard.Utils.tpl = (function(){
"use strict";
var start = "{{",
end = "}}",
path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
undef;
return function(template, data){
// Merge data into the template string
return template.replace(pattern, function(tag, token){
var path = token.split("."),
len = path.length,
lookup = data,
i = 0;
for (; i < len; i++){
lookup = lookup[path[i]];
// Property not found
if (lookup === undef){
throw "tim: '" + path[i] + "' not found in " + tag;
}
// Return the required value
if (i === len - 1){
return lookup;
}
}
});
};
}());
/**
* https://github.com/jeromeetienne/microevent.js
* MicroEvent - to make any js object an event emitter (server or browser)
*
* - pure javascript - server compatible, browser compatible
* - dont rely on the browser doms
* - super simple - you get it immediatly, no mistery, no magic involved
*
* - create a MicroEventDebug with goodies to debug
* - make it safer to use
*/
DrawingBoard.Utils.MicroEvent = function(){};
DrawingBoard.Utils.MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
//I know.
DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
withPadding = !!withPadding || true;
withMargin = !!withMargin || false;
var width = 0,
props;
if (direction == "width") {
props = ['border-left-width', 'border-right-width'];
if (withPadding) props.push('padding-left', 'padding-right');
if (withMargin) props.push('margin-left', 'margin-right');
} else {
props = ['border-top-width', 'border-bottom-width'];
if (withPadding) props.push('padding-top', 'padding-bottom');
if (withMargin) props.push('margin-top', 'margin-bottom');
}
for (var i = props.length - 1; i >= 0; i--)
width += parseInt($el.css(props[i]).replace('px', ''), 10);
return width;
};
DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
};
DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
};
DrawingBoard.Utils.isColor = function(string) {
if (!string || !string.length) return false;
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
};
/**
* Packs an RGB color into a single integer.
*/
DrawingBoard.Utils.RGBToInt = function(r, g, b) {
var c = 0;
c |= (r & 255) << 16;
c |= (g & 255) << 8;
c |= (b & 255);
return c;
};
/**
* Returns informations on the pixel located at (x,y).
*/
DrawingBoard.Utils.pixelAt = function(image, x, y) {
var i = (y * image.width + x) * 4;
var c = DrawingBoard.Utils.RGBToInt(
image.data[i],
image.data[i + 1],
image.data[i + 2]
);
return [
i, // INDEX
x, // X
y, // Y
c // COLOR
];
};
/**
* Compares two colors with the given tolerance (between 0 and 255).
*/
DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
if (tolerance === 0) {
return (a === b);
}
var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
ga = (a >> 8) & 255, gb = (b >> 8) & 255,
ba = a & 255, bb = b & 255;
return (Math.abs(ra - rb) <= tolerance)
&& (Math.abs(ga - gb) <= tolerance)
&& (Math.abs(ba - bb) <= tolerance);
};
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
}());
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
/**
* pass the id of the html element to put the drawing board into
* and some options : {
* controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
* instead of simple strings, you can pass an object to define a control opts
* ie ['Color', { Navigation: { reset: false }}]
* controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
* background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
* color: pencil color ("#000000" by default)
* size: pencil size (3 by default)
* webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
* droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it,
* errorMessage: html string to put in the board's element on browsers that don't support canvas.
* stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default
* }
*/
DrawingBoard.Board = function(id, opts) {
this.opts = this.mergeOptions(opts);
this.ev = new DrawingBoard.Utils.MicroEvent();
this.id = id;
this.$el = $(document.getElementById(id));
if (!this.$el.length)
return false;
var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
else tpl = '<div class="drawing-board-controls"></div>' + tpl;
this.$el.addClass('drawing-board').append(tpl);
this.dom = {
$canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
$canvas: this.$el.find('.drawing-board-canvas'),
$cursor: this.$el.find('.drawing-board-cursor'),
$controls: this.$el.find('.drawing-board-controls')
};
$.each(['left', 'right', 'center'], $.proxy(function(n, val) {
if (this.opts.controlsPosition.indexOf(val) > -1) {
this.dom.$controls.attr('data-align', val);
return false;
}
}, this));
this.canvas = this.dom.$canvas.get(0);
this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
this.color = this.opts.color;
if (!this.ctx) {
if (this.opts.errorMessage)
this.$el.html(this.opts.errorMessage);
return false;
}
this.storage = this._getStorage();
this.initHistory();
//init default board values before controls are added (mostly pencil color and size)
this.reset({ webStorage: false, history: false, background: false });
//init controls (they will need the default board values to work like pencil color and size)
this.initControls();
//set board's size after the controls div is added
this.resize();
//reset the board to take all resized space
this.reset({ webStorage: false, history: false, background: true });
this.restoreWebStorage();
this.initDropEvents();
this.initDrawEvents();
};
DrawingBoard.Board.defaultOpts = {
controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
controlsPosition: "top left",
color: "#000000",
size: 1,
background: "#fff",
eraserColor: "background",
fillTolerance: 100,
fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
webStorage: 'session',
droppable: false,
enlargeYourContainer: false,
errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true
};
DrawingBoard.Board.prototype = {
mergeOptions: function(opts) {
opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
if (!opts.background && opts.eraserColor === "background")
opts.eraserColor = "transparent";
return opts;
},
/**
* Canvas reset/resize methods: put back the canvas to its default values
*
* depending on options, can set color, size, background back to default values
* and store the reseted canvas in webstorage and history queue
*
* resize values depend on the `enlargeYourContainer` option
*/
reset: function(opts) {
opts = $.extend({
color: this.opts.color,
size: this.opts.size,
webStorage: true,
history: true,
background: false
}, opts);
this.setMode('pencil');
if (opts.background) {
this.resetBackground(this.opts.background, $.proxy(function() {
if (opts.history) this.saveHistory();
}, this));
}
if (opts.color) this.setColor(opts.color);
if (opts.size) this.ctx.lineWidth = opts.size;
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
// this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
if (opts.webStorage) this.saveWebStorage();
// if opts.background we already dealt with the history
if (opts.history && !opts.background) this.saveHistory();
this.blankCanvas = this.getImg();
this.ev.trigger('board:reset', opts);
},
resetBackground: function(background, callback) {
background = background || this.opts.background;
var bgIsColor = DrawingBoard.Utils.isColor(background);
var prevMode = this.getMode();
this.setMode('pencil');
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
if (bgIsColor) {
this.ctx.fillStyle = background;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.history.initialize(this.getImg());
if (callback) callback();
} else if (background)
this.setImg(background, {
callback: $.proxy(function() {
this.history.initialize(this.getImg());
if (callback) callback();
}, this)
});
this.setMode(prevMode);
},
resize: function() {
this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
var canvasWidth, canvasHeight;
var widths = [
this.$el.width(),
DrawingBoard.Utils.boxBorderWidth(this.$el),
DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
];
var heights = [
this.$el.height(),
DrawingBoard.Utils.boxBorderHeight(this.$el),
this.dom.$controls.height(),
DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
];
var that = this;
var sum = function(values, multiplier) { //make the sum of all array values
multiplier = multiplier || 1;
var res = values[0];
for (var i = 1; i < values.length; i++) {
res = res + (values[i]*multiplier);
}
return res;
};
var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
if (this.opts.enlargeYourContainer) {
canvasWidth = this.$el.width();
canvasHeight = this.$el.height();
this.$el.width( sum(widths) );
this.$el.height( sum(heights) );
} else {
canvasWidth = sub(widths);
canvasHeight = sub(heights);
}
this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
this.dom.$canvas.css('width', canvasWidth + 'px');
this.dom.$canvas.css('height', canvasHeight + 'px');
this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;
},
/**
* Controls:
* the drawing board can has various UI elements to control it.
* one control is represented by a class in the namespace DrawingBoard.Control
* it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
*
*/
initControls: function() {
this.controls = [];
if (!this.opts.controls.length || !DrawingBoard.Control) return false;
for (var i = 0; i < this.opts.controls.length; i++) {
var c = null;
if (typeof this.opts.controls[i] == "string")
c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
else if (typeof this.opts.controls[i] == "object") {
for (var controlName in this.opts.controls[i]) break;
c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
}
if (c) {
this.addControl(c);
}
}
},
//add a new control or an existing one at the position you want in the UI
//to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }"
//the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
addControl: function(control, optsOrPos, pos) {
if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
return false;
var opts = typeof optsOrPos == "object" ? optsOrPos : {};
pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
if (typeof control == "string")
control = new window['DrawingBoard']['Control'][control](this, opts);
if (pos)
this.dom.$controls.children().eq(pos).before(control.$el);
else
this.dom.$controls.append(control.$el);
if (!this.controls)
this.controls = [];
this.controls.push(control);
this.dom.$controls.removeClass('drawing-board-controls-hidden');
},
/**
* History methods: undo and redo drawed lines
*/
initHistory: function() {
this.history = new SimpleUndo({
maxLength: 30,
provider: $.proxy(function(done) {
done(this.getImg());
}, this),
onUpdate: $.proxy(function() {
this.ev.trigger('historyNavigation');
}, this)
});
},
saveHistory: function() {
this.history.save();
},
restoreHistory: function(image) {
this.setImg(image, {
callback: $.proxy(function() {
this.saveWebStorage();
}, this)
});
},
goBackInHistory: function() {
this.history.undo($.proxy(this.restoreHistory, this));
},
goForthInHistory: function() {
this.history.redo($.proxy(this.restoreHistory, this));
},
/**
* Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
*/
setImg: function(src, opts) {
opts = $.extend({
stretch: this.opts.stretchImg,
callback: null
}, opts);
var ctx = this.ctx;
var img = new Image();
var oldGCO = ctx.globalCompositeOperation;
img.onload = function() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (opts.stretch) {
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
} else {
ctx.drawImage(img, 0, 0);
}
ctx.globalCompositeOperation = oldGCO;
if (opts.callback) {
opts.callback();
}
};
img.src = src;
},
getImg: function() {
return this.canvas.toDataURL("image/png");
},
downloadImg: function() {
var img = this.getImg();
img = img.replace("image/png", "image/octet-stream");
// window.open(img);
window.location.href = img;
},
/**
* WebStorage handling : save and restore to local or session storage
*/
saveWebStorage: function() {
if (window[this.storage]) {
window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
}
},
restoreWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
}
},
clearWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
window[this.storage].removeItem('drawing-board-' + this.id);
this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
}
},
_getStorage: function() {
if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
return this.opts.webStorage + 'Storage';
},
/**
* Drop an image on the canvas to draw on it
*/
initDropEvents: function() {
if (!this.opts.droppable)
return false;
this.dom.$canvas.on('dragover dragenter drop', function(e) {
e.stopPropagation();
e.preventDefault();
});
this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
},
_onCanvasDrop: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var files = e.dataTransfer.files;
if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
return false;
var fr = new FileReader();
fr.readAsDataURL(files[0]);
fr.onload = $.proxy(function(ev) {
this.setImg(ev.target.result, {
callback: $.proxy(function() {
this.saveHistory();
}, this)
});
this.ev.trigger('board:imageDropped', ev.target.result);
this.ev.trigger('board:userAction');
}, this);
},
/**
* set and get current drawing mode
*
* possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
*/
setMode: function(newMode, silent) {
silent = silent || false;
newMode = newMode || 'pencil';
this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
if (this.opts.eraserColor === "transparent")
this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
else {
if (newMode === "eraser") {
if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
this.ctx.strokeStyle = this.opts.background;
else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
this.ctx.strokeStyle = this.opts.eraserColor;
} else if (!this.mode || this.mode === "eraser") {
this.ctx.strokeStyle = this.color;
}
if (newMode === "filler")
this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
}
this.mode = newMode;
if (!silent)
this.ev.trigger('board:mode', this.mode);
},
getMode: function() {
return this.mode || "pencil";
},
setColor: function(color) {
var that = this;
color = color || this.color;
if (!DrawingBoard.Utils.isColor(color))
return false;
this.color = color;
if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
var setStrokeStyle = function(mode) {
if (mode !== "eraser")
that.strokeStyle = that.color;
that.ev.unbind('board:mode', setStrokeStyle);
};
this.ev.bind('board:mode', setStrokeStyle);
} else
this.ctx.strokeStyle = this.color;
},
/**
* Fills an area with the current stroke color.
*/
fill: function(e) {
if (this.getImg() === this.blankCanvas) {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.ctx.fillStyle = this.color;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
return;
}
var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
// constants identifying pixels components
var INDEX = 0, X = 1, Y = 2, COLOR = 3;
// target color components
var stroke = this.ctx.strokeStyle;
var r = parseInt(stroke.substr(1, 2), 16);
var g = parseInt(stroke.substr(3, 2), 16);
var b = parseInt(stroke.substr(5, 2), 16);
// starting point
var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10));
var startColor = start[COLOR];
var tolerance = this.opts.fillTolerance;
var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38
// no need to continue if starting and target colors are the same
if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance))
return;
// pixels to evaluate
var queue = [start];
// loop vars
var pixel, x, y;
var maxX = img.width - 1;
var maxY = img.height - 1;
function updatePixelColor(pixel) {
img.data[pixel[INDEX]] = r;
img.data[pixel[INDEX] + 1] = g;
img.data[pixel[INDEX] + 2] = b;
}
while ((pixel = queue.pop())) {
if (useHack)
updatePixelColor(pixel);
if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) {
if (!useHack)
updatePixelColor(pixel);
if (pixel[X] > 0) // west
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
if (pixel[X] < maxX) // east
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
if (pixel[Y] > 0) // north
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
if (pixel[Y] < maxY) // south
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
}
}
this.ctx.putImageData(img, 0, 0);
},
/**
* Drawing handling, with mouse or touch
*/
initDrawEvents: function() {
this.isDrawing = false;
this.isMouseHovering = false;
this.coords = {};
this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
this._onInputStart(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
this._onInputMove(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mousemove', $.proxy(function(e) {
}, this));
this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
this._onInputStop(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mouseover', $.proxy(function(e) {
this._onMouseOver(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mouseout', $.proxy(function(e) {
this._onMouseOut(e, this._getInputCoords(e) );
}, this));
$('body').on('mouseup touchend', $.proxy(function(e) {
this.isDrawing = false;
}, this));
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
},
draw: function() {
//if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
//todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
//we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) });
this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
this.dom.$cursor.removeClass('drawing-board-utils-hidden');
} else {
this.dom.$cursor.addClass('drawing-board-utils-hidden');
}
if (this.isDrawing) {
var currentMid = this._getMidInputCoords(this.coords.current);
this.ctx.beginPath();
this.ctx.moveTo(currentMid.x, currentMid.y);
this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
this.ctx.stroke();
this.coords.old = this.coords.current;
this.coords.oldMid = currentMid;
}
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
},
_onInputStart: function(e, coords) {
this.coords.current = this.coords.old = coords;
this.coords.oldMid = this._getMidInputCoords(coords);
this.isDrawing = true;
if (!window.requestAnimationFrame) this.draw();
this.ev.trigger('board:startDrawing', {e: e, coords: coords});
e.stopPropagation();
e.preventDefault();
},
_onInputMove: function(e, coords) {
this.coords.current = coords;
this.ev.trigger('board:drawing', {e: e, coords: coords});
if (!window.requestAnimationFrame) this.draw();
e.stopPropagation();
e.preventDefault();
},
_onInputStop: function(e, coords) {
if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
this.isDrawing = false;
this.saveWebStorage();
this.saveHistory();
this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
this.ev.trigger('board:userAction');
e.stopPropagation();
e.preventDefault();
}
},
_onMouseOver: function(e, coords) {
this.isMouseHovering = true;
this.coords.old = this._getInputCoords(e);
this.coords.oldMid = this._getMidInputCoords(this.coords.old);
this.ev.trigger('board:mouseOver', {e: e, coords: coords});
},
_onMouseOut: function(e, coords) {
this.isMouseHovering = false;
this.ev.trigger('board:mouseOut', {e: e, coords: coords});
},
_getInputCoords: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var
rect = this.canvas.getBoundingClientRect(),
width = this.dom.$canvas.width(),
height = this.dom.$canvas.height()
;
var x, y;
if (e.touches && e.touches.length == 1) {
x = e.touches[0].pageX;
y = e.touches[0].pageY;
} else {
x = e.pageX;
y = e.pageY;
}
x = x - this.dom.$canvas.offset().left;
y = y - this.dom.$canvas.offset().top;
x *= (width / rect.width);
y *= (height / rect.height);
return {
x: x,
y: y
};
},
_getMidInputCoords: function(coords) {
return {
x: this.coords.old.x + coords.x>>1,
y: this.coords.old.y + coords.y>>1
};
},
//Canvasのサイズを写真の横縦割合に合わせて変更
resizeCanvasToRatio: function(ratio){
//上位クラスの横幅と縦幅を修正
this.$el.width('100%');
this.$el.height('100%');
var w = this.$el.width();
var h = w*ratio; //横幅によって縦幅を決める。
//Canvasの縁のサイズを設定
this.dom.$canvasWrapper.css('width', w + 'px');
this.dom.$canvasWrapper.css('height', h + 'px');
//Canvasのcssサイズを設定
this.dom.$canvas.css('width', w + 'px');
this.dom.$canvas.css('height', h + 'px');
//Canvas内のサイズを設定
this.canvas.width = w;
this.canvas.height = h;
//Canvasを再設定して変数を新しいサイズに合わせる。
//しなければ手書きが変になる。
this.reset({ webStorage: false, history: false, background: true });
},
//CanvasのBackgroundをイメージファイルのurlに設定(Android)
setBackground: function setBackground(url){
this.canvas.setAttribute('style',"background-image:url('"+url+"');background-size:cover;background-repeat:no-repeat;");
},
//パスをもらってoverlayイメージをCanvasの中に入れる。(iOS)
createOverlayImageElement: function(imageSrc) {
var imgEnt = document.createElement("img");
imgEnt.setAttribute("id", "overlay");
imgEnt.setAttribute("src", imageSrc);
this.dom.$canvasWrapper.append(imgEnt);
},
//Canvasの修正可否を返す。
isCanvasEdited: function(){
//history.positionは修正回数
//修正がないと、position変数が0。
return this.history.position > 0;
}
};
DrawingBoard.Control = function(drawingBoard, opts) {
this.board = drawingBoard;
this.opts = $.extend({}, this.defaults, opts);
this.$el = $(document.createElement('div')).addClass('drawing-board-control');
if (this.name)
this.$el.addClass('drawing-board-control-' + this.name);
this.board.ev.bind('board:reset', $.proxy(this.onBoardReset, this));
this.initialize.apply(this, arguments);
return this;
};
DrawingBoard.Control.prototype = {
name: '',
defaults: {},
initialize: function() {
},
addToBoard: function() {
this.board.addControl(this);
},
onBoardReset: function(opts) {
}
};
//extend directly taken from backbone.js
DrawingBoard.Control.extend = function(protoProps, staticProps) {
var parent = this;
var child;
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
$.extend(child, parent, staticProps);
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
if (protoProps) $.extend(child.prototype, protoProps);
child.__super__ = parent.prototype;
return child;
};
DrawingBoard.Control.Color = DrawingBoard.Control.extend({
name: 'colors',
initialize: function() {
this.initTemplate();
var that = this;
this.$el.on('click', '.drawing-board-control-colors-picker', function(e) {
var color = $(this).attr('data-color');
that.board.setColor(color);
that.$el.find('.drawing-board-control-colors-current')
.css('background-color', color)
.attr('data-color', color);
that.board.ev.trigger('color:changed', color);
that.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
e.preventDefault();
});
this.$el.on('click', '.drawing-board-control-colors-current', function(e) {
that.$el.find('.drawing-board-control-colors-rainbows').toggleClass('drawing-board-utils-hidden');
e.preventDefault();
});
$('body').on('click', function(e) {
var $target = $(e.target);
var $relatedButton = $target.hasClass('drawing-board-control-colors-current') ? $target : $target.closest('.drawing-board-control-colors-current');
var $myButton = that.$el.find('.drawing-board-control-colors-current');
var $popup = that.$el.find('.drawing-board-control-colors-rainbows');
if ( (!$relatedButton.length || $relatedButton.get(0) !== $myButton.get(0)) && !$popup.hasClass('drawing-board-utils-hidden') )
$popup.addClass('drawing-board-utils-hidden');
});
},
initTemplate: function() {
var tpl = '<div class="drawing-board-control-inner">' +
'<div class="drawing-board-control-colors-current" style="background-color: {{color}}" data-color="{{color}}"></div>' +
'<div class="drawing-board-control-colors-rainbows">{{rainbows}}</div>' +
'</div>';
var oneColorTpl = '<div class="drawing-board-control-colors-picker" data-color="{{color}}" style="background-color: {{color}}"></div>';
var rainbows = '';
$.each([0.75, 0.5, 0.25], $.proxy(function(key, val) {
var i = 0;
var additionalColor = null;
rainbows += '<div class="drawing-board-control-colors-rainbow">';
if (val == 0.25) additionalColor = this._rgba(0, 0, 0, 1);
if (val == 0.5) additionalColor = this._rgba(150, 150, 150, 1);
if (val == 0.75) additionalColor = this._rgba(255, 255, 255, 1);
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: additionalColor.toString() });
while (i <= 330) {
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: this._hsl2Rgba(this._hsl(i-60, 1, val)).toString() });
i+=30;
}
rainbows += '</div>';
}, this));
this.$el.append( $( DrawingBoard.Utils.tpl(tpl, {color: this.board.color, rainbows: rainbows }) ) );
this.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
},
onBoardReset: function(opts) {
this.board.setColor(this.$el.find('.drawing-board-control-colors-current').attr('data-color'));
},
_rgba: function(r, g, b, a) {
return { r: r, g: g, b: b, a: a, toString: function() { return "rgba(" + r +", " + g + ", " + b + ", " + a + ")"; } };
},
_hsl: function(h, s, l) {
return { h: h, s: s, l: l, toString: function() { return "hsl(" + h +", " + s*100 + "%, " + l*100 + "%)"; } };
},
_hex2Rgba: function(hex) {
var num = parseInt(hex.substring(1), 16);
return this._rgba(num >> 16, num >> 8 & 255, num & 255, 1);
},
//conversion function (modified a bit) taken from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
_hsl2Rgba: function(hsl) {
var h = hsl.h/360, s = hsl.s, l = hsl.l, r, g, b;
function hue2rgb(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
if (s === 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = Math.floor( (hue2rgb(p, q, h + 1/3)) * 255);
g = Math.floor( (hue2rgb(p, q, h)) * 255);
b = Math.floor( (hue2rgb(p, q, h - 1/3)) * 255);
}
return this._rgba(r, g, b, 1);
}
});
DrawingBoard.Control.DrawingMode = DrawingBoard.Control.extend({
name: 'drawingmode',
defaults: {
pencil: true,
eraser: true,
filler: true
},
initialize: function() {
this.prevMode = this.board.getMode();
$.each(["pencil", "eraser", "filler"], $.proxy(function(k, value) {
if (this.opts[value]) {
this.$el.append('<button class="drawing-board-control-drawingmode-' + value + '-button" data-mode="' + value + '"></button>');
}
}, this));
this.$el.on('click', 'button[data-mode]', $.proxy(function(e) {
var value = $(e.currentTarget).attr('data-mode');
var mode = this.board.getMode();
if (mode !== value) this.prevMode = mode;
var newMode = mode === value ? this.prevMode : value;
this.board.setMode( newMode );
e.preventDefault();
}, this));
this.board.ev.bind('board:mode', $.proxy(function(mode) {
this.toggleButtons(mode);
}, this));
this.toggleButtons( this.board.getMode() );
},
toggleButtons: function(mode) {
this.$el.find('button[data-mode]').each(function(k, item) {
var $item = $(item);
$item.toggleClass('active', mode === $item.attr('data-mode'));
});
}
});
DrawingBoard.Control.Navigation = DrawingBoard.Control.extend({
name: 'navigation',
defaults: {
back: true,
forward: true,
reset: true
},
initialize: function() {
var el = '';
if (this.opts.back) el += '<button class="drawing-board-control-navigation-back">&larr;</button>';
if (this.opts.forward) el += '<button class="drawing-board-control-navigation-forward">&rarr;</button>';
if (this.opts.reset) el += '<button class="drawing-board-control-navigation-reset">&times;</button>';
this.$el.append(el);
if (this.opts.back) {
var $back = this.$el.find('.drawing-board-control-navigation-back');
this.board.ev.bind('historyNavigation', $.proxy(this.updateBack, this, $back));
this.$el.on('click', '.drawing-board-control-navigation-back', $.proxy(function(e) {
this.board.goBackInHistory();
e.preventDefault();
}, this));
this.updateBack($back);
}
if (this.opts.forward) {
var $forward = this.$el.find('.drawing-board-control-navigation-forward');
this.board.ev.bind('historyNavigation', $.proxy(this.updateForward, this, $forward));
this.$el.on('click', '.drawing-board-control-navigation-forward', $.proxy(function(e) {
this.board.goForthInHistory();
e.preventDefault();
}, this));
this.updateForward($forward);
}
if (this.opts.reset) {
this.$el.on('click', '.drawing-board-control-navigation-reset', $.proxy(function(e) {
this.board.reset({ background: true });
e.preventDefault();
}, this));
}
},
updateBack: function($back) {
if (this.board.history.canUndo()) {
$back.removeAttr('disabled');
} else {
$back.attr('disabled', 'disabled');
}
},
updateForward: function($forward) {
if (this.board.history.canRedo()) {
$forward.removeAttr('disabled');
} else {
$forward.attr('disabled', 'disabled');
}
}
});
DrawingBoard.Control.Size = DrawingBoard.Control.extend({
name: 'size',
defaults: {
type: "auto",
dropdownValues: [1, 3, 6, 10, 20, 30, 40, 50],
min: 1,
max: 50
},
types: ['dropdown', 'range'],
initialize: function() {
if (this.opts.type == "auto")
this.opts.type = this._iHasRangeInput() ? 'range' : 'dropdown';
var tpl = $.inArray(this.opts.type, this.types) > -1 ? this['_' + this.opts.type + 'Template']() : false;
if (!tpl) return false;
this.val = this.board.opts.size;
this.$el.append( $( tpl ) );
this.$el.attr('data-drawing-board-type', this.opts.type);
this.updateView();
var that = this;
if (this.opts.type == "range") {
this.$el.on('change', '.drawing-board-control-size-range-input', function(e) {
that.val = $(this).val();
that.updateView();
that.board.ev.trigger('size:changed', that.val);
e.preventDefault();
});
}
if (this.opts.type == "dropdown") {
this.$el.on('click', '.drawing-board-control-size-dropdown-current', $.proxy(function(e) {
this.$el.find('.drawing-board-control-size-dropdown').toggleClass('drawing-board-utils-hidden');
}, this));
this.$el.on('click', '[data-size]', function(e) {
that.val = parseInt($(this).attr('data-size'), 0);
that.updateView();
that.board.ev.trigger('size:changed', that.val);
e.preventDefault();
});
}
},
_rangeTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<input type="range" min="{{min}}" max="{{max}}" value="{{size}}" step="1" class="drawing-board-control-size-range-input">' +
'<span class="drawing-board-control-size-range-current"></span>' +
'</div>';
return DrawingBoard.Utils.tpl(tpl, {
min: this.opts.min,
max: this.opts.max,
size: this.board.opts.size
});
},
_dropdownTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<div class="drawing-board-control-size-dropdown-current"><span></span></div>' +
'<ul class="drawing-board-control-size-dropdown">';
$.each(this.opts.dropdownValues, function(i, size) {
tpl += DrawingBoard.Utils.tpl(
'<li data-size="{{size}}"><span style="width: {{size}}px; height: {{size}}px; border-radius: {{size}}px;"></span></li>',
{ size: size }
);
});
tpl += '</ul></div>';
return tpl;
},
onBoardReset: function(opts) {
this.updateView();
},
updateView: function() {
var val = this.val;
this.board.ctx.lineWidth = val;
this.$el.find('.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span').css({
width: val + 'px',
height: val + 'px',
borderRadius: val + 'px',
marginLeft: -1*val/2 + 'px',
marginTop: -1*val/2 + 'px'
});
this.$el.find('.drawing-board-control-inner').attr('title', val);
if (this.opts.type == 'dropdown') {
var closest = null;
$.each(this.opts.dropdownValues, function(i, size) {
if (closest === null || Math.abs(size - val) < Math.abs(closest - val))
closest = size;
});
this.$el.find('.drawing-board-control-size-dropdown').addClass('drawing-board-utils-hidden');
}
},
_iHasRangeInput: function() {
var inputElem = document.createElement('input'),
smile = ':)',
docElement = document.documentElement,
inputElemType = 'range',
available;
inputElem.setAttribute('type', inputElemType);
available = inputElem.type !== 'text';
inputElem.value = smile;
inputElem.style.cssText = 'position:absolute;visibility:hidden;';
if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
docElement.appendChild(inputElem);
defaultView = document.defaultView;
available = defaultView.getComputedStyle &&
defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
(inputElem.offsetHeight !== 0);
docElement.removeChild(inputElem);
}
return !!available;
}
});
DrawingBoard.Control.Download = DrawingBoard.Control.extend({
name: 'download',
initialize: function() {
this.$el.append('<button class="drawing-board-control-download-button"></button>');
this.$el.on('click', '.drawing-board-control-download-button', $.proxy(function(e) {
this.board.downloadImg();
e.preventDefault();
}, this));
}
});
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
(function() {
'use strict';
/**
* SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
*
* options are: {
* * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
* * `maxLength` : the maximum number of items in history
* * `onUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
* }
*
*/
var SimpleUndo = function(options) {
var settings = options ? options : {};
var defaultOptions = {
provider: function() {
throw new Error("No provider!");
},
maxLength: 30,
onUpdate: function() {}
};
this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
this.initialItem = null;
this.clear();
};
function truncate (stack, limit) {
while (stack.length > limit) {
stack.shift();
}
}
SimpleUndo.prototype.initialize = function(initialItem) {
this.stack[0] = initialItem;
this.initialItem = initialItem;
};
SimpleUndo.prototype.clear = function() {
this.stack = [this.initialItem];
this.position = 0;
this.onUpdate();
};
SimpleUndo.prototype.save = function() {
this.provider(function(current) {
if (this.position >= this.maxLength) truncate(this.stack, this.maxLength);
this.position = Math.min(this.position,this.stack.length - 1);
this.stack = this.stack.slice(0, this.position + 1);
this.stack.push(current);
this.position++;
this.onUpdate();
}.bind(this));
};
SimpleUndo.prototype.undo = function(callback) {
if (this.canUndo()) {
var item = this.stack[--this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.redo = function(callback) {
if (this.canRedo()) {
var item = this.stack[++this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.canUndo = function() {
return this.position > 0;
};
SimpleUndo.prototype.canRedo = function() {
return this.position < this.count();
};
SimpleUndo.prototype.count = function() {
return this.stack.length - 1; // -1 because of initial item
};
//exports
// node module
if (typeof module != 'undefined') {
module.exports = SimpleUndo;
}
// browser global
if (typeof window != 'undefined') {
window.SimpleUndo = SimpleUndo;
}
})();
\ No newline at end of file
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
DrawingBoard.Utils = {};
/*!
* Tim (lite)
* github.com/premasagar/tim
*//*
A tiny, secure JavaScript micro-templating script.
*/
DrawingBoard.Utils.tpl = (function(){
"use strict";
var start = "{{",
end = "}}",
path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
undef;
return function(template, data){
// Merge data into the template string
return template.replace(pattern, function(tag, token){
var path = token.split("."),
len = path.length,
lookup = data,
i = 0;
for (; i < len; i++){
lookup = lookup[path[i]];
// Property not found
if (lookup === undef){
throw "tim: '" + path[i] + "' not found in " + tag;
}
// Return the required value
if (i === len - 1){
return lookup;
}
}
});
};
}());
/**
* https://github.com/jeromeetienne/microevent.js
* MicroEvent - to make any js object an event emitter (server or browser)
*
* - pure javascript - server compatible, browser compatible
* - dont rely on the browser doms
* - super simple - you get it immediatly, no mistery, no magic involved
*
* - create a MicroEventDebug with goodies to debug
* - make it safer to use
*/
DrawingBoard.Utils.MicroEvent = function(){};
DrawingBoard.Utils.MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
//I know.
DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
withPadding = !!withPadding || true;
withMargin = !!withMargin || false;
var width = 0,
props;
if (direction == "width") {
props = ['border-left-width', 'border-right-width'];
if (withPadding) props.push('padding-left', 'padding-right');
if (withMargin) props.push('margin-left', 'margin-right');
} else {
props = ['border-top-width', 'border-bottom-width'];
if (withPadding) props.push('padding-top', 'padding-bottom');
if (withMargin) props.push('margin-top', 'margin-bottom');
}
for (var i = props.length - 1; i >= 0; i--)
width += parseInt($el.css(props[i]).replace('px', ''), 10);
return width;
};
DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
};
DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
};
DrawingBoard.Utils.isColor = function(string) {
if (!string || !string.length) return false;
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
};
/**
* Packs an RGB color into a single integer.
*/
DrawingBoard.Utils.RGBToInt = function(r, g, b) {
var c = 0;
c |= (r & 255) << 16;
c |= (g & 255) << 8;
c |= (b & 255);
return c;
};
/**
* Returns informations on the pixel located at (x,y).
*/
DrawingBoard.Utils.pixelAt = function(image, x, y) {
var i = (y * image.width + x) * 4;
var c = DrawingBoard.Utils.RGBToInt(
image.data[i],
image.data[i + 1],
image.data[i + 2]
);
return [
i, // INDEX
x, // X
y, // Y
c // COLOR
];
};
/**
* Compares two colors with the given tolerance (between 0 and 255).
*/
DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
if (tolerance === 0) {
return (a === b);
}
var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
ga = (a >> 8) & 255, gb = (b >> 8) & 255,
ba = a & 255, bb = b & 255;
return (Math.abs(ra - rb) <= tolerance)
&& (Math.abs(ga - gb) <= tolerance)
&& (Math.abs(ba - bb) <= tolerance);
};
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
}());
...@@ -1402,4 +1402,9 @@ ...@@ -1402,4 +1402,9 @@
<string name="Report_5">報告(5)</string> <string name="Report_5">報告(5)</string>
<string name="Routine_5">定期点検(5)</string> <string name="Routine_5">定期点検(5)</string>
<string name="ReportReply_5">報告(回答)(5)</string> <string name="ReportReply_5">報告(回答)(5)</string>
<!--編集画面-->
<string name="msg_confirm_close_edit_page">編集を終了しますか?\n(保存されていない変更は破棄されます。)</string>
<string name="msg_error_edit_page_save">ファイルの保存中に予想以外のエラーが発生しました。\n編集画面を終了します。</string>
<string name="msg_error_edit_page_open">編集画面の表示に失敗しました。</string>
</resources> </resources>
...@@ -1410,4 +1410,9 @@ ...@@ -1410,4 +1410,9 @@
<string name="Report_5">보고(5)</string> <string name="Report_5">보고(5)</string>
<string name="Routine_5">정기점검(5)</string> <string name="Routine_5">정기점검(5)</string>
<string name="ReportReply_5">보고(응답)(5)</string> <string name="ReportReply_5">보고(응답)(5)</string>
<!--編集画面-->
<string name="msg_confirm_close_edit_page">편집을 종료하시겠습니까?\n(저장되지 않은 변경 사항은 삭제됩니다.)</string>
<string name="msg_error_edit_page_save">파일 저장 중에 예기치 않은 오류가 발생했습니다.\n편집창을 종료합니다.</string>
<string name="msg_error_edit_page_open">편집창을 여는데 실패했습니다.</string>
</resources> </resources>
\ No newline at end of file
...@@ -1408,4 +1408,10 @@ ...@@ -1408,4 +1408,10 @@
<string name="Report_5">Report(5)</string> <string name="Report_5">Report(5)</string>
<string name="Routine_5">Routine(5)</string> <string name="Routine_5">Routine(5)</string>
<string name="ReportReply_5">ReportReply(5)</string> <string name="ReportReply_5">ReportReply(5)</string>
<!--編集画面-->
<string name="msg_confirm_close_edit_page">Are you sure you want to finish editing?\n(Unsaved changes will be discarded.)</string>
<string name="msg_error_edit_page_save">An unexpected error has occurred while saving the image.\nClosing the editing Page.</string>
<string name="msg_error_edit_page_open">Failed to open the edit tool.</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/photoEditDialog"
style="@style/Theme_CustomProgressDialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:orientation="vertical">
<WebView
android:id="@+id/edit_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/edit_page_close_btn"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|right"
android:layout_marginRight="7dp"
android:layout_marginTop="7dp"
android:background="@color/operation_search_button_color"
android:clickable="true"
android:elevation="0dp"
android:src="@drawable/btn_close"
android:tint="@color/background" />
</FrameLayout>
...@@ -8,7 +8,10 @@ import android.support.multidex.MultiDexApplication; ...@@ -8,7 +8,10 @@ import android.support.multidex.MultiDexApplication;
import jp.agentec.abook.abv.bl.common.ABVEnvironment; import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.log.Logger; import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.abook.abv.cl.helper.ABVUncaughtExceptionHandler; import jp.agentec.abook.abv.cl.helper.ABVUncaughtExceptionHandler;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.abook.abv.ui.common.util.Initializer; import jp.agentec.abook.abv.ui.common.util.Initializer;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper; import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
import jp.agentec.adf.util.FileUtil; import jp.agentec.adf.util.FileUtil;
...@@ -49,6 +52,9 @@ public class ABVApplication extends MultiDexApplication { ...@@ -49,6 +52,9 @@ public class ABVApplication extends MultiDexApplication {
//添付ファイル臨時保存場所削除 //添付ファイル臨時保存場所削除
FileUtil.delete(ABVEnvironment.getInstance().getCacheTempAttachedImageDirPath()); FileUtil.delete(ABVEnvironment.getInstance().getCacheTempAttachedImageDirPath());
// 絞り検索のfetchDateをローカルに保存された値を取得して設定する
ABVDataCache.getInstance().setTempApertureMasterDataFetchDate(PreferenceUtil.getUserPref(this, AppDefType.UserPrefKey.APERTURE_MASTER_DATA_FETCH_DATE, null));
} }
@Override @Override
......
...@@ -28,6 +28,7 @@ import org.json.adf.JSONObject; ...@@ -28,6 +28,7 @@ import org.json.adf.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
...@@ -35,7 +36,6 @@ import java.util.List; ...@@ -35,7 +36,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import jp.agentec.abook.abv.bl.acms.client.json.content.ContentJSON;
import jp.agentec.abook.abv.bl.acms.type.OperationType; import jp.agentec.abook.abv.bl.acms.type.OperationType;
import jp.agentec.abook.abv.bl.common.ABVEnvironment; import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.Callback; import jp.agentec.abook.abv.bl.common.Callback;
...@@ -74,9 +74,9 @@ import jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper; ...@@ -74,9 +74,9 @@ import jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper;
import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper; import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper; import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
import jp.agentec.abook.abv.ui.viewer.activity.CheckOZDViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.CheckOZDViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.NoPdfViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.NoPdfViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.PhotoEditActivity;
import jp.agentec.abook.abv.ui.viewer.foxitPdf.FoxitPdfCore; import jp.agentec.abook.abv.ui.viewer.foxitPdf.FoxitPdfCore;
import jp.agentec.adf.util.DateTimeUtil; import jp.agentec.adf.util.DateTimeUtil;
import jp.agentec.adf.util.FileUtil; import jp.agentec.adf.util.FileUtil;
...@@ -139,6 +139,12 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -139,6 +139,12 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
public int mButtonStatus; // 保存ボタンチェック public int mButtonStatus; // 保存ボタンチェック
protected boolean mAddReport; // 作業追加区分 protected boolean mAddReport; // 作業追加区分
// 編集
protected String mEditFilePath; //再編集する時、Webからもらうクリックしたイメージファイルのパス
protected String mEnablePhotoEdit; //最初ファイルをアップロードする時、Webからもらう編集可能可否のパラメタ 0:編集する。 1:編集しない。
protected PhotoEditActivity mPhotoEditDialog;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
...@@ -859,6 +865,7 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -859,6 +865,7 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
} }
public void commonShouldOverrideUrlLoading (Uri uri, OperationTaskDto operationTaskDto) { public void commonShouldOverrideUrlLoading (Uri uri, OperationTaskDto operationTaskDto) {
Logger.d(TAG, "Uri : %s", uri);
//parent method //parent method
Map<String, String> abookCheckParam = new HashMap<String, String>(); Map<String, String> abookCheckParam = new HashMap<String, String>();
for (String key : uri.getQueryParameterNames()) { for (String key : uri.getQueryParameterNames()) {
...@@ -1000,7 +1007,14 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -1000,7 +1007,14 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
} }
} else if (mCmd.equals(ABookKeys.CMD_SAVE_ATTACHED)) { } else if (mCmd.equals(ABookKeys.CMD_SAVE_ATTACHED)) {
mAttachedFileName = abookCheckParam.get(ABookKeys.FILE_NAME); mAttachedFileName = abookCheckParam.get(ABookKeys.FILE_NAME);
//編集可否を判別
mEnablePhotoEdit = abookCheckParam.get(ABookKeys.EDITABLE) == null ? "0" : abookCheckParam.get(ABookKeys.EDITABLE); //添付したファイルの編集可否のパラメタ
getAttachedDataUrl(); getAttachedDataUrl();
} else if (mCmd.equals(ABookKeys.CMD_EDIT_ATTACHED)){
//編集パラメタ(
mAttachedFileName = abookCheckParam.get(ABookKeys.FILE_NAME); //再編集するファイルの名
mEditFilePath = abookCheckParam.get(ABookKeys.FILE_PATH); //再編集するファイルのパス
openEditPage();
} else if (mCmd.equals(ABookKeys.CMD_SHOW_REPORT_OZD)) { } else if (mCmd.equals(ABookKeys.CMD_SHOW_REPORT_OZD)) {
mReportFileName = abookCheckParam.get(ABookKeys.REPORT_FILE_NAME); mReportFileName = abookCheckParam.get(ABookKeys.REPORT_FILE_NAME);
// 作業報告画面改善 // 作業報告画面改善
...@@ -1084,6 +1098,10 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -1084,6 +1098,10 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
FileUtil.delete(mLocalFile); FileUtil.delete(mLocalFile);
} }
mLocalFile = null; mLocalFile = null;
//編集可能可否をチェックする。
if (mEnablePhotoEdit.equals("1")) { //編集の場合、編集画面を開ける。
commonOpenEditPage();
}
} catch (Exception e) { } catch (Exception e) {
isError = true; isError = true;
Logger.e(TAG, e); Logger.e(TAG, e);
...@@ -1151,6 +1169,10 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -1151,6 +1169,10 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
}); });
} }
public void openEditPage(){
webViewLoadUrl("javascript:android.openEditPage()");
}
/** /**
* 位置情報取得 * 位置情報取得
* @param showPermissionDialogFlg * @param showPermissionDialogFlg
...@@ -1308,4 +1330,59 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -1308,4 +1330,59 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
} }
} }
} }
/**
* 下位のWebviewからコールする編集画面
*/
public void commonOpenEditPage(){
//ダイアローグの上、新しいViewを作るため、Threadで実行する。Threadを使わない場合エラー発 生
runOnUiThread(new Runnable() {
@Override
public void run() {
//添付した写真はTempパスにある。
//保存されてある写真はoperationフォルダにある。
//保存されてある写真を修正する場合、ファイルをTempにコピーした後に編集する。
final String tempImagePath = ABVEnvironment.getInstance().getTempFilePath(getContentId(), mTaskKey, mAttachedFileName); //添付ファイルのTempパス
String savedImagePath = ABVEnvironment.getInstance().getOperationTaskReportLevelDirPath(mOperationId, mTaskKey,0) + "/" + mAttachedFileName; //保存された写真のパス
//保存されたイメージがあり、編集したいイメージのパスがTempがない場合、ファイルをTempにコピー
// パスがTempと一緒の場合、イメージが一回以上修正され、上書きしない。
if(FileUtil.exists(savedImagePath) && !mEditFilePath.equals(tempImagePath)){
try {
FileUtil.copy(savedImagePath,tempImagePath,true); //保存されたファイルをTempパスにコピー
} catch (IOException e) {
e.printStackTrace();
return;
}
}
if(FileUtil.exists(tempImagePath)){ //Tempパスにファイルがある場合、編集を実行
//編集画面をコール
PhotoEditActivity dialog = new PhotoEditActivity(ABVContentViewActivity.this, tempImagePath); //ロードして編集するイメージのパスを設定
showPhotoEditDialog(dialog);
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
//Web Cacheが更新されて編集画面のダイアローグ閉じた後、JavascriptにてImgタグを再生成してThumbnailを更新
webViewLoadUrl(String.format("javascript:document.getElementById('reportForm').contentWindow.changeThumbnailTagForAnd('%s','%s')", mAttachedFileName, tempImagePath));
afterABookCheckApi(mCmd, mTaskKey, 0, "", null);
mPhotoEditDialog = null;
}
});
}
else{
Logger.e(TAG, "image file is not found");
showSimpleAlertDialog(R.string.error,R.string.msg_error_edit_page_open);
}
}
});
}
protected void showPhotoEditDialog(PhotoEditActivity photoEditDialog) {
if (this.mPhotoEditDialog != null) {
this.mPhotoEditDialog.dismiss();
}
else {
this.mPhotoEditDialog = photoEditDialog;
this.mPhotoEditDialog.show();
}
}
} }
...@@ -62,8 +62,11 @@ public interface AppDefType { ...@@ -62,8 +62,11 @@ public interface AppDefType {
String OPERATION_REPORT_TYPES = "operationReportTypes"; String OPERATION_REPORT_TYPES = "operationReportTypes";
String RESOURCE_PATTERN_TYPE = "resourcePatternType"; // 文言リソースパターン String RESOURCE_PATTERN_TYPE = "resourcePatternType"; // 文言リソースパターン
String OPERATION_GROUP_MASERT_MODE = "operation_group_master"; // 通常・作業種別モード(画面) String OPERATION_GROUP_MASERT_MODE = "operation_group_master"; // 通常・作業種別モード(画面)
String OPERATION_GROUP_MASERT_ID = "operation_group_master_id"; // 作業種別のID String OPERATION_GROUP_MASERT_ID = "operation_group_master_id"; // 作業種別のID
String APERTURE_MASTER_DATA_FETCH_DATE = "apertureMasterDataFetchDate"; // 絞り検索マスタデータのFetchDate
} }
/** /**
......
...@@ -54,7 +54,9 @@ import java.util.List; ...@@ -54,7 +54,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import jp.agentec.abook.abv.bl.acms.client.AcmsClient; import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.ApertureMasterDataJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationDataJSON; import jp.agentec.abook.abv.bl.acms.client.json.OperationDataJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetApertureMasterDataParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.GetOperationDataParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.GetOperationDataParameters;
import jp.agentec.abook.abv.bl.acms.type.DownloadStatusType; import jp.agentec.abook.abv.bl.acms.type.DownloadStatusType;
import jp.agentec.abook.abv.bl.acms.type.OperationType; import jp.agentec.abook.abv.bl.acms.type.OperationType;
...@@ -88,6 +90,7 @@ import jp.agentec.abook.abv.bl.dto.TaskDto; ...@@ -88,6 +90,7 @@ import jp.agentec.abook.abv.bl.dto.TaskDto;
import jp.agentec.abook.abv.bl.dto.TaskReportDto; import jp.agentec.abook.abv.bl.dto.TaskReportDto;
import jp.agentec.abook.abv.bl.logic.AbstractLogic; import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.OperationGroupMasterLogic; import jp.agentec.abook.abv.bl.logic.OperationGroupMasterLogic;
import jp.agentec.abook.abv.bl.logic.ApertureMasterDataLogic;
import jp.agentec.abook.abv.bl.logic.OperationLogic; import jp.agentec.abook.abv.bl.logic.OperationLogic;
import jp.agentec.abook.abv.bl.logic.PushMessageLogic; import jp.agentec.abook.abv.bl.logic.PushMessageLogic;
import jp.agentec.abook.abv.cl.util.PreferenceUtil; import jp.agentec.abook.abv.cl.util.PreferenceUtil;
...@@ -199,6 +202,11 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -199,6 +202,11 @@ public class OperationListActivity extends ABVUIActivity {
// 作業種別のサービスオプション値を保持用フラグ // 作業種別のサービスオプション値を保持用フラグ
private boolean mOperationGroupMasterServiceOperationFlg; private boolean mOperationGroupMasterServiceOperationFlg;
// 絞り検索マスタデータ
// private Date mApertureLastEditDate;
// 絞り検索マスタLogic
private ApertureMasterDataLogic mApertureMasterDataLogic = AbstractLogic.getLogic(ApertureMasterDataLogic.class);
// ビューの作成 // ビューの作成
private class ReloadHandler implements Runnable { private class ReloadHandler implements Runnable {
@Override @Override
...@@ -465,6 +473,9 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -465,6 +473,9 @@ public class OperationListActivity extends ABVUIActivity {
getABVUIDataCache().saveLastUpdateTime(); getABVUIDataCache().saveLastUpdateTime();
// リソースパターンを取得し、ローカルに保存する。 // リソースパターンを取得し、ローカルに保存する。
setResourcePattern(); setResourcePattern();
// 絞り検索マスタデータ最新更新する時fetchDateをローカルに保存する。
setApertureMasterDataFetchDate();
if (mOperationGroupMasterServiceOperationFlg == ABVDataCache.getInstance().serviceOption.isOperationGroupMaster()) { if (mOperationGroupMasterServiceOperationFlg == ABVDataCache.getInstance().serviceOption.isOperationGroupMaster()) {
// サービスオプションが変わってない場合は、ビューは作らずにデータのみ更新 // サービスオプションが変わってない場合は、ビューは作らずにデータのみ更新
refreshOperationList(); refreshOperationList();
...@@ -718,6 +729,9 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -718,6 +729,9 @@ public class OperationListActivity extends ABVUIActivity {
mOperationLogic.createJsonForOpenABookCheckPano(operationDto.operationId, operationDto.contentId, contentPath); mOperationLogic.createJsonForOpenABookCheckPano(operationDto.operationId, operationDto.contentId, contentPath);
mOperationLogic.createJsonForOperationContent(operationDto.operationId, contentPath, operationDto.reportType == ReportType.RoutineTask); mOperationLogic.createJsonForOperationContent(operationDto.operationId, contentPath, operationDto.reportType == ReportType.RoutineTask);
// 絞り検索マスタデータを受信、JSONファイルを生成
mApertureMasterDataLogic.initializeApertureMasterData(ABVDataCache.getInstance().getTempApertureMasterDataFetchDate());
// サーバ作業後、対応必要 // サーバ作業後、対応必要
StringBuffer path = new StringBuffer(); StringBuffer path = new StringBuffer();
path.append(contentPath); path.append(contentPath);
...@@ -2161,4 +2175,11 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -2161,4 +2175,11 @@ public class OperationListActivity extends ABVUIActivity {
break; break;
} }
} }
/**
* 絞り検索の日付を設定
*/
private void setApertureMasterDataFetchDate() {
putUserPref(AppDefType.UserPrefKey.APERTURE_MASTER_DATA_FETCH_DATE, ABVDataCache.getInstance().getTempApertureMasterDataFetchDate());
}
} }
...@@ -486,6 +486,11 @@ public class HTMLWebViewActivity extends ParentWebViewActivity { ...@@ -486,6 +486,11 @@ public class HTMLWebViewActivity extends ParentWebViewActivity {
public void getAttachedDataUrl(String data) { public void getAttachedDataUrl(String data) {
commonAttachedDataUrl(data); commonAttachedDataUrl(data);
} }
@JavascriptInterface
public void openEditPage() {
commonOpenEditPage();
}
} }
@Override @Override
......
...@@ -525,6 +525,11 @@ public class HTMLXWalkWebViewActivity extends ParentWebViewActivity { ...@@ -525,6 +525,11 @@ public class HTMLXWalkWebViewActivity extends ParentWebViewActivity {
public void getAttachedDataUrl(String data) { public void getAttachedDataUrl(String data) {
commonAttachedDataUrl(data); commonAttachedDataUrl(data);
} }
@org.xwalk.core.JavascriptInterface
public void openEditPage() {
commonOpenEditPage();
}
} }
@Override @Override
......
...@@ -5,7 +5,6 @@ import android.content.Context; ...@@ -5,7 +5,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.net.Uri; import android.net.Uri;
import android.view.View; import android.view.View;
import android.webkit.ValueCallback;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
...@@ -34,7 +33,6 @@ import jp.agentec.abook.abv.cl.util.ContentLogUtil; ...@@ -34,7 +33,6 @@ import jp.agentec.abook.abv.cl.util.ContentLogUtil;
import jp.agentec.abook.abv.cl.util.PreferenceUtil; import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.launcher.android.R; import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.activity.ABVContentViewActivity; import jp.agentec.abook.abv.ui.common.activity.ABVContentViewActivity;
import jp.agentec.abook.abv.ui.common.activity.ABVUIActivity;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType; import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.abook.abv.ui.common.constant.ErrorCode; import jp.agentec.abook.abv.ui.common.constant.ErrorCode;
import jp.agentec.abook.abv.ui.common.constant.ErrorMessage; import jp.agentec.abook.abv.ui.common.constant.ErrorMessage;
...@@ -136,13 +134,17 @@ public class ParentWebViewActivity extends ABVContentViewActivity { ...@@ -136,13 +134,17 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
boolean isError = false; boolean isError = false;
if (data != null) { if (data != null) {
try { try {
String imagePath = ABVEnvironment.getInstance().getTempFilePath(getContentId(), mTaskKey, mAttachedFileName); final String imagePath = ABVEnvironment.getInstance().getTempFilePath(getContentId(), mTaskKey, mAttachedFileName);
ABookCheckWebViewHelper.getInstance().decodeToImage(data, imagePath); ABookCheckWebViewHelper.getInstance().decodeToImage(data, imagePath);
//アプリ内のファイルのみ削除(Galleryファイルは削除しない) //アプリ内のファイルのみ削除(Galleryファイルは削除しない)
if (mLocalFile != null && mLocalFile.getPath().contains(getPackageName())) { if (mLocalFile != null && mLocalFile.getPath().contains(getPackageName())) {
FileUtil.delete(mLocalFile); FileUtil.delete(mLocalFile);
} }
mLocalFile = null; mLocalFile = null;
//編集可能可否をチェックする。
if (mEnablePhotoEdit == 0) { //編集の場合、編集画面を開ける。
commonOpenEditPage();
}
} catch (Exception e) { } catch (Exception e) {
isError = true; isError = true;
Logger.e(TAG, e); Logger.e(TAG, e);
......
package jp.agentec.abook.abv.ui.viewer.activity;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.text.Layout;
import android.util.Base64;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
public class PhotoEditActivity extends Dialog {
private final String TAG = "PhotoEditActivity";
private final String editToolPath = "file:///android_asset/Drawing/index.html"; //EditToolのパス。
private final String editToolName = "Drawing"; //EditToolの名
private WebView editPageWebView; //ダイアローグの上に編集画面をロード
private Bitmap bitmapOfPhoto; //写真のBitmap情報
private int photoWidth;
private int photoHeight;
private String photoFilePath; //ロードして修正するファイルのパス
private JsInf jsInf = new JsInf(); //Javascript Interface
private Context context;
private int screenRequestedOrientation;
/**
* 生成される時、イメージパスを取得。
* @param context
* @param photoFilePath イメージパス
*/
public PhotoEditActivity(Context context, String photoFilePath){
super(context);
this.context = context;
this.photoFilePath = photoFilePath;
}
/**
* ダイアローグが生成される時の処理
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//ダイアローグの設定
screenRequestedOrientation = ((Activity) context).getRequestedOrientation(); //編集前の画面方向を保存する。
((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); // 縦画面固定
requestWindowFeature(Window.FEATURE_NO_TITLE); //ダイアローグのタイトルバーを消す
setContentView(R.layout.photo_edit_dialog); //ダイアローグにWebViewを設定
setCanceledOnTouchOutside(false); //編集画面の外をタッチしてもダイアローグを閉じらないようにする。
//写真のBitmapをロード
bitmapOfPhoto = BitmapFactory.decodeFile(photoFilePath);
photoWidth = bitmapOfPhoto.getWidth();
photoHeight = bitmapOfPhoto.getHeight();
//ダイアローグのサイズの設定
//画面のサイズを取得
Rect displayRectangle = new Rect();
Window window = ((Activity) context).getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(displayRectangle);
//画面の方向によってダイアローグのサイズを決定
int screenOrientation = context.getResources().getConfiguration().orientation; //画面の方向
switch (screenOrientation){
case Surface.ROTATION_0: case Surface.ROTATION_180: //横
float photoRatio = (float) photoWidth / (float) photoHeight; //画面の横と縦の割合
float displayHeight = displayRectangle.height() * 0.9f; //画面の縦幅の90パーセント
float displayWidth = displayRectangle.width() * 0.9f; //画面の縦幅の90パーセント
int dialogWidth = (int) (displayHeight * photoRatio); //ダイアローグの横幅を計算
if(dialogWidth < displayWidth){ //計算した横幅が画面より小さい場合はそのまま設定
getWindow().setLayout(dialogWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
}
else{ //計算した横幅が画面より大き場合は画面の横幅に設定
getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
break;
case Surface.ROTATION_90: case Surface.ROTATION_270: //縦
//画面方向が縦の場合、画面の横幅に設定
getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
break;
}
//クローズボタン
final ImageButton imageButton = findViewById(R.id.edit_page_close_btn);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss();
}
});
//WebViewの設定
editPageWebView = findViewById(R.id.edit_webview);
editPageWebView.setOverScrollMode(View.OVER_SCROLL_NEVER); //オーバースクロールしない。
editPageWebView.setVerticalScrollBarEnabled(false); //スクロールバーを消す。
editPageWebView.addJavascriptInterface(jsInf, "android");
editPageWebView.getSettings().setJavaScriptEnabled(true); //Javascriptを有効にする。
editPageWebView.setVisibility(View.GONE);
//ページをロード
editPageWebView.loadUrl(editToolPath);
editPageWebView.reload();
editPageWebView.setVisibility(View.VISIBLE);
editPageWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
//ページロードの終了後の処理
super.onPageFinished(view, url);
if (url.contains(editToolName)) {
//最初ページロードの場合
//ページロードの終了後のCanvas設定
float photoRatio = (float) (bitmapOfPhoto.getHeight()) / (float) bitmapOfPhoto.getWidth(); //写真の割合を計算
view.loadUrl(String.format("javascript:resizeCanvasToRatio(%f);", photoRatio)); //写真の割合によってCanvasのサイズを決める。
view.loadUrl(String.format("javascript:setBackground('%s');", photoFilePath)); //Canvasの背景をイメージにする
}
}
});
editPageWebView.setDownloadListener(new DownloadListener() {
/**
* ダウンロードが始まるときの処理。urlとcontentLength以外のパラメタは内容がない。
* @param url HTML CanvasのBase 64情報
* @param contentLength urlの長さ
*/
@Override
public void onDownloadStart(String url, String userAgent,
String contentDisposition, String mimeType,
long contentLength) {
//編集画面の保存ボタンを押した時の処理
//手書きのイメージの処理
//手書きのBase64情報をBitmapに変換
Bitmap bitmapOfDrawing = decodeBase64ToBitmap(url);
//写真と手書きを合わせる
Bitmap completeImage = mergeImages(bitmapOfDrawing, bitmapOfPhoto);
//合わせたイメージをphotoFilePathに保存
boolean isError = saveBitmapToPath(completeImage, photoFilePath, 100);
if (isError) {
//保存中にエラーが発生した場合、既存ファイルを保存する。
saveBitmapToPath(bitmapOfPhoto, photoFilePath, 100);
//保存失敗のアラートを表示する。
showAlertDialog(R.string.error,R.string.msg_error_edit_page_save, false);
} else {
//保存中にエラーがない場合、進行する。
bitmapOfPhoto.recycle(); //メモリーでBitmapを解放
editPageWebView.loadUrl(photoFilePath); //編集画面のダイアローグが閉じる前、イメージファイルをロードしてWeb Cacheを更新したら、作業報告画面の画像が更新される。
superDismiss(); //ダウンロードの後はすぐ終了。
}
}
});
}
@Override
public void show() {
super.show();
//HTMLファイルがない場合アラートを表示して終了
//TODO: EditToolのパスが決まるとFileUtil.exists(editToolPath)に変える
//現在Edit ToolがAssetsフォルダにあり、FileUtil.exists(editToolPath)を使えない。
//下記のコードはパスが変わるる場合、削除予定
try {
if (context.getResources().getAssets().list(editToolName).length == 0){
showAlertDialog(R.string.error,R.string.msg_error_edit_page_open,false);
}
} catch (IOException e) {
e.printStackTrace();
}
//下記のコードは後他のパスに移動する場合、コメント解除予定
// if (!FileUtil.exists(editToolPath)){
// showAlertDialog(R.string.error,R.string.opening_file_error,false);
// }
}
/**
* ダイアローグを閉じる。
*/
private void superDismiss(){
super.dismiss();
((Activity) context).setRequestedOrientation(screenRequestedOrientation); // 縦画面固定
}
/**
* クローズボタンやバックボタンを押す時、閉じらなく、編集画面終了を確認するアラートを表示する。
*/
@Override
public void dismiss() {
editPageWebView.loadUrl("javascript:android.confirmDismiss(isCanvasEdited());"); //修正可否をJavascriptで確認
}
private class JsInf {
/**
* 修正がない場合、ダイアローグがなく終了
* @param isCanvasEdited イメージが修正されたか
*/
@JavascriptInterface
public void confirmDismiss(boolean isCanvasEdited){
if (isCanvasEdited) {
//修正事項がある場合、確認ダイアローグを開ける。
showAlertDialog(R.string.end, R.string.msg_confirm_close_edit_page, true); //ダイアローグの開ける。
}
else{
//修正事項がない場合、すぐ終了。
superDismiss();
}
}
}
/**
* 終了確認ダイアローグを開ける。
* @param title ダイアローグのタイトル
* @param message ダイアローグのメッセージ
* @param cancellable キャンサル可能可否
*/
private void showAlertDialog(int title, int message, boolean cancellable){
ABookAlertDialog aBookAlertDialog = AlertDialogUtil.createAlertDialog(context, title, message);
aBookAlertDialog.setIcon(R.drawable.icon);
aBookAlertDialog.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//OKボタンを押すと編集画面を閉じる
superDismiss();
}
});
if (cancellable) {
//キャンセルボタンを押すと、何もしない。
aBookAlertDialog.setNegativeButton(R.string.cancel, null);
}
aBookAlertDialog.show();
}
/**
* Base64のイメージ情報をBitmapに変換する。
* @param url Base64のイメージ情報
* @return 完成されたBitmap
*/
private Bitmap decodeBase64ToBitmap(String url){
url = url.split(",")[1];
String base64EncodedString = url.replace(" ", "+");
byte[] decodedBytes = Base64.decode(base64EncodedString, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
}
/**
* Bitmapをpathに保存する。
* @param bitmap 保存するBitmap
* @param path 保存するパス
*/
private boolean saveBitmapToPath(Bitmap bitmap, String path, int quality){
try {
OutputStream os = new FileOutputStream(new File(path));
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os);
os.flush();
os.close();
bitmap.recycle();
} catch (Exception e) {
e.printStackTrace();
return true;
}
return false;
}
/**
* 写真2枚を合わせる
* @param topImage 上のイメージ
* @param bottomImage 下のイメージ
* @return 合わせたイメージを返す。
*/
private Bitmap mergeImages(Bitmap topImage, Bitmap bottomImage) {
topImage = scaleBitmap(topImage, bottomImage.getWidth(), bottomImage.getHeight());
Bitmap result = Bitmap.createBitmap(bottomImage.getWidth(), bottomImage.getHeight(), Bitmap.Config.ARGB_8888);
//Canvasに2枚のイメージを描く。
Canvas canvas = new Canvas(result);
canvas.drawBitmap(bottomImage, 0f, 0f, null);
canvas.drawBitmap(topImage, 0f, 0f, null);
topImage.recycle();
return result;
}
/**
* Bitmapのサイズを変更
* @param bitmap 変更するイメージ
* @param newWidth 新しい横幅
* @param newHeight 新しい縦幅
* @return 変更したイメージを返す。
*/
public static Bitmap scaleBitmap(Bitmap bitmap, int newWidth, int newHeight) {
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
float scaleX = newWidth / (float) bitmap.getWidth();
float scaleY = newHeight / (float) bitmap.getHeight();
float pivotX = 0;
float pivotY = 0;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(scaleX, scaleY, pivotX, pivotY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, 0, 0, null);
return scaledBitmap;
}
}
\ No newline at end of file
...@@ -533,5 +533,10 @@ public class OperationTaskLayout extends RelativeLayout { ...@@ -533,5 +533,10 @@ public class OperationTaskLayout extends RelativeLayout {
public void getAttachedDataUrl(String data) { public void getAttachedDataUrl(String data) {
((ABVContentViewActivity) OperationTaskLayout.this.mContext).commonAttachedDataUrl(data); ((ABVContentViewActivity) OperationTaskLayout.this.mContext).commonAttachedDataUrl(data);
} }
@JavascriptInterface
public void openEditPage() {
((ABVContentViewActivity) OperationTaskLayout.this.mContext).commonOpenEditPage();
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment