Add some HTML design to export, except messages.

NB Testing the layout, the app is not in a working condition.
This commit is contained in:
John Preston 2018-06-29 22:10:21 +01:00
parent e466dc9fc7
commit 66822f7333
29 changed files with 1280 additions and 348 deletions

View File

@ -1,3 +0,0 @@
.page_wrap {
background-color: #fff;
}

View File

@ -0,0 +1,247 @@
body {
margin: 0;
font: 12px/18px 'Open Sans',"Lucida Grande","Lucida Sans Unicode",Arial,Helvetica,Verdana,sans-serif;
}
.clearfix:after {
content: " ";
visibility: hidden;
display: block;
height: 0;
clear: both;
}
.pull_left {
float: left;
}
.pull_right {
float: right;
}
.page_wrap {
background-color: #ffffff;
color: #000000;
}
.page_wrap a {
color: #168acd;
text-decoration: none;
}
.page_wrap a:hover {
text-decoration: underline;
}
.page_header {
position: fixed;
background-color: #ffffff;
width: 100%;
border-bottom: 1px solid #e3e6e8;
}
.page_header .content {
width: 480px;
margin: 0 auto;
border-radius: 0 !important;
}
.page_header a.content {
background-image: url(../images/back.png);
background-repeat: no-repeat;
background-position: 24px 21px;
background-size: 24px 24px;
}
.bold {
color: #212121;
}
.details {
color: #70777b;
}
.page_header .content .text {
padding: 24px 24px 22px 24px;
font-size: 22px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.page_header a.content .text {
padding: 24px 24px 22px 82px;
}
.page_body {
padding-top: 64px;
width: 480px;
margin: 0 auto;
}
.page_about {
padding: 24px 24px;
}
.with_divider {
border-top: 1px solid #e3e6e8;
}
.userpic_link {
display: block;
text-decoration: none;
}
.userpic_link:hover {
text-decoration: none;
}
.userpic {
display: block;
border-radius: 50%;
overflow: hidden;
}
.userpic .initials {
display: block;
color: #fff;
text-align: center;
text-transform: uppercase;
user-select: none;
}
.userpic1 {
background-color: #ff5555;
}
.userpic2 {
background-color: #64bf47;
}
.userpic3 {
background-color: #ffab00;
}
.userpic4 {
background-color: #4f9cd9;
}
.userpic5 {
background-color: #9884e8;
}
.userpic6 {
background-color: #e671a5;
}
.userpic7 {
background-color: #47bcd1;
}
.userpic8 {
background-color: #ff8c44;
}
.personal_info {
padding: 24px;
}
.personal_info .userpic .initials {
font-size: 30px;
}
.personal_info .rows {
float: left;
padding-right: 24px;
}
.personal_info .names {
width: 164px;
}
.personal_info .info {
width: 124px;
}
.personal_info .bio {
width: 400px;
}
.personal_info .row {
padding-bottom: 16px;
}
a.block_link {
display: block;
text-decoration: none !important;
border-radius: 4px;
}
a.block_link:hover {
text-decoration: none !important;
background-color: #f5f7f8;
}
.sections {
padding: 11px 0;
}
.section {
height: 48px;
background-position: 24px 12px;
background-repeat: no-repeat;
background-size: 24px 24px;
}
.section .counter {
float: right;
padding: 14px 24px 0;
font-size: 15px;
}
.section .label {
padding: 15px 0 0 82px;
font-size: 15px;
font-weight: 700;
}
.section.calls {
background-image: url(../images/calls.png);
}
.section.chats {
background-image: url(../images/chats.png);
}
.section.contacts {
background-image: url(../images/contacts.png);
}
.section.frequent {
background-image: url(../images/frequent.png);
}
.section.photos {
background-image: url(../images/photos.png);
}
.section.sessions {
background-image: url(../images/sessions.png);
}
.section.web {
background-image: url(../images/web.png);
}
@media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
.section.calls {
background-image: url(../images/calls@2x.png);
}
.section.chats {
background-image: url(../images/chats@2x.png);
}
.section.contacts {
background-image: url(../images/contacts@2x.png);
}
.section.frequent {
background-image: url(../images/frequent@2x.png);
}
.section.photos {
background-image: url(../images/photos@2x.png);
}
.section.sessions {
background-image: url(../images/sessions@2x.png);
}
.section.web {
background-image: url(../images/web@2x.png);
}
.page_header a.content {
background-image: url(../images/back@2x.png);
}
}
.list_page .page_about {
padding: 16px 24px 0;
font-size: 11px;
}
.list_page .entry_list {
padding: 16px 0;
}
.list_page .entry {
padding: 10px 16px;
}
.list_page .entry .userpic .initials {
font-size: 18px;
}
.list_page .entry .body {
margin-left: 66px;
}
.list_page .entry .name {
padding: 4px 0 2px;
font-weight: 700;
font-size: 14px;
}
.list_page .entry .subname {
padding-top: 4px;
}
.list_page .entry .details_entry {
padding-top: 4px;
}
.list_page .entry .info {
font-size: 11px;
padding-top: 5px;
}
.history {
padding: 16px 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

View File

@ -1,6 +1,22 @@
<RCC>
<qresource prefix="/export">
<file alias="css/style.css">../css/export_style.css</file>
<file alias="css/style.css">../export_html/css/style.css</file>
<file alias="images/back.png">../export_html/images/back.png</file>
<file alias="images/back@2x.png">../export_html/images/back@2x.png</file>
<file alias="images/calls.png">../export_html/images/calls.png</file>
<file alias="images/calls@2x.png">../export_html/images/calls@2x.png</file>
<file alias="images/chats.png">../export_html/images/chats.png</file>
<file alias="images/chats@2x.png">../export_html/images/chats@2x.png</file>
<file alias="images/contacts.png">../export_html/images/contacts.png</file>
<file alias="images/contacts@2x.png">../export_html/images/contacts@2x.png</file>
<file alias="images/frequent.png">../export_html/images/frequent.png</file>
<file alias="images/frequent@2x.png">../export_html/images/frequent@2x.png</file>
<file alias="images/photos.png">../export_html/images/photos.png</file>
<file alias="images/photos@2x.png">../export_html/images/photos@2x.png</file>
<file alias="images/sessions.png">../export_html/images/sessions.png</file>
<file alias="images/sessions@2x.png">../export_html/images/sessions@2x.png</file>
<file alias="images/web.png">../export_html/images/web.png</file>
<file alias="images/web@2x.png">../export_html/images/web@2x.png</file>
</qresource>
<qresource prefix="/gui">
<file alias="fonts/OpenSans-Regular.ttf">../fonts/OpenSans-Regular.ttf</file>

View File

@ -8,15 +8,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/data/export_data_types.h"
#include "export/export_settings.h"
#include "export/output/export_output_file.h"
#include "core/mime_type.h"
#include <QtCore/QDateTime>
#include <QtCore/QRegularExpression>
#include <QtGui/QImageReader>
namespace App { // Hackish..
QString formatPhone(QString phone);
} // namespace App
QString FillAmountAndCurrency(uint64 amount, const QString &currency);
QString formatSizeText(qint64 size);
namespace Export {
namespace Data {
@ -24,6 +27,7 @@ namespace {
constexpr auto kUserPeerIdShift = (1ULL << 32);
constexpr auto kChatPeerIdShift = (2ULL << 32);
constexpr auto kMaxImageSize = 10000;
} // namespace
@ -39,6 +43,43 @@ int32 BarePeerId(PeerId peerId) {
return int32(peerId & 0xFFFFFFFFULL);
}
int PeerColorIndex(int32 bareId) {
const auto index = std::abs(bareId) % 7;
const int map[] = { 0, 7, 4, 1, 6, 3, 5 };
return map[index];
}
int StringBarePeerId(const Utf8String &data) {
auto result = 0xFF;
for (const auto ch : data) {
result *= 239;
result += ch;
result &= 0xFF;
}
return result;
}
int ApplicationColorIndex(int applicationId) {
static const auto official = std::map<int, int> {
{ 1, 0 }, // iOS
{ 7, 0 }, // iOS X
{ 6, 1 }, // Android
{ 21724, 1 }, // Android X
{ 2834, 2 }, // macOS
{ 2496, 3 }, // Webogram
{ 2040, 4 }, // Desktop
{ 1429, 5 }, // Windows Phone
};
if (const auto i = official.find(applicationId); i != end(official)) {
return i->second;
}
return PeerColorIndex(applicationId);
}
int DomainApplicationId(const Utf8String &data) {
return 0x1000 + StringBarePeerId(data);
}
bool IsChatPeerId(PeerId peerId) {
return (peerId & kChatPeerIdShift) == kChatPeerIdShift;
}
@ -440,6 +481,43 @@ UserpicsSlice ParseUserpicsSlice(
return result;
}
QString WriteImageThumb(
const QString &basePath,
const QString &largePath,
int width,
int height,
const QString &postfix) {
if (largePath.isEmpty()) {
return QString();
}
const auto path = basePath + largePath;
QImageReader reader(path);
if (!reader.canRead()) {
return QString();
}
const auto size = reader.size();
if (size.isEmpty()
|| size.width() >= kMaxImageSize
|| size.height() >= kMaxImageSize) {
return QString();
}
auto image = reader.read();
if (image.isNull()) {
return QString();
}
const auto format = reader.format();
const auto lastSlash = largePath.lastIndexOf('/');
const auto firstDot = largePath.indexOf('.', lastSlash + 1);
const auto thumb = (firstDot >= 0)
? largePath.mid(0, firstDot) + postfix + largePath.mid(firstDot)
: largePath + postfix;
const auto result = Output::File::PrepareRelativePath(basePath, thumb);
if (!image.save(basePath + result, reader.format(), reader.quality())) {
return QString();
}
return result;
}
ContactInfo ParseContactInfo(const MTPUser &data) {
auto result = ContactInfo();
data.match([&](const MTPDuser &data) {
@ -459,6 +537,13 @@ ContactInfo ParseContactInfo(const MTPUser &data) {
return result;
}
int ContactColorIndex(const ContactInfo &data) {
if (data.userId != 0) {
return PeerColorIndex(data.userId);
}
return PeerColorIndex(StringBarePeerId(data.phoneNumber));
}
User ParseUser(const MTPUser &data) {
auto result = User();
result.info = ParseContactInfo(data);
@ -1066,6 +1151,7 @@ bool AppendTopPeers(ContactsList &to, const MTPcontacts_TopPeers &data) {
Session ParseSession(const MTPAuthorization &data) {
return data.match([&](const MTPDauthorization &data) {
auto result = Session();
result.applicationId = data.vapi_id.v;
result.platform = ParseString(data.vplatform);
result.deviceModel = ParseString(data.vdevice_model);
result.systemVersion = ParseString(data.vsystem_version);
@ -1170,7 +1256,12 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
info.type = peer.user()
? DialogTypeFromUser(*peer.user())
: DialogTypeFromChat(*peer.chat());
info.name = peer.name();
info.name = peer.user()
? peer.user()->info.firstName
: peer.name();
info.lastName = peer.user()
? peer.user()->info.lastName
: Utf8String();
info.input = peer.input();
}
info.topMessageId = fields.vtop_message.v;
@ -1297,5 +1388,9 @@ Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency) {
QString::fromUtf8(currency)).toUtf8();
}
Utf8String FormatFileSize(int64 size) {
return formatSizeText(size).toUtf8();
}
} // namespace Data
} // namespace Export

View File

@ -26,6 +26,9 @@ using PeerId = uint64;
PeerId UserPeerId(int32 userId);
PeerId ChatPeerId(int32 chatId);
int32 BarePeerId(PeerId peerId);
int PeerColorIndex(int32 bareId);
int ApplicationColorIndex(int applicationId);
int DomainApplicationId(const Utf8String &data);
Utf8String ParseString(const MTPstring &data);
@ -77,6 +80,13 @@ struct Image {
File file;
};
QString WriteImageThumb(
const QString &basePath,
const QString &largePath,
int width,
int height,
const QString &postfix = "_thumb");
struct ContactInfo {
int32 userId = 0;
Utf8String firstName;
@ -88,6 +98,7 @@ struct ContactInfo {
};
ContactInfo ParseContactInfo(const MTPUser &data);
int ContactColorIndex(const ContactInfo &data);
struct Photo {
uint64 id = 0;
@ -230,6 +241,7 @@ std::vector<int> SortedContactsIndices(const ContactsList &data);
bool AppendTopPeers(ContactsList &to, const MTPcontacts_TopPeers &data);
struct Session {
int applicationId = 0;
Utf8String platform;
Utf8String deviceModel;
Utf8String systemVersion;
@ -485,6 +497,7 @@ struct DialogInfo {
};
Type type = Type::Unknown;
Utf8String name;
Utf8String lastName;
MTPInputPeer input = MTP_inputPeerEmpty();
int32 topMessageId = 0;
@ -526,14 +539,13 @@ MessagesSlice ParseMessagesSlice(
const QString &mediaFolder);
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
Utf8String FormatDateTime(
TimeId date,
QChar dateSeparator = QChar('.'),
QChar timeSeparator = QChar(':'),
QChar separator = QChar(' '));
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency);
Utf8String FormatFileSize(int64 size);
} // namespace Data
} // namespace Export

View File

@ -56,19 +56,11 @@ std::unique_ptr<AbstractWriter> CreateWriter(Format format) {
Unexpected("Format in Export::Output::CreateWriter.");
}
Stats AbstractWriter::produceTestExample(const QString &path) {
Stats AbstractWriter::produceTestExample(
const QString &path,
const Environment &environment) {
auto result = Stats();
const auto folder = QDir(path).absolutePath();
auto environment = Environment();
environment.internalLinksDomain = "https://t.me/";
environment.aboutTelegram = "About Telegram";
environment.aboutContacts = "About contacts";
environment.aboutFrequent = "About frequent";
environment.aboutSessions = "About sessions";
environment.aboutWebSessions = "About web sessions";
environment.aboutChats = "About chats";
environment.aboutLeftChats = "About left chats";
auto settings = Settings();
settings.format = format();
settings.path = (folder.endsWith('/') ? folder : (folder + '/'))

View File

@ -90,7 +90,9 @@ public:
virtual ~AbstractWriter() = default;
Stats produceTestExample(const QString &path);
Stats produceTestExample(
const QString &path,
const Environment &environment);
};

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace Output {
namespace details {
class HtmlContext {
public:
[[nodiscard]] QByteArray pushTag(
const QByteArray &tag,
std::map<QByteArray, QByteArray> &&attributes = {});
[[nodiscard]] QByteArray popTag();
[[nodiscard]] QByteArray indent() const;
[[nodiscard]] bool empty() const;
private:
struct Tag {
QByteArray name;
bool block = true;
};
std::vector<Tag> _tags;
};
struct UserpicData;
} // namespace details
class HtmlWriter : public AbstractWriter {
public:
@ -59,9 +82,11 @@ public:
~HtmlWriter();
private:
using Context = details::HtmlContext;
using UserpicData = details::UserpicData;
class Wrap;
Result copyFile(
[[nodiscard]] Result copyFile(
const QString &source,
const QString &relativePath) const;
@ -70,34 +95,66 @@ private:
std::unique_ptr<Wrap> fileWithRelativePath(const QString &path) const;
QString messagesFile(int index) const;
Result writeSavedContacts(const Data::ContactsList &data);
Result writeFrequentContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeSavedContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeFrequentContacts(const Data::ContactsList &data);
Result writeSessions(const Data::SessionsList &data);
Result writeWebSessions(const Data::SessionsList &data);
[[nodiscard]] Result writeSessions(const Data::SessionsList &data);
[[nodiscard]] Result writeWebSessions(const Data::SessionsList &data);
Result writeChatsStart(
[[nodiscard]] Result writeChatsStart(
const Data::DialogsInfo &data,
const QByteArray &listName,
const QByteArray &about,
const QString &fileName);
Result writeChatStart(const Data::DialogInfo &data);
Result writeChatSlice(const Data::MessagesSlice &data);
Result writeChatEnd();
Result writeChatsEnd();
Result switchToNextChatFile(int index);
[[nodiscard]] Result writeChatStart(const Data::DialogInfo &data);
[[nodiscard]] Result writeChatSlice(const Data::MessagesSlice &data);
[[nodiscard]] Result writeChatEnd();
[[nodiscard]] Result writeChatsEnd();
[[nodiscard]] Result switchToNextChatFile(int index);
void pushSection(
int priority,
const QByteArray &label,
const QByteArray &type,
int count,
const QString &path);
[[nodiscard]] Result writeSections();
[[nodiscard]] Result writeDefaultPersonal(
const Data::PersonalInfo &data);
[[nodiscard]] Result writeDelayedPersonal(const QString &userpicPath);
[[nodiscard]] Result writePreparedPersonal(
const Data::PersonalInfo &data,
const QString &userpicPath);
void pushUserpicsSection();
[[nodiscard]] QString writeUserpicThumb(
const QString &largePath,
const UserpicData &userpic,
const QString &postfix = "_thumb");
[[nodiscard]] QString userpicsFilePath() const;
Settings _settings;
Environment _environment;
Stats *_stats = nullptr;
struct SavedSection;
std::vector<SavedSection> _savedSections;
std::unique_ptr<Wrap> _summary;
bool _summaryNeedDivider = false;
bool _haveSections = false;
int _selfColorIndex = 0;
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
int _userpicsCount = 0;
std::unique_ptr<Wrap> _userpics;
int _dialogsCount = 0;
int _dialogIndex = 0;
QString _dialogsRelativePath;
Data::DialogInfo _dialog;
int _messagesCount = 0;

View File

@ -69,6 +69,8 @@ void SuggestBox::prepare() {
}, content->lifetime());
}
} // namespace
Environment PrepareEnvironment() {
auto result = Environment();
const auto utfLang = [](LangKey key) {
@ -85,8 +87,6 @@ Environment PrepareEnvironment() {
return result;
}
} // namespace
QPointer<BoxContent> SuggestStart() {
ClearSuggestStart();
return Ui::show(Box<SuggestBox>(), LayerOption::KeepOther).data();

View File

@ -19,8 +19,12 @@ class SeparatePanel;
} // namespace Ui
namespace Export {
struct Environment;
namespace View {
Environment PrepareEnvironment();
QPointer<BoxContent> SuggestStart();
void ClearSuggestStart();

View File

@ -90,6 +90,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_history.h"
#include "styles/style_boxes.h"
#ifdef _DEBUG
#include "export/output/export_output_html.h"
#include "export/output/export_output_stats.h"
#include "export/view/export_view_panel_controller.h"
#include "platform/platform_specific.h"
#else
#error "test"
#endif
namespace {
bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) {
@ -252,6 +261,10 @@ MainWidget::MainWidget(
Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived));
Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail));
Export::Output::HtmlWriter writer;
writer.produceTestExample(psDownloadPath(), Export::View::PrepareEnvironment());
crl::on_main([] { App::quit(); });
_ptsWaiter.setRequesting(true);
updateScrollColors();
setupConnectingWidget();

View File

@ -109,7 +109,9 @@
'<@(style_files)',
'<!@(<(list_sources_command) <(qt_moc_list_sources_arg))',
'telegram_sources.txt',
'<(res_loc)/css/export_style.css',
'<(res_loc)/export_html/css/style.css',
'<(res_loc)/export_html/images/back.png',
'<(res_loc)/export_html/images/back@2x.png',
],
'sources!': [
'<!@(<(list_sources_command) <(qt_moc_list_sources_arg) --exclude_for <(build_os))',