Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video message transcription, Apps and Channels tabs in search, Folders sidebar and sidebar resize #388

Merged
merged 60 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d56cf3a
First steps to video transcription
H7GhosT Dec 17, 2024
f8741de
Animate back
H7GhosT Dec 17, 2024
6cf9fcd
Add acutal transcription and loader
H7GhosT Dec 17, 2024
ca68026
Make next prev working properly, fix animation of canvas...
H7GhosT Dec 17, 2024
5a98de0
Show time sent when audio mode
H7GhosT Dec 17, 2024
8f71466
Show premium popup if user is not premium
H7GhosT Dec 17, 2024
717d320
Some fixes
H7GhosT Dec 17, 2024
737ab7f
Fix random error
H7GhosT Dec 17, 2024
5f8867d
Fix playing video from audio view
H7GhosT Dec 18, 2024
663f4fe
Fix overflowing animated canvas
H7GhosT Dec 21, 2024
08abbc1
Some refactoring
H7GhosT Dec 21, 2024
511b940
Fix round video time
H7GhosT Dec 22, 2024
089cfa5
Show channels in search tab
H7GhosT Dec 18, 2024
44ac22d
Add apps tab and implement search
H7GhosT Dec 19, 2024
722f559
Add afterPerforming
H7GhosT Dec 19, 2024
cef00e6
Remove comments, console.logs
H7GhosT Dec 21, 2024
4cefc43
Don't show channels tab if no channels, hide my apps if no apps
H7GhosT Dec 22, 2024
9bc7fd4
Show channels in search tab
H7GhosT Dec 18, 2024
b833954
Add icons
H7GhosT Dec 20, 2024
80279b7
Add folders sidebar
H7GhosT Dec 20, 2024
ad5a10a
Context menu for folders and some improvements
H7GhosT Dec 20, 2024
aced1c9
A few more improvements
H7GhosT Dec 20, 2024
778239a
Ability to resize left sidebar
H7GhosT Dec 21, 2024
9279304
Some improvements
H7GhosT Dec 21, 2024
ac50882
Small improvements
H7GhosT Dec 21, 2024
56b4062
Remove comments, some refactoring
H7GhosT Dec 21, 2024
a203251
Remove comments again?
H7GhosT Dec 21, 2024
30f8561
Remove comment again
H7GhosT Dec 21, 2024
d630a47
Remove duplicate
H7GhosT Dec 21, 2024
2d7d408
Adjust background when changing sidebar width
H7GhosT Dec 21, 2024
e60451e
Folders sidebar improvements round 1
H7GhosT Dec 23, 2024
91383bb
Improvements round 2
H7GhosT Dec 23, 2024
0f010aa
Settings popup, stories when collapsed or resizing
H7GhosT Dec 23, 2024
07ee563
Add Ctrl-F shortcut
H7GhosT Dec 24, 2024
77ae145
Add scroll to vertical tabs, fix background gradient when right open
H7GhosT Dec 24, 2024
68beadf
Fix stories when folders sidebar
H7GhosT Dec 24, 2024
c08f981
Fix missing ripple
H7GhosT Dec 24, 2024
7e7f3f2
Remove margin-bottom from last section when settings in popup
H7GhosT Dec 24, 2024
9ba3c78
Add missing classname
H7GhosT Dec 24, 2024
1133764
Remove -1
H7GhosT Dec 24, 2024
1f209bf
Fix chat background again
H7GhosT Dec 24, 2024
b49f97d
Some improvements
H7GhosT Dec 25, 2024
e90cbac
Fix lang injection order
H7GhosT Dec 25, 2024
7211bc5
Fix switching nested chats flickering
H7GhosT Dec 25, 2024
8d03a59
Fix scss warning
H7GhosT Dec 25, 2024
668d3de
Fix floating menu when too close to window margin
H7GhosT Dec 25, 2024
4be22c6
Make 'a' lowercase
H7GhosT Dec 25, 2024
8024f68
Fix menu shifting when sidebar collapsed
H7GhosT Dec 25, 2024
027caec
Remove space and fix scrollbar color
H7GhosT Dec 25, 2024
b1fd633
Minor improvement
H7GhosT Dec 25, 2024
bcdf53b
Fix black line when right open and switching chats
H7GhosT Dec 25, 2024
cdfe063
Don't open settings modal if too small screen
H7GhosT Dec 25, 2024
df8c4ee
Rollback css changes for .btn-corner
H7GhosT Dec 25, 2024
52a480a
Fix collapsing left sidebar while right is open
H7GhosT Dec 26, 2024
a48efec
Exclude Shift+F from trigger search and react to Escape too
H7GhosT Dec 26, 2024
337d6c3
Video message transcription (#6)
H7GhosT Dec 26, 2024
1eef287
Merge branch 'apps-channels-tabs-in-search' into app-improvements-dec…
H7GhosT Dec 26, 2024
dec98fe
Merge branch 'left-sidebar-folders-and-resize' into app-improvements-…
H7GhosT Dec 26, 2024
371f722
Fix chat not closing when floating left sidebar (#9)
H7GhosT Dec 26, 2024
38eab84
Merge branch 'master' into pr/H7GhosT/388
morethanwords Feb 3, 2025
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
5 changes: 4 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,15 @@ <h4 class="phone"></h4>
</div>
</div>
</div>
<div class="sidebar-left-overlay"></div>
<div class="whole page-chats" style="display: none;" id="page-chats">
<div id="main-columns" class="tabs-container" data-animation="navigation">
<div class="folders-sidebar" id="folders-sidebar"></div>
<div class="sidebar-left-placeholder"></div>
<div class="tabs-tab chatlist-container sidebar sidebar-left main-column" id="column-left">
<div class="sidebar-slider tabs-container">
<div class="tabs-tab sidebar-slider-item item-main">
<div class="sidebar-header can-have-forum">
<div class="sidebar-header main-search-sidebar-header can-have-forum">
<div class="sidebar-header__btn-container">
<div class="animated-menu-icon"></div>
<div class="btn-icon sidebar-back-button"></div>
Expand Down
587 changes: 295 additions & 292 deletions public/assets/fonts/tgico.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/assets/fonts/tgico.ttf
Binary file not shown.
Binary file modified public/assets/fonts/tgico.woff
Binary file not shown.
5 changes: 5 additions & 0 deletions public/assets/img/add-chats-to-folder-shape.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion src/components/appMediaPlaybackController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import setCurrentTime from '../helpers/dom/setCurrentTime';

export type MediaItem = {mid: number, peerId: PeerId};

type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
export type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;

const SHOULD_USE_SAFARI_FIX = (() => {
try {
Expand Down Expand Up @@ -711,6 +711,10 @@ export class AppMediaPlaybackController extends EventListenerBase<{
return true;
}

public getPlayingMedia() {
return this.playingMedia;
}

public play = () => {
return this.toggle(true);
};
Expand Down
3 changes: 2 additions & 1 deletion src/components/appNavigationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' |
'esg' | 'multiselect' | 'input-helper' | 'autocomplete-helper' | 'markup' |
'global-search' | 'voice' | 'mobile-search' | 'filters' | 'global-search-focus' |
'toast' | 'dropdown' | 'forum' | 'stories' | 'stories-focus' | 'topbar-search',
'toast' | 'dropdown' | 'forum' | 'stories' | 'stories-focus' | 'topbar-search' |
'settings-popup',
onPop: (canAnimate: boolean) => boolean | void,
onEscape?: () => boolean,
noHistory?: boolean,
Expand Down
189 changes: 185 additions & 4 deletions src/components/appSearchSuper..ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import useHeavyAnimationCheck, {getHeavyAnimationPromise} from '../hooks/useHeav
import I18n, {LangPackKey, i18n, join} from '../lib/langPack';
import findUpClassName from '../helpers/dom/findUpClassName';
import {getMiddleware, Middleware, MiddlewareHelper} from '../helpers/middleware';
import {ChannelParticipant, Chat, ChatFull, ChatParticipant, Document, Message, MessageMedia, MessagesChats, Photo, StoryItem, Update, User, WebPage} from '../layer';
import {ChannelParticipant, Chat, ChatFull, ChatParticipant, Document, Message, MessageMedia, MessagesChats, Peer, Photo, StoryItem, Update, User, WebPage} from '../layer';
import SortedUserList from './sortedUserList';
import findUpTag from '../helpers/dom/findUpTag';
import appSidebarRight from './sidebarRight';
Expand Down Expand Up @@ -91,6 +91,8 @@ import getFwdFromName from '../lib/appManagers/utils/messages/getFwdFromName';
import SidebarSlider from './slider';
import setBlankToAnchor from '../lib/richTextProcessor/setBlankToAnchor';
import cancelClickOrNextIfNotClick from '../helpers/dom/cancelClickOrNextIfNotClick';
import createElementFromMarkup from '../helpers/createElementFromMarkup';
import numberThousandSplitter from '../helpers/number/numberThousandSplitter';

// const testScroll = false;

Expand All @@ -110,7 +112,7 @@ export type SearchSuperContext = {

export type SearchSuperMediaType = 'stories' | 'members' | 'media' |
'files' | 'links' | 'music' | 'chats' | 'voice' | 'groups' | 'similar' |
'savedDialogs' | 'saved';
'savedDialogs' | 'saved' | 'channels' | 'apps';
export type SearchSuperMediaTab = {
inputFilter?: SearchSuperType,
name: LangPackKey,
Expand Down Expand Up @@ -1215,7 +1217,7 @@ export default class AppSearchSuper {

if(!length && !contentTab.childElementCount) {
const div = document.createElement('div');
div.innerText = 'Nothing interesting here yet...';
div.append(i18n('Chat.Search.NothingFound'));
div.classList.add('position-center', 'text-center', 'content-empty', 'no-select');

parent.append(div);
Expand Down Expand Up @@ -1787,6 +1789,180 @@ export default class AppSearchSuper {
return xd.onChatsScroll();
}


private appendShowMoreButton(group: SearchGroup, toggleClassName = 'is-short-5') {
let shouldShowMore = false;

const showMoreButton: HTMLDivElement = createElementFromMarkup(`
<div class="search-group__show-more"></div>
`);
group.nameEl.append(showMoreButton);

updateShowMoreContent();

attachClickEvent(showMoreButton, () => {
shouldShowMore = !shouldShowMore;
updateShowMoreContent();
});
function updateShowMoreContent() {
showMoreButton.replaceChildren(i18n(shouldShowMore ? 'Separator.ShowLess' : 'Separator.ShowMore'));
group.container.classList.toggle(toggleClassName, !shouldShowMore);
}
}

private async renderPeerDialogs(peerIds: PeerId[], group: SearchGroup, middleware: Middleware, type?: 'bots' | 'channels') {
if(!middleware()) return;

for(const peerId of peerIds) {
const {dom} = appDialogsManager.addDialogNew({
peerId,
container: group.list,
avatarSize: 'abitbigger',
wrapOptions: {
middleware
}
});

const peer = await this.managers.appPeersManager.getPeer(peerId);
const username = await this.managers.appPeersManager.getPeerUsername(peerId);

if('participants_count' in peer) {
dom.lastMessageSpan.append(await getChatMembersString(peerId.toChatId()));
} else if('bot_active_users' in peer) {
dom.lastMessageSpan.append(i18n('BotUsers', [numberThousandSplitter(peer.bot_active_users)]));
} else if(username) {
dom.lastMessageSpan.append('@' + username);
} else if(type === 'bots') {
dom.lastMessageSpan.append(i18n('UnknownBotUsers'));
}
};
}

private async loadChannels({mediaTab, middleware}: SearchSuperLoadTypeOptions) {
if(this.searchContext.query) {
const group = new SearchGroup('Channels', 'channels');
group.setActive();
group.nameEl.style.display = 'none';

const SEARCH_LIMIT = 200; // will get filtered anyway
const {results: globalResults} = await this.managers.appUsersManager.searchContacts(this.searchContext.query, SEARCH_LIMIT);
const filteredResultsWithUndefined = await Promise.all(
globalResults.map(async user => await this.managers.appPeersManager.isBroadcast(user) ? user : undefined)
)
const filteredResults = filteredResultsWithUndefined.filter(Boolean);

this.renderPeerDialogs(filteredResults.map(user => user.toPeerId(true)), group, middleware);

if(filteredResults.length) {
mediaTab.contentTab.append(group.container);
}
this.afterPerforming(filteredResults.length, mediaTab.contentTab);

this.loaded[mediaTab.type] = true;
return;
}

const dialogs = await this.managers.dialogsStorage.getCachedDialogs();
const channelDialogsWithUndefined = await Promise.all(dialogs.map(async dialog => await this.managers.appPeersManager.isBroadcast(dialog.peerId) ? dialog : undefined))
const channelDialogs = channelDialogsWithUndefined.filter(Boolean);

if(channelDialogs.length) {
const group = new SearchGroup('Chat.Search.JoinedChannels', 'channels');
group.setActive();
mediaTab.contentTab.append(group.container);

const SHOW_MORE_LIMIT = 5;
if(channelDialogs.length > SHOW_MORE_LIMIT) this.appendShowMoreButton(group);

this.renderPeerDialogs(channelDialogs.map(dialog => dialog.peerId), group, middleware);
}

const recommendations = await this.managers.appChatsManager.getGenericChannelRecommendations();

if(recommendations.chats.length) {
const group = new SearchGroup('SimilarChannels', 'channels');
group.setActive();
mediaTab.contentTab.append(group.container);

this.renderPeerDialogs(recommendations.chats.map(chat => chat.id.toPeerId(true)), group, middleware);
}

this.afterPerforming(1, mediaTab.contentTab);
this.loaded[mediaTab.type] = true;
}

private async loadApps({mediaTab, middleware}: SearchSuperLoadTypeOptions) {
if(this.searchContext.query) {
const group = new SearchGroup('ChatList.Filter.Bots', 'apps');
group.setActive();

const SEARCH_LIMIT = 200; // will get filtered anyway
const {results: globalResults} = await this.managers.appUsersManager.searchContacts(this.searchContext.query, SEARCH_LIMIT);
const filteredResultsWithUndefined = await Promise.all(
globalResults.map(async user => await this.managers.appPeersManager.isBot(user) ? user : undefined)
)
const filteredResults = filteredResultsWithUndefined.filter(Boolean);

this.renderPeerDialogs(filteredResults.map(user => user.toPeerId(false)), group, middleware, 'bots');

if(filteredResults.length) {
mediaTab.contentTab.append(group.container);
}
this.afterPerforming(filteredResults.length, mediaTab.contentTab);

this.loaded[mediaTab.type] = true;
return;
}

const myTopApps = await rootScope.managers.appUsersManager.getTopPeers('bots_app');

if(myTopApps.length) {
const group = new SearchGroup('MiniApps.Apps', 'apps');
group.setActive();
mediaTab.contentTab.append(group.container);

const SHOW_MORE_LIMIT = 5;
if(myTopApps.length > SHOW_MORE_LIMIT) this.appendShowMoreButton(group);

this.renderPeerDialogs(myTopApps.map(app => app.id.toPeerId(false)), group, middleware, 'bots');
}

const group = new SearchGroup('MiniApps.Popular', 'apps');
group.setActive();
mediaTab.contentTab.append(group.container);

type GetPopularAppsResult = ReturnType<typeof rootScope.managers.appAttachMenuBotsManager.getPopularAppBots>;
let currentOffset: string | null = '', loadPromise: GetPopularAppsResult;
const APPS_LIMIT_PER_LOAD = 20;

const loadMoreApps = async() => {
if(loadPromise || !middleware() || currentOffset === null) return;

loadPromise = rootScope.managers.appAttachMenuBotsManager.getPopularAppBots(currentOffset, APPS_LIMIT_PER_LOAD);
const {nextOffset, userIds} = await loadPromise;

await this.renderPeerDialogs(userIds.map(id => id.toPeerId(false)), group, middleware, 'bots');

currentOffset = nextOffset || null;
loadPromise = undefined;
}

const scrollTarget = this.scrollable.container;

scrollTarget.addEventListener('scroll', () => {
const offset = 120;
if(this.mediaTab !== mediaTab) return; // There is one scrollable for all tabs
if(scrollTarget.scrollTop + scrollTarget.clientHeight >= scrollTarget.scrollHeight - offset) {
loadMoreApps();
}
});

await loadMoreApps();

this.afterPerforming(1, mediaTab.contentTab);
this.loaded[mediaTab.type] = true;
}

private loadType(options: SearchSuperLoadTypeOptions) {
const {
mediaTab,
Expand All @@ -1797,6 +1973,7 @@ export default class AppSearchSuper {
} = options;
const {type, inputFilter} = mediaTab;


let promise = this.loadPromises[type];
if(promise) {
return promise;
Expand All @@ -1810,6 +1987,10 @@ export default class AppSearchSuper {
promise = this.loadSimilarChannels(options);
} else if(type === 'savedDialogs') {
promise = this.loadSavedDialogs(options);
} else if(type === 'channels') {
promise = this.loadChannels(options);
} else if(type === 'apps') {
promise = this.loadApps(options);
}

if(promise) {
Expand Down Expand Up @@ -2321,7 +2502,7 @@ export default class AppSearchSuper {
return;
}

if(!this.historyStorage[tab.inputFilter]) {
if(tab.inputFilter && !this.historyStorage[tab.inputFilter]) {
const parent = tab.contentTab.parentElement;
// if(!testScroll) {
if(!parent.querySelector('.preloader')) {
Expand Down
21 changes: 14 additions & 7 deletions src/components/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
audioEl.classList.add('is-out');
}

let waveform = (doc.attributes.find((attribute) => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || new Uint8Array([]);
let waveform = (doc.attributes.find((attribute) => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio)?.waveform || new Uint8Array([]);
waveform = decodeWaveform(waveform.slice(0, 63));

const {svg, container: svgContainer, availW} = createWaveformBars(waveform, doc.duration);
Expand All @@ -181,7 +181,10 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
timeDiv.classList.add('audio-time');
audioEl.append(waveformContainer, timeDiv);

if(audioEl.transcriptionState !== undefined) {
if(audioEl.customAudioToTextButton) {
audioEl.classList.add('can-transcribe');
audioEl.append(audioEl.customAudioToTextButton);
} else if(audioEl.transcriptionState !== undefined) {
audioEl.classList.add('can-transcribe');
const speechRecognitionDiv = document.createElement('div');
speechRecognitionDiv.classList.add('audio-to-text-button');
Expand Down Expand Up @@ -463,7 +466,8 @@ export const findMediaTargets = (anchor: HTMLElement, anchorMid: number/* , useS

const selector = selectors.join(', ');

const elements = Array.from(container.querySelectorAll(selector)) as HTMLElement[];
let elements = Array.from(container.querySelectorAll(selector)) as HTMLElement[];
elements = elements.filter(element => element === anchor || element.matches(':not([data-to-be-skipped="1"])'));
const idx = elements.indexOf(anchor);

const mediaItems: MediaItem[] = elements.map((element) => ({peerId: element.dataset.peerId.toPeerId(), mid: +element.dataset.mid}));
Expand All @@ -483,7 +487,7 @@ export const findMediaTargets = (anchor: HTMLElement, anchorMid: number/* , useS
};

export default class AudioElement extends HTMLElement {
public audio: HTMLAudioElement;
public audio: HTMLMediaElement;
public preloader: ProgressivePreloader;
public message: Message.message;
public withTime = false;
Expand All @@ -496,6 +500,8 @@ export default class AudioElement extends HTMLElement {
public managers: AppManagers;
public transcriptionState: number;
public uploadingFileName: string;
public shouldWrapAsVoice?: boolean;
public customAudioToTextButton?: HTMLElement;

private listenerSetter = new ListenerSetter();
private onTypeDisconnect: () => void;
Expand Down Expand Up @@ -544,15 +550,15 @@ export default class AudioElement extends HTMLElement {
this.append(downloadDiv);
}

const onTypeLoad = await (isVoice ? wrapVoiceMessage(this) : wrapAudio(this));
const onTypeLoad = await (isVoice || this.shouldWrapAsVoice ? wrapVoiceMessage(this) : wrapAudio(this));

const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement;
audioTimeDiv.textContent = getDurationStr();

const onLoad = this.onLoad = (autoload: boolean) => {
this.onLoad = undefined;

const audio = this.audio = appMediaPlaybackController.addMedia(this.message, autoload);
const audio = this.audio ??= appMediaPlaybackController.addMedia(this.message, autoload) as HTMLMediaElement;

const readyPromise = this.readyPromise = deferredPromise<void>();
if(this.audio.readyState >= this.audio.HAVE_CURRENT_DATA) readyPromise.resolve();
Expand Down Expand Up @@ -780,7 +786,8 @@ export default class AudioElement extends HTMLElement {
inputFilter: {_: 'inputMessagesFilterEmpty'},
useSearch: false
})) {
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this, this.message.mid/* , this.searchContext.useSearch */);
const thisTarget = this.dataset.toBeSkipped ? this.audio.parentElement : this;
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(thisTarget, this.message.mid/* , this.searchContext.useSearch */);
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
}
}
Expand Down
Loading