Skip to content
Merged
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
2 changes: 1 addition & 1 deletion app/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fireshare",
"version": "1.6.12",
"version": "1.6.13",
"private": true,
"dependencies": {
"@emotion/react": "^11.9.0",
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export const SORT_OPTIONS = [
{ value: 'oldest', label: 'Oldest' },
{ value: 'most_views', label: ' ↓ Views' },
{ value: 'least_views', label: '↑ Views' },
{ value: 'name_asc', label: 'Name A→Z' },
{ value: 'name_desc', label: 'Name Z→A' },
]

export const AUTH_REQUIRED_PAGES = ['/', '/settings']
3 changes: 3 additions & 0 deletions app/client/src/components/cards/ImageUploadCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,12 @@ const ImageUploadCard = React.forwardRef(function ImageUploadCard(
const handleGameChange = async (_, newValue) => {
if (!newValue) {
setSelectedGame(null)
setUploadToGameFolder(false)
return
}
if (newValue._source === 'db') {
setSelectedGame(newValue)
setUploadToGameFolder(true)
return
}
setGameCreating(true)
Expand All @@ -159,6 +161,7 @@ const ImageUploadCard = React.forwardRef(function ImageUploadCard(
const created = (await GameService.createGame(gameData)).data
setAllGames((prev) => [...prev, created])
setSelectedGame({ ...created, _source: 'db' })
setUploadToGameFolder(true)
} catch {
setSelectedGame(null)
}
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/components/cards/UploadCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ const UploadCard = React.forwardRef(function UploadCard(
}
if (newValue._source === 'db') {
setSelectedGame(newValue)
setUploadToGameFolder(true)
return
}
// New game from SteamGridDB — create it in the DB
Expand All @@ -350,6 +351,7 @@ const UploadCard = React.forwardRef(function UploadCard(
const created = (await GameService.createGame(gameData)).data
setAllGames((prev) => [...prev, created])
setSelectedGame({ ...created, _source: 'db' })
setUploadToGameFolder(true)
} catch {
setSelectedGame(null)
}
Expand Down
3 changes: 3 additions & 0 deletions app/client/src/components/modal/UpdateDetailsModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ const DateField = ({ selectedDate, selectedTime, onDateChange, onTimeChange }) =
selected={selectedDate}
onSelect={(d) => onDateChange(d || null)}
defaultMonth={selectedDate || new Date()}
captionLayout="dropdown"
startMonth={new Date(1970, 0)}
endMonth={new Date(new Date().getFullYear() + 1, 11)}
/>
<Box sx={{ px: 1, pb: 1, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography sx={{ color: '#FFFFFFB3', fontSize: 13 }}>Time</Typography>
Expand Down
15 changes: 8 additions & 7 deletions app/client/src/components/modal/VideoModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ const DateField = ({ selectedDate, selectedTime, onDateChange, onTimeChange }) =
selected={selectedDate}
onSelect={(d) => onDateChange(d || null)}
defaultMonth={selectedDate || new Date()}
captionLayout="dropdown"
startMonth={new Date(1970, 0)}
endMonth={new Date(new Date().getFullYear() + 1, 11)}
/>
<Box sx={{ px: 1, pb: 1, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography sx={{ color: '#FFFFFFB3', fontSize: 13 }}>Time</Typography>
Expand Down Expand Up @@ -511,7 +514,10 @@ const VideoModal = ({
const cropCleared = cropChanged && cropStart === null && cropEnd === null

if (cropChanged) {
setVideo((prev) => ({ ...prev, info: { ...prev.info, start_time: cropStart, end_time: cropEnd, has_crop: false } }))
setVideo((prev) => ({
...prev,
info: { ...prev.info, start_time: cropStart, end_time: cropEnd, has_crop: false },
}))
}
if (cropApplied) {
setCropProcessing(true)
Expand Down Expand Up @@ -776,12 +782,7 @@ const VideoModal = ({
>
<VideoJSPlayer
key={`${vid.video_id}-${playerVersion}`}
sources={getVideoSources(
vid.video_id,
vid?.info,
vid.extension,
{ forceOriginal: editMode },
)}
sources={getVideoSources(vid.video_id, vid?.info, vid.extension, { forceOriginal: editMode })}
poster={getPosterUrl()}
autoplay={autoplay}
controls={true}
Expand Down
49 changes: 38 additions & 11 deletions app/client/src/components/modal/datepicker-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,51 @@
color: #FFFFFF33;
}

/* Nav buttons */
/* Nav buttons — hidden in dropdown caption mode */
.fireshare-rdp .rdp-nav {
position: absolute;
top: 14px;
right: 16px;
display: none;
}

/* Year / month dropdown caption */
.fireshare-rdp .rdp-dropdowns {
display: flex;
gap: 4px;
gap: 6px;
align-items: center;
}

.fireshare-rdp .rdp-dropdown_root {
position: relative;
}

.fireshare-rdp .rdp-button_previous,
.fireshare-rdp .rdp-button_next {
.fireshare-rdp .rdp-dropdown {
appearance: none;
-webkit-appearance: none;
background: #FFFFFF0D;
border: 1px solid #FFFFFF26;
border-radius: 6px;
color: white;
border: 1px solid #FFFFFF1A;
font-size: 14px;
font-weight: 600;
font-family: inherit;
padding: 4px 28px 4px 10px;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
}

.fireshare-rdp .rdp-dropdown:hover {
background-color: #FFFFFF1A;
border-color: #FFFFFF55;
}

.fireshare-rdp .rdp-button_previous:hover,
.fireshare-rdp .rdp-button_next:hover {
background: #FFFFFF1F;
.fireshare-rdp .rdp-dropdown:focus {
outline: none;
border-color: #3399FF;
box-shadow: 0 0 0 2px #3399FF33;
}

.fireshare-rdp .rdp-dropdown option {
background: #0d1f33;
color: white;
}
43 changes: 43 additions & 0 deletions app/client/src/components/player/VideoJSPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,48 @@ function SpacebarToggle() {
return null
}

/**
* FrameStepKeys — listens for , and . keys to step one frame backward/forward.
* Assumes 30 fps since HTML5 video does not expose the actual frame rate.
* Must be rendered inside <Player.Provider>.
*/
const FRAME_DURATION = 1 / 30
const FRAME_STEP_INTERVAL_MS = 150

function FrameStepKeys() {
const media = Player.useMedia()
const lastStepAt = useRef(0)

useEffect(() => {
if (!media) return

const handleKeyDown = (e) => {
if (e.key !== ',' && e.key !== '.') return
const tag = document.activeElement?.tagName?.toLowerCase()
if (tag === 'input' || tag === 'textarea' || tag === 'select') return

const now = Date.now()
if (now - lastStepAt.current < FRAME_STEP_INTERVAL_MS) return
lastStepAt.current = now

e.preventDefault()
media.pause()
media.currentTime = Math.min(
Math.max(media.currentTime + (e.key === '.' ? FRAME_DURATION : -FRAME_DURATION), 0),
media.duration || 0,
)
// Force the browser to decode and paint the new frame even if play()
// has never been called (before first play the renderer stays frozen).
media.play().then(() => media.pause()).catch(() => {})
}

document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [media])

return null
}

/**
* VideoJSPlayer — a drop-in replacement powered by Video.js 10.
*
Expand Down Expand Up @@ -331,6 +373,7 @@ const VideoJSPlayer = ({
startTime={startTime}
/>
<SpacebarToggle />
<FrameStepKeys />
</Player.Provider>
)
}
Expand Down
5 changes: 5 additions & 0 deletions app/client/src/views/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ const Dashboard = ({
return (b.view_count || 0) - (a.view_count || 0)
} else if (dateSortOrder.value === 'least_views') {
return (a.view_count || 0) - (b.view_count || 0)
} else if (dateSortOrder.value === 'name_asc' || dateSortOrder.value === 'name_desc') {
const nameA = (a.info?.title || '').toLowerCase()
const nameB = (b.info?.title || '').toLowerCase()
const cmp = nameA.localeCompare(nameB)
return dateSortOrder.value === 'name_asc' ? cmp : -cmp
} else {
const dateA = a.recorded_at ? new Date(a.recorded_at) : new Date(0)
const dateB = b.recorded_at ? new Date(b.recorded_at) : new Date(0)
Expand Down
8 changes: 8 additions & 0 deletions app/client/src/views/GameVideos.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ const GameVideos = ({ cardSize, authenticated, searchText }) => {
return [...filteredVideos].sort((a, b) => {
if (sortOrder.value === 'most_views') return (b.view_count || 0) - (a.view_count || 0)
if (sortOrder.value === 'least_views') return (a.view_count || 0) - (b.view_count || 0)
if (sortOrder.value === 'name_asc' || sortOrder.value === 'name_desc') {
const cmp = (a.info?.title || '').toLowerCase().localeCompare((b.info?.title || '').toLowerCase())
return sortOrder.value === 'name_asc' ? cmp : -cmp
}
const dateA = a.recorded_at ? new Date(a.recorded_at) : new Date(0)
const dateB = b.recorded_at ? new Date(b.recorded_at) : new Date(0)
return sortOrder.value === 'newest' ? dateB - dateA : dateA - dateB
Expand All @@ -273,6 +277,10 @@ const GameVideos = ({ cardSize, authenticated, searchText }) => {
return tagged.sort((a, b) => {
if (sortOrder.value === 'most_views') return b.views - a.views
if (sortOrder.value === 'least_views') return a.views - b.views
if (sortOrder.value === 'name_asc' || sortOrder.value === 'name_desc') {
const cmp = (a.item.info?.title || '').toLowerCase().localeCompare((b.item.info?.title || '').toLowerCase())
return sortOrder.value === 'name_asc' ? cmp : -cmp
}
return sortOrder.value === 'newest' ? b.date - a.date : a.date - b.date
})
}, [sortedVideos, filteredImages, sortOrder])
Expand Down
5 changes: 5 additions & 0 deletions app/client/src/views/ImageFeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ const ImageFeed = ({ authenticated, searchText, cardSize, selectedImageFolder, o
return (b.view_count || 0) - (a.view_count || 0)
} else if (sortOrder.value === 'least_views') {
return (a.view_count || 0) - (b.view_count || 0)
} else if (sortOrder.value === 'name_asc' || sortOrder.value === 'name_desc') {
const nameA = (a.info?.title || '').toLowerCase()
const nameB = (b.info?.title || '').toLowerCase()
const cmp = nameA.localeCompare(nameB)
return sortOrder.value === 'name_asc' ? cmp : -cmp
} else {
const dateA = a.created_at ? new Date(a.created_at) : new Date(0)
const dateB = b.created_at ? new Date(b.created_at) : new Date(0)
Expand Down
5 changes: 5 additions & 0 deletions app/client/src/views/TagVideos.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ const TagVideos = ({ cardSize, authenticated, searchText }) => {
return (b.view_count || 0) - (a.view_count || 0)
} else if (sortOrder.value === 'least_views') {
return (a.view_count || 0) - (b.view_count || 0)
} else if (sortOrder.value === 'name_asc' || sortOrder.value === 'name_desc') {
const nameA = (a.info?.title || '').toLowerCase()
const nameB = (b.info?.title || '').toLowerCase()
const cmp = nameA.localeCompare(nameB)
return sortOrder.value === 'name_asc' ? cmp : -cmp
} else {
const dateA = a.recorded_at ? new Date(a.recorded_at) : new Date(0)
const dateB = b.recorded_at ? new Date(b.recorded_at) : new Date(0)
Expand Down
12 changes: 6 additions & 6 deletions app/server/fireshare/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,15 @@ def scan_videos(root):
logger.debug(f"Updating Video {video_id}, available=True")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "available": True })
if not existing.created_at:
created_at = datetime.fromtimestamp(os.path.getctime(f"{videos_path}/{path}"))
created_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
logger.debug(f"Updating Video {video_id}, created_at={created_at}")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "created_at": created_at })
if not existing.updated_at:
updated_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
logger.debug(f"Updating Video {video_id}, updated_at={updated_at}")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "updated_at": updated_at })
else:
created_at = datetime.fromtimestamp(os.path.getctime(f"{videos_path}/{path}"))
created_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
updated_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
recorded_at = util.extract_date_from_file(vf)
v = Video(video_id=video_id, extension=vf.suffix, path=path, available=True, created_at=created_at, updated_at=updated_at, recorded_at=recorded_at)
Expand Down Expand Up @@ -423,15 +423,15 @@ def scan_video(ctx, path, tag_ids, game_id, title):
logger.debug(f"Updating Video {video_id}, available=True")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "available": True })
if not existing.created_at:
created_at = datetime.fromtimestamp(os.path.getctime(f"{videos_path}/{path}"))
created_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
logger.debug(f"Updating Video {video_id}, created_at={created_at}")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "created_at": created_at })
if not existing.updated_at:
updated_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
logger.debug(f"Updating Video {video_id}, updated_at={updated_at}")
db.session.query(Video).filter_by(video_id=existing.video_id).update({ "updated_at": updated_at })
else:
created_at = datetime.fromtimestamp(os.path.getctime(f"{videos_path}/{path}"))
created_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
updated_at = datetime.fromtimestamp(os.path.getmtime(f"{videos_path}/{path}"))
recorded_at = util.extract_date_from_file(video_file)
v = Video(video_id=video_id, extension=video_file.suffix, path=path, available=True, created_at=created_at, updated_at=updated_at, recorded_at=recorded_at)
Expand Down Expand Up @@ -1033,7 +1033,7 @@ def scan_images(root):
db.session.commit()
logger.debug(f"Regenerated derived data for existing image {iid}")
else:
created_at = datetime.fromtimestamp(os.path.getctime(str(img_file)))
created_at = datetime.fromtimestamp(os.path.getmtime(str(img_file)))
updated_at = datetime.fromtimestamp(os.path.getmtime(str(img_file)))
source_folder = rel_path.split('/')[0] if '/' in rel_path else None
img = Image(image_id=iid, extension=img_file.suffix, path=rel_path,
Expand Down Expand Up @@ -1182,7 +1182,7 @@ def scan_image(ctx, path, game_id, tag_ids, title):
else:
logger.debug(f"Image {iid} already indexed")
else:
created_at = datetime.fromtimestamp(os.path.getctime(str(img_file)))
created_at = datetime.fromtimestamp(os.path.getmtime(str(img_file)))
updated_at = datetime.fromtimestamp(os.path.getmtime(str(img_file)))
source_folder = rel_path.split('/')[0] if '/' in rel_path else None
img = Image(image_id=iid, extension=img_file.suffix, path=rel_path,
Expand Down
7 changes: 3 additions & 4 deletions app/server/fireshare/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1438,12 +1438,11 @@ def extract_date_from_file(file_path: Path):
logger.debug(f"Using filename date for {file_path.name}: {filename_date}")
return filename_date

# 3. Fall back to file creation time
# 3. Fall back to file modification time (mtime is stable; ctime changes on move/chmod)
try:
# Use ctime on Unix (inode change time, often creation) or creation time on Windows
created_timestamp = os.path.getctime(file_path)
created_timestamp = os.path.getmtime(file_path)
created_date = datetime.fromtimestamp(created_timestamp)
logger.debug(f"Using file creation date for {file_path.name}: {created_date}")
logger.debug(f"Using file mtime for {file_path.name}: {created_date}")
return created_date
except Exception as ex:
logger.warning(f"Failed to get file creation time for {file_path}: {ex}")
Expand Down
Loading