Search code examples
javascripthtmlcss

CSS resize property is not working with the sidebar that I created


Description

I am developing a website that has a button on the top right side and if the user clicks this button, the sidebar with a chatbot shows up. I want to make this chatbot sidebar resizable by using the resize property to allow the user to resize the sidebar with their mouse.

img1

Current Behavior

Even though I have added the resize property to .sidebar-content, I can't resize the chatbot sidebar.

/* Side bar */
.sidebar {
    position: fixed;
    top: 10px;
    right: 10px;
    height: 100%;
    width: 100%;
    max-width: 500px;
    background-color: none;
    overflow-x: hidden;
    transition: 0.5s;
    padding-top: 60px;
    color: white;
}

.sidebar-content {
    resize: both;
    display: none;
}

const promptInput = document.getElementById("userInput");
const chatContainer = document.getElementById("chatContainer");
const typingIndicator = document.getElementById("typingIndicator");
const sidebar = document.getElementById("sidebar");
const sidebarContent = document.getElementById("sidebarContent");
const imageContainer = document.getElementById("imageContainer");

async function sendMessage() {
  const prompt = promptInput.value.trim();
  if (!prompt && imageContainer.children.length === 0) {
    alert("Please enter a message or add an image.");  // Browser pop up message
    return;
  }

  // Collect image data
  const images = Array.from(imageContainer.querySelectorAll('.img-preview'))
    .map(img => img.src.split(',')[1]); // Extract base64 data

  addMessage(prompt, 'user', images);
  promptInput.value = "";

  showTypingIndicator();

  const generatedText = await generateText(prompt, images);
  addMessage(generatedText, 'bot');

  hideTypingIndicator();
  //clearImagePreviews(); Add this code if you want the image in the imageContainer disappear if the user sends the image.
}

async function generateText(prompt, images) {
  try {
    const response = await fetch("https://idx-polapo-invest-test-6163648-nylaufaarq-uc.a.run.app/generate_text_stream", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ prompt, images }),
    });

    if (!response.ok) {
      console.error("Error:", response.statusText);
      return "Error occurred while generating response.";
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let isFinished = false;
    let generatedTextContent = "";

    while (!isFinished) {
      const { done, value } = await reader.read();
      if (done) {
        isFinished = true;
        break;
      }
      generatedTextContent += decoder.decode(value, { stream: true });
    }

    return generatedTextContent;
  } catch (error) {
    console.error("Error:", error);
    return "An error occurred.";
  }
}

function addMessage(text, type, images = []) {

  let md = window.markdownit();

  const messageDiv = document.createElement("div");
  messageDiv.className = `message ${type}`;

  const messageContent = document.createElement("div");
  messageContent.className = "message-bubble fadeIn";
  const render = () => {
    messageContent.innerHTML = md.render(text);
  }
  render()

  images.forEach(src => {
    const img = document.createElement("img");
    img.src = `data:image/png;base64,${src}`;
    img.classList.add("message-image");
    messageContent.appendChild(img);
  });

  messageDiv.appendChild(messageContent);
  chatContainer.appendChild(messageDiv);
  chatContainer.scrollTop = chatContainer.scrollHeight;
}

function clearImagePreviews() {
  while (imageContainer.firstChild) {
    imageContainer.removeChild(imageContainer.firstChild);
  }
  checkImageContainerVisibility();
}

let typingTimeout;

function showTypingIndicator() {
  clearTimeout(typingTimeout);
  typingIndicator.style.display = "inline-block";
}

function hideTypingIndicator() {
  typingTimeout = setTimeout(() => {
    typingIndicator.style.display = "none";
  }, 1000);
}

function handleKeyPress(event) {
  if (event.key === "Enter") {
    sendMessage();
  }
}

function toggleSidebar() {
  if (sidebar.style.width === "500px") {
    sidebar.style.width = "0";
    sidebarContent.style.display = "none";
  } else {
    sidebar.style.width = "500px";
    sidebarContent.style.display = "block";
  }
}

window.onload = () => addMessage("Hello! How can I assist you today?", 'bot');

document.addEventListener('DOMContentLoaded', () => {
  const textInput = document.getElementById('userInput');

  textInput.addEventListener('paste', (event) => {
    const items = (event.clipboardData || window.clipboardData).items;
    for (const item of items) {
      if (item.type.indexOf('image') !== -1) {
        const file = item.getAsFile();
        const reader = new FileReader();
        reader.onload = (event) => {
          displayImage(event.target.result);
        };
        reader.readAsDataURL(file);
        event.preventDefault();
      }
    }
  });

  function displayImage(src) {
    const imgContainer = document.createElement('div');
    imgContainer.classList.add('img-preview-container');

    const img = document.createElement('img');
    img.src = src;
    img.classList.add('img-preview');

    const removeButton = document.createElement('button');
    removeButton.classList.add('remove-button');
    removeButton.textContent = '✖';
    removeButton.addEventListener('click', () => {
      imgContainer.remove();
      checkImageContainerVisibility();
    });

    imgContainer.appendChild(img);
    imgContainer.appendChild(removeButton);
    imageContainer.appendChild(imgContainer);
    checkImageContainerVisibility();

    const all_images = imageContainer.querySelectorAll('.img-preview-container');
    all_images.forEach(img => img.style.width = `${100 / all_images.length - 10}%`);
  }

  function checkImageContainerVisibility() {
    if (imageContainer.children.length > 0) {
      imageContainer.classList.remove('hidden');
    } else {
      imageContainer.classList.add('hidden');
    }
  }

  // Initial check to hide image container if empty
  checkImageContainerVisibility();
});

// Backtest Result and Detailed Report buttons
document.getElementById('backtestResultButton').addEventListener('click', function() {
  const cs_model = document.getElementById('cs_model').value;
  const ts_model = document.getElementById('ts_model').value;

  fetch('/Backtest_result', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
  })
    .then(response => response.json())
    .then(data => {
      if (data.error) {
        console.error('Error:', data.error);
        return;
      }
      document.getElementById('backtestResult').innerHTML = `
        <div class="image" id="image1">
          <img src="data:image/png;base64,${data.port_weights_img}" alt="Portfolio Weights" style="width: 80%; height: auto;">
        </div>
        <div class="image" id="image2">
          <img src="data:image/png;base64,${data.asset_performance_img}" alt="Asset Performance" style="width: 80%; height: auto;">
        </div>
        <div class="image" id="image3">
          <img src="data:image/png;base64,${data.portfolio_performance_img}" alt="Portfolio Performance" style="width: 80%; height: auto;">
        </div>
      `;
      document.getElementById('backtestResult').classList.add('active');
      document.getElementById('detailedReport').classList.remove('active');
    })
    .catch(error => {
      console.error('Error:', error);
    });
});

document.getElementById('detailedReportButton').addEventListener('click', function() {
  const cs_model = document.getElementById('cs_model').value;
  const ts_model = document.getElementById('ts_model').value;

  fetch('/generate_html_report', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
  })
    .then(response => response.json())
    .then(data => {
      if (data.error) {
        console.error('Error:', data.error);
        return;
      }
      document.getElementById('detailedReport').innerHTML = data.report_html;
      document.getElementById('detailedReport').classList.add('active');
      document.getElementById('backtestResult').classList.remove('active');
    })
    .catch(error => {
      console.error('Error:', error);
    });
});
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    color: #333;
    margin: 0;
    padding: 0;
    text-align: center;
}

h1 {
    color: #333;
    text-align: center;
    margin: 20px 0;
}

/* Generate report button styling */
button, input[type="submit"] {
    padding: 12px 24px;
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-size: 16px;
    transition: background-color 0.3s, transform 0.3s;
}

button:hover, input[type="submit"]:hover {
    background-color: #0056b3;
    transform: translateY(-2px);
}

/* Form styling */
form {
    background: #fff;
    border-radius: 8px;
    padding: 20px;
    max-width: 600px;
    margin: 20px auto;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
    font-size: 16px;
}

select, input[type="submit"] {
    width: calc(100% - 22px);
    padding: 12px;
    margin-bottom: 20px;
    border-radius: 6px;
    border: 1px solid #ddd;
    font-size: 16px;
}

select {
    background-color: #f9f9f9;
}

/* Report page styling */
.report-container {
    background: #fff;
    border-radius: 8px;
    padding: 20px;
    max-width: 1200px;
    margin: 20px auto;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    overflow: auto;
}

a {
    color: #007bff;
    text-decoration: none;
    font-weight: 500;
}

a:hover {
    text-decoration: underline;
}

/* Section that will show report content */
.report-content {
    margin-top: 20px;
}

.report-content iframe {
    width: 100%;
    border: none;
    height: 600px;
}



/* Side bar */
.sidebar {
    position: fixed;
    top: 10px;
    right: 10px;
    height: 100%;
    width: 100%;
    max-width: 500px;
    background-color: none;
    overflow-x: hidden;
    transition: 0.5s;
    padding-top: 60px;
    color: white;
}

.sidebar-content {
    resize: both;
    display: none;
}

.sidebar-content h2 {
    text-align: center;
}

.sidebar-content p {
    padding: 10px;
}

.toggle-button {
    position: fixed;
    top: 10px;
    right: 10px;
    padding: 15px;
    background-color: #3182ce;
    color: white;
    border: none;
    cursor: pointer;
}

.toggle-button:hover {
    background-color: #2c5282;
}

img {
    max-width: 90%;
    margin: 20px 0;
    border: 1px solid #ddd;
    border-radius: 10px;
}

.image-container {
    width: 100%;
    /*padding: 12px 18px;*/
    overflow: hidden;

    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    background-color: #fff;
    /*border: 1px solid #444;*/
    min-height: 50px;
    max-height: 70px;
    margin-bottom: 10px;

    border: 2px solid #e2e2e2;
    border-radius: 8px 8px 8px 8px;
}

.hidden {
    display: none;
}

.img-preview-container {
    position: relative;
    display: inline-block;
    max-width: 15%;
}

.img-preview {
    max-width: 100%;
    border-radius: 5px;
}


.remove-button:hover {
    background-color: #45a049;
}

.remove-button {
    position: absolute;
    top: 5px;
    right: 5px;
    background-color: #ff4d4d;
    border: none;
    border-radius: 50%;
    color: white;
    cursor: pointer;
    width: 20px;
    height: 20px;
    font-size: 12px;
    line-height: 20px;
    text-align: center;
    padding: 0;
}



/* Chat Bot */
.container {
    margin-top: 0;
    width: 90%;
    max-width: 450px;
    margin: 10px auto 0;
    background-color: #fff;
    border-radius: 12px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
    padding: 20px;
    transition: all 0.3s;
}

.chat {
    overflow-y: auto;
    height: 400px;
    margin-bottom: 20px;
    border-bottom: 2px solid #e2e2e2;
}

.message {
    display: flex;
    margin-bottom: 12px;
}

.message.user {
    justify-content: flex-end;
}

.message-bubble {
    padding: 12px 18px;
    max-width: 70%;
    border-radius: 20px;
    line-height: 1.6;
    font-size: 0.95rem;
}

.message.user .message-bubble {
    background-color: #3182ce;
    color: white;
}

.message.bot .message-bubble {
    background-color: #e2e2e2;
    color: #333;
}


.message-image {
    max-width: 100px; /* Set the maximum width for the image */
    max-height: 100px; /* Set the maximum height for the image */
    margin: 5px;
    display: inline-block;
    object-fit: cover; /* Ensures the image retains its aspect ratio */
}


input[type="text"] {
    flex: 1;
    padding: 12px 18px;
    border: 2px solid #e2e2e2;
    border-radius: 8px 0 0 8px;
    font-size: 1rem;
    outline: none;
    color: black;
}

.send-button {
    width: 110px;
    background-color: #3182ce;
    color: white;
    padding: 12px 18px;
    border: none;
    border-radius: 0 8px 8px 0;
    cursor: pointer;
    transition: background-color 0.3s;
}

.send-button:hover {
    background-color: #2c5282;
}

.footer {
    text-align: center;
    padding: 15px 0;
    font-size: 0.9rem;
    color: #666;
    position: static;
    border-top: 1px solid #e2e2e2;
    background-color: #fff;
    position: fixed;
    bottom: 0;
    width: 100%;
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.fadeIn {
    animation: fadeIn 1s;
}

@media (max-width: 600px) {
    .container {
        width: 95%;
        margin: 10px auto 0;
    }

    .chat {
        height: 300px;
    }

    .input-container {
        max-width: 95%;
    }

    input[type="text"],
    .send-button {
        padding: 10px 14px;
        font-size: 0.9rem;
    }

    .footer {
        font-size: 0.8rem;
        margin-top: 30px;
    }
}

.typing-indicator {
    display: none;
    align-items: center;
    justify-content: flex-end;
    margin-top: 8px;
    width: 10px;
    height: 10px;
    background-color: #333;
    border-radius: 50%;
    margin-left: 4px;
    animation: typing 1s infinite;
}

@keyframes typing {
    0%,
    100% {
        transform: scale(1);
        opacity: 1;
    }

    50% {
        transform: scale(1.2);
        opacity: 0.7;
    }
}


/*the backtest result and detailed report buttons */
.button-container {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin: 20px 0;
}

.content {
    display: flex;
    justify-content: center;
    margin-top: 20px;
}

.content-box {
    display: none;
    flex-direction: row;
}

.content-box.active {
    display: flex;
}

/*Backtest_result style*/
.image img {
    width: auto;
    height: auto; 
    margin: 10px auto; 
    display: block; 
    cursor: pointer; 
}

#detailedReport {
    width: 90%; 
    margin: 0 auto; 
  }
  
/*image modal style*/
.modal {
    display: none; 
    position: fixed;
    z-index: 1;
    padding-top: 100px;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgb(0,0,0);
    background-color: rgba(0,0,0,0.9);
  }

.modal-content {
    margin: auto;
    display: block;
    width: 80%;
    max-width: 700px;
  }

.modal-content, #caption {
    -webkit-animation-name: zoom;
    -webkit-animation-duration: 0.6s;
    animation-name: zoom;
    animation-duration: 0.6s;
  }

@-webkit-keyframes zoom {
    from { -webkit-transform: scale(0) } 
    to { -webkit-transform: scale(1) }
  }
@keyframes zoom {
    from { transform: scale(0) } 
    to { transform: scale(1) }
  }

.close {
    position: absolute;
    top: 50px;
    right: 50px;
    color: #f1f1f1;
    font-size: 40px;
    font-weight: bold;
    transition: 0.3s;
  }
  
.close:hover,
.close:focus {
    color: #bbb;
    text-decoration: none;
    cursor: pointer;
  }
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Portfolio Backtesting</title>
    <link
      rel="stylesheet"
      href="{{ url_for('static', filename='css/style.css') }}"
    />
  </head>
  <body>
    <h1>Portfolio Backtesting</h1>
    <form id="reportForm" method="post" action="/">
      <label for="cs_model">Choose Cross-Sectional Model:</label>
      <select id="cs_model" name="cs_model">
        <option value="EW">Equal Weight (EW)</option>
        <option value="MSR">Maximum Sharpe Ratio (MSR)</option>
        <option value="GMV">Global Minimum Variance (GMV)</option>
        <option value="MDP">Minimum Drawdown Risk (MDP)</option>
        <option value="EMV">Equal Risk Contribution (EMV)</option>
        <option value="RP">Risk Parity (RP)</option>
      </select>

      <label for="ts_model">Choose Time-Series Model:</label>
      <select id="ts_model" name="ts_model">
        <option value="VT">Volatility Targeting (VT)</option>
        <option value="CVT">Conditional Value at Risk Targeting (CVT)</option>
        <option value="KL">Kelly Criterion (KL)</option>
        <option value="CPPI">
          Constant Proportion Portfolio Insurance (CPPI)
        </option>
      </select>

      <button type="submit" class="button">Generate Report</button>
    </form>

    <div class="button-container">
      <button type="button" id="backtestResultButton" class="button">
        Backtest Result
      </button>
      <button type="button" id="detailedReportButton" class="button">
        Detailed Report
      </button>
    </div>

    <div class="content">
      <div id="backtestResult" class="content-box"></div>
      <div id="detailedReport" class="content-box"></div>
    </div>

    <div class="sidebar" id="sidebar">
      <button class="toggle-button" onclick="toggleSidebar()">☰</button>
      <div class="sidebar-content" id="sidebarContent">
        <div class="container bg-white rounded-lg shadow-md">
          <h1 class="text-3xl font-bold mb-4 text-center">ChatBot</h1>
          <div class="chat" id="chatContainer"></div>
          <div class="image-container hidden" id="imageContainer"></div>
          <div class="flex">
            <input
              type="text"
              id="userInput"
              placeholder="Type your message here..."
              class="outline-none"
              onkeyup="handleKeyPress(event)"
            />
            <button class="send-button" onclick="sendMessage()">Send</button>
          </div>
          <div class="typing-indicator" id="typingIndicator"></div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/markdown-it.min.js"></script>
    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
    <!-- image moddal -->
    <div id="myModal" class="modal">
      <span class="close">&times;</span>
      <img class="modal-content" id="img01" />
      <div id="caption"></div>
    </div>

    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
    <script>
      document
        .getElementById("backtestResultButton")
        .addEventListener("click", function () {
          const cs_model = document.getElementById("cs_model").value;
          const ts_model = document.getElementById("ts_model").value;

          fetch("/Backtest_result", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
          })
            .then((response) => response.json())
            .then((data) => {
              if (data.error) {
                console.error("Error:", data.error);
                return;
              }
              document.getElementById("backtestResult").innerHTML = `
          <div class="image" id="image1">
            <img src="data:image/png;base64,${data.port_weights_img}" alt="Portfolio Weights">
          </div>
          <div class="image" id="image2">
            <img src="data:image/png;base64,${data.asset_performance_img}" alt="Asset Performance">
          </div>
          <div class="image" id="image3">
            <img src="data:image/png;base64,${data.portfolio_performance_img}" alt="Portfolio Performance">
          </div>
        `;
              document.getElementById("backtestResult").classList.add("active");
              document
                .getElementById("detailedReport")
                .classList.remove("active");

              // image click event
              const images = document.querySelectorAll(".image img");
              images.forEach((img) => {
                img.addEventListener("click", function () {
                  const modal = document.getElementById("myModal");
                  const modalImg = document.getElementById("img01");
                  const captionText = document.getElementById("caption");
                  modal.style.display = "block";
                  modalImg.src = this.src;
                  captionText.innerHTML = this.alt;
                });
              });
            })
            .catch((error) => {
              console.error("Error:", error);
            });
        });

      document
        .getElementById("detailedReportButton")
        .addEventListener("click", function () {
          const cs_model = document.getElementById("cs_model").value;
          const ts_model = document.getElementById("ts_model").value;

          fetch("/generate_html_report", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
          })
            .then((response) => response.json())
            .then((data) => {
              if (data.error) {
                console.error("Error:", data.error);
                return;
              }
              document.getElementById("detailedReport").innerHTML =
                data.report_html;
              document.getElementById("detailedReport").classList.add("active");
              document
                .getElementById("backtestResult")
                .classList.remove("active");
            })
            .catch((error) => {
              console.error("Error:", error);
            });
        });

      // modal close
      const modal = document.getElementById("myModal");
      const span = document.getElementsByClassName("close")[0];
      span.onclick = function () {
        modal.style.display = "none";
      };
    </script>
  </body>
</html>


Solution

  • try adding overflow: auto;

    .sidebar-content {
        resize: both;
        overflow: auto;
        display: none;
    }
    

    and don't forget to specify max-width/min-width and max-height/min-height if required.