Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions typescript/api/src/api/components/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@ export namespace Action {
};
}

/**
* @ignore - not implemented
*/
export namespace PickDate {
export type Props = BaseActionProps & {
}
export type Props = Omit<BaseActionProps, "title"> & {
title?: string;
onChange: (date: Date | null) => void;
min?: Date;
max?: Date;
type?: PickDateType;
};
}
}

Expand All @@ -112,6 +114,11 @@ export type Quicklink = {
icon?: Icon;
};

export enum PickDateType {
Date = "date",
DateTime = "date-time",
}

const ActionRoot: React.FC<ActionProps> = ({ icon, ...props }) => {
const serializedIcon = icon ? serializeProtoImage(icon) : icon;
const stableIdRef = useRef<string | undefined>(undefined);
Expand Down Expand Up @@ -242,10 +249,38 @@ const SubmitForm: React.FC<Action.SubmitForm.Props> = ({
return <action {...nativeProps} />;
};

// TODO: implement date picker action. This probably requires a full rework of the action panel.
const PickDate: React.FC<Action.PickDate.Props> = () => {
return null;
}
const PickDate: React.FC<Action.PickDate.Props> = ({
title = "Pick Date",
onChange,
min,
max,
type = PickDateType.Date,
icon,
...props
}) => {
const stableIdRef = useRef<string | undefined>(undefined);
if (!stableIdRef.current) {
stableIdRef.current = randomUUID();
}

const nativeProps: React.JSX.IntrinsicElements["action"] = {
...props,
stableId: stableIdRef.current,
title,
type: "pick-date",
pickDate: {
min: min?.toISOString(),
max: max?.toISOString(),
dateType: type,
},
icon: icon ? serializeProtoImage(icon) : icon,
onAction: (dateStr: string | null) => {
onChange(dateStr ? new Date(dateStr) : null);
},
};

return <action {...nativeProps} />;
};

const CreateQuicklink: React.FC<Action.CreateQuicklink.Props> = ({
title = "Create Quicklink",
Expand Down Expand Up @@ -290,8 +325,16 @@ export const Action = Object.assign(ActionRoot, {
ShowInFinder,
CreateQuicklink,
PickDate: Object.assign(PickDate, {
// TODO: to implement too
isFullDay: () => false
Type: PickDateType,
isFullDay: (value: Date | null | undefined) => {
if (!value) return false;
return (
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0 &&
value.getMilliseconds() === 0
);
},
}),
Style: {
Regular: "regular",
Expand Down
7 changes: 6 additions & 1 deletion typescript/api/types/jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,18 @@ declare module "react" {
};
action: {
title: string;
onAction: () => void;
onAction: ((dateStr: string | null) => void) | (() => void);
onSubmit?: Function;
shortcut?: Keyboard.Shortcut | Keyboard.Shortcut.Common;
icon?: SerializedImageLike;
autoFocus?: boolean;
type?: string;
quicklink?: Quicklink;
pickDate?: {
min?: string;
max?: string;
dateType: string;
};
stableId?: string;
};
"tag-list": {
Expand Down
3 changes: 3 additions & 0 deletions vicinae/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ set(SRCS
src/extension/extension-grid.hpp
src/extension/extension-grid.cpp

include/extension/date-picker-dialog.hpp
src/extension/date-picker-dialog.cpp

include/extension/extension.hpp
src/extension/extension.cpp

Expand Down
1 change: 1 addition & 0 deletions vicinae/include/extend/action-model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct ActionModel {
std::optional<Keyboard::Shortcut> shortcut;
QString type;
QJsonObject quicklink;
QJsonObject pickDate;
std::optional<QString> stableId;
};

Expand Down
22 changes: 22 additions & 0 deletions vicinae/include/extension/date-picker-dialog.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once
#include "theme.hpp"
#include <optional>
#include <qdatetime.h>
#include <qdialog.h>
#include <qtmetamacros.h>

class QDateTimeEdit;

class DatePickerDialog : public QDialog {
Q_OBJECT

QDateTimeEdit *m_picker;
bool m_includeTime;

public:
DatePickerDialog(bool includeTime, std::optional<QDateTime> min, std::optional<QDateTime> max,
QWidget *parent = nullptr);

signals:
void dateSelected(const QString &isoDate);
};
27 changes: 27 additions & 0 deletions vicinae/include/extension/extension-view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "extend/action-model.hpp"
#include "extend/model-parser.hpp"
#include "extension/extension-command-controller.hpp"
#include "extension/date-picker-dialog.hpp"
#include "ui/image/url.hpp"
#include "ui/action-pannel/action.hpp"
#include "ui/views/simple-view.hpp"
Expand Down Expand Up @@ -58,6 +59,32 @@ class ExtensionSimpleView : public SimpleView {
return action;
}

if (model.type == "pick-date") {
auto pickDateObj = model.pickDate;
QString dateType = pickDateObj.value("dateType").toString("date");
std::optional<QDateTime> minDt, maxDt;

if (pickDateObj.contains("min")) {
minDt = QDateTime::fromString(pickDateObj.value("min").toString(), Qt::ISODateWithMs);
}
if (pickDateObj.contains("max")) {
maxDt = QDateTime::fromString(pickDateObj.value("max").toString(), Qt::ISODateWithMs);
}

ImageURL actionIcon = model.icon ? ImageURL(*model.icon) : ImageURL::builtin("calendar");

auto action = new StaticAction(model.title, actionIcon, [this, model, dateType, minDt, maxDt]() {
auto dialog = new DatePickerDialog(dateType == "date-time", minDt, maxDt);
connect(dialog, &DatePickerDialog::dateSelected, this,
[this, model](const QString &isoDate) { notify(model.onAction, {isoDate}); });
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->exec();
});

if (model.stableId) { action->setId(*model.stableId); }
return action;
}

auto action = new StaticAction(model.title, model.icon, [this, model]() {
qDebug() << "notify action" << model.onAction;
notify(model.onAction, {});
Expand Down
2 changes: 2 additions & 0 deletions vicinae/src/extend/action-model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ ActionModel ActionPannelParser::parseAction(const QJsonObject &instance) {

if (props.contains("quicklink")) { action.quicklink = props.value("quicklink").toObject(); }

if (props.contains("pickDate")) { action.pickDate = props.value("pickDate").toObject(); }

if (props.contains("stableId")) { action.stableId = props.value("stableId").toString(); }

return action;
Expand Down
59 changes: 59 additions & 0 deletions vicinae/src/extension/date-picker-dialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "extension/date-picker-dialog.hpp"
#include <qboxlayout.h>
#include <qdatetimeedit.h>
#include <qpushbutton.h>

DatePickerDialog::DatePickerDialog(bool includeTime, std::optional<QDateTime> min,
std::optional<QDateTime> max, QWidget *parent)
: QDialog(parent), m_includeTime(includeTime) {
setWindowTitle("Select Date");
setModal(false);
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
setAttribute(Qt::WA_ShowWithoutActivating, false);
setFocusPolicy(Qt::StrongFocus);

auto layout = new QVBoxLayout(this);

m_picker = new QDateTimeEdit(this);
m_picker->setCalendarPopup(true);
m_picker->setDateTime(QDateTime::currentDateTime());
m_picker->setButtonSymbols(QAbstractSpinBox::NoButtons);

if (includeTime) {
m_picker->setDisplayFormat("yyyy-MM-dd HH:mm");
} else {
m_picker->setDisplayFormat("yyyy-MM-dd");
}

if (min && min->isValid()) m_picker->setMinimumDateTime(*min);
if (max && max->isValid()) m_picker->setMaximumDateTime(*max);

layout->addWidget(m_picker);

auto buttonLayout = new QHBoxLayout;
auto cancelBtn = new QPushButton("Cancel", this);
auto okBtn = new QPushButton("OK", this);
okBtn->setDefault(true);

buttonLayout->addStretch();
buttonLayout->addWidget(cancelBtn);
buttonLayout->addWidget(okBtn);
layout->addLayout(buttonLayout);

connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
connect(okBtn, &QPushButton::clicked, this, [this]() {
QString isoDate;
if (m_includeTime) {
isoDate = m_picker->dateTime().toUTC().toString(Qt::ISODateWithMs) + "Z";
} else {
// For date-only, use UTC noon to avoid timezone day-shift issues
isoDate = m_picker->date().toString("yyyy-MM-dd") + "T12:00:00.000Z";
}
emit dateSelected(isoDate);
accept();
});

setStyleSheet(ThemeService::instance().inputStyleSheet());
connect(&ThemeService::instance(), &ThemeService::themeChanged, this,
[this]() { setStyleSheet(ThemeService::instance().inputStyleSheet()); });
}