chore: test deploy snapshot
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { env } from '../config/env.js';
|
||||||
import { db } from '../db/client.js';
|
import { db } from '../db/client.js';
|
||||||
|
|
||||||
type PlayAppEnvironment = 'preview' | 'test' | 'prod';
|
type PlayAppEnvironment = 'preview' | 'test' | 'prod';
|
||||||
@@ -18,6 +19,23 @@ type PlayAppSeedEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PLAY_APP_TABLE = 'play_apps';
|
const PLAY_APP_TABLE = 'play_apps';
|
||||||
|
|
||||||
|
function getRequestAccessToken(request: FastifyRequest) {
|
||||||
|
const tokenHeader = request.headers['x-access-token'];
|
||||||
|
return Array.isArray(tokenHeader) ? tokenHeader[0]?.trim() ?? '' : String(tokenHeader ?? '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensurePlayAppWriteAuthorized(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
if (getRequestAccessToken(request) === env.SERVER_COMMAND_ACCESS_TOKEN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.code(403).send({
|
||||||
|
message: '권한 토큰이 필요합니다.',
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_ENTRIES: PlayAppSeedEntry[] = [
|
const DEFAULT_ENTRIES: PlayAppSeedEntry[] = [
|
||||||
{
|
{
|
||||||
id: 'baseball-ticket-bay',
|
id: 'baseball-ticket-bay',
|
||||||
@@ -546,15 +564,27 @@ export async function registerPlayAppRoutes(app: FastifyInstance) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/play-apps', async (request) => {
|
app.post('/api/play-apps', async (request, reply) => {
|
||||||
|
if (!ensurePlayAppWriteAuthorized(request, reply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return createPlayAppEntry(request.body);
|
return createPlayAppEntry(request.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/play-apps/:id', async (request) => {
|
app.put('/api/play-apps/:id', async (request, reply) => {
|
||||||
|
if (!ensurePlayAppWriteAuthorized(request, reply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return updatePlayAppEntry(request.params, request.body);
|
return updatePlayAppEntry(request.params, request.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/api/play-apps/:id', async (request) => {
|
app.delete('/api/play-apps/:id', async (request, reply) => {
|
||||||
|
if (!ensurePlayAppWriteAuthorized(request, reply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return deletePlayAppEntry(request.params);
|
return deletePlayAppEntry(request.params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { DeleteOutlined, LinkOutlined, PlusOutlined, QrcodeOutlined, ReloadOutlined, SaveOutlined, StopOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, LinkOutlined, PlusOutlined, QrcodeOutlined, ReloadOutlined, SaveOutlined, StopOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||||
import { Alert, App, Button, Card, Checkbox, Drawer, Empty, Flex, Form, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tabs, Tag, Typography } from 'antd';
|
import { Alert, App, Button, Card, Checkbox, Drawer, Empty, Flex, Form, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tabs, Tag, Typography } from 'antd';
|
||||||
import { useEffect, useLayoutEffect, useMemo, useState, type Key, type MouseEvent as ReactMouseEvent } from 'react';
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useState, type Key, type MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import { getReadyPlayAppEntries } from '../../views/play/apps/apps/appsRegistry';
|
import {
|
||||||
|
getReadyPlayAppEntries,
|
||||||
|
loadPlayAppEntriesFromServer,
|
||||||
|
} from '../../views/play/apps/apps/appsRegistry';
|
||||||
import { copyTextToClipboard } from '../../utils/clipboard';
|
import { copyTextToClipboard } from '../../utils/clipboard';
|
||||||
import { openExternalLinkInNewWindow } from './mainChatPanel/linkNavigation';
|
import { openExternalLinkInNewWindow } from './mainChatPanel/linkNavigation';
|
||||||
import { resolveChatPathForSession } from './chatSessionRouting';
|
import { resolveChatPathForSession } from './chatSessionRouting';
|
||||||
@@ -56,18 +59,33 @@ const MANAGEMENT_APP_OPTIONS = [
|
|||||||
{ value: 'error-log', label: '에러 로그', description: '앱 로그와 장애 이력 조회', category: '관리' },
|
{ value: 'error-log', label: '에러 로그', description: '앱 로그와 장애 이력 조회', category: '관리' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const PLAY_APP_OPTIONS = getReadyPlayAppEntries().map((entry) => ({
|
|
||||||
value: entry.id,
|
|
||||||
label: entry.name,
|
|
||||||
description: entry.searchDescription ?? `${entry.name} 앱 실행`,
|
|
||||||
category: 'Play' as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const APP_OPTIONS = [...MANAGEMENT_APP_OPTIONS, ...PLAY_APP_OPTIONS];
|
|
||||||
const APP_OPTION_LABEL_MAP = new Map(APP_OPTIONS.map((option) => [option.value, option.label] as const));
|
|
||||||
const ADMIN_PERMISSION_PRESET: SharedResourcePermission[] = ['view', 'download', 'comment', 'upload', 'manage'];
|
const ADMIN_PERMISSION_PRESET: SharedResourcePermission[] = ['view', 'download', 'comment', 'upload', 'manage'];
|
||||||
const ADMIN_ALLOWED_APP_PRESET = MANAGEMENT_APP_OPTIONS.map((option) => option.value);
|
const ADMIN_ALLOWED_APP_PRESET = MANAGEMENT_APP_OPTIONS.map((option) => option.value);
|
||||||
|
|
||||||
|
type AppOption = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
category: '관리' | 'Play';
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildPlayAppOptionsFromCache() {
|
||||||
|
return getReadyPlayAppEntries().map((entry) => ({
|
||||||
|
value: entry.id,
|
||||||
|
label: entry.name,
|
||||||
|
description: entry.searchDescription ?? `${entry.name} 앱 실행`,
|
||||||
|
category: 'Play' as const,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppOptions() {
|
||||||
|
return [...MANAGEMENT_APP_OPTIONS, ...buildPlayAppOptionsFromCache()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppOptionLabelMap(appOptions: AppOption[]) {
|
||||||
|
return new Map(appOptions.map((option) => [option.value, option.label] as const));
|
||||||
|
}
|
||||||
|
|
||||||
type SharedResourceManagementSharedPreview = {
|
type SharedResourceManagementSharedPreview = {
|
||||||
managedResourceTokenId?: string | null;
|
managedResourceTokenId?: string | null;
|
||||||
sharePath?: string | null;
|
sharePath?: string | null;
|
||||||
@@ -188,10 +206,6 @@ function formatPermissionLabel(permission: SharedResourcePermission) {
|
|||||||
return PERMISSION_OPTIONS.find((option) => option.value === permission)?.label ?? permission;
|
return PERMISSION_OPTIONS.find((option) => option.value === permission)?.label ?? permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAppLabel(appId: string) {
|
|
||||||
return APP_OPTION_LABEL_MAP.get(appId) ?? appId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDurationMinutesLabel(totalMinutes: number | null | undefined) {
|
function formatDurationMinutesLabel(totalMinutes: number | null | undefined) {
|
||||||
const normalizedMinutes = Number(totalMinutes ?? 0);
|
const normalizedMinutes = Number(totalMinutes ?? 0);
|
||||||
|
|
||||||
@@ -513,12 +527,34 @@ export function SharedResourceManagementPage({
|
|||||||
const [detailData, setDetailData] = useState<{ token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] } | null>(null);
|
const [detailData, setDetailData] = useState<{ token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] } | null>(null);
|
||||||
const [nowMs, setNowMs] = useState(() => Date.now());
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||||
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'settings' | 'history'>('basic');
|
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'settings' | 'history'>('basic');
|
||||||
|
const [appOptions, setAppOptions] = useState<AppOption[]>(buildAppOptions);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
|
||||||
const [qrPreviewTokenId, setQrPreviewTokenId] = useState<string | null>(null);
|
const [qrPreviewTokenId, setQrPreviewTokenId] = useState<string | null>(null);
|
||||||
const [conversationDrawer, setConversationDrawer] = useState<ConversationDrawerState | null>(null);
|
const [conversationDrawer, setConversationDrawer] = useState<ConversationDrawerState | null>(null);
|
||||||
const [conversationDrawerKey, setConversationDrawerKey] = useState(0);
|
const [conversationDrawerKey, setConversationDrawerKey] = useState(0);
|
||||||
const [form] = Form.useForm<SharedResourceTokenFormValue>();
|
const [form] = Form.useForm<SharedResourceTokenFormValue>();
|
||||||
const [modalApi, modalContextHolder] = Modal.useModal();
|
const [modalApi, modalContextHolder] = Modal.useModal();
|
||||||
|
const appOptionLabelMap = useMemo(() => buildAppOptionLabelMap(appOptions), [appOptions]);
|
||||||
|
const formatAppLabel = useCallback(
|
||||||
|
(appId: string) => appOptionLabelMap.get(appId) ?? appId,
|
||||||
|
[appOptionLabelMap],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
void loadPlayAppEntriesFromServer().then(() => {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppOptions(buildAppOptions());
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (disableInstallMetadata || typeof window === 'undefined') {
|
if (disableInstallMetadata || typeof window === 'undefined') {
|
||||||
@@ -1604,13 +1640,13 @@ export function SharedResourceManagementPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={`앱 권한 ${APP_OPTIONS.length}`}
|
label={`앱 권한 ${appOptions.length}`}
|
||||||
name="allowedAppIds"
|
name="allowedAppIds"
|
||||||
extra="공유 토큰 상세에서 최종 허용 앱 목록을 직접 저장합니다."
|
extra="공유 토큰 상세에서 최종 허용 앱 목록을 직접 저장합니다."
|
||||||
>
|
>
|
||||||
<Checkbox.Group style={{ width: '100%' }}>
|
<Checkbox.Group style={{ width: '100%' }}>
|
||||||
<div className="shared-resource-management-page__permission-grid">
|
<div className="shared-resource-management-page__permission-grid">
|
||||||
{APP_OPTIONS.map((option) => (
|
{appOptions.map((option) => (
|
||||||
<label key={option.value} className="shared-resource-management-page__permission-card">
|
<label key={option.value} className="shared-resource-management-page__permission-card">
|
||||||
<div className="shared-resource-management-page__permission-card-header">
|
<div className="shared-resource-management-page__permission-card-header">
|
||||||
<div className="shared-resource-management-page__permission-card-copy">
|
<div className="shared-resource-management-page__permission-card-copy">
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { DeleteOutlined, EditOutlined, PlusOutlined, SaveOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined, PlusOutlined, SaveOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||||
import { Alert, App, Button, Card, Checkbox, Descriptions, Empty, Form, Input, InputNumber, List, Modal, Space, Switch, Table, Tabs, Tag, Typography } from 'antd';
|
import { Alert, App, Button, Card, Checkbox, Descriptions, Empty, Form, Input, InputNumber, List, Modal, Space, Switch, Table, Tabs, Tag, Typography } from 'antd';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { getReadyPlayAppEntries } from '../../views/play/apps/apps/appsRegistry';
|
import {
|
||||||
|
getReadyPlayAppEntries,
|
||||||
|
loadPlayAppEntriesFromServer,
|
||||||
|
} from '../../views/play/apps/apps/appsRegistry';
|
||||||
import { confirmWithKeyboard } from './modalKeyboard';
|
import { confirmWithKeyboard } from './modalKeyboard';
|
||||||
import {
|
import {
|
||||||
deleteTokenSetting,
|
deleteTokenSetting,
|
||||||
@@ -66,16 +69,6 @@ const MANAGEMENT_APP_OPTIONS: AppOption[] = [
|
|||||||
{ value: 'error-log', label: '에러 로그', description: '앱 로그와 장애 이력 조회', category: '관리' },
|
{ value: 'error-log', label: '에러 로그', description: '앱 로그와 장애 이력 조회', category: '관리' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLAY_APP_OPTIONS: AppOption[] = getReadyPlayAppEntries().map((entry) => ({
|
|
||||||
value: entry.id,
|
|
||||||
label: entry.name,
|
|
||||||
description: entry.searchDescription ?? `${entry.name} 앱 실행`,
|
|
||||||
category: 'Play',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const APP_OPTIONS: AppOption[] = [...MANAGEMENT_APP_OPTIONS, ...PLAY_APP_OPTIONS];
|
|
||||||
const APP_OPTION_LABEL_MAP = new Map(APP_OPTIONS.map((item) => [item.value, item.label] as const));
|
|
||||||
|
|
||||||
const EMPTY_FORM_VALUE: TokenSettingFormValue = {
|
const EMPTY_FORM_VALUE: TokenSettingFormValue = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -175,8 +168,21 @@ function formatQuotaSummary(setting: TokenSettingRecord) {
|
|||||||
return [`7일 ${formatTokenLimit(setting.maxTokensPer7Days)}`, `5시간 ${formatTokenLimit(setting.maxTokensPer5Hours)}`].join(' / ');
|
return [`7일 ${formatTokenLimit(setting.maxTokensPer7Days)}`, `5시간 ${formatTokenLimit(setting.maxTokensPer5Hours)}`].join(' / ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAppLabels(appIds: string[]) {
|
function buildPlayAppOptionsFromCache() {
|
||||||
return appIds.map((item) => APP_OPTION_LABEL_MAP.get(item) ?? item);
|
return getReadyPlayAppEntries().map((entry) => ({
|
||||||
|
value: entry.id,
|
||||||
|
label: entry.name,
|
||||||
|
description: entry.searchDescription ?? `${entry.name} 앱 실행`,
|
||||||
|
category: 'Play',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppOptions() {
|
||||||
|
return [...MANAGEMENT_APP_OPTIONS, ...buildPlayAppOptionsFromCache()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppOptionLabelMap(appOptions: AppOption[]) {
|
||||||
|
return new Map(appOptions.map((item) => [item.value, item.label] as const));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TokenSettingManagementPage({
|
export function TokenSettingManagementPage({
|
||||||
@@ -223,11 +229,33 @@ export function TokenSettingManagementPage({
|
|||||||
const [saveErrorMessage, setSaveErrorMessage] = useState('');
|
const [saveErrorMessage, setSaveErrorMessage] = useState('');
|
||||||
const [saveSuccessMessage, setSaveSuccessMessage] = useState('');
|
const [saveSuccessMessage, setSaveSuccessMessage] = useState('');
|
||||||
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'quota' | 'apps' | 'history'>('basic');
|
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'quota' | 'apps' | 'history'>('basic');
|
||||||
|
const [appOptions, setAppOptions] = useState<AppOption[]>(buildAppOptions);
|
||||||
const [activities, setActivities] = useState<TokenSettingActivityRecord[]>([]);
|
const [activities, setActivities] = useState<TokenSettingActivityRecord[]>([]);
|
||||||
const [isActivityLoading, setIsActivityLoading] = useState(false);
|
const [isActivityLoading, setIsActivityLoading] = useState(false);
|
||||||
const [form] = Form.useForm<TokenSettingFormValue>();
|
const [form] = Form.useForm<TokenSettingFormValue>();
|
||||||
const [modalApi, modalContextHolder] = Modal.useModal();
|
const [modalApi, modalContextHolder] = Modal.useModal();
|
||||||
const lastHydratedFormKeyRef = useRef('');
|
const lastHydratedFormKeyRef = useRef('');
|
||||||
|
const appOptionLabelMap = useMemo(() => buildAppOptionLabelMap(appOptions), [appOptions]);
|
||||||
|
const resolveAppLabelsMemo = useCallback(
|
||||||
|
(appIds: string[]) => appIds.map((item) => appOptionLabelMap.get(item) ?? item),
|
||||||
|
[appOptionLabelMap],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
void loadPlayAppEntriesFromServer().then(() => {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppOptions(buildAppOptions());
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const selectedTokenSetting = useMemo(
|
const selectedTokenSetting = useMemo(
|
||||||
() => tokenSettings.find((item) => item.id === selectedTokenSettingId) ?? null,
|
() => tokenSettings.find((item) => item.id === selectedTokenSettingId) ?? null,
|
||||||
@@ -491,9 +519,9 @@ export function TokenSettingManagementPage({
|
|||||||
{item.description || '설명 없음'}
|
{item.description || '설명 없음'}
|
||||||
</div>
|
</div>
|
||||||
<Space size={[6, 6]} wrap>
|
<Space size={[6, 6]} wrap>
|
||||||
{resolveAppLabels(item.allowedAppIds).map((label) => (
|
{resolveAppLabelsMemo(item.allowedAppIds).map((label) => (
|
||||||
<Tag key={`${item.id}-${label}`}>{label}</Tag>
|
<Tag key={`${item.id}-${label}`}>{label}</Tag>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
@@ -723,7 +751,7 @@ export function TokenSettingManagementPage({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'apps',
|
key: 'apps',
|
||||||
label: `앱 권한 ${APP_OPTIONS.length}`,
|
label: `앱 권한 ${appOptions.length}`,
|
||||||
children: (
|
children: (
|
||||||
<div className="token-setting-management-page__section-scroll">
|
<div className="token-setting-management-page__section-scroll">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -743,7 +771,7 @@ export function TokenSettingManagementPage({
|
|||||||
>
|
>
|
||||||
<Checkbox.Group style={{ width: '100%' }}>
|
<Checkbox.Group style={{ width: '100%' }}>
|
||||||
<div className="token-setting-management-page__app-grid">
|
<div className="token-setting-management-page__app-grid">
|
||||||
{APP_OPTIONS.map((option) => (
|
{appOptions.map((option) => (
|
||||||
<div key={option.value} className="token-setting-management-page__app-card">
|
<div key={option.value} className="token-setting-management-page__app-card">
|
||||||
<div className="token-setting-management-page__app-card-header">
|
<div className="token-setting-management-page__app-card-header">
|
||||||
<div className="token-setting-management-page__app-card-title">
|
<div className="token-setting-management-page__app-card-title">
|
||||||
|
|||||||
@@ -120,8 +120,9 @@ const SHARE_ROOM_SWITCH_CACHE_MESSAGE_LIMIT = 32;
|
|||||||
const SHARE_IMMEDIATE_SEND_TOGGLE_HOLD_MS = 1000;
|
const SHARE_IMMEDIATE_SEND_TOGGLE_HOLD_MS = 1000;
|
||||||
const SHARE_EDGE_NAVIGATION_HOTZONE_PX = 38;
|
const SHARE_EDGE_NAVIGATION_HOTZONE_PX = 38;
|
||||||
const SHARE_EDGE_GESTURE_MIN_HORIZONTAL_PX = 16;
|
const SHARE_EDGE_GESTURE_MIN_HORIZONTAL_PX = 16;
|
||||||
const SHARE_EDGE_GESTURE_OPEN_APPS_PX = 100;
|
const SHARE_EDGE_GESTURE_OPEN_APPS_PX = 72;
|
||||||
const SHARE_EDGE_APPS_GESTURE_HOTZONE_PX = 110;
|
const SHARE_EDGE_APPS_GESTURE_HOTZONE_PX = 110;
|
||||||
|
const SHARE_EDGE_GESTURE_OPEN_APPS_MAX_VERTICAL_DEVIATION_PX = 34;
|
||||||
const SHARE_MINIMIZED_ACTION_TAP_TOLERANCE_PX = 8;
|
const SHARE_MINIMIZED_ACTION_TAP_TOLERANCE_PX = 8;
|
||||||
const SHARE_HISTORY_PAGE_SIZE = 40;
|
const SHARE_HISTORY_PAGE_SIZE = 40;
|
||||||
const SHARE_ACCESS_PIN_PROMPT_TTL_OPTIONS = [
|
const SHARE_ACCESS_PIN_PROMPT_TTL_OPTIONS = [
|
||||||
@@ -2053,6 +2054,27 @@ function isMobileTouchViewport() {
|
|||||||
&& (window.matchMedia('(pointer: coarse)').matches || window.matchMedia('(hover: none)').matches);
|
&& (window.matchMedia('(pointer: coarse)').matches || window.matchMedia('(hover: none)').matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getShareViewportWidth() {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(
|
||||||
|
Math.min(
|
||||||
|
window.innerWidth,
|
||||||
|
window.visualViewport?.width ?? window.innerWidth,
|
||||||
|
document.documentElement?.clientWidth ?? window.innerWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isShareAppOpenGestureSwipe(touch: Touch, startX: number, startY: number) {
|
||||||
|
const movedLeft = startX - touch.clientX;
|
||||||
|
const movedUpDown = Math.abs(touch.clientY - startY);
|
||||||
|
|
||||||
|
return movedLeft > SHARE_EDGE_GESTURE_OPEN_APPS_PX && movedUpDown <= SHARE_EDGE_GESTURE_OPEN_APPS_MAX_VERTICAL_DEVIATION_PX;
|
||||||
|
}
|
||||||
|
|
||||||
function isShareEdgeGestureIgnoredTarget(target: EventTarget | null) {
|
function isShareEdgeGestureIgnoredTarget(target: EventTarget | null) {
|
||||||
if (isTypingTarget(target)) {
|
if (isTypingTarget(target)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -9763,9 +9785,7 @@ export function ChatSharePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (touch.clientX >= Math.max(0, getShareViewportWidth() - SHARE_EDGE_APPS_GESTURE_HOTZONE_PX)) {
|
||||||
touch.clientX >= window.innerWidth - SHARE_EDGE_APPS_GESTURE_HOTZONE_PX
|
|
||||||
) {
|
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
tracking = {
|
tracking = {
|
||||||
@@ -9805,7 +9825,9 @@ export function ChatSharePage() {
|
|||||||
const deltaX = touch.clientX - tracking.startX;
|
const deltaX = touch.clientX - tracking.startX;
|
||||||
|
|
||||||
if (tracking.direction === 'apps') {
|
if (tracking.direction === 'apps') {
|
||||||
if (deltaX <= SHARE_EDGE_GESTURE_OPEN_APPS_PX * -1) {
|
const isSwipeLeftEnough = isShareAppOpenGestureSwipe(touch, tracking.startX, tracking.startY);
|
||||||
|
|
||||||
|
if (isSwipeLeftEnough) {
|
||||||
if (deltaX <= SHARE_EDGE_GESTURE_MIN_HORIZONTAL_PX * -1) {
|
if (deltaX <= SHARE_EDGE_GESTURE_MIN_HORIZONTAL_PX * -1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -9837,7 +9859,11 @@ export function ChatSharePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tracking.direction === 'apps' && !tracking.opened) {
|
if (
|
||||||
|
tracking.direction === 'apps'
|
||||||
|
&& !tracking.opened
|
||||||
|
&& isShareAppOpenGestureSwipe(touch, tracking.startX, tracking.startY)
|
||||||
|
) {
|
||||||
const deltaX = touch.clientX - tracking.startX;
|
const deltaX = touch.clientX - tracking.startX;
|
||||||
|
|
||||||
if (deltaX <= SHARE_EDGE_GESTURE_OPEN_APPS_PX * -1) {
|
if (deltaX <= SHARE_EDGE_GESTURE_OPEN_APPS_PX * -1) {
|
||||||
@@ -9868,6 +9894,7 @@ export function ChatSharePage() {
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
activeProcessInspectorRequestId,
|
activeProcessInspectorRequestId,
|
||||||
|
activeShareRoomSessionId,
|
||||||
isCreateRoomOpen,
|
isCreateRoomOpen,
|
||||||
isOriginReplyModalOpen,
|
isOriginReplyModalOpen,
|
||||||
isRoomSettingsOpen,
|
isRoomSettingsOpen,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user