update to session card design #342

This commit is contained in:
CyferShepard
2025-03-30 13:39:25 +02:00
parent 7b4f1f4694
commit 1a57684624
3 changed files with 224 additions and 214 deletions

View File

@@ -86,20 +86,37 @@ function SessionCard(props) {
}
return (
<Card className="stat-card" style={cardStyle}>
<Card className="session-card" style={cardStyle}>
<div className="card-device-image-overlay">
<img
className="card-device-image"
src={
baseUrl +
"/proxy/web/assets/img/devices/?devicename=" +
(props.data.session.Client.toLowerCase() === "jellyfin mobile (ios)" &&
props.data.session.DeviceName.toLowerCase() === "iphone"
? "apple"
: props.data.session.Client.toLowerCase().includes("web")
? clientData.find((item) => props.data.session.DeviceName.toLowerCase().includes(item)) || "other"
: clientData.find((item) => props.data.session.Client.toLowerCase().includes(item)) || "other")
}
alt=""
/>
</div>
<IpInfoModal show={ipModalVisible} onHide={() => setIPModalVisible(false)} ipAddress={ipAddressLookup} />
<div style={cardBgStyle} className="rounded-top">
<Row className="h-100">
<Col className="d-none d-lg-block stat-card-banner">
<Row className="h-100 p-0 m-0">
<Col className="d-none d-lg-block session-card-banner-image">
<Card.Img
variant="top"
className={
props.data.session.NowPlayingItem.Type === "Audio"
? "stat-card-image-audio rounded-0 rounded-start"
: "stat-card-image rounded-0 rounded-start"
: "session-card-item-image"
}
src={
baseUrl+"/proxy/Items/Images/Primary?id=" +
baseUrl +
"/proxy/Items/Images/Primary?id=" +
(props.data.session.NowPlayingItem.SeriesId
? props.data.session.NowPlayingItem.SeriesId
: props.data.session.NowPlayingItem.Id) +
@@ -107,72 +124,81 @@ function SessionCard(props) {
}
/>
</Col>
<Col className="w-100 h-100">
<Col className="w-100 h-100 m-0 px-0">
<Card.Body className="w-100 h-100 p-1 pb-2">
<Container className="h-100 d-flex flex-column justify-content-between g-0">
<Row className="d-flex justify-content-end" style={{ fontSize: "smaller" }}>
<Row className="d-flex justify-content-start session-details" style={{ fontSize: "smaller" }}>
<Col className="col-10">
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="ACTIVITY_TABLE.DEVICE" />
</Col>
<Col className="col-auto ellipse">{props.data.session.DeviceName}</Col>
</Row>
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="ACTIVITY_TABLE.CLIENT" />
</Col>
<Col className="col-auto ellipse">
{props.data.session.DeviceName +
" - " +
props.data.session.Client +
" " +
props.data.session.ApplicationVersion}
{props.data.session.Client + " " + props.data.session.ApplicationVersion}
</Col>
</Row>
<Row className="d-flex flex-column flex-md-row">
{props.data.session.NowPlayingItem.VideoStream !== "" &&
<Col className="col-auto ellipse">
<span>{props.data.session.NowPlayingItem.VideoStream}</span>
{props.data.session.NowPlayingItem.VideoStream !== "" && (
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="VIDEO" />
</Col>
}
<Col className="col-auto ellipse">
{props.data.session.NowPlayingItem.AudioStream !== "" &&
<span>{props.data.session.NowPlayingItem.AudioStream}</span>
}
</Col>
<Col className="col-auto ellipse">
{props.data.session.NowPlayingItem.SubtitleStream !== "" &&
<Col className="col-auto ellipse">{props.data.session.NowPlayingItem.VideoStream}</Col>
</Row>
)}
{props.data.session.NowPlayingItem.AudioStream !== "" && (
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="AUDIO" />
</Col>
<Col className="col-auto ellipse">{props.data.session.NowPlayingItem.AudioStream}</Col>
</Row>
)}
{props.data.session.NowPlayingItem.SubtitleStream !== "" && (
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="SUBTITLES" />
</Col>
<Col className="col-auto ellipse">
<Tooltip title={props.data.session.NowPlayingItem.SubtitleStream}>
<span>{props.data.session.NowPlayingItem.SubtitleStream}</span>
</Tooltip>
}
</Col>
</Row>
</Col>
</Row>
)}
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">
<Trans i18nKey="ACTIVITY_TABLE.IP_ADDRESS" />
</Col>
<Col className="col-auto ellipse">
{isRemoteSession(props.data.session.RemoteEndPoint) &&
(window.env.JS_GEOLITE_ACCOUNT_ID ?? import.meta.env.JS_GEOLITE_ACCOUNT_ID) ? (
(window.env.JS_GEOLITE_ACCOUNT_ID ?? import.meta.env.JS_GEOLITE_ACCOUNT_ID) ? (
<Link
className="text-decoration-none text-white"
onClick={() => showIPDataModal(props.data.session.RemoteEndPoint)}
>
<Trans i18nKey="ACTIVITY_TABLE.IP_ADDRESS" />: {props.data.session.RemoteEndPoint}
{props.data.session.RemoteEndPoint}
</Link>
) : (
<span>
<Trans i18nKey="ACTIVITY_TABLE.IP_ADDRESS" />: {props.data.session.RemoteEndPoint}
</span>
<span>{props.data.session.RemoteEndPoint}</span>
)}
</Col>
</Row>
</Col>
<Col className="col-2 d-flex justify-content-center">
<img
className="card-device-image"
src={
baseUrl+"/proxy/web/assets/img/devices/?devicename=" +
(props.data.session.Client.toLowerCase() === "jellyfin mobile (ios)" &&
props.data.session.DeviceName.toLowerCase() === "iphone"
? "apple"
: props.data.session.Client.toLowerCase().includes("web")
? clientData.find((item) => props.data.session.DeviceName.toLowerCase().includes(item)) || "other"
: clientData.find((item) => props.data.session.Client.toLowerCase().includes(item)) || "other")
}
alt=""
/>
<Row>
<Col className="col-auto session-details-title text-end text-uppercase">ETA</Col>
<Col className="col-auto ellipse">
{getETAFromTicks(
props.data.session.NowPlayingItem.RunTimeTicks - props.data.session.PlayState.PositionTicks
)}
</Col>
</Row>
</Col>
</Row>
@@ -191,14 +217,12 @@ function SessionCard(props) {
</Link>
</Card.Text>
</Row>
) : (props.data.session.NowPlayingItem.Type === "Audio" && props.data.session.NowPlayingItem.Artists.length > 0) ? (
) : props.data.session.NowPlayingItem.Type === "Audio" &&
props.data.session.NowPlayingItem.Artists.length > 0 ? (
<Col className="col-auto p-0">
<Card.Text>
{props.data.session.NowPlayingItem.Artists[0]}
</Card.Text>
<Card.Text>{props.data.session.NowPlayingItem.Artists[0]}</Card.Text>
</Col>
) :
(
) : (
<></>
)}
<Row className="d-flex flex-row justify-content-between p-0 m-0">
@@ -231,7 +255,7 @@ function SessionCard(props) {
{props.data.session.UserPrimaryImageTag !== undefined ? (
<img
className="session-card-user-image"
src={baseUrl+"/proxy/Users/Images/Primary?id=" + props.data.session.UserId + "&quality=50"}
src={baseUrl + "/proxy/Users/Images/Primary?id=" + props.data.session.UserId + "&quality=50"}
alt=""
/>
) : (

View File

@@ -63,25 +63,21 @@ function Sessions() {
return "";
}
let transcodeType = i18next.t("SESSIONS.DIRECT_PLAY");
let transcodeVideoCodec = "";
if (row.TranscodingInfo && !row.TranscodingInfo.IsVideoDirect){
if (row.TranscodingInfo && !row.TranscodingInfo.IsVideoDirect) {
transcodeType = i18next.t("SESSIONS.TRANSCODE");
transcodeVideoCodec = ` -> ${row.TranscodingInfo.VideoCodec.toUpperCase()}`;
}
let bitRate = convertBitrate(
row.TranscodingInfo
? row.TranscodingInfo.Bitrate
: videoStream.BitRate);
let bitRate = convertBitrate(row.TranscodingInfo ? row.TranscodingInfo.Bitrate : videoStream.BitRate);
const originalVideoCodec = videoStream.Codec.toUpperCase();
return `${i18next.t("VIDEO")}: ${transcodeType} (${originalVideoCodec}${transcodeVideoCodec} - ${bitRate})`;
}
return `${transcodeType} (${originalVideoCodec}${transcodeVideoCodec} - ${bitRate})`;
};
const getAudioStream = (row) => {
let mediaTypeAudio = row.NowPlayingItem.Type === 'Audio';
let mediaTypeAudio = row.NowPlayingItem.Type === "Audio";
let streamIndex = row.PlayState.AudioStreamIndex;
if ((streamIndex === undefined || streamIndex === -1) && !mediaTypeAudio) {
return "";
@@ -89,31 +85,29 @@ function Sessions() {
let transcodeType = i18next.t("SESSIONS.DIRECT_PLAY");
let transcodeCodec = "";
if (row.TranscodingInfo && !row.TranscodingInfo.IsAudioDirect){
if (row.TranscodingInfo && !row.TranscodingInfo.IsAudioDirect) {
transcodeType = i18next.t("SESSIONS.TRANSCODE");
transcodeCodec = ` -> ${row.TranscodingInfo.AudioCodec.toUpperCase()}`;
}
let bitRate = "";
if (mediaTypeAudio) {
bitRate = " - " + convertBitrate(
row.TranscodingInfo
? row.TranscodingInfo.Bitrate
: row.NowPlayingItem.Bitrate);
bitRate = " - " + convertBitrate(row.TranscodingInfo ? row.TranscodingInfo.Bitrate : row.NowPlayingItem.Bitrate);
}
let originalCodec = "";
if (mediaTypeAudio){
if (mediaTypeAudio) {
originalCodec = `${row.NowPlayingItem.Container.toUpperCase()}`;
}
else if (row.NowPlayingItem.MediaStreams && row.NowPlayingItem.MediaStreams.length && streamIndex < row.NowPlayingItem.MediaStreams.length) {
originalCodec = row.NowPlayingItem.MediaStreams[streamIndex].Codec.toUpperCase();
} else if (
row.NowPlayingItem.MediaStreams &&
row.NowPlayingItem.MediaStreams.length &&
streamIndex < row.NowPlayingItem.MediaStreams.length
) {
originalCodec = row.NowPlayingItem.MediaStreams[streamIndex].Codec.toUpperCase();
}
return originalCodec != "" ? `${i18next.t("AUDIO")}: ${transcodeType} (${originalCodec}${transcodeCodec}${bitRate})`
: `${i18next.t("AUDIO")}: ${transcodeType}`;
}
return originalCodec != "" ? `${transcodeType} (${originalCodec}${transcodeCodec}${bitRate})` : `${transcodeType}`;
};
const getSubtitleStream = (row) => {
let result = "";
@@ -129,7 +123,7 @@ function Sessions() {
}
if (row.NowPlayingItem.MediaStreams && row.NowPlayingItem.MediaStreams.length) {
result = `${i18next.t("SUBTITLES")}: ${row.NowPlayingItem.MediaStreams[subStreamIndex].DisplayTitle}`;
result = `${row.NowPlayingItem.MediaStreams[subStreamIndex].DisplayTitle}`;
}
return result;

View File

@@ -1,207 +1,199 @@
.sessions-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(auto, 520px));
grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/
/* margin-right: 20px; */
display: grid;
grid-template-columns: repeat(auto-fit, minmax(auto, 520px));
grid-auto-rows: 270px; /* max-width+offset so 215 + 20*/
/* margin-right: 20px; */
}
.session-details {
margin: 0px !important;
padding: 0px !important;
}
.session-details-title {
color: lightgray !important;
min-width: 80px;
padding: 0px !important;
}
.card-device-image-overlay {
position: absolute;
top: 5px; /* Adjust as needed */
right: 5px; /* Adjust as needed */
z-index: 10; /* Ensure it appears above other elements */
}
.session-card {
display: flex;
color: white;
background-color: grey;
/* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */
max-height: 180px;
max-width: 500px;
/* margin-left: 20px; */
/* margin-bottom: 10px; */
background-size: cover;
border-radius: 8px 8px 0px 8px;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto 1fr;
position: relative; /* Add this */
border: 0 !important;
background-color: var(--background-color) !important;
color: white;
max-width: 520px;
max-height: 220px;
}
.progress-bar {
/* grid-row: 2 / 3;
/* grid-row: 2 / 3;
grid-column: 1/3; */
height: 5px;
background-color: #101010 !important;
border-radius: 0px 0px 8px 8px;
height: 5px;
background-color: #101010 !important;
border-radius: 0px 0px 8px 8px;
}
.progress-custom {
height: 100%;
background-color: #00A4DC;
transition: width 0.2s ease-in-out;
border-radius: 0px 0px 0px 8px;
height: 100%;
background-color: #00a4dc;
transition: width 0.2s ease-in-out;
border-radius: 0px 0px 0px 8px;
}
.card-banner {
max-height: inherit;
height: 215px;
grid-row: 1 / 2;
grid-column-start: 1;
max-height: inherit;
height: 215px;
grid-row: 1 / 2;
grid-column-start: 1;
}
.card-details {
padding-left: 5px;
max-height: inherit;
width: inherit;
padding-left: 5px;
max-height: inherit;
width: inherit;
grid-row: 1 / 3;
grid-row: 1 / 3;
grid-column: 2 / 3;
backdrop-filter: blur(1px);
background-color: rgb(0, 0, 0, 0.6);
border-radius: 0px 8px 0px 0px;
grid-column: 2 / 3;
backdrop-filter: blur(1px);
background-color: rgb(0, 0, 0, 0.6);
border-radius: 0px 8px 0px 0px;
}
.card-banner-image {
border-radius: 8px 0px 0px 0px;
max-height: inherit;
/* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */
.session-card-banner-image {
max-width: 160px !important;
padding: 0px !important;
margin: 0px !important;
}
.card-user {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
bottom: 30px;
right: 0;
padding-right: 5px;
position: absolute;
bottom: 30px;
right: 0;
padding-right: 5px;
position: absolute;
}
.session-card-user-image {
border-radius: 50%;
max-width: 30px;
max-height: 30px;
margin-right: 5px;
border-radius: 50%;
max-width: 30px;
max-height: 30px;
margin-right: 5px;
}
.card-user-image-default
{
/* width: 50px !important; */
font-size: large;
.card-user-image-default {
/* width: 50px !important; */
font-size: large;
}
.card-username {
grid-row: 1 / 2;
grid-column: 2 / 3;
grid-row: 1 / 2;
grid-column: 2 / 3;
}
.card-username a {
text-decoration: none;
color: white;
text-decoration: none;
color: white;
}
.card-username:hover a{
color: rgb(0, 164, 219);
}
.card-username:hover a {
color: rgb(0, 164, 219);
}
.card-device {
margin-top: 5px;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
/* grid-column-gap: 10px; */
margin-top: 5px;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
/* grid-column-gap: 10px; */
}
.card-device-name {
font-size: small;
grid-row: 1 / 2;
grid-column: 2 / 3;
font-size: small;
grid-row: 1 / 2;
grid-column: 2 / 3;
}
.card-device-image {
max-width: 40px;
margin-top:5px;
width: 100%;
height: min-content;
max-width: 40px;
margin-top: 5px;
width: 100%;
height: min-content;
}
.card-client {
font-size: small;
grid-row: 2 / 3;
grid-column: 2 / 3;
font-size: small;
grid-row: 2 / 3;
grid-column: 2 / 3;
}
.card-item-name {
bottom: 45px;
margin-left: 5px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
bottom: 45px;
margin-left: 5px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: absolute;
position: absolute;
}
.card-playback-position {
bottom: 5px;
/* right: 5px; */
/* text-align: right; */
/* position: absolute; */
bottom: 5px;
/* right: 5px; */
/* text-align: right; */
/* position: absolute; */
}
.device-info {
margin-bottom: 100%;
margin-bottom: 100%;
}
.card-ip {
grid-row: 2 / 3;
grid-column: 2 / 3;
grid-row: 2 / 3;
grid-column: 2 / 3;
}
.card-text >a{
text-decoration: none !important;
color: white !important;
}
.card-text a:hover{
color: var(--secondary-color) !important;
}
.ellipse
{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
.card-text > a {
text-decoration: none !important;
color: white !important;
}
.card-text a:hover {
color: var(--secondary-color) !important;
}
.ellipse {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.session-card-item-image {
width: 160px !important;
height: 220px;
border-radius: 8px 0px 0px 0px !important;
}
@media (max-width: 350px) {
.card-device-image {
display: none;
}
.card-device-image {
display: none;
}
.card-client-version, .session-card-user-image
{
display: none !important;
}
}
.card-client-version,
.session-card-user-image {
display: none !important;
}
}