diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..1d953f4bd7 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..72bc6de7f8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + + name = "session-desktop"; + packages = with pkgs; [ + nodejs_24 + nodeenv + python314 + cmake + gnumake + nodeenv + (pkgs.yarn.override { nodejs = null; }) + ]; + + env = { + }; + + shellHook = '' + ''; + + LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [ nss nspr dbus cups gtk3 libxcomposite libgbm expat libxkbcommon alsa-lib ]; + +} diff --git a/stylesheets/_global.scss b/stylesheets/_global.scss index a7e8ec5ce2..70c564c8ce 100644 --- a/stylesheets/_global.scss +++ b/stylesheets/_global.scss @@ -177,3 +177,5 @@ fieldset { // This hides the outline altogether. We might consider adding a new variable linked to a theme setting to fix this, for all users outline: none; } + +$mobile-ui-breakpoint: 799px; diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 40a0a011b2..ada356c21b 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -281,7 +281,7 @@ // Module: Conversation List Item .module-conversation-list-item { - max-width: 300px; + max-width: 100%; display: flex; flex-direction: row; padding-inline-end: 16px; diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 10582558fe..f7ef03d25c 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -19,12 +19,20 @@ } .session-conversation { - position: relative; - flex-grow: 1; + width: 100%; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; display: flex; flex-direction: column; - width: var(--main-panel-content-width); + flex-grow: 1; height: 100%; + @media screen and (min-width: $mobile-ui-breakpoint) { + width: var(--main-panel-content-width); + position: relative; + } .selection-mode { .messages-container > *:not(.message-selected) { @@ -34,6 +42,13 @@ } } +.mobile-close-conversation { + display: block; + @media screen and (min-width: $mobile-ui-breakpoint) { + display: none; + } +} + .conversation-content { display: flex; flex-grow: 1; diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index aea1a74008..f15a48a5f4 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -16,3 +16,20 @@ flex-grow: 0; padding-inline-end: 5px; } + +.module-session-inbox-view__styled_gutter { + transition: none; + width: 100%; + @media screen and (min-width: $mobile-ui-breakpoint) { + width: var(--left-panel-width); + } +} +.mobile-active-conversation { + width: 1px; + margin-left: -1px; + @media screen and (min-width: $mobile-ui-breakpoint) { + width: 100%; + margin-left: 0px; + max-width: var(--left-panel-width); + } +} diff --git a/ts/components/SessionInboxView.tsx b/ts/components/SessionInboxView.tsx index 531e2b1c43..16576509f8 100644 --- a/ts/components/SessionInboxView.tsx +++ b/ts/components/SessionInboxView.tsx @@ -1,16 +1,35 @@ import { Provider } from 'react-redux'; -import styled from 'styled-components'; import { AnimatePresence } from 'framer-motion'; import { LeftPane } from './leftpane/LeftPane'; import { SessionMainPanel } from './SessionMainPanel'; import { SessionTheme } from '../themes/SessionTheme'; -import { Flex } from './basic/Flex'; +import { useSelectedConversationKey } from '../state/selectors/selectedConversation'; +import styled, { css } from 'styled-components'; +import { ReactiveFlex } from './basic/ReactiveFlex'; -const StyledGutter = styled.div` - width: var(--left-panel-width) !important; - transition: none; +const StyledGutter = styled.div<{ $conversationActive: boolean }>` + @media screen and (min-width: 799px) { + position: inherit; + width: var(--left-panel-width); + } + + ${({ $conversationActive }) => + $conversationActive + ? css`` + : css` + z-index: 1; + width: 100%; + position: fixed; + top: 0; + right: 0; + bottom: 0; + `} `; +const MobileStyledGutter = (props: any) => ( + +); + export const SessionInboxView = () => { if (!window.inboxStore) { throw new Error('window.inboxStore is undefined in SessionInboxView'); @@ -21,12 +40,12 @@ export const SessionInboxView = () => { - - + + - + - + diff --git a/ts/components/SessionMainPanel.tsx b/ts/components/SessionMainPanel.tsx index 88ff5b6447..66ddfe3255 100644 --- a/ts/components/SessionMainPanel.tsx +++ b/ts/components/SessionMainPanel.tsx @@ -1,5 +1,4 @@ import { useAppIsFocused } from '../hooks/useAppFocused'; - import { SmartSessionConversation } from '../state/smart/SessionConversation'; import { useHTMLDirection } from '../util/i18n/rtlSupport'; diff --git a/ts/components/SplitViewContainer.tsx b/ts/components/SplitViewContainer.tsx index 0a37b07845..5a81e5618a 100644 --- a/ts/components/SplitViewContainer.tsx +++ b/ts/components/SplitViewContainer.tsx @@ -50,7 +50,7 @@ const TopSplitViewPanel = ({ topHeight: number | undefined; setTopHeight: (value: number) => void; }) => { - const topRef = useRef(null); + const topRef = useRef(null); useEffect(() => { if (topRef.current) { if (!topHeight) { diff --git a/ts/components/basic/ReactiveFlex.tsx b/ts/components/basic/ReactiveFlex.tsx new file mode 100644 index 0000000000..852da2d355 --- /dev/null +++ b/ts/components/basic/ReactiveFlex.tsx @@ -0,0 +1,110 @@ +import { HTMLMotionProps, motion } from 'framer-motion'; +import styled from 'styled-components'; +import { HTMLDirection } from '../../util/i18n/rtlSupport'; + +export interface FlexProps { + children?: any; + className?: string; + $container?: boolean; + // Container Props + $flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'; + $justifyContent?: + | 'flex-start' + | 'flex-end' + | 'center' + | 'space-between' + | 'space-around' + | 'space-evenly' + | 'initial' + | 'inherit'; + $flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse'; + $flexGap?: string; + $alignItems?: + | 'stretch' + | 'center' + | 'flex-start' + | 'flex-end' + | 'baseline' + | 'initial' + | 'inherit'; + // Child Props + $flexGrow?: number; + $flexShrink?: number; + $flexBasis?: number; + // Common Layout Props + $padding?: string; + $margin?: string; + width?: string; + $maxWidth?: string; + $minWidth?: string; + height?: string; + $maxHeight?: string; + $minHeight?: string; + $overflow?: 'hidden' | 'visible' | 'scroll' | 'auto'; + $overflowX?: 'hidden' | 'visible' | 'scroll' | 'auto'; + $overflowY?: 'hidden' | 'visible' | 'scroll' | 'auto'; + // RTL support + dir?: HTMLDirection; + $paddingInline?: string; + $paddingBlock?: string; + $marginInline?: string; + $marginBlock?: string; +} + +export const ReactiveFlex = styled.div` + display: block; + + @media screen and (min-width: 799px) { + display: ${props => (props.$container ? 'flex' : 'block')}; + justify-content: ${props => props.$justifyContent || 'flex-start'}; + flex-direction: ${props => props.$flexDirection || 'row'}; + flex-grow: ${props => (props.$flexGrow !== undefined ? props.$flexGrow : '0')}; + flex-basis: ${props => (props.$flexBasis !== undefined ? props.$flexBasis : 'auto')}; + flex-shrink: ${props => (props.$flexShrink !== undefined ? props.$flexShrink : '1')}; + flex-wrap: ${props => (props.$flexWrap !== undefined ? props.$flexWrap : 'nowrap')}; + gap: ${props => props.$flexGap || undefined}; + align-items: ${props => props.$alignItems || 'stretch'}; + margin: ${props => props.$margin || ''}; + padding: ${props => props.$padding || ''}; + width: ${props => props.width || 'auto'}; + max-width: ${props => props.$maxWidth || 'none'}; + min-width: ${props => props.$minWidth || 'none'}; + height: ${props => props.height || 'auto'}; + max-height: ${props => props.$maxHeight || 'none'}; + min-height: ${props => props.$minHeight || 'none'}; + overflow: ${props => (props.$overflow !== undefined ? props.$overflow : undefined)}; + overflow-x: ${props => (props.$overflowX !== undefined ? props.$overflowX : undefined)}; + overflow-y: ${props => (props.$overflowY !== undefined ? props.$overflowY : undefined)}; + direction: ${props => props.dir || undefined}; + padding-inline: ${props => props.$paddingInline || undefined}; + padding-block: ${props => props.$paddingBlock || undefined}; + margin-inline: ${props => props.$marginInline || undefined}; + margin-block: ${props => props.$marginBlock || undefined}; + } +`; + +export const AnimatedFlex = styled(motion.div) & FlexProps>` + display: ${props => (props.$container ? 'flex' : 'block')}; + justify-content: ${props => props.$justifyContent || 'flex-start'}; + flex-direction: ${props => props.$flexDirection || 'row'}; + flex-grow: ${props => (props.$flexGrow !== undefined ? props.$flexGrow : '0')}; + flex-basis: ${props => (props.$flexBasis !== undefined ? props.$flexBasis : 'auto')}; + flex-shrink: ${props => (props.$flexShrink !== undefined ? props.$flexShrink : '1')}; + flex-wrap: ${props => (props.$flexWrap !== undefined ? props.$flexWrap : 'nowrap')}; + gap: ${props => props.$flexGap || undefined}; + align-items: ${props => props.$alignItems || 'stretch'}; + margin: ${props => props.$margin || '0'}; + padding: ${props => props.$padding || '0'}; + width: ${props => props.width || 'auto'}; + max-width: ${props => props.$maxWidth || 'none'}; + min-width: ${props => props.$minWidth || 'none'}; + height: ${props => props.height || 'auto'}; + max-height: ${props => props.$maxHeight || 'none'}; + min-height: ${props => props.$minHeight || 'none'}; + overflow: ${props => (props.$overflow !== undefined ? props.$overflow : undefined)}; + direction: ${props => props.dir || undefined}; + padding-inline: ${props => props.$paddingInline || undefined}; + padding-block: ${props => props.$paddingBlock || undefined}; + margin-inline: ${props => props.$marginInline || undefined}; + margin-block: ${props => props.$marginBlock || undefined}; +`; diff --git a/ts/components/conversation/header/ConversationHeader.tsx b/ts/components/conversation/header/ConversationHeader.tsx index 96c9828e50..8332b91269 100644 --- a/ts/components/conversation/header/ConversationHeader.tsx +++ b/ts/components/conversation/header/ConversationHeader.tsx @@ -29,6 +29,9 @@ import { ConversationTypeEnum } from '../../../models/types'; import { Constants } from '../../../session'; import { useShowConversationSettingsFor } from '../../menuAndSettingsHooks/useShowConversationSettingsFor'; import { sectionActions } from '../../../state/ducks/section'; +import { SessionLucideIconButton } from '../../icon/SessionIconButton'; +import { LUCIDE_ICONS_UNICODE } from '../../icon/lucide'; +import { resetConversationExternal } from '../../../state/ducks/conversations'; const StyledConversationHeader = styled.div` display: flex; @@ -36,8 +39,11 @@ const StyledConversationHeader = styled.div` align-items: center; height: var(--main-view-header-height); position: relative; - padding: 0px var(--margins-lg) 0px var(--margins-sm); background: var(--background-primary-color); + padding: 0px var(--margins-lg) 0px var(--margins-lg); + @media screen and (min-width: 799px) { + padding: 0px var(--margins-lg) 0px var(--margins-sm); + } `; export const ConversationHeaderWithDetails = () => { @@ -62,6 +68,13 @@ export const ConversationHeaderWithDetails = () => { width="100%" $flexGrow={1} > +
+ window?.inboxStore?.dispatch(resetConversationExternal())} + /> +
@@ -80,8 +93,8 @@ export const ConversationHeaderWithDetails = () => { onAvatarClick={ showConvoSettingsCb ? () => { - showConvoSettingsCb({ settingsModalPage: 'default' }); - } + showConvoSettingsCb({ settingsModalPage: 'default' }); + } : undefined } pubkey={selectedConvoKey} diff --git a/ts/components/leftpane/LeftPane.tsx b/ts/components/leftpane/LeftPane.tsx index 07989b9f0a..93f7d968ac 100644 --- a/ts/components/leftpane/LeftPane.tsx +++ b/ts/components/leftpane/LeftPane.tsx @@ -11,14 +11,18 @@ import { useIsRtl } from '../../util/i18n/rtlSupport'; export const leftPaneListWidth = 300; // var(--left-panel-width) without the 80px of the action gutter const StyledLeftPane = styled.div<{ $isRtl: boolean }>` - width: ${() => `${leftPaneListWidth}px`}; - height: 100%; + width: 100%; + @media screen and (min-width: 799px) { + width: ${() => `${leftPaneListWidth}px`}; + height: 100%; + border-left: 1px solid var(--border-color); + border-right: 1px solid var(--border-color); + } + height: calc(100% - 76px); display: inline-flex; flex-direction: column; position: relative; flex-shrink: 0; - border-left: 1px solid var(--border-color); - border-right: 1px solid var(--border-color); direction: ${({ $isRtl }) => ($isRtl ? 'rtl' : 'ltr')}; `; @@ -38,7 +42,13 @@ const CallContainer = () => { const StyledLeftPaneSession = styled.div` display: flex; + flex-direction: column-reverse; height: 100%; + width: 100%; + + @media screen and (min-width: 799px) { + flex-direction: row; + } `; export const LeftPane = () => { diff --git a/ts/components/leftpane/LeftPaneList.tsx b/ts/components/leftpane/LeftPaneList.tsx index 0905474495..48b9713669 100644 --- a/ts/components/leftpane/LeftPaneList.tsx +++ b/ts/components/leftpane/LeftPaneList.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components'; export const StyledLeftPaneList = styled.div` + height: 100%; flex-grow: 1; flex-shrink: 1; diff --git a/ts/components/leftpane/LeftPaneSectionContainer.tsx b/ts/components/leftpane/LeftPaneSectionContainer.tsx index 07c500314b..e573d5642f 100644 --- a/ts/components/leftpane/LeftPaneSectionContainer.tsx +++ b/ts/components/leftpane/LeftPaneSectionContainer.tsx @@ -1,14 +1,26 @@ import styled from 'styled-components'; export const LeftPaneSectionContainer = styled.div` - width: var(--actions-panel-width); display: flex; - flex-direction: column; + width: 100%; + justify-content: space-between; align-items: center; - overflow-y: auto; - flex-shrink: 0; + background: var(--background-primary-color); - .session-icon-button { - padding: 30px 20px; + @media screen and (min-width: 799px) { + width: var(--actions-panel-width); + flex-direction: column; + align-items: center; + overflow-y: auto; + flex-shrink: 0; + + .session-icon-button { + padding: 30px 20px; + } + } + .mobile-active-conversation & { + @media screen and (max-width: 679px) { + z-index: -1; + } } `; diff --git a/ts/components/leftpane/LeftPaneSectionHeader.tsx b/ts/components/leftpane/LeftPaneSectionHeader.tsx index 2dbb4f021d..b04439ba27 100644 --- a/ts/components/leftpane/LeftPaneSectionHeader.tsx +++ b/ts/components/leftpane/LeftPaneSectionHeader.tsx @@ -27,6 +27,7 @@ const StyledLeftPaneSectionHeader = styled(Flex)` height: var(--main-view-header-height); padding-inline-end: 7px; transition: var(--default-duration); + background: var(--background-primary-color); `; const SectionTitle = styled(H4)` diff --git a/ts/components/lightbox/Lightbox.tsx b/ts/components/lightbox/Lightbox.tsx index 483bc91735..9510a3c20d 100644 --- a/ts/components/lightbox/Lightbox.tsx +++ b/ts/components/lightbox/Lightbox.tsx @@ -29,6 +29,46 @@ type Props = { const CONTROLS_WIDTH = 50; const CONTROLS_SPACING = 10; +const MainContainer = styled.div` + display: flex; + flex-direction: row; + flex-grow: 1; + min-height: 0; + min-width: 0; + overflow: hidden; + padding: 40px 40px 0px; + @media screen and (min-width: 799px) { + padding: 40px 40px 0px; + } +`; + +const ControlsOffset = styled.div` + @media screen and (min-width: 799px) { + width: ${CONTROLS_WIDTH}; + margin-right: ${CONTROLS_SPACING}; + flex-shrink: 0; + } +`; + +const Controls = styled.div` + display: flex; + flex-direction: column; + flex-shrink: 0; + justify-content: space-between; + margin-left: ${CONTROLS_SPACING}, + position: fixed; + top: 0px; + right: 40px; + bottom: 0px; + width: ${CONTROLS_WIDTH}px; + @media screen and (min-width: 799px) { + position: relative; + top: unset; + right: unset; + bottom: unset; + } +`; + const styles = { container: { display: 'flex', @@ -132,10 +172,10 @@ interface IconButtonProps { onClick?: () => void; style?: CSSProperties; unicode: - | LUCIDE_ICONS_UNICODE.CHEVRON_RIGHT - | LUCIDE_ICONS_UNICODE.CHEVRON_LEFT - | LUCIDE_ICONS_UNICODE.X - | LUCIDE_ICONS_UNICODE.ARROW_DOWN_TO_LINE; + | LUCIDE_ICONS_UNICODE.CHEVRON_RIGHT + | LUCIDE_ICONS_UNICODE.CHEVRON_LEFT + | LUCIDE_ICONS_UNICODE.X + | LUCIDE_ICONS_UNICODE.ARROW_DOWN_TO_LINE; } const IconButton = ({ onClick, unicode }: IconButtonProps) => { @@ -287,8 +327,8 @@ export const Lightbox = (props: Props) => { return (
-
-
+ +
{!isUndefined(contentType) ? ( @@ -302,7 +342,7 @@ export const Lightbox = (props: Props) => { {caption ?
{caption}
: null}
-
+ @@ -314,8 +354,8 @@ export const Lightbox = (props: Props) => { style={styles.saveButton} /> ) : null} -
-
+ +
{onPrevious ? ( diff --git a/ts/components/registration/components/Hero.tsx b/ts/components/registration/components/Hero.tsx index 3abcf7fb64..573b5d616d 100644 --- a/ts/components/registration/components/Hero.tsx +++ b/ts/components/registration/components/Hero.tsx @@ -1,13 +1,17 @@ import styled from 'styled-components'; const StyledHeroContainer = styled.div` - width: 40%; - height: 100%; + display: none; - div { - width: 100%; + @media screen and (min-width: 799px) { + display: block; + width: 40%; height: 100%; - overflow: hidden; + div { + width: 100%; + height: 100%; + overflow: hidden; + } } `; diff --git a/ts/localization b/ts/localization index e4af757c96..d74dd043af 160000 --- a/ts/localization +++ b/ts/localization @@ -1 +1 @@ -Subproject commit e4af757c969056eb07d69c21f9b6658f45a438a5 +Subproject commit d74dd043af8998ec5cf2d29e3c28596efd111794 diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts index 21375eec45..4a4d85d686 100644 --- a/ts/mains/main_node.ts +++ b/ts/mains/main_node.ts @@ -254,8 +254,8 @@ function getDefaultWindowSize() { return { defaultWidth: 880, defaultHeight: openDevToolsTestIntegration() ? 1000 : 820, // the dev tools open at the bottom hide some stuff which should be visible - minWidth: 880, - minHeight: 600, + minWidth: 380, + minHeight: 300, }; } diff --git a/ts/util/attachment/attachments_files.ts b/ts/util/attachment/attachments_files.ts index f7737ba9e2..613c566d74 100644 --- a/ts/util/attachment/attachments_files.ts +++ b/ts/util/attachment/attachments_files.ts @@ -8,6 +8,14 @@ import { encryptAttachmentBufferRenderer, } from './local_attachments_encrypter'; +import { promises as fs } from 'fs'; + +async function ensureFile(filePath: string) { + await fs.mkdir(path.dirname(filePath), { recursive: true }); + const handle = await fs.open(filePath, 'a'); + await handle.close(); +} + export const createReader = (root: string) => { if (!isString(root)) { throw new TypeError("'root' must be a path"); @@ -78,7 +86,7 @@ const createWriterForExisting = (root: string) => { throw new Error('Invalid relative path'); } - await fse.ensureFile(normalized); + await ensureFile(normalized); if (!isArrayBuffer(arrayBuffer)) { throw new TypeError("'bufferIn' must be an array buffer"); } diff --git a/ts/util/logger/renderer_process_logging.ts b/ts/util/logger/renderer_process_logging.ts index 471c031cab..c07806840e 100644 --- a/ts/util/logger/renderer_process_logging.ts +++ b/ts/util/logger/renderer_process_logging.ts @@ -11,7 +11,7 @@ import pino from 'pino'; import { LogLevel, - cleanArgs, + //cleanArgs, getLogLevelString, levelMaxLength, type LogEntryType, @@ -64,14 +64,14 @@ function logAtLevel(level: LogLevel, ...args: ReadonlyArray): void { const prefix = getLogLevelString(level).toUpperCase().padEnd(levelMaxLength, ' '); console._log(prefix, now(), ...args); - const levelString = getLogLevelString(level); - const msg = cleanArgs(args); + //const levelString = getLogLevelString(level); + //const msg = cleanArgs(args); if (!globalLogger) { throw new Error('Logger has not been initialized yet'); } // then we also log with the globalLogger, that logs to stdout and the rotating file - globalLogger[levelString](msg); + //globalLogger[levelString](msg); } window.log = {